为 AI Agent 开放网站:从可读、可发现到可调用与付费

2026-08-06 41 预计阅读时间: 1 分钟
来源: blog.cloudflare.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

网站正在迎来一种新的访问者:AI Agent。它们通常不渲染 CSS、不点击广告,却可能代表一个有明确需求、也愿意付费的人类用户。粗暴封禁所有自动化流量,挡住的不只是爬虫,也可能是正在替客户检索资料、比较服务或执行采购的 Agent。

真正需要解决的问题,不是简单地“允许还是禁止机器人”,而是建立一套让发布者与 Agent 能够合作的接口:内容可读、能力可发现、操作可调用、交易可付费,同时保留身份验证、授权、限流和审计边界。

网页能打开,不等于 Agent 能使用

传统网站围绕浏览器构建。导航藏在 JavaScript 状态里,正文与推荐模块混在 DOM 中,价格可能只有视觉样式,没有稳定的数据字段。人类能通过布局理解页面,Agent 却需要可预测的结构。

面向 Agent 的“可读”不意味着删除 HTML,而是补充机器友好的表达,例如:

  • 为文章、产品和服务提供稳定的 URL 与结构化字段。
  • 明确区分正文、作者、更新时间、价格、许可证和使用限制。
  • 对动态页面提供 JSON 表达,避免要求 Agent 执行完整前端应用。
  • 返回准确的 HTTP 状态码,不用 200 OK 包装所有错误。
  • 在响应中说明内容能否缓存、引用或用于后续处理。

HTML 仍然服务人类,结构化接口则减少 Agent 猜测页面含义的成本。两者可以共享同一个内容源,避免出现两套数据长期漂移。

四个接口层次

标题中的四个关键词可以对应四类工程能力。

可读:提供稳定的数据形状

Agent 应当能直接取得正文、摘要、元数据和约束条件。字段版本需要显式管理,删除或改名时也应有迁移周期。

可发现:公开能力清单

仅仅存在 API 还不够,Agent 必须知道入口在哪里、支持什么动作、需要何种认证。可以通过约定明确的清单地址描述能力。具体路径和格式仍需要生态形成共识,不应把某个自定义 JSON 当成已经确立的行业标准。

可调用:把动作设计成受控工具

搜索、预订、购买和提交任务应成为参数明确的操作。每个操作需要输入约束、幂等策略、超时语义和机器可解析的错误响应。高风险动作还应要求人类确认,不能因为调用方是 Agent 就跳过权限检查。

可付费:让价值交换进入协议

可付费不只是返回一个价格。完整流程还涉及报价有效期、币种、税费、支付授权、退款、收据和争议处理。Agent 可以替用户发起流程,但最终扣款必须受用户授权和支付系统规则约束。

一个可运行的最小服务

下面是一个概念性实践示例,并不代表来源已经规定了这些路径或字段。它使用 Python 标准库实现三个接口:能力发现、结构化内容读取和报价创建。报价只返回模拟结账地址,不会真实扣款。

将代码保存为 agent_service.py,使用 Python 3.10 或更高版本运行:

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
import time
import uuid

ARTICLE = {
    "id": "agent-ready-web",
    "title": "Designing services for agents",
    "summary": "A structured article that agents can retrieve without rendering CSS.",
    "updated_at": "2025-01-15T09:00:00Z",
    "license": "read-only",
}

MANIFEST = {
    "schema_version": "0.1-example",
    "name": "Example Publisher",
    "capabilities": {
        "read_article": {
            "method": "GET",
            "path": "/agent/articles/{id}",
            "authentication": "none",
        },
        "create_quote": {
            "method": "POST",
            "path": "/agent/quotes",
            "authentication": "Bearer token",
        },
    },
}

class Handler(BaseHTTPRequestHandler):
    def send_json(self, status, payload):
        body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        if self.path == "/.well-known/agent.json":
            return self.send_json(200, MANIFEST)
        if self.path == "/agent/articles/agent-ready-web":
            return self.send_json(200, ARTICLE)
        self.send_json(404, {"error": "not_found"})

    def do_POST(self):
        if self.path != "/agent/quotes":
            return self.send_json(404, {"error": "not_found"})
        if self.headers.get("Authorization") != "Bearer demo-token":
            return self.send_json(401, {"error": "invalid_token"})

        length = int(self.headers.get("Content-Length", "0"))
        try:
            request = json.loads(self.rfile.read(length) or b"{}")
        except json.JSONDecodeError:
            return self.send_json(400, {"error": "invalid_json"})

        if request.get("product_id") != "research-report":
            return self.send_json(400, {"error": "unknown_product"})

        quote_id = str(uuid.uuid4())
        self.send_json(201, {
            "quote_id": quote_id,
            "amount": 1900,
            "currency": "USD",
            "expires_at_unix": int(time.time()) + 600,
            "requires_human_confirmation": True,
            "checkout_url": f"https://pay.example.test/checkout/{quote_id}",
        })

if __name__ == "__main__":
    server = ThreadingHTTPServer(("127.0.0.1", 8080), Handler)
    print("Listening on http://127.0.0.1:8080")
    server.serve_forever()

启动服务并依次测试发现、读取和报价接口:

python3 agent_service.py

curl -s http://127.0.0.1:8080/.well-known/agent.json
curl -s http://127.0.0.1:8080/agent/articles/agent-ready-web
curl -s -X POST http://127.0.0.1:8080/agent/quotes \
  -H 'Authorization: Bearer demo-token' \
  -H 'Content-Type: application/json' \
  -d '{"product_id":"research-report","quantity":1}'

这个示例刻意把“报价”和“扣款”分开。生产系统应将 checkout_url 接入真实支付服务,通过签名 webhook 确认付款,并使用幂等键防止 Agent 重试时创建重复订单。

开放接口不等于放弃控制

Agent 流量仍然可能滥用资源,调用者背后有付费用户也不等于每次请求都可信。发布者至少需要处理以下边界:

  • 使用 API Key、OAuth 或短期令牌标识调用方,并按主体授权。
  • 对读取和写入操作设置不同的速率限制与配额。
  • 对购买、删除、发布等动作要求二次确认或人类批准。
  • 记录调用主体、参数摘要、结果和关联交易,避免日志泄露敏感数据。
  • 对外部文本和工具返回值做校验,防止提示注入影响后续动作。
  • 给清单与接口定义版本,保留旧版本的下线窗口。
  • 明确内容许可。可读取不自动意味着可训练、可转载或可永久存储。

识别 Agent 时也不能只依赖 User-Agent 请求头,因为它很容易伪造。更可靠的方案需要令牌、签名请求、调用方注册以及异常行为检测共同参与。

从低风险入口开始

接入 Agent 不必一开始就开放支付和写操作。更稳妥的采用顺序是:先发布结构化只读内容,再增加能力清单和搜索接口;当认证、限流与审计稳定后,再开放报价、预订等可逆操作;真实扣款、内容发布和账号变更则应放在明确的人类授权之后。

上线前可以检查四件事:Agent 是否能在不渲染前端的情况下理解内容,是否能发现接口及其约束,是否能以幂等方式调用动作,以及支付是否保留清晰的用户授权、收据和退款路径。做到这些,网站面对的就不再是一团难以区分的自动化流量,而是一类可以被识别、约束并服务的新客户入口。


相关推荐