ข้ามไปยังเนื้อหาหลัก
คู่มือ

วิธีเพิ่มบริบทของ actor ลงในแอป Node.js

คู่มือแบบทีละขั้นตอนสำหรับการสร้าง middleware ที่บันทึกว่าใครหรืออะไรอยู่เบื้องหลังแต่ละคำขอ เพื่อให้คุณศึกษาทราฟฟิกจริงก่อนบังคับใช้อะไรก็ตาม

Toshendra Sharma

โดย Toshendra Sharmaผู้ก่อตั้งและ CEO, AgentCredia · Updated November 13, 2026

ระดับเริ่มต้น · อ่าน 9 นาที

ทำไมต้องเริ่มจากบริบทของ actor แทนที่จะใช้กฎ?

ทีมส่วนใหญ่ที่เริ่มสังเกตเห็น AI agent ในทราฟฟิกของตัวเอง มักจะหยิบกฎมาใช้ก่อน ปิดกั้น user agent เพิ่ม challenge หรือทำ rate limit ให้เข้มขึ้น ปัญหาคือ กฎที่เขียนก่อนที่คุณจะเข้าใจทราฟฟิก มักไปกระทบคนผิด ผู้ช่วยที่ถูกต้องตามปกติถูกบล็อก ผู้ใช้จริงถูก challenge และระบบอัตโนมัติที่คุณกังวลจริงๆ ก็ยังเข้ามาได้ต่อ เพราะมันไม่เคยระบุตัวเองตั้งแต่แรก

บริบทของ actor คือขั้นก่อนกฎ สำหรับทุก request คุณบันทึกสิ่งที่แอปของคุณสังเกตได้ว่าใครหรืออะไรเป็นผู้กระทำอยู่ คนที่โต้ตอบกับระบบ คนที่ทำงานร่วมกับ AI assistant agent อัตโนมัติ หรือบางอย่างที่คุณยังแยกไม่ออก คุณบันทึกหลักฐานที่อยู่เบื้องหลังมุมมองนั้น และเก็บมันไว้ข้างผลลัพธ์ของ request หลังจากทำแบบนี้หนึ่งหรือสองสัปดาห์ คุณจะได้สิ่งที่ไม่มีเทมเพลตกฎใดให้ได้ นั่นคือแผนที่ของทราฟฟิกของคุณเอง ถ้าคุณยังไม่แน่ใจว่า site ของคุณมี agent เข้ามาจริงหรือไม่ ให้เริ่มจาก วิธีดูว่าทราฟฟิกของเว็บไซต์มาจาก AI agent หรือไม่

คู่มือนี้สร้างชั้นนั้นในแอปพลิเคชัน Node.js ด้วย middleware แบบธรรมดา มันใช้ได้กับ Express และแนวคิดเดียวกันก็ใช้ต่อได้กับ Fastify, Koa, Hono หรือ route handler ของ Next.js

สิ่งที่คุณจะสร้าง

เมื่อจบแล้ว คุณจะมี:

  1. middleware ที่ดึงหลักฐานของ actor จากแต่ละ request
  2. โมเดลการจำแนกที่เล็กและชัดเจน พร้อมระบุฐานของแต่ละผลลัพธ์
  3. observation logs แบบมีโครงสร้าง ที่ไม่เปลี่ยนวิธีจัดการ request
  4. วิธีแนบผลลัพธ์เข้ากับ request เพื่อให้ handler ถัดไปอ่านได้

ไม่มีอะไรในที่นี้ที่จะบล็อก request นั่นเป็นการตั้งใจ

ขั้นที่ 1: เซิร์ฟเวอร์ของคุณมองเห็นหลักฐานอะไรได้จริง?

