尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

LangChain agent 调 deepseek-v4-flash 报 401?TaoToken 这样改 base_url

LangChain agent 调 deepseek-v4-flash 报 401?TaoToken 这样改 base_url LangChain agent 调 deepseek-v4-flash 报 401大概率不是 create_agent 的 middleware 写错也不是 tools 没配好而是ChatDeepSeek的 base_url 把请求送到了一个不稳定的通道。TaoToken 是一个统一接入的 API 兼容通道你可以先在官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建一把 Key然后把 base_url 改成 https://taotoken.net/api同一个 agent.invoke 就能恢复。这次排障的思路很简单不动 agent 逻辑只动模型入口。下面按 LangChain agent 的常用步骤走一遍先看 401 从哪来再准备 Key接着改ChatDeepSeek最后把 MCP 和流式输出也顺带验证。1. 401 报错先别怪 agent先看 base_url 指向的通道1.1 模型请求的发送路径LangChain 的 create_agent 本身不直接发起模型请求真正干活的是构造出来的ChatDeepSeek实例。每次 agent 调用模型时都是按照base_url api_key model拼出一个 HTTP 请求。401 表示服务端收到了请求但身份验证失败。注意这里不是说你的代码没权限而是服务端不认这把 Key或者这个 Key 不允许访问目标模型。常见的两个原因base_url 填错或所指向的通道不稳定Key 过期、额度用完但服务端用 401 表达。这两种情况都不需要碰 agent 的 tools、middleware、state schema。把ChatDeepSeek(...)里的几个参数对一遍通常比改 agent 结构更快。1.2 原来填的地址为什么会踩坑原文创建 agent 时写的是ChatDeepSeek(modeldeepseek-v4-flash, base_urlhttps://api.siliconflow.cn/v1)。这类 OpenAI 兼容地址本身没问题LangChain 解析很正常。问题在于这个通道的稳定性不在你掌控范围。一旦它挂掉报错会五花八门401、连接超时、响应流中断甚至偶尔成功偶尔失败。这种报错最容易让人误判成 agent 的中间件写错了。TaoToken 提供统一 API 接入把通道层收口到一个能随时看 Key、看用量的地方。你不需要为每个模型维护不同地址只需要在官网创建 Key然后把 base_url 替换为https://taotoken.net/api。对 LangChain 来说这就是一次参数替换。2. 准备材料去 TaoToken 创建一把 Key并确认模型 ID2.1 注册并创建 API Key打开 TaoToken注册登录后进入控制台创建 API Key。创建出来的字符串复制到本地本文统一用YOUR_API_KEY占位。创建页面通常只完整显示一次 Key复制后先存到安全位置避免后面重新生成。2.2 模型 ID 以模型广场为准deepseek-v4-flash 是原文示例里可用的模型 ID但模型上下架和命名会变化。配置前先打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的模型广场搜索一下 deepseek-v4-flash确认当前显示的 ID 是否和代码里一致。如果模型广场显示的是其他名称就把代码里的model改成广场上的写法不要照搬教程里的旧 ID。2.3 顺手记住控制台位置创建 Key 后控制台里会记录每次调用的用量。后面验证 agent.invoke 时回到同一个页面看是否入账能帮你快速区分“代码没调通”和“模型没计费”。这一步不需要额外配置只是提前认个路。3. 把 ChatDeepSeek 的 base_url 指向统一接入地址3.1 最小可运行的 create_agent下面代码是原文 create_agent 的最小化版本唯一改动是base_url和api_keyfrom langchain.agents import create_agent from langchain_core.messages import HumanMessage from langchain_deepseek import ChatDeepSeek model ChatDeepSeek( modeldeepseek-v4-flash, # 以模型广场为准 base_urlhttps://taotoken.net/api, # 注意末尾不要加 /v1 api_keyYOUR_API_KEY, # 从官网控制台创建 ) agent create_agent(modelmodel) response agent.invoke({messages: [HumanMessage(你好简单介绍一下自己)]}) print(response[messages][-1].content)注意几点base_url是https://taotoken.net/api不要加/v1api_key是你刚从官网创建的那把不是原来的 SiliconFlow Key。如果你用的langchain-deepseek版本较旧不识别api_key参数可以改成设置环境变量DEEPSEEK_API_KEYYOUR_API_KEY后在ChatDeepSeek里只填model和base_url。两种方式效果一样。3.2 动态指定模型时override 的模型也要同走一个 base_url原文有一个动态指定模型的例子通过wrap_model_call中间件在请求到达模型层之前用request.override(modelfinal_model)切换模型。这里经常被忽略的是中间件只是修改了目标模型实例真正发请求的仍是构造好的ChatDeepSeek。如果外层model已经指向https://taotoken.net/api但final_model还是旧地址就可能出现“小模型能跑、大模型 401”的割裂。建议把所有模型实例统一到同一把 Key 和同一个 base_urlfrom typing import Callable from langchain.agents import create_agent from langchain.agents.middleware import ModelRequest, ModelResponse, wrap_model_call from langchain_core.messages import HumanMessage from langchain_core.tools import tool from langchain_deepseek import ChatDeepSeek mini_model ChatDeepSeek( modeldeepseek-v4-flash, base_urlhttps://taotoken.net/api, api_keyYOUR_API_KEY, ) model ChatDeepSeek( modeldeepseek-v4-pro, # 以模型广场为准 base_urlhttps://taotoken.net/api, api_keyYOUR_API_KEY, ) tool def get_location(): 获取用户位置 return 北京 wrap_model_call def change_model( request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse], ): if len(request.messages) 2: final_model model else: final_model mini_model return handler(request.override(modelfinal_model)) agent create_agent( modelmini_model, tools[get_location], middleware[change_model], ) response agent.invoke({messages: [HumanMessage(你知道我的位置吗)]}) print(response[messages][-1].content)这个示例覆盖了原文 1.1 的动态指定模型和动态指定工具。如果你的业务只需要固定模型直接把middleware[change_model]去掉即可。原文还有一个dynamic_prompt装饰器用于按上下文切换系统提示词。它修改的是发给模型的 Prompt不改变请求地址因此和 base_url 完全正交换 Key 不会影响它。3.3 结构化输出和自定义状态原文 1.2 和 1.3 的结构化输出、自定义状态都不涉及网络参数。create_agent(modelmodel, response_formatContactInfo)和state_schemaState都是在模型层之上组合能力只要底层模型能靠新 base_url 跑通这些参数照常使用。需要留意的是排查 401 时最好先去掉response_format等最小 agent 通了再加回来避免把模型调用错误和结构化输出错误混在一起。4. 验证 agent.invoke同步调用与流式输出4.1 同步调用先确认返回值上面第三节的代码跑通后控制台应该能看到模型返回内容。注意response[messages][-1]和response.value[messages][-1]是两种取值方式不同 LangChain 版本推荐不同写法。如果打印出来是空字符串先看是否把推理内容误当成正常内容。4.2 流式输出区分推理内容和正常回答原文 1.5 提到 ds 系列模型默认带推理内容。在stream_modemessages下推理内容出现在AIMessageChunk.additional_kwargs[reasoning_content]正常内容出现在content。换到新 base_url 后这个数据结构不变。下面的代码把推理内容和正文分开打印from langchain.agents import create_agent from langchain_core.messages import HumanMessage, ToolMessage from langchain_deepseek import ChatDeepSeek model ChatDeepSeek( modeldeepseek-v4-flash, base_urlhttps://taotoken.net/api, api_keyYOUR_API_KEY, ) agent create_agent(modelmodel) last_chunk_type normal for chunk in agent.stream( {messages: [HumanMessage(简单介绍一下你自己)]}, stream_modemessages, versionv2, ): data chunk[data][0] if isinstance(data, ToolMessage): print(\n工具返回, data.content) elif data.content: print(data.content, end, flushTrue) last_chunk_type normal elif data.additional_kwargs.get(reasoning_content): if last_chunk_type normal: print(\n推理内容) print(data.additional_kwargs[reasoning_content], end, flushTrue) last_chunk_type reasoning如果你的 agent 没有绑定工具ToolMessage分支不会触发不影响验证。4.3 中断操作不额外配 Key原文 1.4 的 HumanInTheLoopMiddleware 只作用于工具调用前的中断不参与模型网络层。interrupt_on{get_weather: True}、description_prefix这些参数都不需要因为换 base_url 而改变。也就是说这一层完全不用动。5. MCP 快速上手MultiServerMCPClient 里同样只换 Base URL5.1 两种传输方式回顾原文把 MCP 的传输方式分成 stdio 和 streamable-http。stdio 适合本地子进程客户端把服务端拉起来通过标准输入输出通信streamable-http 适合远程服务直接请求一个 URL。LangChain 的MultiServerMCPClient负责客户端能力服务端可以用 fastmcpmcp.run(transportstdio)或mcp.run(transportstreamable-http, port8080)。这些地址和模型无关不要替换成https://taotoken.net/api。5.2 完整客户端示例下面的代码把 MCP 工具和 ChatDeepSeek 接在一起。MCP 服务地址保持本地或远程配置模型实例的 base_url 走统一接入import asyncio from langchain.agents import create_agent from langchain_deepseek import ChatDeepSeek from langchain_mcp_adapters.client import MultiServerMCPClient from langchain_mcp_adapters.tools import load_mcp_tools async def main(): client MultiServerMCPClient( { Math: { transport: streamable-http, url: http://127.0.0.1:8080/mcp, }, } ) async with client.session(Math) as session_math: tools await load_mcp_tools(session_math) model ChatDeepSeek( modeldeepseek-v4-flash, base_urlhttps://taotoken.net/api, api_keyYOUR_API_KEY, ) agent create_agent( modelmodel, toolstools, system_prompt你是善于调用工具回答用户问题的助手回答前先判断是否需要调用工具。, ) response await agent.ainvoke( {messages: [{role: user, content: 3 5 等于多少}]} ) print(response[messages][-1].content) if __name__ __main__: asyncio.run(main())5.3 有状态 MCP 会话原文补充了有状态会话默认每次工具调用会创建新的 ClientSession如果需要跨工具调用维护上下文就用client.session(服务名)创建持久会话。上面代码用的正是有状态方式。如果只是临时取工具可以用await client.get_tools()。无论哪种方式模型侧始终使用同一把 Key 和同一个 base_url。6. 跑通后去控制台对账排掉 401 之外的三个小坑6.1 先用模型对话页验证同一把 Key代码跑通后建议到 TaoToken 模型对话 里用同一把 Key 发一条消息。页面能返回、代码不能返回问题基本出在 base_url 或 Key 传递页面也报 401则多半是 Key 创建后没有复制完整或模型 ID 过时。若要长期跑 agent 任务可以看一下 Coding Plan 的套餐是否够用Key 的创建和管理回到 控制台 API Keys。6.2 三个常见小坑第一base_url多写/v1。统一接入地址是https://taotoken.net/api不少从 OpenAI 迁移的习惯性加/v1导致请求路径错误返回 401 或 404。第二复制 Key 时带上了空格或换行。建议在代码里先打印len(api_key)如果和创建页显示的长度不一致重新复制。第三模型 ID 过期。模型广场下架旧 ID 后请求会直接被拒绝。排障时回到 TaoToken 搜一下当前 ID。6.3 一点排障顺序这次改 base_url 只花了几分钟比改 agent 结构快得多。现在遇到 401我自己的顺序是先拿同一把 Key 去模型对话页面测再用最小create_agent跑最后才怀疑 middleware、response_format、MCP 工具。前面三步跑通后再逐步把动态指定模型、流式输出、MCP 加回去每加一层都确认一次调用成功。这套顺序对 LangChain agent 和普通 LangChain 脚本都适用。
返回列表