Skip to content

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 });

完整源码:server.js · client.js

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. 观察重点 ​

  1. Tool calling 的核心不是“让模型直接执行代码”,而是模型产出结构化调用意图,宿主执行工具。
  2. RAG 的核心不是“上向量库”这件事本身,而是先检索缩小上下文,再生成。
  3. SSE 前端 demo 的核心不是页面样式,而是事件边界清晰:工具事件、流式 token、完成事件分开。

8. 常见误区 ​

误区实际情况
做 RAG 必须先接 Pinecone / Qdrant不是。先把 chunk、排序、引用来源这些基本骨架跑通更重要。
Tool calling 就是模型直接运行命令不是。模型只产出结构化调用请求,宿主才真正执行。
流式前端一定要先搭 Vue/React不一定。先用 Node SSE + CLI 客户端验证事件协议更小更稳。

9. 对应知识库文档 ​

10. 完整源码 ​

站点构建时间:2026/8/24 23:43:17