当前位置: 首页 > news >正文

大模型工具调用的另类用法——结构化json输出

一. 原理

在我的上一篇笔记中,记录了如何使用原生的大模型进行工具调用:https://www.cnblogs.com/nanimono/p/19295032。让大模型进行工具调用本质上并不是让模型自动调用工具并返回结果,而是:

1. 在输入给模型的数据中定义对工具(函数)名字、功能、参数名及介绍的标准化描述,目前推荐使用JSON Schema来进行标准化描述。

JSON Schema例子:

{"type": "function","function": {"name": "get_weather","description": "Get weather of a location, the user should supply the location and date.","parameters": {"type": "object","properties": {"location": { "type": "string", "description": "The city name" },"date": { "type": "string", "description": "The date in format YYYY-mm-dd" },},"required": ["location", "date"]},}}

类似这样的格式就叫做JSON Schema。如果模型原生支持工具调用,这部分会放在tools参数中;如果不支持,就放在prompts里面,并用文字提示强调模型进行必要的工具调用。

2. 模型接到工具调用的说明schema及指令后,如果需要调用工具,会生成一个工具消息来描述自己准备调用的工具,如下面的例子:

ChatCompletionMessageFunctionToolCall(id='call_00_eDQNUXjobdtx9DJI9ZiVsbhX',                                                                function=Function(arguments='{"location": "北京", "date": "2025-12-01"}',                                 name='get_weather'), type='function',index=0
)

3. 用户接收到工具消息后,解析出里面的function.name和function.arguments(arguments是一个JSON字符串),手动进行调用,得到的结果再组装成ToolMessage,跟历史消息一起回传给大模型,让大模型给出最终答案。

============== 以上是工具调用的正常用法 ===============

那么什么是工具调用的非正常用法?利用工具调用的特性,让大模型进行结构化JSON输出。

一般情况下,大模型的输出是一些比较自由的文本,就像跟用户在聊天一样。但在开发场景下,大多数时候,大模型的输出我们还要交给其它程序去进一步处理,所以更希望它输出一些标准的结构化数据,最好的选择就是JSON。

但无论我们怎么在prompt里强调,模型总是有概率跑偏,如何让模型能够比较稳定地进行结构化输出呢?

方案一:对于支持结构化输出的大模型,可以在原生调用模型api时,传入参数'response_format': {'type': 'json_object'},并且在prompt中必须含有 json 字样,并给出希望模型输出的 JSON 格式的样例:

 1 import json
 2 from openai import OpenAI
 3 
 4 client = OpenAI(
 5     api_key="<your api key>",
 6     base_url="https://api.deepseek.com",
 7 )
 8 
 9 system_prompt = """
10 The user will provide some exam text. Please parse the "question" and "answer" and output them in JSON format. 
11 
12 EXAMPLE INPUT: 
13 Which is the highest mountain in the world? Mount Everest.
14 
15 EXAMPLE JSON OUTPUT:
16 {
17     "question": "Which is the highest mountain in the world?",
18     "answer": "Mount Everest"
19 }
20 """
21 
22 user_prompt = "Which is the longest river in the world? The Nile River."
23 
24 messages = [{"role": "system", "content": system_prompt},
25             {"role": "user", "content": user_prompt}]
26 
27 response = client.chat.completions.create(
28     model="deepseek-chat",
29     messages=messages,
30     response_format={
31         'type': 'json_object'
32     }
33 )
34 
35 print(json.loads(response.choices[0].message.content))
View Code

方案二:对于不支持结构化输出的模型,或者结构化输出表现不太好的模型。观察前面模型调用工具的例子,我们会发现,模型在准备调用工具时,输出的工具调用消息,里面的arguments是一个标准JSON字符串!这说明大模型在认为自己要调用工具时,会相对比较稳定地输出JSON。

那我们可以利用大模型的这个特性,定义一种“特殊”的工具,强制让大模型调用这个工具,它输出的工具调用参数就是我们想要的结构化输出了。

那么这个“特殊”工具怎么定义呢?参考上面我们可以知道,只需要定义一个标准JSON Schema:

