数据截至 (上游 commit 5284672feb57)
AI-SDK 工具与 Agent · 跨端共享的提示词设计
30 秒导读: 这一章讲
packages/tools-ai-sdk—— 它把 Context7 的两个动作(把库名解析成库 ID、再按库 ID 拉文档)包装成 Vercel AI SDK 的tool(),再把它们塞进一个会自动多步循环的Context7Agent。但真正的主角不是这几十行胶水代码,而是提示词:同一套「先 resolve 再 query、每问最多 3 次、库 ID 必须是/org/project、一次只查一个概念」的规则,被有意地抄写到三处不同载体(这里的 prompts、02 章的 MCP 工具 description、05 章的 skill/rule)——它其实是一份跨端复用的「工作流契约」。
1. 这是什么(零基础也能懂)
一句话定义: @upstash/context7-tools-ai-sdk 是一个小包,让你用一行代码就能把 Context7 的查文档能力,接进任何基于 Vercel AI SDK 写的 Agent。
解决什么问题 / 给谁用: 假设你在用 AI SDK 写一个自己的编码助手(generateText({ model, tools }) 那套)。你想让它能查到最新的库文档,又不想自己去调 Context7 的 HTTP 接口。这个包给你两样现成的东西:
- 两个开箱即用的工具对象(
resolveLibraryId()/queryDocs()),直接丢进你的tools: {}。 - 一个开箱即用的 Agent(
Context7Agent),连多步循环和系统提示都替你配好了。
用起来什么样: 最小用法就是把两个工具塞进 generateText,SDK 会自己决定何时调用它们。
// 摘自 src/tools/resolve-library-id.ts:16-31 的 JSDoc 示例(真实可跑)
import { resolveLibraryId, queryDocs } from '@upstash/context7-tools-ai-sdk';
import { generateText, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
const { text } = await generateText({
model: openai('gpt-4o'),
prompt: 'Find React documentation about hooks',
tools: {
resolveLibraryId: resolveLibraryId(), // 库名 → 库 ID
queryDocs: queryDocs(), // 库 ID → 文档
},
stopWhen: stepCountIs(5), // 最多循环 5 步
});
一句话直觉: 模型自己不知道 /facebook/react 这个内部 ID 长什么样,所以永远得先问路(resolve)再取货(query)。这个包做的,就是把「问路」「取货」两个动作包成模型能调的按钮,再用一段提示词反复叮嘱它「按顺序按、别乱按、别按太多次」。
本章不重述这两个工具底层怎么发 HTTP 请求、认证怎么走——那是最底层的 SDK 与 API 契约,见 01 章。这里只讲「怎么把 SDK 调用接进 AI SDK」和「提示词怎么设计」。
2. 顶层全景(它大概怎么转)
这个包一共就三层,加一份贯穿全场的提示词。 先看文件职责:
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
resolveLibraryId() / queryDocs() | 用 AI SDK 的 tool() 把 SDK 调用包成工具 | src/tools/resolve-library-id.ts、src/tools/query-docs.ts |
Context7ToolsConfig | 工具/Agent 的配置类型(就一个可选 apiKey) | src/tools/types.ts:4 |
Context7Agent | 继承 ToolLoopAgent,把两工具 + 提示词 + 循环上限打包 | src/agents/context7.ts:38 |
| 提示词一组 | SYSTEM_PROMPT / AGENT_PROMPT / 两个工具 *_DESCRIPTION | src/prompts/system.ts |
| 统一出口 | 把上面全部 re-export,外加 SDK 的类型 | src/index.ts |
主线走一遍(高层,不进代码): 一次「查 React hooks 文档」的调用是这样流动的——
用户 prompt: "Find React hooks docs"
│
▼
┌─────────────────────────────┐
│ Context7Agent (ToolLoopAgent)│ ← 注入 AGENT_PROMPT 当 instructions
│ 循环上限 = stepCountIs(5) │
└─────────────────────────────┘
│ 模型读提示词,决定第 1 步
▼
① resolveLibraryId("React") ──► 返回候选库列表(含 /reactjs/react.dev 等)
│ 模型挑出最佳库 ID
▼
② queryDocs("/reactjs/react.dev", "hooks") ──► 返回文档片段
│
▼
③ 模型综合文档,写出带代码示例的回答
怎么读这张图: 从上到下是时间顺序。①②是模型在循环里自己发起的两次工具调用,③是循环结束后的最终作答。「先①后②」不是代码硬编码的,而是提示词逼出来的——这正是下一节的重点。
3. 核心机制(逐个,由浅入深)
3.1 用 tool() 把一个 SDK 调用包成 AI SDK 工具
它要解决的小问题: AI SDK 认的「工具」是一个特定形状的对象——要有 description(告诉模型这工具干嘛)、inputSchema(参数的 Zod schema)、execute(真正执行的函数)。而 Context7 SDK 给的是一个普通的 client.searchLibrary(...) 异步调用。中间需要一层适配。
思路: 写一个工厂函数 resolveLibraryId(config),内部 new Context7({ apiKey }),然后用 SDK 的 tool() 把三件套拼出来。参数校验交给 Zod,执行体里 try/catch 兜底,失败也返回字符串(而不是抛异常),这样模型能读到错误信息、自行重试。
真实实现:
// src/tools/resolve-library-id.ts:33-66 resolveLibraryId
export function resolveLibraryId(config: Context7ToolsConfig = {}) {
const { apiKey } = config;
const getClient = () => new Context7({ apiKey }); // 无 key 时回落到环境变量
return tool({
description: RESOLVE_LIBRARY_ID_DESCRIPTION, // ← 提示词从 @prompts 注入
inputSchema: z.object({ query: z.string()..., libraryName: z.string()... }),
execute: async ({ query, libraryName }) => {
try {
const results = await client.searchLibrary(query, libraryName, { type: "txt" });
if (!results || results.length === 0) return `No libraries found ...`; // 字符串,非异常
return results;
} catch (error) { return `Error searching for libraries: ...`; }
},
});
}
queryDocs 是同一个模子(src/tools/query-docs.ts:33-68),只 是把 searchLibrary 换成 client.getContext(query, libraryId, ...)。
几个值得记的细节:
inputSchema里的.describe()也是提示词。 参数说明不是给人看的注释,而是喂给模型的指令。例如libraryId的说明直接写死了格式契约:Exact Context7-compatible library ID (e.g., '/mongodb/docs', ...) ... in the format '/org/project' or '/org/project/version'见src/tools/query-docs.ts:41-44。query参数的说明更是把「单概念一次查询」的规则写进了 schema:src/tools/query-docs.ts:45-49。- 配置只有一个字段。
Context7ToolsConfig全部内容就是可选的apiKey(src/tools/types.ts:4-9);不传就用CONTEXT7_API_KEY环境变量。 - 错误不抛,返回字符串。 让模型「看见」错误、在循环里自愈,而不是让整个
generateText崩掉。
3.2 Context7Agent:把循环、工具、提示词一次打包
它要解决的小问题: 3.1 的两个工具还得用户自己塞进 generateText、自己设循环上限、自己写系统提示。对只想「拿来即用」的人太啰嗦。
思路: 继承 AI SDK 的 ToolLoopAgent(自带「调工具 → 读结果 → 再决定」的多步循环),在构造函数里把默认值 全填好。
真实实现:
// src/agents/context7.ts:38-63 Context7Agent
export class Context7Agent extends ToolLoopAgent<never, ToolSet> {
constructor(config: Context7AgentConfig) {
const {
model,
stopWhen = stepCountIs(5), // ① 默认最多循环 5 步
instructions,
apiKey,
tools,
...agentSettings
} = config;
super({
...agentSettings,
model,
instructions: instructions || AGENT_PROMPT, // ② 默认注入 AGENT_PROMPT
tools: {
...tools, // ③ 允许用户加自己的工具
resolveLibraryId: resolveLibraryId({ apiKey }),
queryDocs: queryDocs({ apiKey }),
},
stopWhen,
});
}
}
三个关键设计,对号入座:
| 记号 | 机制 | 作用 |
|---|---|---|
| ① | stopWhen = stepCountIs(5) | 用「步数」当循环刹车,防止模型无限调工具;用户可覆盖 |
| ② | instructions || AGENT_PROMPT | 不传就注入内置的强约束工作流提示;传了就完全尊重用户的 |
| ③ | { ...tools, resolveLibraryId, queryDocs } | 用户自带的工具和 Context7 两工具并存,而非二选一 |
stepCountIs(5) 和「≤3 次」是两回事,别混。 stepCountIs 是代码层的硬刹车(SDK 数循环步数);而「每个工具每问不超过 3 次」是提示词层的软约束(靠模型自觉,见 3.3)。一硬一软,双保险。
apiKey 的来源链: Context7Agent 的 apiKey → 传给 resolveLibraryId({ apiKey }) / queryDocs({ apiKey }) → 传给 new Context7({ apiKey }) → 若为 undefined,SDK 内部回落到 CONTEXT7_API_KEY 环境变量(HTTP/认证细节见 01 章)。
3.3 横切主线:提示词工程 = 把「工作流」写成自然语言约束
这是本章真正的核心。 前面的工具/Agent 只是骨架;让它可靠工作的,是 src/prompts/system.ts 里那几段字符串。它们把一套流程规则翻译成模型能读懂的强指令。
为什么需要它: 模型很聪明,但没约束时会「抄近路」——比如跳过 resolve 直接猜库 ID、或反复对同一问题狂调工具。提示词的活,就是把这些坏习惯一条条堵死。
看 AGENT_PROMPT 怎么下命令(src/prompts/system.ts:20-47):
- 开头就是全大写的
CRITICAL WORKFLOW - YOU MUST FOLLOW THESE STEPS,把四步(resolve → 选最佳库 → query → 带例作答)编号列死。 - 第 2 步给出选库的打分维度:官方源、名字相似度、描述相关性、源可信度(High/Medium 更好)、代码片段覆盖、benchmark 分。
- 结尾
IMPORTANT三连:必须先resolveLibraryId、不准跳过它、每个工具每问不超过 3 次、永远注明用了哪个库 ID。
同一套规则,还散落 在工具 description 里当「就近提醒」。 除了顶层的 AGENT_PROMPT,每个工具自己的 description 也复述关键约束——因为模型在决定要不要调这个工具的那一刻,读到的是工具 description,而不是遥远的系统提示。
| 规则 | 在 AGENT_PROMPT 里 | 在工具 *_DESCRIPTION 里 |
|---|---|---|
| 先 resolve 再 query | 第 1 步 + IMPORTANT 首条 | You MUST call this function before 'queryDocs' ...(system.ts:54) |
库 ID = /org/project | 隐含在示例里 | description 明写 /org/project' or '/org/project/version'(system.ts:54、80) |
| 每问 ≤ 3 次 | Do not call either tool more than 3 times(system.ts:46) | 两个 description 各自复述(system.ts:73、82) |
| 用户已给库 ID 则可跳过 resolve | —— | UNLESS the user explicitly provides a library ID ...(system.ts:54) |
还有一个更轻量的 SYSTEM_PROMPT(src/prompts/system.ts:8-15): 三句话版本,给「不需要强流程、只想要个文档助手」的场景。AGENT_PROMPT 是它的「加强约束版」。这两者的分工,本身就体现了「提示词按场景分级」的思路。
4. 「工作流契约」:同一套规则,跨三种载体复用
把视野拉高:Context7 把「怎么正确用这两个工具」当成一份契约,在整个仓库里反复抄写到不同载体。 这不是重复劳动,而是刻意为之——因为同一个 Agent 规则要在不同运行时(AI SDK / MCP 客户端 / 各家 skill 系统)都生效,而这些运行时读提示词的地方各不相同。
同一条规则,三处措辞落地:
| 规则 | 04 章:本包 prompts | 02 章:MCP 工具 description | 05 章:skill / rule 自然语言 |
|---|---|---|---|
| 载体 | AGENT_PROMPT + 工具 description(src/prompts/system.ts) | registerTool 的 description(packages/mcp/src/index.ts:140,227) | SKILL.md 的 Workflow / c7-docs.md 步骤 |
| 先 resolve 再 query | 「Step 1 ALWAYS ... resolveLibraryId」 | 「You MUST call this function before 'Query Documentation'」(:140) | 「1. Resolve the library ID. 2. Query the docs.」 |
| 库 ID 格式 | description 内联示例 | 「format: /org/project」(mcp index.ts:143,239) | 「their Context7 IDs (/org/project format)」 |
| 每问 ≤ 3 次 | 「more than 3 times per question」 | 「Do not call this tool more than 3 times」(:170,229) | 「Do not call either tool more than 3 times per question」 |
| 单概念一次查询 | query 参数 .describe()(query-docs.ts:45-49) | 参数 description(mcp index.ts:239) | 「scoped to a single concept ... separate call per concept」 |
| 已给 ID 可跳过 resolve | 「UNLESS the user explicitly provides ...」 | 同措辞(:140,227) | 「If the user supplies a library ID ... skip step 1」 |
这份契约是被「同步」维护的,不是巧合。 pi 包在自己的 prompts 文件顶部写了一行注释,把这件事挑明:
// packages/pi/lib/prompts.ts:1-4
// Tool titles, descriptions, and parameter descriptions are copied verbatim
// from @upstash/context7-mcp (packages/mcp/src/index.ts) so pi and MCP clients
// give the LLM identical instructions. Update both together when tweaking guidance.
一句话:「先 resolve 再 query、每问≤3 次、库 ID=/org/project、单概念一次查询」是 Context7 的产品级规则,提示词只是它在某个载体上的一次投影。 想改行为,得同时改所有投影(见 05 章讲的「分发即产品」)。
小注意: 三处措辞并非逐字相同。本包
AGENT_PROMPT是为 AI SDK 场景重写的四步版;MCP description 更长(带完整字段清单);skill 是给「宿主 Agent」读的散文。契约一致,措辞随载体裁剪——这正是提示词工程的手艺所在。
5. pi:pi.dev 上的一层极薄封 装
packages/pi 是把同两个工具接进 pi.dev 编码 Agent 的扩展,整个入口只有注册两行——pi.registerTool(resolveLibraryIdTool) / pi.registerTool(queryDocsTool)(packages/pi/extensions/context7.ts:5-8);它不复用本包,而是用 typebox 写自己的工具、并把工具描述逐字抄自 MCP(见上一节注释),是「同一份工作流契约 + 又一种载体」的最小示例。
6. 边界与局限
- 工具本身不判「够不够好」。 「≤3 次就收手」「选最佳库」全靠模型读提示词自觉;代码层只有
stepCountIs一道硬刹车,数的是总步数而非每工具调用数——真要让某工具刚好卡在 3 次,提示词是唯一防线。 - 无重试/无缓存/无并发控制。
execute出错就返回一句错误字符串,交给模型自己决定要不要再试;这一层不做退避或去重。 - 提示词是「建议」不是「强制」。 模型可以违背
AGENT_PROMPT(比如跳过 resolve);包不做运行时校验来拦截违规调用。 - 依赖较新的 AI SDK。 peerDependency 要求
ai >= 6.0.0、zod >= 4.0.0(packages/tools-ai-sdk/package.json:31-35),ToolLoopAgent/stepCountIs是 v6 的 API。 ./agent单独出口。 除主入口外还构建了./agent子出口(tsup.config.ts:4-7),让只想要 Agent 的人少打包工具代码。
7. 横向对比(同库兄弟章)
- 想知道这两个工具底层怎么跟后端对话(HTTP、认证、
/org/project从哪来)→ 01 章 SDK 与 API 契约。 - 想看同一套工具在 MCP 服务器里怎么暴露、传输与安全怎么做、tool description 的完整版长什么样 → 02 章 MCP 服务器。
- 想要命令行里直接查文档/一键接入 → 03 章 ctx7 CLI。
- 想理解这份「工作流契约」如何以 Skills / Plugins / Rules 的形态分发到各家 Agent → 05 章 分发即产品。
- 全景与阅读地图 → index。
8. 代码地图(导航索引)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 统一出口(工具/Agent/prompts + SDK 类型) | packages/tools-ai-sdk/src/index.ts | resolveLibraryId queryDocs Context7Agent SYSTEM_PROMPT AGENT_PROMPT |
resolve 工具(tool() 包装) | packages/tools-ai-sdk/src/tools/resolve-library-id.ts:33 | resolveLibraryId |
query 工具(tool() 包装) | packages/tools-ai-sdk/src/tools/query-docs.ts:33 | queryDocs |
| 工具配置类型 | packages/tools-ai-sdk/src/tools/types.ts:4 | Context7ToolsConfig |
| 多步 Agent | packages/tools-ai-sdk/src/agents/context7.ts:38 | Context7Agent / stepCountIs |
| 强约束工作流提示词 | packages/tools-ai-sdk/src/prompts/system.ts:20 | AGENT_PROMPT |
| 精简版系统提示词 | packages/tools-ai-sdk/src/prompts/system.ts:8 | SYSTEM_PROMPT |
| 工具 description(契约投影) | packages/tools-ai-sdk/src/prompts/system.ts:52,78 | RESOLVE_LIBRARY_ID_DESCRIPTION / QUERY_DOCS_DESCRIPTION |
| pi 薄封装入口 | packages/pi/extensions/context7.ts:5 | context7 |
| pi「逐字抄自 MCP」注释 | packages/pi/lib/prompts.ts:1 | (文件头注释) |
| 契约的 MCP 投影(对比用) | packages/mcp/src/index.ts:140,227 | tool description |