ทำไมเอเจนต์ AI จึงต้องมีเส้นทางที่แตกต่างผ่านแอปของคุณ?
สัดส่วนของ request ไปยัง application ของคุณที่มาจากเอเจนต์ AI กำลังเพิ่มขึ้นอย่างต่อเนื่อง: ผู้ช่วยที่ดึงเอกสารสำหรับผู้ใช้ เครื่องมือวิจัยที่อ่านหน้า product และ workflow อัตโนมัติที่เรียก API ของคุณ ส่วนใหญ่ทำงานผ่าน interface ที่ออกแบบมาสำหรับคน พวกเขาดาวน์โหลดหน้า HTML ทั้งหมด แยกวิเคราะห์ layout ตาม navigation ที่ออกแบบมาสำหรับเมาส์ และลองใหม่เมื่อหน้าที่พึ่งพา script หนักไม่แสดงผลตามที่คาด
สิ่งนี้ไม่มีประสิทธิภาพสำหรับทุกฝ่าย เอเจนต์ได้รับเนื้อหาที่มีสัญญาณรบกวนและทำผิดพลาด เซิร์ฟเวอร์ของคุณใช้ทรัพยากรไปกับการเรนเดอร์หน้าที่ไม่มีใครเปิดดู และทราฟฟิกก็ยากจะทำความเข้าใจ เพราะมันดูเหมือนเบราว์เซอร์ที่ทำงานผิดปกติ ถ้าคุณยังไม่แน่ใจว่าทราฟฟิกของคุณส่วนนี้มากแค่ไหน ให้เริ่มจาก วิธีดูว่าทราฟฟิกของเว็บไซต์มาจากเอเจนต์ AI หรือไม่
แนวทางที่ดีกว่าคือให้เอเจนต์มีเส้นทางที่มีโครงสร้าง: เนื้อหาและการกระทำในรูปแบบที่สร้างมาสำหรับโปรแกรม ค้นพบได้ในตำแหน่งมาตรฐาน และมีขีดจำกัดที่ให้รางวัลกับไคลเอนต์ที่ระบุตัวเองอย่างถูกต้อง คู่มือนี้แสดงวิธีทำเช่นนั้นโดยไม่ต้องสร้างแอปพลิเคชันอีกชุดหนึ่ง
ขั้นตอนที่ 1: ตัดสินใจว่าเอเจนต์ควรทำอะไรได้บ้าง
เริ่มจากเจตนา ไม่ใช่เทคโนโลยี ดูที่ route ที่คุณเห็นทราฟฟิกของเอเจนต์อยู่แล้ว แล้วจัดกลุ่ม:
- อ่านเนื้อหา: เอกสาร ข้อมูล product หน้า pricing บทความช่วยเหลือ โปรไฟล์สาธารณะ
- สอบถามข้อมูล: search, การค้นหาใน catalog, availability, status
- ดำเนินการ: สร้าง อัปเดต หรือลบสิ่งใดสิ่งหนึ่งแทนผู้ใช้
การอ่านและการสอบถามเป็นตัวเลือกที่ดีสำหรับเส้นทางที่มีโครงสร้างและเปิดให้ไคลเอนต์ที่ทำงานอย่างเหมาะสมได้ แต่การดำเนินการต้องมีการยืนยันตัวตนและการอนุญาตไม่ว่าไคลเอนต์จะระบุตัวเองอย่างไรก็ตาม และประเด็นนี้ครอบคลุมอยู่ในคู่มือเรื่องการปกป้องการกระทำที่อ่อนไหว
ขั้นตอนที่ 2: เสนอรูปแบบที่เป็นมิตรกับเครื่อง
สำหรับ route ของเนื้อหา ให้เสนอข้อมูลชุดเดียวกันโดยไม่มีส่วนตกแต่งของหน้า สองรูปแบบนี้ครอบคลุมความต้องการส่วนใหญ่:
- Markdown สำหรับเนื้อหาเชิงบรรยาย เช่น เอกสารและบทความ ซึ่งส่งด้วย media type
text/markdownที่ลงทะเบียนไว้ใน RFC 7763 มันเก็บ heading รายการ ลิงก์ และ code block ไว้ และมีขนาดกะทัดรัด - JSON สำหรับ record ที่มีโครงสร้าง เช่น product แผนบริการ หรือสถานะ
ใช้ content negotiation เพื่อให้ URL เดิมคงอยู่และให้ไคลเอนต์ขอรูปแบบที่ต้องการ:
import express from "express";
const app = express();
app.get("/docs/:slug", async (req, res) => {
const doc = await loadDoc(req.params.slug);
if (!doc) return res.status(404).end();
res.vary("Accept");
const preferred = req.accepts(["text/html", "text/markdown", "application/json"]);
if (preferred === "text/markdown") {
return res.type("text/markdown; charset=utf-8").send(doc.markdown);
}
if (preferred === "application/json") {
return res.json({ title: doc.title, updatedAt: doc.updatedAt, body: doc.markdown });
}
return res.render("doc", { doc });
});ส่ง header Vary: Accept (RFC 9110 on Vary) เพื่อให้ cache เก็บแต่ละ representation แยกกัน หลายทีมยังเปิดเผย alternate URL อย่างชัดเจน เช่น เติม .md ต่อท้าย path ของเอกสาร เพราะไคลเอนต์บางตัวหาสิ่งนี้ได้ง่ายกว่าการทำ negotiation ให้ลิงก์จากหน้า HTML ด้วย <link rel="alternate" type="text/markdown" href="..."> เพื่อให้ค้นพบได้ ลิงก์แบบมี type เช่นนี้เป็นไปตามโมเดล Web Linking
ขั้นตอนที่ 3: เอเจนต์ค้นหาเส้นทางที่มีโครงสร้างของคุณได้อย่างไร?
เอเจนต์จะใช้สิ่งที่หาไม่เจอไม่ได้ ประกาศ entry point ในตำแหน่งที่ไคลเอนต์อัตโนมัติมักจะมองหา:
/llms.txtที่ root ของ site: สรุปสั้นๆ ใน Markdown ว่า site ของคุณเสนออะไร พร้อมลิงก์ไปยังหน้าที่มีประโยชน์ที่สุดและเวอร์ชันที่มีโครงสร้าง ตามข้อเสนอ llms.txt- คำอธิบาย API เช่นเอกสาร OpenAPI ที่ลิงก์จากเอกสารสำหรับนักพัฒนาและจาก
/llms.txt - alternate links ในหน้า HTML ที่ชี้ไปยังเวอร์ชัน Markdown หรือ JSON
- sitemap ที่แสดงรายการ canonical content URLs ของคุณ ในรูปแบบที่กำหนดโดย Sitemaps protocol
llms.txt แบบย่อ:
# Example App
> Example App helps teams plan projects. This file lists the best entry points for automated clients.
## Docs
- [Getting started](https://example.com/docs/getting-started.md): setup in five steps
- [API reference](https://example.com/openapi.json): OpenAPI description of the public API
## Policies
- [Automated access](https://example.com/docs/automated-access.md): limits and how to sign requestsเก็บหน้าไว้หน้าหนึ่งที่อธิบายกติกาสำหรับไคลเอนต์อัตโนมัติ: route ใดเปิดอยู่ ขีดจำกัดใดใช้บังคับ และไคลเอนต์จะระบุตัวเองด้วย signed request อย่างไรเพื่อรับการปฏิบัติที่ดีกว่า กติกาที่ชัดเจนเปลี่ยนการคาดเดาให้เป็นความร่วมมือ
ขั้นตอนที่ 4: กำหนดเส้นทางตาม actor class ไม่ใช่ตาม user agent string
เมื่อคุณมี response ที่มีโครงสร้างแล้ว ให้ตัดสินใจว่าใครควรได้รับโดยค่าเริ่มต้น ใช้ actor class และ basis ของมัน แทนการ parse user agent string แบบเฉพาะกิจ ผู้ปฏิบัติการ AI เปิดเผย user agent ของตนจริง เช่นที่ OpenAI และ Anthropic ทำสำหรับ crawler และ fetcher ของพวกเขา แต่การจับคู่ string ก็ยังเป็นเพียงการประกาศเท่านั้น
type ActorClass = "human" | "human_ai" | "agent" | "unknown";
type Basis = "verified" | "declared" | "inferred" | "unknown";
function preferStructured(actor: ActorClass, basis: Basis, accept: string): boolean {
if (accept.includes("text/markdown") || accept.includes("application/json")) return true;
return actor === "agent" && (basis === "verified" || basis === "declared");
}สองหลักการนี้ช่วยให้ปลอดภัย:
- คำขอที่ระบุชัดเจนมาก่อนเสมอ หากไคลเอนต์ขอ Markdown หรือ JSON ให้ส่งตามนั้น ไม่ว่า class จะเป็นอะไร
- ค่าเริ่มต้นไม่ตัดสิทธิ์ใคร ผู้เยี่ยมชมที่เป็น Human หรือ Unknown จะได้รับหน้าเต็มเสมอ การเลือกค่าเริ่มต้นแบบมีโครงสร้างสำหรับเอเจนต์เปลี่ยนแค่รูปแบบ ไม่ใช่สิ่งที่พวกเขามีสิทธิ์ดู
ขั้นตอนที่ 5: เอเจนต์ AI ควรได้รับ rate limits แบบใด?
rate limits คือจุดที่บริบทของ actor ให้ประโยชน์ชัดที่สุด แทนที่จะใช้ขีดจำกัดเดียวกับทุกคน ให้กำหนดงบประมาณที่เหมาะสมสำหรับแต่ละการผสมของ class และ basis:
| Actor and basis | Read routes | Notes |
|---|---|---|
| Agent, verified | ขีดจำกัดสูงกว่า | ผู้ปฏิบัติการระบุตัวตนและรับผิดชอบได้ |
| Agent, declared or inferred | ขีดจำกัดมาตรฐาน | คำกล่าวอ้างยังตรวจสอบไม่ได้ |
| Unknown | ขีดจำกัดมาตรฐาน | โดยค่าเริ่มต้นไม่ลงโทษ |
| Human or Human + AI | ขีดจำกัดมาตรฐาน | การใช้งานแบบโต้ตอบแทบไม่ถึงขีดจำกัด |
กำหนด key ของขีดจำกัดตามตัวระบุที่เสถียรที่สุดที่มีอยู่ สำหรับ agent ที่ verified แล้ว นั่นคือ operator origin และ key identifier ของเขา สำหรับรายอื่น ให้ใช้ credential หรือ network-level key
function limitKey(actor: { actor: ActorClass; basis: Basis; operator?: string }, ip: string, apiKey?: string) {
if (actor.actor === "agent" && actor.basis === "verified" && actor.operator) return `op:${actor.operator}`;
if (apiKey) return `key:${apiKey}`;
return `ip:${ip}`;
}
function limitFor(actor: { actor: ActorClass; basis: Basis }): number {
if (actor.actor === "agent" && actor.basis === "verified") return 600;
return 120;
}ตัวเลขด้านบนเป็นเพียงตัวแทนค่า กำหนดของคุณจาก baseline ที่คุณสังเกตได้ เมื่อไคลเอนต์เกินขีดจำกัด ให้ส่ง 429 Too Many Requests พร้อม header Retry-After และลิงก์ไปยังหน้า automated access ของคุณ เอเจนต์ที่สร้างมาดีจะถอยและอ่านมัน
ขั้นตอนที่ 6: เก็บการกระทำไว้หลังการอนุญาต
เส้นทางที่มีโครงสร้างสำหรับการอ่านไม่ใช่เส้นทางอ้อมรอบโมเดลความปลอดภัยของคุณ การกระทำใดๆ ที่เปลี่ยนข้อมูลยังคงต้องมี:
- การยืนยันตัวตนด้วย credential ที่แอปพลิเคชันของคุณออกให้,
- การอนุญาตสำหรับการกระทำนั้นโดยเฉพาะ และ
- การตรวจสอบเพิ่มเติมใดๆ ที่ระดับความเสี่ยงของ route นั้นกำหนดไว้
ลายเซ็นที่ verified บอกคุณได้ว่าไคลเอนต์ใดกำลังเรียกอยู่ แต่มันไม่ได้บอกว่ไคลเอนต์นั้นอาจกระทำแทนผู้ใช้รายใดได้หรือไม่ แยกสองคำถามนี้ออกจากกัน โพสต์ของเราเกี่ยวกับ สิ่งที่คำขอของเอเจนต์ AI ที่ลงนามแล้วพิสูจน์ได้ อธิบายความแตกต่างไว้
ขั้นตอนที่ 7: วัดผลกระทบ
เปิดใช้เส้นทางที่มีโครงสร้างในโหมด observation ก่อน จากนั้นเปรียบเทียบ:
- สัดส่วนของคำขอจากเอเจนต์ที่ใช้รูปแบบที่มีโครงสร้าง,
- ต้นทุนและ latency ของเซิร์ฟเวอร์สำหรับทราฟฟิกเอเจนต์ก่อนและหลัง,
- อัตราความผิดพลาดและการลองใหม่ของไคลเอนต์เอเจนต์,
- มีไคลเอนต์อัตโนมัติกี่รายที่ย้ายจาก declared ไป verified หลังจากคุณเผยแพร่แนวทางการลงนาม
ตัวเลขเหล่านี้แสดงว่าเส้นทางที่มีโครงสร้างกำลังทำงานตามหน้าที่หรือไม่ และควรขยายไปตรงไหนต่อ
ขั้นตอนที่ 8: กำหนดเวอร์ชันและจัดทำเอกสารให้เส้นทางที่มีโครงสร้าง
ปฏิบัติต่อเส้นทางที่มีโครงสร้างเหมือน interface สาธารณะ เพราะเอเจนต์จะสร้างต่อยอดจากมัน นิสัยไม่กี่อย่างช่วยให้มันเชื่อถือได้:
- คง URL ให้เสถียร ถ้าเอเจนต์เรียนรู้ว่า
/docs/getting-started.mdมีอยู่ มันก็ควรใช้งานต่อได้ เมื่อเนื้อหาย้าย ให้ส่ง301ไปยังตำแหน่งใหม่แทน404 - ใส่เวลาการอัปเดต ใส่ field
updatedAtใน response แบบ JSON และ headerLast-Modifiedบน Markdown เพื่อให้เอเจนต์แคชได้อย่างเหมาะสมและรีเฟรชเฉพาะเมื่อมีบางอย่างเปลี่ยนไป - รองรับ conditional requests ตอบ
If-None-MatchและIf-Modified-Sinceด้วย304 Not Modifiedเอเจนต์ที่ poll เนื้อหาของคุณจะมีต้นทุนน้อยลงมากเมื่อสามารถ revalidate แทนการดาวน์โหลดทุกอย่างใหม่อีกครั้ง - ทำ breaking changes ด้วยเวอร์ชันใหม่ หาก shape ของ JSON ต้องเปลี่ยน ให้เพิ่ม path เวอร์ชันใหม่หรือ media type ใหม่ และเก็บของเดิมไว้ตามช่วงเวลาที่ประกาศ
- เผยแพร่ changelog สำหรับหน้า automated access ผู้ปฏิบัติการของเอเจนต์ที่ทำงานเรียบร้อยจะอ่านมัน และมันทำให้คุณมีที่สำหรับประกาศ route ที่มีโครงสร้างใหม่
app.get("/api/plans", async (req, res) => {
const plans = await loadPlans();
const etag = `"${plans.version}"`;
res.set("ETag", etag);
res.set("Cache-Control", "public, max-age=300");
if (req.get("if-none-match") === etag) return res.status(304).end();
return res.json({ updatedAt: plans.updatedAt, plans: plans.items });
});แนวปฏิบัติเหล่านี้ยังเป็นประโยชน์ต่อ frontend ของคุณเองและการเชื่อมต่อกับพาร์ตเนอร์ เส้นทางที่มีโครงสร้างที่เสถียร แคชได้ และมีเอกสาร จะกลายเป็นวิธีที่ง่ายที่สุดที่โปรแกรมใดๆ จะใช้ทำงานกับ application ของคุณได้อย่างรวดเร็ว
ข้อผิดพลาดที่พบบ่อย
- ส่งข้อเท็จจริงต่างกันให้เอเจนต์และคน เวอร์ชันที่มีโครงสร้างควรมีข้อมูลเดียวกับหน้าเดิม เนื้อหาที่แตกต่างกันทำให้ผู้ใช้ที่ถามผู้ช่วยเกี่ยวกับ product ของคุณสับสน
- ซ่อนเส้นทางที่มีโครงสร้าง หากไม่ได้ลิงก์จาก
/llms.txt, เอกสาร API หรือหน้าต่างๆ เอง เอเจนต์จะยังคง scrape HTML ต่อไป - ให้รางวัลกับคำกล่าวอ้าง ขีดจำกัดที่สูงขึ้นควรสงวนไว้สำหรับไคลเอนต์ที่ verified แล้ว user agent string ที่คัดลอกมาควรไม่ทำให้ได้งบประมาณที่มากขึ้น
- ลืม cache หากไม่มี
Vary: Accept, cache อาจเสิร์ฟ Markdown ให้กับเบราว์เซอร์ได้
Proof of Actor เข้ามาอยู่ตรงไหน
Proof of Actor ให้ actor class และ basis ที่คุณต้องใช้เพื่อกำหนดเส้นทางเอเจนต์ไปยังเนื้อหาที่มีโครงสร้างและตั้งขีดจำกัดที่เป็นธรรม มันตรวจจับเอเจนต์แบบ signed, declared และ inferred ช่วยให้คุณเข้าใจว่าพวกเขาใช้ route ของคุณอย่างไร และปล่อยการตัดสินใจเรื่อง routing ไว้ในโค้ดของคุณ
หากต้องการออกแบบเส้นทางที่มีโครงสร้างสำหรับ application ของคุณร่วมกับทีมของเรา สมัครขอสิทธิ์เข้าถึงก่อนใคร สำหรับสถานการณ์ API และ MCP โดยเฉพาะ ดู APIs และ MCP Servers และอ่าน ใครกำลังเรียก MCP server ของคุณ? เพื่อดูว่าบริบทของ actor นำไปใช้กับการเรียกเครื่องมือของ Model Context Protocol อย่างไร
แหล่งอ้างอิง
- RFC 9110: HTTP Semantics: content negotiation (Section 12),
Vary(Section 12.5.5), conditional requests (Section 13) และRetry-After(Section 10.2.3) - RFC 7763: The text/markdown Media Type: การเสิร์ฟ Markdown representations
- RFC 8288: Web Linking:
alternatelinks แบบมีชนิด - RFC 6585: Additional HTTP Status Codes, Section 4:
429 Too Many Requests - llms.txt proposal: รูปแบบไฟล์
/llms.txt - OpenAPI Specification: คำอธิบาย API ที่อ่านได้โดยเครื่อง
- Sitemaps protocol: รูปแบบไฟล์ sitemap
- OpenAI: Overview of OpenAI Crawlers: user agents และ IP ranges ที่ OpenAI เผยแพร่
- Anthropic: crawler and fetcher documentation: ClaudeBot, Claude-User และ Claude-SearchBot
- Model Context Protocol specification (2026-07-28): revision ปัจจุบันของ MCP ณ เดือนพฤศจิกายน 2026