เซิร์ฟเวอร์เห็นน้อยกว่าที่คนส่วนใหญ่มักคิด ก่อนเขียนโค้ด ให้ลิสต์สัญญาณที่มีอยู่ที่ขอบระบบของคุณ:

  • Request signatures. ลูกค้าอัตโนมัติบางรายลงนาม request ด้วย HTTP Message Signatures (RFC 9421) โดยส่ง header Signature-Input และ Signature มักมาพร้อมกับ header Signature-Agent ที่ชี้ไปยัง public key ของผู้ดำเนินการ ตามที่อธิบายไว้ในร่าง protocol ของ IETF Web Bot Auth
  • Declared identity. client อาจระบุตัวเองใน header User-Agent ใน header From หรือผ่าน metadata ของโปรโตคอล เช่นชื่อ client ของ MCP โดย MCP client รายงานชื่อ clientInfo ที่เลือกเองใน initialize request ของ revision 2025-11-25 และ revision 2026-07-28 ก็ส่งต่อ identity ของ client เป็น metadata รายคำขอ สิ่งเหล่านี้คือการประกาศตัว และใครก็สามารถก็อปปี้ได้
  • Authentication context. request มี session cookie, bearer token หรือ API key หรือไม่ และเป็น credential ชนิดใด
  • รูปแบบของโปรโตคอล. Accept headers, client ต้องการ HTML หรือ JSON, มันโหลด assets ของหน้าเว็บหรือไม่ และจัดการ redirects กับ cookies อย่างไร
  • Declarations ของแอปพลิเคชัน. ผลิตภัณฑ์ของคุณเองอาจรู้สิ่งที่เครือข่ายไม่รู้ เช่น ผู้ใช้เลือกตัวเลือก "AI assisted" หรือ request มาจากฟีเจอร์ assistant ที่ฝังอยู่ในระบบของคุณ

แต่ละสัญญาณมีน้ำหนักต่างกัน ลายเซ็นที่ตรวจสอบได้ยืนยันได้ ส่วน user agent string เป็นเพียงการอ้างว่าเป็นอะไรบางอย่าง Cookie ที่หายไปก็คือหายไปเท่านั้น การแยกสิ่งเหล่านี้ออกจากกันคือหัวใจของแบบฝึกนี้

ขั้นที่ 2: กำหนดโมเดลของผลลัพธ์อย่างชัดเจน

กำหนดรูปแบบของ actor result ก่อนเขียน logic การตรวจจับ โมเดลที่เล็กและมีชนิดชัดเจนช่วยให้ทุกคนเข้าใจตรงกันว่าการจำแนกหมายถึงอะไร คลาสทั้งสี่ที่ใช้ที่นี่อธิบายไว้ใน Human, AI-assisted or agent: a practical taxonomy for application traffic

TS
export type ActorClass = "human" | "human_ai" | "agent" | "unknown";
export type EvidenceBasis = "verified" | "declared" | "inferred" | "unknown";

export interface ActorEvidence {
  signaturePresent: boolean;
  signatureAgent?: string;
  declaredClient?: string;
  credentialType: "session" | "api_key" | "bearer" | "none";
  acceptsHtml: boolean;
  appDeclaredAssist: boolean;
}

export interface ActorResult {
  actor: ActorClass;
  basis: EvidenceBasis;
  reasons: string[];
  evidence: ActorEvidence;
  observedAt: string;
}

มีสองฟิลด์ที่สำคัญที่สุด actor คือ class ส่วน basis บอกว่าทำไมคุณจึงเชื่อเช่นนั้น ผลลัพธ์ของ agent ที่อิงจากลายเซ็นที่ตรวจสอบได้ กับผลลัพธ์ของ agent ที่อิงจาก user agent string ต่างก็เป็น "agent" เหมือนกัน แต่ในภายหลังควรได้รับการจัดการต่างกันมาก ดังนั้นอย่ารวมมันเข้าด้วยกัน

ขั้นที่ 3: ดึงหลักฐานใน middleware

ขั้นตอนการดึงข้อมูลจะอ่าน header และสถานะของ request มันยังไม่ตัดสินอะไรทั้งนั้น

TS
import type { Request, Response, NextFunction } from "express";

const KNOWN_AGENT_TOKENS = ["bot", "crawler", "spider", "agent", "gpt", "claude"];