{"type": "function","function": {# 函数名和描述可以随意定义"name": "format_output","description": "这是一个结构化输出方法,输出前必须调用","parameters": {"type": "object","properties": {# 需要结构化输出的数据描述放在这里"location": { "type": "string", "description": "The city name" },"date": { "type": "string", "description": "The date in format YYYY-mm-dd" },},# 必填项,根据需要的输出情况填写"required": ["location", "date"]},}}
}

然后在调用模型api时,用上面的schema填充tools(注意tools是个数组),指定tool_choice参数为'required'(不同模型或框架值略有不同,要参照对应文档),强制模型调用结构化输出方法,即可。

二. 封装

langchain有一个llm.with_struct_output()方法,帮助开发者完成结构化输出,这个方法支持两种模式:'function_calling'和'json_mode',分别对应上面的两种方案。

with_struct_output()核心部分源码:

 1 def with_structured_output(
 2     self,
 3     schema: Optional[_DictOrPydanticClass] = None,
 4     *,
 5     method: Literal["function_calling", "json_mode"] = "function_calling",
 6     include_raw: bool = False,
 7     **kwargs: Any,
 8 ) -> Runnable[LanguageModelInput, _DictOrPydantic]:
 9     if kwargs:
10         raise ValueError(f"Received unsupported arguments {kwargs}")
11     is_pydantic_schema = _is_pydantic_class(schema)
12     if method == "function_calling":
13         if schema is None:
14             raise ValueError(
15                 "schema must be specified when method is 'function_calling'. "
16                 "Received None."
17             )
18         llm = self.bind_tools([schema], tool_choice="any")
19         if is_pydantic_schema:
20             output_parser: OutputParserLike = PydanticToolsParser(
21                 tools=[schema], first_tool_only=True
22             )
23         else:
24             key_name = convert_to_openai_tool(schema)["function"]["name"]
25             output_parser = JsonOutputKeyToolsParser(
26                 key_name=key_name, first_tool_only=True
27             )
28     elif method == "json_mode":
29         llm = self.bind(response_format={"type": "json_object"})
30         output_parser = (
31             PydanticOutputParser(pydantic_object=schema)
32             if is_pydantic_schema
33             else JsonOutputParser()
34         )
35     else:
36         raise ValueError(
37             f"Unrecognized method argument. Expected one of 'function_calling' or "
38             f"'json_mode'. Received: '{method}'"
39         )
40 
41     if include_raw:
42         parser_assign = RunnablePassthrough.assign(
43             parsed=itemgetter("raw") | output_parser, parsing_error=lambda _: None
44         )
45         parser_none = RunnablePassthrough.assign(parsed=lambda _: None)
46         parser_with_fallback = parser_assign.with_fallbacks(
47             [parser_none], exception_key="parsing_error"
48         )
49         return RunnableMap(raw=llm) | parser_with_fallback
50     else:
51         return llm | output_parser
View Code

这个方法的参数有三个:schema、method、和include_raw。schema可以传标准JSON Schema或者Pydantic类;method就是指定两种模式,默认function_calling;include_raw默认False,表示是否包含原始输出(暂时不关心这个)。

插播一条关于Pydantic类的简要介绍,前面的JSON Schema虽然准确,但代码比较长。Pydantic类似前端的TypeScript,可以用类的方式,用更少的代码描述JSON Schema。

from langchain_core.pydantic_v1 import BaseModel, Fieldclass QAExtra(BaseModel):"""一个问答键值对工具,传递对应的假设性问题+答案"""question: str = Field(description="假设性问题")answer: str = Field(description="假设性问题对应的答案")# 在Pydantic类的处理中,会调用如下语句,将pydantic转化成标准JSON Schema
QAExtra.model_json_schema()

完整的从Pydantic到伪装工具调用流程:

Pydantic类 Weather↓ model_json_schema()
JSON Schema↓ convert_to_openai_tool()
OpenAI Tool (name="Weather")↓ bind_tools()
LLM被"强制"调用Weather工具↓ 输出 tool_calls
PydanticToolsParser 解析为 Weather 实例

如果method选择了json_object模式,就比较简单了,直接设置llm.bind(response_format={"type": "json_object"}),但注意要在prompt中插入schema或者JSON的描述。在json_object模式中,没有用到传入的schema。

源码中在进行工具调用后,会使用特定的OutputParser将模型返回转成python dict,方便后续使用。

三. 总结

以上就是让智能体进行结构化输出的两种方式,无论是框架还是自己构造方法,其基本原理都是一样的。

同时,比较推荐使用Pydantic类对输出进行结构化描述和校验,就算你使用response_format为json_object的形式,也可以将Pydantic类使用model_json_schema()转化成schema后,使用模板插值替换到prompt中,来规范化模型prompt,防止手动定义出错。

如果使用langchain的with_struct_output()方法,则应该明白这个方法的底层原理,不要只是单纯使用。

http://www.jsqmd.com/news/60875/

相关文章:

  • 2025年Deepseek知识库本地化部署服务商:别让知识卡壳拖垮你的业务
  • canopen规范DS301/302/401/402
  • linux 操作系统中清空文件内容的两种方式对比
  • Flathub常用软件
  • 2025年长沙烘焙西点口碑不错培训学校推荐,专业技能培训企业
  • 北京能够上门回收名家字画的公司机构 北京上门收画
  • 2025年工业显示解决方案商口碑排行榜:友达光电口碑出众
  • 2025年佛山五大AI搜索geo服务商排行榜,新测评精选AI
  • 2025年安徽AI搜索推广专业公司TOP5推荐,诚信高效的A
  • 脑电以及AI在酿酒领域究竟能发挥什么样的作用呢
  • 2025年湖南蛋糕培训学校年度排名:教学模式、环境与联系指南
  • 学习差的孩子用学习机是智商税?
  • 2025年金属幕墙胶五大正规厂商推荐,幕墙胶专业供应商实力全
  • Bean专题
  • 2025年智能BI本地化部署服务:BI部署方案商的核心价值与实践路径
  • 2025年知识库本地化部署厂商全景扫描:企业AI知识库私有化落地的唯一清单
  • 【2025最新】美图看看下载安装及使用教程(详细步骤 + 批量处理指南)
  • electron+vue——托盘图标及菜单实现 - 前端
  • NVIDIA CUDA-X 库
  • 今年广东自习室加盟代理 优质品牌推荐谁?
  • 全场景通信工业级可编程工控机ECM50-A06方案介绍
  • 2025年12月八大重金属检测仪、成分分析仪、光谱仪、ROHS检测仪、镀层测厚仪厂家推荐榜单及选购指南
  • 美国留学申请文书创新权威认证榜单!谁是实力榜首?
  • 串口关键字抓取
  • 奇奇怪怪的特性
  • postgres json数据处理
  • smart_IO
  • 2025年12月振动时效机TOP3实力厂商新盘点:技术适配与服务特色双视角
  • 2025年中国十大护眼照明品牌推荐:口碑好的声控护眼灯有哪些
  • 2025重质碳酸钙行业TOP5权威推荐:鼎成钙业,甄选企业助