给AI Agent单独建网站:按需检索技能文档,告别长Prompt上下文污染
发布时间:2026/10/14 9:25:52 锦皓数字建站

1. 为什么我要给 Agent 单独做一个网站先说结论这个网站不是给人看的是给 AI Agent 看的。它解决的核心问题是——当你的 Agent 需要调用外部能力时不再靠一段又长又臭的 system prompt 硬塞而是像人查手册一样按需检索、按需加载。我做这个东西的起因很直接。过去大半年我一直在折腾各种 Agent 工作流从最简单的单轮工具调用到多步规划、多 Agent 协作。踩得最深的坑不是模型不够聪明而是上下文污染和能力漂移。你给 Agent 塞 20 个工具的说明它到第 8 轮对话时已经忘了第 3 个工具的参数格式你给它写 3000 字的操作规范它执行到一半开始自由发挥。这不是模型的问题是信息组织方式的问题。传统做法是把所有能力描述、参数 schema、示例、边界条件全部塞进 system prompt 或者 function calling 的 definitions 里。这种做法在工具数量少于 5 个、流程少于 3 步时还能凑合一旦规模上去token 消耗爆炸不说模型注意力被稀释得厉害。我实测过一个场景12 个工具、每个工具平均 200 字描述光工具定义就吃掉 2400 token模型在第二轮就开始混淆两个参数名相似的接口。所以我换了个思路把 Agent 需要的能力做成一个可检索的网站。Agent 不需要一次性知道所有东西它只需要知道“去哪里找”和“怎么问”。网站本身承载结构化的技能文档skills每个 skill 是一个独立页面包含用途、输入输出、调用示例、常见错误、边界条件。Agent 通过一个极简的检索接口按需拉取拉取到的内容直接注入当前上下文用完即弃。这个方案的核心价值有三个。第一上下文按需加载token 消耗从 O(全部工具) 降到 O(当前任务相关工具)。第二技能可独立迭代改一个 skill 不影响其他 skill不需要重新调 prompt。第三Agent 行为可观测它检索了什么、加载了什么、执行结果如何全都有日志可查排查问题从“猜模型为什么抽风”变成“看它查了哪页文档”。适合谁来参考如果你正在做 Agent 工具调用、多步任务规划、或者任何需要让模型稳定执行外部能力的场景这套思路可以直接抄。如果你只是玩单轮问答那暂时用不上。下面我把整个设计思路、核心细节、实操过程和踩坑记录全部拆开讲。2. 整体架构设计与核心思路拆解2.1 为什么是“网站”而不是“向量库”或“长 prompt”很多人第一反应是要做按需检索上向量数据库啊RAG 不就是干这个的我一开始也试过。把技能文档切片、embedding、存向量库Agent 用自然语言查询返回 top-k 片段。实测下来有三个致命问题。第一切片破坏结构。一个 skill 的“参数说明”和“调用示例”被切到两个 chunk 里Agent 检索到参数说明却没拿到示例调用时参数格式全错。你可能会说那就按 skill 粒度切不切细。但 skill 粒度切了之后检索精度直线下降因为整个 skill 的 embedding 被平均化了查询“如何处理超时”可能返回一个完全不相关的 skill。第二向量检索不可解释。Agent 为什么选了这个 skill 没选那个你只能看相似度分数没法追溯。而网站方案里Agent 的检索路径是明确的它访问了/skills/timeout-handling加载了这页执行了里面的步骤。出问题直接看访问日志。第三维护成本。向量库要重新 embedding、要调 chunk size、要处理版本一致性。网站就是静态页面加一个检索接口改内容直接改 Markdown重新部署即可版本用 git 管回滚就是 checkout。至于长 prompt前面已经说了token 和注意力双重爆炸不在一个量级上。所以最终选型静态站点 结构化 skill 页面 极简检索 API。站点用 Markdown 写内容构建时生成 HTML 和一份 JSON 索引。Agent 不直接读 HTML而是通过检索 API 拿 JSON 格式的 skill 内容这样解析成本最低。2.2 核心数据模型一个 skill 到底包含什么这是整个项目最关键的决策。skill 的字段设计直接决定了 Agent 能不能稳定执行。我迭代了四个版本最终定下来的结构如下。字段类型是否必填作用idstring是唯一标识用于检索和引用namestring是人类可读名称intentstring是一句话说明这个 skill 解决什么问题when_to_usearray是触发条件列表Agent 据此判断是否加载when_not_to_usearray是排除条件防止误用inputsobject是输入参数 schema含类型、必填、默认值outputsobject是输出结构说明stepsarray是执行步骤每步含动作和预期结果examplesarray是至少一个完整调用示例failure_modesarray是常见失败模式及处理方式versionstring是语义化版本号重点说几个容易被忽略的字段。when_not_to_use是我踩坑之后加的。早期只有when_to_use结果 Agent 看到“处理文件”就加载文件 skill哪怕当前任务是处理数据库记录。加了排除条件后误加载率从 23% 降到 6% 左右。failure_modes也是血泪教训。Agent 执行失败时如果没有明确的失败处理指引它会自己编一个处理方式往往越编越错。现在每个 skill 强制要求写至少两条失败模式比如“接口返回 429 时等待 2 秒重试最多 3 次仍失败则上报”。steps的设计有个细节每步必须包含“预期结果”。这样 Agent 执行完一步可以自检不符合预期就停下来而不是闷头往下走。这个改动让多步任务的完成率提升了将近 40%。2.3 检索接口的设计让 Agent 用最少的 token 找到对的东西检索接口我设计得极其克制只有两个端点。第一个是GET /api/skills返回所有 skill 的轻量索引每个 skill 只含 id、name、intent、when_to_use 的摘要。这个索引很小50 个 skill 也就 3KB 左右Agent 可以一次性加载用来做粗筛。第二个是GET /api/skills/{id}返回完整 skill 内容。Agent 粗筛之后只拉取真正需要的 skill 全文。为什么不做一个自然语言搜索接口因为实测下来让 Agent 自己根据轻量索引做判断比让它构造查询语句再检索更稳。Agent 擅长的是“读列表做选择”不擅长“构造精确查询”。轻量索引直接给它它扫一眼就能选出相关的两三个然后精确拉取。这里有个关键优化轻量索引里的when_to_use只保留最核心的三条每条不超过 15 个字。太长会撑大索引太短又失去区分度。15 字是我测出来的甜点值。另外检索接口返回的内容全部是纯 JSON不带任何 HTML 标签、不带导航、不带页脚。Agent 拿到就能直接解析不需要清洗。这一点很重要很多站点给 Agent 返回带一堆导航栏的 HTMLtoken 浪费严重。3. 核心细节解析与实操要点3.1 skill 内容怎么写才能让 Agent 不跑偏写 skill 文档和写人类文档是两回事。人类能容忍模糊Agent 不能。我总结了四条硬规则。第一条动词开头一句话说清动作和对象。不要写“文件处理相关功能”要写“读取指定路径的文本文件并返回内容”。Agent 对动词敏感对名词堆砌不敏感。第二条参数说明必须带类型和示例值。不要写“path文件路径”要写“pathstring必填示例/data/input/report.txt”。Agent 看到示例值会直接模仿格式没有示例它就开始猜。第三条步骤之间要有明确的依赖标注。如果第 3 步依赖第 2 步的输出必须写“使用第 2 步返回的 file_id 作为输入”。不写的话Agent 可能跳过第 2 步直接编一个 file_id。第四条失败模式要写“如果……则……”。不要写“注意处理异常”要写“如果返回 404则检查路径是否存在不存在则上报用户”。条件加动作Agent 才能执行。我举个实际对比。早期版本我写的是调用天气接口获取数据注意处理各种错误情况。Agent 执行时遇到超时它自己编了个“重试 10 次”的逻辑直接把接口打挂了。改成调用天气接口。如果 3 秒内无响应等待 1 秒后重试最多 2 次。如果返回 5xx直接上报不重试。之后同类问题再没出现过。3.2 版本管理与兼容性skill 改了老 Agent 怎么办这是很多人会忽略的问题。skill 不是写完就完了它会迭代。参数加了、步骤改了、返回值结构变了正在运行的 Agent 怎么办我的方案是语义化版本 兼容窗口。每个 skill 有 version 字段遵循 major.minor.patch。破坏性变更升 major新增字段升 minor修文案升 patch。检索接口支持?version参数。Agent 在初始化时声明自己依赖的版本范围比如1.2.0 2.0.0。服务端返回匹配的最新版本。如果请求的版本范围没有匹配项返回 409 并附带可用版本列表Agent 据此决定是降级使用还是上报。同时维护一个deprecated标记。旧版本 skill 在 major 升级后保留 30 天标记为废弃但可访问给 Agent 侧留出升级时间。30 天后返回 410。这套机制听起来重但实现很简单就是构建时生成多版本 JSON路由层做版本匹配。我实测下来Agent 侧几乎不需要改代码只要在配置里声明版本范围即可。3.3 缓存策略别让 Agent 每次都重新拉取Agent 执行任务时可能反复调用同一个 skill。如果每次都走网络请求延迟和 token 都浪费。我在 Agent 侧加了一层本地缓存。缓存 key 是skill_id versionvalue 是完整 skill JSON。TTL 设为 10 分钟。为什么是 10 分钟因为 skill 更新频率很低10 分钟内基本不会变同时 10 分钟也足够覆盖一个典型任务的执行周期。缓存失效策略是写时失效。当服务端 skill 更新时通过一个轻量的通知机制Agent 下次请求时带If-None-Match服务端返回 304 或新内容来触发更新。不用 websocket不用轮询就靠 HTTP 缓存语义简单可靠。实测数据加缓存前一个 8 步任务平均发起 14 次 skill 拉取加缓存后降到 3 次。token 消耗降低约 35%。注意缓存一定要带版本号。我早期缓存 key 只用 skill_id结果 skill 更新后 Agent 还在用旧缓存排查了半天才发现是缓存没失效。4. 实操过程与核心环节实现4.1 从零搭建目录结构与构建流程整个项目就是一个静态站点生成器加一个薄 API 层。目录结构如下。agent-skills-site/ ├── skills/ # skill 源文件Markdown frontmatter │ ├── file-read.md │ ├── http-request.md │ └── ... ├── build/ # 构建脚本 │ ├── generate-index.js # 生成轻量索引 │ └── generate-full.js # 生成完整 JSON ├── api/ # API 层 │ └── server.js # 极简 HTTP 服务 ├── dist/ # 构建产物 │ ├── index.json │ └── skills/ │ ├── file-read.json │ └── ... └── package.jsonskill 源文件用 Markdown 加 YAML frontmatter。frontmatter 放结构化字段正文放详细说明和示例。构建时解析 frontmatter 生成 JSON正文转成纯文本附在 JSON 的detail字段里。构建命令就两条node build/generate-index.js node build/generate-full.js第一条扫描skills/目录提取每个文件的 frontmatter生成dist/index.json。第二条生成每个 skill 的完整 JSON 到dist/skills/。API 层用 Node 原生 http 模块不引框架。两个路由读文件返回 JSON加 CORS 头和缓存头。整个 server.js 不到 80 行。4.2 一个完整 skill 的编写示例以“读取文本文件”为例源文件skills/file-read.md内容如下。--- id: file-read name: 读取文本文件 intent: 读取指定路径的文本文件并返回其内容 when_to_use: - 需要获取本地文件内容 - 用户提供了文件路径 - 任务涉及分析文档 when_not_to_use: - 文件在远程服务器 - 需要写入文件 - 路径是目录而非文件 inputs: path: type: string required: true example: /data/input/report.txt encoding: type: string required: false default: utf-8 outputs: content: type: string description: 文件文本内容 size: type: integer description: 文件字节数 steps: - action: 检查 path 是否存在 expected: 返回布尔值 - action: 检查 path 是否为文件 expected: 返回布尔值若为目录则报错 - action: 按 encoding 读取内容 expected: 返回字符串 - action: 统计字节数 expected: 返回整数 examples: - input: { path: /data/input/report.txt } output: { content: 季度报告..., size: 2048 } failure_modes: - condition: 文件不存在 action: 返回错误码 FILE_NOT_FOUND提示用户检查路径 - condition: 无读取权限 action: 返回错误码 PERMISSION_DENIED提示用户检查权限 - condition: 编码不支持 action: 回退到 utf-8 并记录警告 version: 1.2.0 ---正文部分写详细说明比如为什么先检查存在再检查类型、大文件如何处理、编码回退的具体逻辑。这部分会被转成纯文本放进detail字段Agent 在需要深入理解时加载。4.3 Agent 侧的调用流程Agent 侧的逻辑分四步。第一步初始化时拉取GET /api/skills拿到轻量索引缓存在内存。第二步接到任务后扫描索引中的when_to_use选出候选 skill。选择逻辑很简单任务描述与when_to_use有任意一条语义匹配即入选。这里不用 embedding用关键词匹配加简单规则就够因为when_to_use本身就是精炼过的。第三步对候选 skill 逐个拉取完整内容GET /api/skills/{id}?version1.0.0检查when_not_to_use是否命中。命中则排除未命中则加载。第四步按steps执行每步执行后对照expected自检。不符合预期则查failure_modes按指引处理。整个流程里Agent 的 prompt 只需要包含一段极简的“如何检索 skill”的说明大约 200 字。相比之前塞 2000 字工具定义上下文干净了不止一个量级。4.4 参数计算token 消耗对比实测我拿一个真实任务做了对比测试。任务读取文件、调用接口、解析返回、写入结果共 4 个能力。传统方案4 个工具定义全部塞 prompt每个约 250 字合计 1000 字约 1500 token。每轮对话都携带8 轮对话就是 12000 token。网站方案轻量索引约 400 token一次性加载。执行时按需拉取 4 个 skill 全文每个约 300 token合计 1200 token。缓存命中后不再重复拉取。总计约 1600 token。token 消耗降低约 87%。而且这还没算注意力稀释带来的准确率提升。实测任务完成率从 71% 提升到 94%。5. 常见问题与排查技巧实录5.1 Agent 加载了错误的 skill 怎么办这是最高频的问题。排查顺序如下。先看轻量索引里的when_to_use是不是写得太宽泛。比如“处理数据”这种描述几乎什么任务都能匹配上。改成“对 CSV 文件做行列筛选”就精确多了。再看when_not_to_use是不是漏了关键排除项。我遇到过一个案例Agent 在“读取远程文件”任务里加载了本地文件 skill因为when_not_to_use里没写“文件在远程服务器”。补上之后问题消失。最后看 Agent 的选择逻辑是不是太激进。如果它只要有一条匹配就加载容易误选。改成“至少两条匹配才加载”误加载率明显下降。5.2 skill 更新后 Agent 行为异常九成是缓存问题。检查 Agent 侧缓存 key 是否包含版本号。如果只用了 skill_id旧缓存不会失效。其次是版本范围声明问题。如果 Agent 声明1.0.0服务端返回了 2.0.0而 2.0.0 有破坏性变更Agent 就会异常。解决办法是收紧版本范围或者让 Agent 在加载后校验 version 字段是否符合预期。还有一个隐蔽问题构建产物没更新。改了 skill 源文件但忘了跑构建脚本dist 里还是旧内容。建议在 CI 里加一步校验源文件 hash 与 dist hash 不一致就报错。5.3 常见问题速查表现象可能原因排查动作解决方式Agent 不加载任何 skill索引拉取失败检查 API 可达性加超时重试失败则降级加载了不相关 skillwhen_to_use 过宽审查触发条件收窄描述加排除项执行到一半卡住steps 缺预期结果检查步骤定义每步补 expected 字段参数格式错误inputs 缺示例值检查 schema每个参数补 example失败后乱处理failure_modes 缺失检查失败模式至少补两条条件动作更新后行为异常缓存未失效检查缓存 keykey 加版本号版本不匹配版本范围过宽检查声明范围收紧范围或加校验5.4 几个我踩过的坑第一个坑早期我把 skill 正文写得很长觉得越详细越好。结果 Agent 加载后正文里的“背景介绍”“设计理念”这些内容干扰了它执行。后来我把正文严格限制为“补充说明”只写步骤里放不下的细节比如边界条件、性能注意事项。正文长度控制在 500 字以内。第二个坑steps里用了“然后”“接着”这种连接词。Agent 对连接词不敏感它需要的是明确的序号和依赖标注。改成“第 1 步”“第 2 步使用第 1 步的输出”之后执行顺序再没乱过。第三个坑检索接口返回了带缩进的 JSON。Agent 解析没问题但 token 浪费在空格上。改成紧凑 JSON 后同样内容省了约 15% token。别小看这点规模上去很可观。第四个坑没做限流。Agent 在死循环里反复拉取同一个 skill把服务打挂了。后来加了简单的令牌桶限流每个 Agent 实例每分钟最多 60 次请求。正常任务根本用不到这个量触发限流基本就是出问题了顺便当告警用。6. 这套方案还能怎么扩展目前这套东西跑在我自己的几个 Agent 工作流上稳定运行了几个月。后续我打算往三个方向扩展。第一个方向是skill 组合。现在 skill 是独立的但实际任务往往需要多个 skill 串联。我打算加一个composite类型的 skill它的 steps 里直接引用其他 skill 的 idAgent 加载一个组合 skill 就能拿到完整流程。这样常见的多步任务可以预编排减少 Agent 的规划负担。第二个方向是执行反馈回流。Agent 执行 skill 的成功率、失败模式分布这些数据目前只存在日志里。我想把它们聚合起来自动标记出“高失败率 skill”提醒我优化。更进一步如果某个 failure_mode 频繁触发可以自动生成一个补丁建议。第三个方向是skill 的自动生成。现在 skill 是手写的写一个熟练了大概 15 分钟。如果能把常见的 API 文档、操作手册自动转成 skill 格式效率会高很多。这个还在探索核心难点是自动提取when_to_use和failure_modes这两块目前还得靠人判断。最后分享一个小心得skill 的intent字段我建议用“给 5 岁小孩解释”的标准来写。越简单直接Agent 判断越准。我有个 skill 的 intent 从“执行 HTTP 请求并处理响应”改成“帮我去网上拿个东西回来”匹配准确率反而提升了。Agent 对口语化、动作化的描述理解得比术语堆砌好得多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。