export function extractEvidence(req: Request): ActorEvidence {
  const ua = (req.get("user-agent") || "").toLowerCase();
  const auth = req.get("authorization") || "";
  const hasSession = Boolean(req.cookies?.session);

  let credentialType: ActorEvidence["credentialType"] = "none";
  if (hasSession) credentialType = "session";
  else if (auth.toLowerCase().startsWith("bearer ")) credentialType = "bearer";
  else if (req.get("x-api-key")) credentialType = "api_key";

  const declaredToken = KNOWN_AGENT_TOKENS.find((t) => ua.includes(t));

  return {
    signaturePresent: Boolean(req.get("signature") && req.get("signature-input")),
    signatureAgent: req.get("signature-agent") || undefined,
    declaredClient: declaredToken ? ua.slice(0, 200) : undefined,
    credentialType,
    acceptsHtml: (req.get("accept") || "").includes("text/html"),
    appDeclaredAssist: req.get("x-app-assist") === "1",
  };
}

ข้อสังเกตบางประการเกี่ยวกับโค้ดนี้:

  • signaturePresent ทำได้แค่บันทึกว่ามี signature headers อยู่จริง การตรวจสอบความถูกต้องเป็นขั้นตอนแยกต่างหาก ซึ่งครอบคลุมไว้ในคู่มือการยืนยัน signed agent requests
  • รายการ token ของ user agent จงใจทำให้เรียบง่าย มันสร้าง declaration ไม่ใช่ verification
  • x-app-assist ใช้แทนสิ่งที่ frontend ของคุณส่งมาเมื่อผู้ใช้ทำงานผ่านฟีเจอร์ AI ที่ฝังอยู่ในระบบของคุณเอง ให้แทนด้วยกลไกจริงของคุณ

ขั้นที่ 4: จำแนกด้วยฐานที่ระบุไว้

ตอนนี้ให้เปลี่ยนหลักฐานเป็นผลลัพธ์ เก็บกฎให้สั้น เรียงจากหลักฐานที่แข็งแรงที่สุดไปหาอ่อนที่สุด และต้องตกไปที่ Unknown เสมอ

TS
export async function classify(
  evidence: ActorEvidence,
  verifySignature: () => Promise<boolean>,
): Promise<ActorResult> {
  const reasons: string[] = [];
  const observedAt = new Date().toISOString();

  if (evidence.signaturePresent && (await verifySignature())) {
    reasons.push("request signature validated");
    return { actor: "agent", basis: "verified", reasons, evidence, observedAt };
  }

  if (evidence.appDeclaredAssist) {
    reasons.push("application reported its built-in assistant");
    return { actor: "human_ai", basis: "declared", reasons, evidence, observedAt };
  }

  if (evidence.declaredClient) {
    reasons.push("client declared itself as automated");
    return { actor: "agent", basis: "declared", reasons, evidence, observedAt };
  }

  if (evidence.credentialType === "session" && evidence.acceptsHtml) {
    reasons.push("interactive browser session");
    return { actor: "human", basis: "inferred", reasons, evidence, observedAt };
  }

  reasons.push("insufficient evidence");
  return { actor: "unknown", basis: "unknown", reasons, evidence, observedAt };
}

สังเกตว่าสิ่งนี้ไม่ทำอะไรบ้าง มันไม่ยกระดับ declaration ให้กลายเป็น verification มันไม่ระบุว่าเป็น Human ให้กับ request ที่ไม่มีสัญญาณของการโต้ตอบ เมื่อมันบอกไม่ได้ มันจะบอกว่า Unknown Unknown เป็นคำตอบที่ถูกต้องและมีประโยชน์ และบอกคุณได้ชัดเจนว่าขอบเขตการมองเห็นของคุณจบลงตรงไหน เราอธิบายกรณีนี้ไว้อย่างละเอียดใน ทำไม Unknown จึงเป็นผลลัพธ์ที่ถูกต้องสำหรับทราฟฟิก AI

ขั้นที่ 5: เชื่อมเข้าระบบในโหมดสังเกต

ติดตั้ง middleware ตั้งแต่ต้น แนบผลลัพธ์เข้ากับ request และบันทึกมันไว้ อย่า branch ตามผลลัพธ์

