首页 / 资讯中心 / 文章详情

langchain deepseek结构化输出实战:用TaoToken统一Key跑通ChatDeepSeek的JSON解析链路

langchain deepseek结构化输出实战:用TaoToken统一Key跑通ChatDeepSeek的JSON解析链路 ★ FEATURED ARTICLE
1. 为什么 ChatOpenAI 调 DeepSeek 结构化输出总是 400结构化输出这件事说白了就是让模型别自由发挥必须按你给定的字段吐 JSON。LangChain 里最省心的写法是with_structured_output()传一个 Pydantic 模型进去返回的直接是对象不用自己json.loads。但把模型换成 DeepSeek 之后很多人第一步就卡住了明明 DeepSeek 官方说兼容 OpenAI 接口为什么ChatOpenAI一调就报 400我先把结论摆出来问题不在 DeepSeek 不支持结构化输出而在ChatOpenAI.with_structured_output()默认走的协议和 DeepSeek 的兼容层对不上。你如果只改base_url和model底层还是按 OpenAI 那套json_schema的response_format发请求DeepSeek 的兼容接口对这个类型直接回你一句This response_format type is unavailable now。这个报错长这样openai.BadRequestError: Error code: 400 - {error: {message: This response_format type is unavailable now, type: invalid_request_error, param: None, code: invalid_request_error}}看到 400 别急着怀疑 Key 或网络这大概率是协议层不匹配。本文要解决的就是这条链路从 Pydantic 模型定义到with_structured_output绑定再到解析失败重试全程用 TaoToken 的统一 Key 跑通ChatDeepSeek。适合已经在用 LangChain、想给 DeepSeek 加结构化输出、又被 400 报错劝退的人。核心检索词就三个langchain、deepseek、结构化输出。先说清楚两个 SDK 的差别这是后面所有配置的地基。langchain_openai.ChatOpenAI是通用 OpenAI 兼容封装它的with_structured_output()默认method是json_schema也就是往请求里塞一个严格的 JSON Schema 约束。而langchain_deepseek.ChatDeepSeek是 DeepSeek 专用封装它的with_structured_output()默认走function_calling也就是用工具调用的方式把字段“逼”出来。DeepSeek 的兼容接口认后者不认前者所以同样是deepseek-chat换 SDK 就活了。还有一个坑模型名。deepseek-reasoner这类推理模型对tool_choice支持有限你用它做结构化输出会撞上deepseek-reasoner does not support this tool_choice。所以结构化输出场景老老实实用deepseek-chat别拿推理模型硬上。理解了这层后面的配置就顺了。下面我按“先接 Key、再写配置、再验证、最后排障”的顺序走一遍每一步都能直接复制。2. TaoToken 统一 Key 接入 ChatDeepSeek 的前置准备在写代码之前先把“钥匙”和“地址”准备好。这一步不做后面所有请求都会在 401 上打转。我用 TaoToken 的原因很简单一个 Key 管多个模型base_url统一切模型只改model字段不用为每个厂商单独维护一套环境变量。对做结构化输出这种要反复试模型的场景省事很多。你需要准备三样东西第一一个可用的 API Key。登录 TaoToken 控制台在 API Keys 页面创建一个复制出来。地址是https://taotoken.net/api-keys创建后只显示一次记得存好。第二确认 Base URL。TaoToken 的 API 入口是https://taotoken.net/api注意这里不带任何查询参数直接作为 OpenAI 兼容的base_url使用。LangChain 的ChatDeepSeek和ChatOpenAI都接受api_base/base_url参数填这个就行。第三确认模型 ID。结构化输出用deepseek-chat不要用deepseek-reasoner。模型 ID 写错会直接 404 或 400这个后面排障章节会细说。环境变量建议这样设避免 Key 硬编码进代码export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用.env文件管理装个python-dotenv在代码开头load_dotenv()即可。我习惯用环境变量因为 CI 和本地都能复用同一套代码。依赖安装这块LangChain 拆包比较细结构化输出需要核心包加对应集成包pip install -U langchain langchain-deepseek langchain-openai pydantic版本上langchain-deepseek建议用较新的版本老版本对with_structured_output的method参数支持不全。装完可以用pip show langchain-deepseek看一眼版本号。pydantic用 v2因为字段描述和可选类型在 v2 里更规范。这里插一句关于“统一 Key”的价值。结构化输出调试阶段你往往要在ChatDeepSeek和ChatOpenAI之间来回切验证到底是协议问题还是模型问题。如果每个 SDK 配一套不同的 Key 和地址切换成本很高还容易把 A 的 Key 填到 B 的地址上报一堆莫名其妙的错。统一入口之后切换只是改一行 import 和一个model字符串排查效率完全不一样。准备好这些就可以进配置环节了。下一节给的是可直接复制的初始化代码包含 Pydantic 模型、ChatDeepSeek初始化和with_structured_output绑定三件套。3. 可复制的 ChatDeepSeek 结构化输出配置这一节是全文的核心给的是能直接跑的最小闭环。我把它拆成三块Pydantic 模型定义、模型初始化、结构化绑定。每一块都标了关键参数照着填就行。先定义 Pydantic 模型。字段描述description很重要它会被塞进请求里指导模型填值写得越清楚解析成功率越高from pydantic import BaseModel, Field from typing import Optional class Joke(BaseModel): Joke to tell user. setup: str Field(descriptionThe setup of the joke) punchline: str Field(descriptionThe punchline to the joke) rating: Optional[int] Field( defaultNone, descriptionHow funny the joke is, from 1 to 10 )注意rating用了Optional[int]并给了默认值。结构化输出里可选字段一定要给默认值否则模型偶尔漏字段时 Pydantic 校验会直接抛错而不是优雅降级。接着初始化ChatDeepSeek。这里的关键是api_base指向 TaoTokenmodel用deepseek-chatimport os from langchain_deepseek import ChatDeepSeek llm ChatDeepSeek( modeldeepseek-chat, api_keyos.environ[TAOTOKEN_API_KEY], api_baseos.environ[TAOTOKEN_BASE_URL], temperature0, max_retries2, )temperature0是为了让输出稳定结构化场景不需要创造性。max_retries2是网络层重试和后面要讲的解析重试不是一回事但能挡掉偶发的连接抖动。然后是结构化绑定。ChatDeepSeek的with_structured_output默认就是function_calling所以你可以不写method但为了显式、可读我建议写出来structured_model llm.with_structured_output(Joke, methodfunction_calling) output structured_model.invoke(Tell me a joke about cats) print(output) print(type(output))跑通后output是一个Joke实例不是 dict也不是字符串。你可以直接output.setup、output.punchline访问字段。print(type(output))会输出class __main__.Joke这是判断结构化是否真正生效的最直接证据。如果你出于某些原因必须用ChatOpenAI那一定要显式指定methodfunction_calling否则默认的json_schema会撞 400from langchain_openai import ChatOpenAI llm_openai ChatOpenAI( modeldeepseek-chat, api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL], temperature0, ) structured_openai llm_openai.with_structured_output(Joke, methodfunction_calling)对比一下两种写法的差异用表格更清楚维度ChatDeepSeekChatOpenAI默认 methodfunction_callingjson_schema调 deepseek-chat直接可用需显式指定 function_calling调 deepseek-reasonertool_choice 报错tool_choice 报错推荐场景DeepSeek 结构化输出通用兼容调试这张表基本解释了 excerpt 里那一串报错的根因不是模型不行是默认协议选错了。把method对齐问题就消了。配置写完后别急着上生产先用一个真实请求验证字段完整性。下一节给验证脚本和成功结果的判断标准。4. 用真实请求验证 JSON 字段完整性配置能跑不代表字段一定完整。结构化输出最怕的是“看起来成功其实字段是空的或类型不对”。所以验证要分两层一层看返回类型一层看字段内容。先写一个带断言的验证脚本from pydantic import BaseModel, Field from typing import Optional import os from langchain_deepseek import ChatDeepSeek class SearchQuery(BaseModel): search_query: str Field(description优化后的搜索查询语句) justification: str Field(description为什么这样改写查询) llm ChatDeepSeek( modeldeepseek-chat, api_keyos.environ[TAOTOKEN_API_KEY], api_baseos.environ[TAOTOKEN_BASE_URL], temperature0, ) structured_llm llm.with_structured_output(SearchQuery, methodfunction_calling) result structured_llm.invoke( 用户问题How does Calcium CT score relate to high cholesterol? ) assert isinstance(result, SearchQuery), f类型不对: {type(result)} assert result.search_query, search_query 为空 assert result.justification, justification 为空 print(字段校验通过) print(search_query:, result.search_query) print(justification:, result.justification)判断成功的标准有三条isinstance(result, SearchQuery)为真、两个字符串字段都非空、打印出来的内容语义合理。三条都过说明链路是通的。如果你更想看到原始 JSON可以用json_mode方式做对照。注意json_mode下 prompt 里必须出现 “JSON” 这个词并且明确要求只输出 JSONstructured_json llm.with_structured_output(SearchQuery, methodjson_mode) output structured_json.invoke( 请只输出 JSON不要输出 Markdown不要输出其它解释文字。 JSON 必须包含这两个字段 { search_query: ..., justification: ... } 用户问题How does Calcium CT score relate to high cholesterol? ) print(output)json_mode和function_calling的区别在于前者靠 prompt 约束后者靠工具调用协议约束。实测下来function_calling更稳因为约束在协议层不依赖模型“听话”。json_mode适合接口不支持工具调用时的兜底。验证时还要注意一个细节invoke的输入如果是纯字符串模型可能把结构化指令和用户问题混在一起理解。更稳的做法是用消息列表把系统指令和用户输入分开from langchain_core.messages import SystemMessage, HumanMessage messages [ SystemMessage(content你是一个查询改写助手只输出结构化结果。), HumanMessage(contentHow does Calcium CT score relate to high cholesterol?), ] result structured_llm.invoke(messages)这样模型对“要输出什么”和“针对什么问题输出”分得更清字段完整性更高。验证通过后建议把这段脚本固化成一个小测试文件每次改模型或改 Key 都跑一遍。结构化输出的回归成本很低但收益很高能挡住大部分配置漂移。5. 结构化输出常见报错排查对照这一节把踩过的坑按报错原文列出来对照着改就行。每个报错都给了触发条件和修复动作。报错一This response_format type is unavailable now完整形态openai.BadRequestError: Error code: 400 - {error: {message: This response_format type is unavailable now, type: invalid_request_error, param: None, code: invalid_request_error}}触发条件用ChatOpenAI调deepseek-chat且没指定method默认走了json_schema。修复换成ChatDeepSeek或者给ChatOpenAI.with_structured_output显式加methodfunction_calling。报错二deepseek-reasoner does not support this tool_choice完整形态openai.BadRequestError: Error code: 400 - {error: {message: deepseek-reasoner does not support this tool_choice, type: invalid_request_error, param: None, code: invalid_request_error}}触发条件模型名写成deepseek-reasoner结构化输出底层要用工具调用推理模型不支持这个tool_choice。修复模型名改成deepseek-chat。结构化输出场景不需要推理模型。报错三401 Unauthorized完整形态openai.AuthenticationError: Error code: 401 - {error: {message: Invalid API key, ...}}触发条件Key 没设、设错、或者环境变量名和代码里读的不一致。修复确认TAOTOKEN_API_KEY已导出代码里os.environ[TAOTOKEN_API_KEY]能取到值。注意别把api_key和api_base填反。报错四local proxy failed/ 连接类错误完整形态类似openai.APIConnectionError: Connection error. local proxy failed触发条件本地网络环境有额外转发层或者base_url写错导致请求发不出去。修复确认api_base是https://taotoken.net/api不要多加路径或参数。检查本机是否有影响出网的全局配置。报错五reading choices相关完整形态类似TypeError: Cannot read properties of undefined (reading choices)触发条件响应体结构和 SDK 预期不一致常见于base_url指到了非兼容端点或者返回了错误页 HTML。修复确认端点正确先用curl直接打一次接口看返回结构再回到 LangChain。报错六OAuth / 认证方式不匹配如果你在别的工具里见过 OAuth 相关报错那是因为有些客户端默认走 OAuth 流程而 API Key 模式不认这套。LangChain 这边用api_key参数即可不涉及 OAuth。排查顺序建议固定成先看报错原文里的message字段再对模型名再对method最后对base_url和 Key。90% 的问题在前两步就能定位。另外提醒一句with_structured_output的解析失败重试可以在外层包一层循环捕获ValidationError后重新invoke并把上次的错误信息拼进 prompt 里让模型修正。这是比单纯重试更有效的策略。6. 把这条链路用起来从调试到长期编码跑通一次不算完真正省事的是把它变成可复用的模式。我现在的习惯是结构化输出的模型初始化单独放一个模块Pydantic 模型按业务分文件with_structured_output的绑定封装成工厂函数。这样换模型、换 Key、加字段都只改一处。如果你要长期做编码类、Agent 类任务反复调模型是常态用按量计费容易失控可以考虑 Coding Plan 这类包周期的方式把成本固定下来。入口在https://taotoken.net/coding-plan。日常只是验证模型输出、试 prompt用模型对话页面就够了地址是https://taotoken.net/chat。需要看 Key 和用量就去控制台https://taotoken.net/console新建 Key 在https://taotoken.net/api-keys。接入文档在https://taotoken.net/doc遇到接口细节先翻这里。回到技术本身最后再强调一次那个最容易被忽略的点ChatOpenAI和ChatDeepSeek的with_structured_output默认method不同这是 DeepSeek 结构化输出报 400 的头号原因。记住这个再遇到类似报错先看method再看模型名基本能自己解决。
阅读完成 · 觉得有帮助?
咨询建站