ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零开始,用 MCP 打造真正“会思考”的 RAG 智能体 —— 实战指南 + 源码解析(TaoToken 统一 Key 接入篇)

从零开始,用 MCP 打造真正“会思考”的 RAG 智能体 —— 实战指南 + 源码解析(TaoToken 统一 Key 接入篇) 1. 为什么你的 RAG 智能体“不会思考”很多人做 RAG 的第一反应是把文档切块、灌进向量库、检索 Top-K、拼进 Prompt。跑通 Demo 没问题但一上真实场景就露馅——用户问“帮我对比一下上周那篇论文和官方文档里的实现差异”传统 RAG 只会拿这句话去向量库捞三条最相似的片段捞回来的可能是两段无关的摘要加一段目录。它不会先想“我该去内部文档还是去网上找”也不会想“这个问题需要先检索再计算”。这就是“文档问答机”和“会思考的智能体”之间的差距。前者是固定管道后者是动态决策LLM 自己判断要不要检索、检索哪个源、检索几次、结果够不够、要不要换个工具再来一轮。要做到这一点靠堆 Prompt 是堆不出来的。你需要一个标准化的工具调用协议让模型能像调用函数一样调用“检索内部知识库”“联网搜索”“查数据库”这些能力。这个协议就是 Model Context ProtocolMCP。你可以把它理解成 AI 世界的 USB-C不管底层是哪个模型、哪个向量库、哪个搜索 API只要双方都按 MCP 说话就能即插即用。本文要交付的是一个最小可跑闭环用 MCP 搭一个 Agentic RAG 智能体内部知识库检索和联网搜索作为两个 MCP Tool 暴露给模型模型自己决定调哪个。模型调用通道统一走 TaoToken 的 Key省去多平台多 Key 来回切换的麻烦。读完你能拿到可复制的settings.json/config.toml骨架、CC Switch 与 Cline 的配置片段以及一套报错排查清单。适合谁已经跑通过基础 RAG、想往 Agent 方向走一步的开发者正在用 Cline / Claude Code 这类工具、想把自有知识库接进编码助手的同学以及被多模型 Key 管理搞烦了、想统一入口的人。2. 前置准备TaoToken 统一 Key 与 MCP 运行环境2.1 为什么这里要引入 TaoTokenAgentic RAG 的一个隐藏成本是模型调用。智能体一轮对话里可能触发多次工具调用每次工具返回后还要再让模型总结Token 消耗是普通问答的好几倍。如果你同时用几家模型——规划用一家、总结用另一家、Embedding 又用第三家——Key 管理、额度监控、接口格式差异会迅速变成负担。TaoToken 在这里的角色是统一 API 通道一个 Key 走 OpenAI 兼容格式模型对话、Embedding、工具编排都从同一个入口出。对 MCP 智能体来说这意味着settings.json里只需要维护一份 base_url 和一份 Key换模型只改模型名不用动接入代码。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址https://taotoken.net/api2.2 环境清单在动手前确认本机具备以下条件缺一个后面都会卡住Python 3.10 及以上MCP 的 Python SDK 对 3.9 以下支持不完整Node.js 18如果你用 Cline / Claude Code 这类基于 Node 的客户端Docker用来跑本地 Qdrant 向量库不想装 Docker 也可以用 Qdrant 的本地文件模式但本文以 Docker 为准一个可用的 TaoToken Key2.3 拿 Key 与验证通道登录 TaoToken 控制台在 API Keys 页面创建一个新 Key。建议按用途分 Key一个给对话模型一个给 Embedding方便后面单独看额度。创建后先别急着写代码用一条 curl 确认通道通curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [{role: user, content: ping}] }返回里有choices[0].message.content就说明通道正常。这一步很重要——后面 MCP 报错时你要能快速区分是“通道问题”还是“MCP 配置问题”。如果这条 curl 就失败先解决 Key 和网络别往下走。提示把 Key 写进环境变量而不是硬编码。Linux/macOS 用export TAOTOKEN_API_KEYsk-xxxWindows 用setx。MCP 客户端读取环境变量的方式在下一节配置里说明。3. 可复制配置settings.json 与 config.toml 骨架3.1 项目目录结构先建一个干净的工作目录后面所有配置都围绕它mcp-agentic-rag/ ├── .env ├── mcp_server.py # MCP Server 主逻辑 ├── rag_engine.py # 检索与 Embedding 封装 ├── settings.json # Cline / CC Switch 读取的 MCP 配置 └── config.toml # 备用配置部分客户端用 TOML3.2 .env 文件# .env TAOTOKEN_API_KEYsk-your-key-here TAOTOKEN_BASE_URLhttps://taotoken.net/api QDRANT_URLhttp://localhost:6333 COLLECTION_NAMEagentic_rag_docs3.3 settings.json 骨架这是 Cline、CC Switch 这类客户端读取 MCP Server 的标准位置。核心是mcpServers字段每个 Server 一个条目{ mcpServers: { agentic-rag: { command: python, args: [/absolute/path/to/mcp-agentic-rag/mcp_server.py], env: { TAOTOKEN_API_KEY: sk-your-key-here, TAOTOKEN_BASE_URL: https://taotoken.net/api, QDRANT_URL: http://localhost:6333, COLLECTION_NAME: agentic_rag_docs } } } }三个容易踩的点args里的路径必须是绝对路径相对路径在客户端启动子进程时解析基准不同env里的 Key 要和你.env保持一致否则会出现“本地跑通、客户端跑不通”的诡异现象Windows 下command建议写python的全路径避免 PATH 问题。3.4 config.toml 备用骨架部分客户端以及一些 CLI 工具用 TOML 格式。内容等价只是语法不同[mcp_servers.agentic-rag] command python args [/absolute/path/to/mcp-agentic-rag/mcp_server.py] [mcp_servers.agentic-rag.env] TAOTOKEN_API_KEY sk-your-key-here TAOTOKEN_BASE_URL https://taotoken.net/api QDRANT_URL http://localhost:6333 COLLECTION_NAME agentic_rag_docs3.5 MCP Server 核心代码mcp_server.py里定义两个 Tool一个查内部向量库一个联网搜索。模型根据 Tool 的 docstring 决定调哪个——docstring 写得好不好直接决定智能体“会不会思考”。import os from typing import List import requests from dotenv import load_dotenv from mcp.server.fastmcp import FastMCP from rag_engine import RAGEngine load_dotenv() QDRANT_URL os.getenv(QDRANT_URL, http://localhost:6333) COLLECTION_NAME os.getenv(COLLECTION_NAME, agentic_rag_docs) mcp_server FastMCP(agentic-rag, host127.0.0.1, port8080, timeout60) rag_engine RAGEngine(qdrant_urlQDRANT_URL, collection_nameCOLLECTION_NAME) mcp_server.tool() def search_internal_docs(query: str) - str: 检索内部私有知识库。当用户问题涉及公司文档、项目笔记、 已上传的技术资料时使用此工具。不适用于实时新闻或公开网页信息。 if not isinstance(query, str): raise TypeError(query must be a string) return rag_engine.answer_question(query, top_k3) mcp_server.tool() def search_web(query: str) - List[str]: 联网搜索公开信息。当内部知识库无法回答、或问题涉及 最新版本、实时动态、公开网页内容时使用此工具。 if not isinstance(query, str): raise TypeError(query must be a string) api_key os.getenv(TAOTOKEN_API_KEY) base_url os.getenv(TAOTOKEN_BASE_URL, https://taotoken.net/api) if not api_key: return [Error: TAOTOKEN_API_KEY not set] try: resp requests.post( f{base_url}/v1/search, json{query: query, timeout: 30000}, headers{Authorization: fBearer {api_key}}, timeout35, ) resp.raise_for_status() return resp.json().get(data, [no results]) except requests.exceptions.RequestException as e: return [fsearch failed: {e}] if __name__ __main__: rag_engine.setup_collection() mcp_server.run()注意两个 docstring 的写法明确说了“什么时候用”和“什么时候不用”。这是让模型做出正确工具选择的关键。如果只写“检索文档”模型在遇到“最新版本”这类问题时可能仍然去查内部库然后返回一堆过时内容。3.6 rag_engine.py 检索封装import uuid from typing import List from qdrant_client import QdrantClient, models from llama_index.embeddings.huggingface import HuggingFaceEmbedding class RAGEngine: def __init__(self, qdrant_url: str, collection_name: str, embed_model: str nomic-ai/nomic-embed-text-v1.5): self.collection_name collection_name self.embed_model HuggingFaceEmbedding( model_nameembed_model, trust_remote_codeTrue ) self.vector_dim len(self.embed_model.get_text_embedding(test)) self.client QdrantClient(urlqdrant_url, prefer_grpcTrue) def setup_collection(self, docs: List[str] None): try: self.client.get_collection(self.collection_name) return except Exception: self.client.create_collection( collection_nameself.collection_name, vectors_configmodels.VectorParams( sizeself.vector_dim, distancemodels.Distance.DOT ), ) if not docs: return embeddings self.embed_model.get_text_embedding_batch(docs) points [ models.PointStruct( idstr(uuid.uuid4()), vectorvec, payload{context: doc} ) for doc, vec in zip(docs, embeddings) ] self.client.upload_points(self.collection_name, pointspoints) def answer_question(self, query: str, top_k: int 3) - str: q_vec self.embed_model.get_query_embedding(query) hits self.client.search( collection_nameself.collection_name, query_vectorq_vec, limittop_k, score_threshold0.4, ) if not hits: return 内部知识库未找到相关内容建议改用联网搜索。 return \n---\n.join(h.payload[context] for h in hits)4. 验证请求跑通最小闭环4.1 启动 Qdrantdocker run -d -p 6333:6333 -p 6334:6334 \ -v $(pwd)/qdrant_storage:/qdrant/storage \ qdrant/qdrant浏览器打开http://localhost:6334能看到 Qdrant 面板就说明起来了。4.2 启动 MCP Servercd mcp-agentic-rag python mcp_server.py看到Uvicorn running on http://127.0.0.1:8080即启动成功。4.3 直接请求验证工具选择先测内部检索场景curl -X POST http://127.0.0.1:8080/mcp \ -H Content-Type: application/json \ -d { messages: [{role: user, content: 我们项目里向量库用的什么距离度量}] }预期返回里tool_calls的name是search_internal_docs。再测联网场景curl -X POST http://127.0.0.1:8080/mcp \ -H Content-Type: application/json \ -d { messages: [{role: user, content: Qdrant 最新版本有什么新特性}] }这次预期name是search_web。如果两次都调了同一个工具说明 docstring 区分度不够回去改描述。4.4 在 Cline 里验证把 3.3 的settings.json放到 Cline 的 MCP 配置路径下重启 Cline。在对话里问一个需要内部知识的问题观察它是否弹出工具调用确认。Cline 会显示调用了哪个 MCP Tool、参数是什么、返回了什么。这一步跑通说明你的智能体已经能在真实客户端里“思考”了。5. 本篇常见报错排查5.1 MCP Server 启动即退出最常见原因是mcp包版本不匹配。执行pip install -U mcp升级到最新然后确认FastMCP的导入路径是from mcp.server.fastmcp import FastMCP。旧版本路径不同会直接 ImportError。5.2 客户端显示 “Server disconnected”九成是settings.json里的路径问题。检查三点args是否为绝对路径command指向的 Python 是否装了mcp和qdrant-client客户端可能用了另一个 Python 环境env里的 Key 是否完整。排查方法是在终端手动执行settings.json里那条完整命令看报什么错。5.3 工具调用返回 “内部知识库未找到相关内容”先确认 Qdrant 里真的有数据curl http://localhost:6333/collections/agentic_rag_docs看points_count。如果是 0说明setup_collection没灌数据检查docs参数是否传了。如果 count 正常但检索不到把score_threshold从 0.4 降到 0.2 试试Embedding 模型不同相似度分布差异很大。5.4 模型不调用工具直接编答案这是 docstring 写得太模糊。MCP 的工具描述就是给模型的指令必须写清楚“什么场景用、什么场景不用”。另外确认客户端开启了工具调用能力部分客户端默认关闭。5.5 通道 401 / 403先跑 2.3 的 curl。如果 curl 也 401是 Key 问题如果 curl 通但 MCP 里报错是env没传进去。注意settings.json的env不会自动继承系统环境变量必须显式写。5.6 超时Agentic RAG 一轮可能触发多次工具调用默认超时容易不够。FastMCP初始化时把timeout设到 60 秒以上联网搜索工具内部再单独设requests超时。6. 下一步把闭环接进你的工作流跑通上面的最小闭环后你手里其实已经有了一个可扩展的骨架。想加“查数据库”工具就再写一个mcp_server.tool()函数想换 Embedding 模型改RAGEngine初始化参数即可想换对话模型在客户端侧改模型名通道还是同一个。如果你打算长期用这套东西做编码辅助或 Agent 开发建议把模型调用统一收敛到 Coding Plan额度管理和模型切换都在一个地方完成https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan想先直观感受一下模型在工具调用场景下的表现可以直接在模型对话里试https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat接入文档和 MCP 配置细节在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc最后留一个我踩过的坑MCP Server 的 docstring 不要写太长模型对工具描述的注意力有限超过三行就开始忽略细节。把“什么时候用”放在第一句比放在最后一句有效得多。
返回列表