TS
import express from "express";
import cookieParser from "cookie-parser";

const app = express();
app.use(cookieParser());

app.use(async (req, res, next) => {
  try {
    const evidence = extractEvidence(req);
    const result = await classify(evidence, async () => false);
    res.locals.actor = result;

    res.on("finish", () => {
      logActor({
        route: req.route?.path ?? req.path,
        method: req.method,
        status: res.statusCode,
        actor: result.actor,
        basis: result.basis,
        reasons: result.reasons,
      });
    });
  } catch {
    res.locals.actor = undefined;
  }
  next();
});

มีสามรายละเอียดที่ทำให้วิธีนี้ปลอดภัย:

  1. บล็อก try รับประกันว่าความล้มเหลวในการตรวจจับจะไม่กระทบ response การสังเกตต้อง fail open
  2. การบันทึก log เกิดบน finish ดังนั้นคุณจะเก็บ status code จริง และสามารถเปรียบเทียบการจำแนกกับผลลัพธ์ได้ในภายหลัง
  3. verifySignature ถูกทำเป็น stub ไปที่ false จนกว่าคุณจะทำการตรวจสอบความถูกต้องจริง ซึ่งจะช่วยให้ signed requests อยู่ในกลุ่ม declared หรือ unknown แทนที่จะขยายความเกินจริง

ขั้นที่ 6: บันทึกเพื่อการวิเคราะห์ ไม่ใช่แค่การ debug

เขียน log ที่คุณสามารถนำไป aggregate ได้ บรรทัดแบบมีโครงสร้างต่อ request เพียงบรรทัดเดียวก็พอ

TS
function logActor(entry: Record<string, unknown>) {
  process.stdout.write(JSON.stringify({ type: "actor_observation", ...entry }) + "\n");
}

อย่าเก็บข้อมูลส่วนบุคคลไว้ใน log เหล่านี้ คุณต้องการ route, class, basis, reasons และ outcome คุณไม่จำเป็นต้องใช้ IP address เต็มๆ, raw cookies หรือ request body เพื่อทำความเข้าใจส่วนผสมของทราฟฟิก ถ้าคุณต้องเก็บตัวระบุใดๆ เพื่อใช้เชื่อมโยง ให้ hash ด้วย rotating salt และตั้งระยะเวลาเก็บรักษาให้สั้น

ขั้นที่ 7: อ่านข้อมูลก่อนลงมือ

ให้ระบบทำงานในโหมดสังเกตนานพอที่จะเห็นรูปแบบประจำสัปดาห์ตามปกติ แล้วตอบคำถามบางข้อ:

  • route ใดดึงดูด agent? โดยปกติ documentation, catalog และ search endpoints มักจะดึงดูด ส่วน account และ checkout flows โดยปกติไม่ควร
  • ผลลัพธ์ของ agent ที่เป็น verified เทียบกับ declared มีสัดส่วนเท่าไร? อัตราส่วนนั้นบอกคุณได้ว่าคุณสามารถพึ่งพา identity ได้มากแค่ไหนเมื่อเขียนนโยบาย
  • Unknown กระจุกตัวอยู่ที่ไหน? อัตรา Unknown ที่สูงใน route ใด route หนึ่ง มักหมายความว่าคุณขาดสัญญาณบางอย่างตรงนั้น เช่น หน้าเว็บที่ไม่เคยรัน frontend code ของคุณ
  • ผลลัพธ์ต่างกันตาม class หรือไม่? เปรียบเทียบอัตราความผิดพลาด ขั้นตอน conversion หรือรายงานการใช้งานผิดวัตถุประสงค์ข้าม class นี่คือจุดที่ไอเดียนโยบายจริงๆ มักเกิดขึ้น

คำตอบมักจะทำให้คุณประหลาดใจ ทีมจำนวนมากพบเป็นประจำว่าส่วนใหญ่ของปัญหา "bot" ของพวกเขา แท้จริงคือ automation ที่ถูกต้องตามปกติ ซึ่งยินดีจะใช้ endpoint แบบมีโครงสร้างหากมีให้ใช้งาน

