Appearance
tool-calling-rag-streaming-demo
1. 实验目标
这个实验刻意把 tool calling / RAG / SSE 流式前端 三件事压成一个最小闭环,不新建前端工程,也不接真实大模型:
- Tool calling:先声明
search_docs工具与 JSON Schema,再由“模型决策层”决定是否调用。 - RAG:对仓库
docs/做最简 chunk + 关键词向量检索,按 Top-K 取回上下文。 - 流式前端 demo:服务端用 SSE 推送
tool_call/tool_result/token/done事件,客户端按 token 增量打印。
它不是“像 ChatGPT 那样完整”,而是回答一个更工程化的问题:最小 Agent 闭环到底要经过哪些可验证环节。
2. 工程要点
- 工具声明与执行分离:
tools.search_docs只定义 schema 和执行函数,真正的“是否调用”由streamChat决策。 - RAG 不接真实 embedding 服务,而是用关键词统计 + 余弦相似度,保留
chunk → top-k → source attribution的工程骨架。 - SSE 事件流明确区分四类事件:
tool_call:展示为什么要调工具tool_result:展示检索命中结果token:模拟打字机流式输出done:携带引用来源,便于前端收口
3. 关键实现速览
3.1 工具声明与调用决策
为什么看这段:Tool calling 的关键不在“会调函数”,而在“工具契约”和“模型决策”分层。
js
const tools = {
search_docs: {
description: '搜索 docs 目录中的 Markdown 文件,返回匹配片段',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string' },
limit: { type: 'number' },
},
required: ['query'],
},
run: ({ query, limit = 3 }) => searchDocs(query, limit),
},
};完整源码:server.js
3.2 最小 RAG:chunk + 余弦相似度
为什么看这段:这里没有接真实向量库,但已经保留了 RAG 的核心判断路径。
js
function searchDocs(query, limit) {
const files = walkMarkdownFiles(DOCS_DIR);
const queryVector = buildKeywordVector(query);
const matches = [];
// ... 对每个 chunk 计算 cosineSimilarity,取 Top-K
return matches.sort((a, b) => b.score - a.score).slice(0, limit);
}完整源码:server.js
3.3 SSE 流式事件
为什么看这段:前端不需要等整段答案生成完成,能先看到工具调用与 token 流。
js
sendEvent(res, 'tool_call', { thought: '问题涉及知识库内容,先检索 docs 获取上下文。', toolCall });
sendEvent(res, 'tool_result', { tool: toolCall.name, matches: results });
for (let index = 0; index < words.length; index++) {
sendEvent(res, 'token', { index, token: words[index] });
}
sendEvent(res, 'done', { question, references });4. 运行方式
bash
cd labs/node/ai-engineering/tool-calling-rag-streaming-demo
node server.js另开一个终端:
bash
cd labs/node/ai-engineering/tool-calling-rag-streaming-demo
node client.js "为什么 RAG 需要 chunk overlap?"5. 可能输出
text
[tool_call] {
"thought": "问题涉及知识库内容,先检索 docs 获取上下文。",
"toolCall": {
"name": "search_docs",
"arguments": {
"query": "为什么 RAG 需要 chunk overlap?",
"limit": 3
}
}
}
[tool_result] {
"tool": "search_docs",
"matches": [
{
"file": "docs/08-ai-engineering/04-rag-and-knowledge-retrieval.md",
"score": 0.44,
"excerpt": "..."
}
]
}
问题:为什么 RAG 需要 chunk overlap?
这个最小 demo 展示了一条最短 AI 工程闭环...6. 预期现象
- 先看到
tool_call,再看到tool_result,最后才是一段一段流出来的回答。 - 问题越贴近仓库现有 docs,
tool_result命中越稳定;问完全无关的问题时,demo 会更诚实地回“基于当前知识库无法回答”。 - 这个 demo 已经足够验证“工具契约、检索注入、流式输出”三段是否串通,但没有接真实 LLM、真实 embedding 模型和浏览器 UI。
7. 观察重点
- Tool calling 的核心不是“让模型直接执行代码”,而是模型产出结构化调用意图,宿主执行工具。
- RAG 的核心不是“上向量库”这件事本身,而是先检索缩小上下文,再生成。
- SSE 前端 demo 的核心不是页面样式,而是事件边界清晰:工具事件、流式 token、完成事件分开。
8. 常见误区
| 误区 | 实际情况 |
|---|---|
| 做 RAG 必须先接 Pinecone / Qdrant | 不是。先把 chunk、排序、引用来源这些基本骨架跑通更重要。 |
| Tool calling 就是模型直接运行命令 | 不是。模型只产出结构化调用请求,宿主才真正执行。 |
| 流式前端一定要先搭 Vue/React | 不一定。先用 Node SSE + CLI 客户端验证事件协议更小更稳。 |
9. 对应知识库文档
- 理论主文档:Agent Tool Calling (Function Calling) 架构与安全防护
- RAG 主文档:RAG 检索增强生成与本地/企业知识库架构
- SSE 主文档:SSE 流式传输、网络中断恢复与大模型打字机打点