ARTICLE DETAIL

资讯详情

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

LangGraph 使用 init_chat_model 一键对接 siliconflow、Ollama 等各大 LLM 供应商并调用 MCP 服务以及流式输出

LangGraph 使用 init_chat_model 一键对接 siliconflow、Ollama 等各大 LLM 供应商并调用 MCP 服务以及流式输出 1. 从多供应商切换的痛点说起init_chat_model 到底解决了什么如果你写过一段时间的 LLM 应用大概率经历过这种场景项目一开始用 OpenAI 的接口后来想换成 SiliconFlow 上的 DeepSeek再后来本地又想跑个 Ollama 做离线兜底。每换一次供应商就要改一遍 import、改一遍 client 初始化、改一遍参数名代码里到处是if provider xxx的分支。这种碎片化的接入方式在 LangGraph 这种需要把模型当成图节点来编排的框架里会变得格外难受。init_chat_model就是 LangChain 团队给出的统一答案。它是langchain.chat_models模块下的一个工厂函数核心能力是用同一套参数签名初始化不同供应商的 ChatModel 实例。你只需要告诉它model_provider是什么、model叫什么剩下的 base_url、鉴权方式、请求格式适配它内部帮你路由到对应的集成包。对于 LangGraph 来说这意味着你的 Agent 节点、工具调用链、状态图都可以在不改业务代码的前提下把底层模型从云端换到本地。这篇文章面向的是已经会用 LangGraph 搭简单 Agent、但被多供应商配置搞烦的开发者。我会把 SiliconFlow、Ollama 这两类典型供应商的初始化参数写清楚再把 MCP 工具注册和流式输出这两块串起来最后给一份可以直接复制运行的骨架代码。你跟着走一遍就能得到一个「换模型只改一行」的多供应商 LLM 应用底座。需要先说明一个容易混淆的点init_chat_model本身不负责网络请求它只是帮你构造出正确的 ChatModel 对象。真正发请求、处理流式响应的是这个对象。所以排查问题时要分清是「初始化阶段参数错了」还是「调用阶段网络/鉴权错了」这两类错误的报错信息完全不同后面第 5 节会专门对照。另外MCPModel Context Protocol在这里的角色是「工具来源」。传统做法是你手写tool装饰器现在可以通过 MCP 客户端把外部服务比如搜索、天气、数据库查询动态注册成工具再喂给create_react_agent。这样工具和模型解耦换模型不影响工具换工具也不影响模型。2. 前置准备TaoToken 统一入口与依赖安装在动手写代码之前先把「模型从哪来」这件事定下来。很多人的痛点是SiliconFlow 要一个 KeyOpenAI 要一个 Key本地 Ollama 又不用 Key管理起来很乱。我的做法是找一个兼容 OpenAI 协议的统一入口把云端模型的调用收敛到一处本地 Ollama 单独走本地回环地址。这样代码里只有两种 base_url一种是远端统一入口一种是http://localhost:11434。TaoToken 就是这样一个兼容 OpenAI 接口协议的入口官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。它的价值在于你不需要为每个云端供应商单独记一套鉴权逻辑只要拿到一个 Key就能通过model_provideropenai加自定义base_url的方式调用多种模型。这正好和init_chat_model的设计思路对上——用 openai 这个 provider 去适配所有兼容 OpenAI 协议的服务。先去控制台创建一个 API Key地址是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。创建完把 Key 复制出来形如sk-开头的一串字符后面配置里会用到。如果你不确定该选哪个模型可以先去模型对话页面试一下手感 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 在网页里发几条消息确认模型能正常响应再回到代码里配置。依赖安装这块核心是三个包。第一个是 LangChain 主体和 LangGraph第二个是 MCP 适配器第三个是具体供应商的集成包。命令如下pip install -U langchain langchain-core langgraph pip install -U langchain-mcp-adapters pip install -U langchain-ollama这里有个坑要提前说init_chat_model对model_provideropenai的支持依赖langchain-openai如果你只装了langchain主体调用时会报找不到 provider。所以补一条pip install -U langchain-openaiOllama 那边你需要先在本地把服务跑起来并且拉一个模型下来。假设你已经装好了 Ollama执行ollama pull qwen2.5:3b ollama serveollama serve默认监听11434端口。验证本地服务是否正常可以 curl 一下curl http://localhost:11434/api/tags返回一个包含模型列表的 JSON就说明本地服务没问题。这一步很重要因为后面init_chat_model(model_providerollama)默认就是连这个地址如果服务没起会直接连接拒绝。环境变量建议统一管理避免 Key 硬编码进代码。在项目根目录建一个.envTAOTOKEN_API_KEYsk-你的key TAVILY_API_KEYtvly-dev-你的key然后代码里用os.getenv读取。MCP 的 Tavily 搜索服务需要一个 Tavily Key去 Tavily 官网注册免费额度即可这里不展开。3. 可复制配置SiliconFlow、Ollama 与 MCP 工具注册片段这一节是全文的核心我把三类配置拆开写云端统一入口、本地 Ollama、MCP 工具。每一段都可以直接复制改掉 Key 就能跑。先说云端。init_chat_model调用 OpenAI 兼容服务时关键参数是model_provider、model、base_url、api_key。注意model填的是服务商侧的模型 ID不是随便写的名字。以 TaoToken 统一入口为例import os from dotenv import load_dotenv from langchain.chat_models import init_chat_model load_dotenv() cloud_model init_chat_model( model_provideropenai, modeldeepseek-ai/DeepSeek-V3, base_urlhttps://taotoken.net/api, api_keyos.getenv(TAOTOKEN_API_KEY), temperature0, )这里model_provideropenai表示走 OpenAI 协议base_url指向统一入口api_key从环境变量读。temperature0是为了让工具调用场景下输出更稳定减少随机性导致的参数格式错误。如果你直接用 SiliconFlow 官方地址把base_url换成https://api.siliconflow.cn/v1/api_key换成 SiliconFlow 的 Keymodel换成deepseek-ai/DeepSeek-R1-Distill-Qwen-7B这类模型 ID 即可。结构完全一样这就是init_chat_model的好处——换供应商只动三个字段。再说本地 Ollama。它的配置更简单因为不需要 Key地址也是默认的local_model init_chat_model( model_providerollama, modelqwen2.5:3b, temperature0, )如果你的 Ollama 不在默认端口或者跑在另一台机器上加一个base_url参数local_model init_chat_model( model_providerollama, modelqwen2.5:3b, base_urlhttp://192.168.1.100:11434, temperature0, )注意 Ollama 的base_url不带/v1后缀这和 OpenAI 兼容服务不一样写错了会 404。这是新手最容易踩的坑之一。接下来是 MCP 工具注册。MCP 客户端通过MultiServerMCPClient管理多个 MCP Server每个 Server 用一段配置描述怎么启动。以 Tavily 搜索为例from langchain_mcp_adapters.client import MultiServerMCPClient mcp_config { tavily-mcp: { command: npx, args: [-y, tavily-mcp], env: { TAVILY_API_KEY: os.getenv(TAVILY_API_KEY), }, disabled: False, autoApprove: [], } }这段配置的含义是用npx拉起tavily-mcp这个包通过环境变量传入 API Key。disabled: False表示启用autoApprove留空表示工具调用需要确认在自动化场景里可以按需调整。command和args是启动 MCP Server 子进程的方式不同 Server 不一样有的用npx有的用python具体看对应文档。把模型和工具组合成 Agent用create_react_agentfrom langgraph.prebuilt import create_react_agent async with MultiServerMCPClient(mcp_config) as client: tools client.get_tools() agent create_react_agent( modelcloud_model, toolstools, )这里client.get_tools()返回的是 LangChain 格式的工具列表可以直接喂给create_react_agent。注意MultiServerMCPClient要用async with管理生命周期因为 MCP Server 是子进程退出时要正确关闭否则会残留进程。如果你想把这段配置写成独立的 JSON 文件方便管理可以这样{ mcpServers: { tavily-mcp: { command: npx, args: [-y, tavily-mcp], env: { TAVILY_API_KEY: tvly-dev-你的key } } } }然后在代码里json.load进来传给MultiServerMCPClient。这种写法在多环境部署时更清晰Key 可以通过环境变量注入而不是写死在 JSON 里。4. 验证请求流式输出与成功结果对照配置写完必须验证两件事模型能不能通、流式输出能不能逐块拿到。先做最小验证不接 MCP直接调模型import asyncio async def test_model(): resp await cloud_model.ainvoke(用一句话介绍 LangGraph) print(resp.content) asyncio.run(test_model())如果这一步报 401说明 Key 或 base_url 有问题如果报连接超时说明网络或地址不对。跑通之后再接流式。流式输出用astream它返回一个异步生成器每次 yield 一个 chunk。对于 ChatModelchunk 是AIMessageChunk内容在.content字段async def test_stream(): async for chunk in cloud_model.astream(讲个关于程序员的笑话): if chunk.content: print(chunk.content, end, flushTrue) asyncio.run(test_stream())flushTrue很重要否则在部分终端里输出会缓冲看起来像卡住了。跑通后你会看到文字一块一块吐出来而不是等全部生成完才显示。接下来验证 Agent 加 MCP 的完整链路。用astream而不是ainvoke这样能看到中间的工具调用过程async def test_agent_stream(): async with MultiServerMCPClient(mcp_config) as client: agent create_react_agent( modelcloud_model, toolsclient.get_tools(), ) async for chunk in agent.astream( { messages: [ {role: system, content: You are a helpful assistant. Please use your tools solving the problem.}, {role: user, content: 帮我查一下广州的天气}, ] } ): print(chunk) asyncio.run(test_agent_stream())agent.astream的 chunk 结构和单模型不一样它是按图节点输出的。你会看到类似{agent: {messages: [...]}}和{tools: {messages: [...]}}的字典分别对应「模型决定调工具」和「工具返回结果」两个阶段。最后模型基于工具结果生成回答也是通过agent节点输出。成功的结果长这样先出现一个包含tool_calls的 AIMessage里面写着要调用的工具名和参数比如tavily_search加查询词然后出现 ToolMessage内容是搜索结果最后出现一个不带 tool_calls 的 AIMessage是模型整合后的自然语言回答。如果你只看到第一步就停了通常是工具执行报错去检查 MCP Server 的 Key 和网络。流式输出在对话场景的落地关键是把 chunk 按类型分流。工具调用阶段的 chunk 不适合直接展示给用户只展示最终回答的文本增量。可以这样过滤async for chunk in agent.astream(inputs): for node_name, node_output in chunk.items(): if node_name agent: for msg in node_output.get(messages, []): if hasattr(msg, content) and msg.content and not getattr(msg, tool_calls, None): print(msg.content, end, flushTrue)这样用户看到的就是平滑的打字机效果工具调用的中间过程被隐藏了。实测下来这种过滤方式在 DeepSeek 和 Qwen 上都能正常工作。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节把最容易撞上的四类报错对照清楚每个都给出定位思路。401 Unauthorized。这是鉴权失败出现在初始化后的第一次调用。原因通常是三种Key 没读到环境变量名写错、.env没加载、Key 本身无效复制时带了空格、base_url 和 Key 不匹配拿 A 家的 Key 去请求 B 家的地址。排查方法是在初始化后打印一下cloud_model.openai_api_base和 Key 的前几位确认值符合预期。注意不要把完整 Key 打日志。local proxy failed / Connection error。这类报错指向网络层。如果你在本地跑 Ollama报Connection refused先确认ollama serve在跑再确认端口对不对。如果是云端模型报连接失败检查 base_url 是否多了或少了/v1。OpenAI 兼容服务通常需要/v1后缀TaoToken 的入口是https://taotoken.net/api具体以文档为准而 Ollama 不需要。这个差异是高频错误点。Error reading choices / KeyError choices。这个报错说明请求发出去了、也收到响应了但响应结构不是预期的 OpenAI 格式。常见于 base_url 指向了一个返回 HTML 错误页的地址或者模型 ID 写错导致服务端返回了非标准错误体。解决办法是先用 curl 直接打一下接口curl https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:deepseek-ai/DeepSeek-V3,messages:[{role:user,content:hi}]}看返回的是不是标准 JSON。如果 curl 也报错问题在配置如果 curl 正常但代码报错问题在代码里的参数传递。OAuth / authentication_error。MCP Server 里有些服务需要 OAuth 流程比如某些云盘、日历类工具。Tavily 用的是 API Key不涉及 OAuth但如果你换成别的 MCP Server可能会遇到需要浏览器授权的情况。这类报错的关键词是invalid_grant、token expired。处理方式是去对应服务的开发者后台重新生成凭证或者检查 MCP Server 配置里的env是否漏了某个必填变量。MCP Server 启动失败时MultiServerMCPClient有时不会立刻抛错而是在get_tools()时返回空列表导致 Agent 没有工具可用。所以初始化后打印一下len(tools)确认工具数量符合预期。还有一个隐蔽的坑create_react_agent的model参数如果传的是字符串而不是 ChatModel 实例行为会不一样。传实例才能保证走你配置的 base_url 和 Key。传字符串它会尝试用默认方式初始化可能连到你没预期的地址。所以务必传init_chat_model返回的对象。6. 多供应商切换骨架与后续扩展方向把前面的片段拼起来就是一个可运行的多供应商骨架。核心思路是把模型初始化抽成一个函数通过环境变量或配置项决定用云端还是本地MCP 配置单独放一个 JSONAgent 构建和流式消费逻辑与模型解耦。import os import asyncio from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent load_dotenv() def build_model(provider: str cloud): if provider cloud: return init_chat_model( model_provideropenai, modeldeepseek-ai/DeepSeek-V3, base_urlhttps://taotoken.net/api, api_keyos.getenv(TAOTOKEN_API_KEY), temperature0, ) elif provider local: return init_chat_model( model_providerollama, modelqwen2.5:3b, temperature0, ) raise ValueError(funknown provider: {provider}) async def run(provider: str, question: str): model build_model(provider) mcp_config { tavily-mcp: { command: npx, args: [-y, tavily-mcp], env: {TAVILY_API_KEY: os.getenv(TAVILY_API_KEY)}, disabled: False, autoApprove: [], } } async with MultiServerMCPClient(mcp_config) as client: agent create_react_agent(modelmodel, toolsclient.get_tools()) async for chunk in agent.astream( {messages: [ {role: system, content: You are a helpful assistant. Please use your tools solving the problem.}, {role: user, content: question}, ]} ): for node_name, node_output in chunk.items(): if node_name agent: for msg in node_output.get(messages, []): if hasattr(msg, content) and msg.content and not getattr(msg, tool_calls, None): print(msg.content, end, flushTrue) if __name__ __main__: asyncio.run(run(cloud, 帮我查一下广州的天气))切换供应商只需要把run(cloud, ...)改成run(local, ...)。这就是init_chat_model带来的实际收益。后续扩展有几个方向值得试。一是把 MCP 配置从代码里抽到独立的mcp_servers.json用json.load读取这样加新工具不用改 Python。二是给 Agent 加 checkpointer用 LangGraph 的持久化能力做多轮对话记忆create_react_agent支持checkpointer参数。三是把流式输出接到 FastAPI 的 SSE 接口上前端用 EventSource 消费做成真正的对话应用。四是做模型降级策略云端超时自动切本地 Ollama保证服务可用性。如果你打算长期跑编码类 Agent或者需要更稳定的调用配额可以了解一下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的完整示例。Claude Code 相关的接入配置也可以在同一份文档里找到对应章节。最后留一个实用技巧调试 MCP 工具时先把autoApprove设成空数组让每次工具调用都走确认流程这样你能清楚看到模型到底想调什么工具、传什么参数。等链路稳定了再按需放开。这个习惯帮我省了很多排查时间。
返回列表