ขั้นที่ 8: เปิดผลลัพธ์ให้ส่วนอื่นของแอปใช้ได้

เมื่อข้อมูลเริ่มไหลแล้ว ให้ handler อ่าน res.locals.actor เพื่อใช้แบบไม่บังคับใช้ เช่น analytics tags, เลือก response format หรือบันทึกว่า ticket ของฝ่ายสนับสนุนมาจาก session ที่มี AI-assisted ใช้งานอยู่ การใช้งานเหล่านี้ช่วยยกระดับประสบการณ์โดยไม่เสี่ยงให้ใครถูกล็อกเอาต์

TS
app.get("/docs/:page", (req, res) => {
  const actor = res.locals.actor as ActorResult | undefined;
  if (actor?.actor === "agent" && req.accepts("text/markdown")) {
    return res.type("text/markdown").send(renderMarkdown(req.params.page));
  }
  return res.render("doc", { page: req.params.page });
});

การบังคับใช้ เช่น challenge, rate limit หรือ step-up checks จะมาทีหลัง หลังจากที่คุณวัด false positives แล้ว คู่มือเกี่ยวกับการ rollout actor-aware policies จะอธิบายกระบวนการนั้นไว้

ข้อผิดพลาดอะไรที่คุณควรหลีกเลี่ยง?

  • มองว่า user agent คือ identity. มันเป็น header ที่เปลี่ยนได้ง่ายที่สุด บันทึกมันเป็น declaration แม้แต่ผู้ดำเนินการ AI ก็ยืนยันเช่นนั้น: แนวทางของ Anthropic สำหรับ hosted browser agent ระบุว่าใครก็ส่ง User-Agent header เดิมได้ และแนะนำให้ตรวจสอบ IP address ที่ประกาศไว้ก่อนจะถือว่าทราฟฟิกนั้นเป็นของจริง
  • ตั้งค่าเริ่มต้นเป็น Human. ถ้าโค้ดของคุณระบุว่า Human ทุกครั้งที่หา bot signals ไม่เจอ ตัวเลข Human ของคุณจะสูงเกินจริง และนโยบายของคุณจะเชื่อถือทราฟฟิกผิดประเภท
  • ผสม class กับ basis เข้าด้วยกัน. ต้องเก็บทั้งสองอย่างเสมอ dashboard ที่แสดงแค่ "Agent" จะซ่อนความต่างระหว่าง operator ที่ตรวจสอบได้กับ string ที่ก็อปปี้มา
  • บล็อกระหว่างการสังเกต. แม้แต่การบล็อกชั่วคราวก็ทำให้การวัดของคุณใช้ไม่ได้ เพราะ client ที่ถูกบล็อกจะเปลี่ยนพฤติกรรม

Proof of Actor เหมาะกับตรงไหน

middleware ด้านบนเป็นจุดเริ่มต้นที่มั่นคง และแสดงโมเดลหลักให้เห็น: หลักฐานมาก่อน มีฐานที่ระบุชัดสำหรับทุกผลลัพธ์, Unknown ยังมองเห็นได้ และการตัดสินใจปล่อยให้เป็นของแอปของคุณ Proof of Actor สร้างต่อจากโมเดลเดียวกันนี้ ด้วย coverage ของสัญญาณที่ลึกขึ้นครอบคลุม server, edge, browser และบริบทของ MCP พร้อม analytics ที่ช่วยให้คุณ Detect, Understand และ Route ได้อย่างมั่นใจ

ถ้าคุณต้องการความช่วยเหลือเพื่อพัฒนาสิ่งนี้ต่อในแอป production ให้ สมัครขอสิทธิ์เข้าถึงก่อน หากต้องการดู decision flow ทั้งหมด ให้อ่าน How Proof of Actor works

แหล่งอ้างอิง

สร้างไปกับเรา

เข้าร่วม Proof of Actor founding developer program

อยากคุยก่อนหรือไม่ พูดคุยกับผู้ก่อตั้ง