惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
Scott Helme
Scott Helme
NISL@THU
NISL@THU
Cisco Talos Blog
Cisco Talos Blog
C
Cybersecurity and Infrastructure Security Agency CISA
AWS News Blog
AWS News Blog
V
Vulnerabilities – Threatpost
J
Java Code Geeks
U
Unit 42
The GitHub Blog
The GitHub Blog
H
Help Net Security
T
Tenable Blog
aimingoo的专栏
aimingoo的专栏
Jina AI
Jina AI
Spread Privacy
Spread Privacy
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
L
Lohrmann on Cybersecurity
T
Threatpost
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Engineering at Meta
Engineering at Meta
A
About on SuperTechFans
I
InfoQ
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog
L
LINUX DO - 最新话题
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Threat Research - Cisco Blogs
C
Check Point Blog
T
The Blog of Author Tim Ferriss
有赞技术团队
有赞技术团队
宝玉的分享
宝玉的分享
Help Net Security
Help Net Security
Google DeepMind News
Google DeepMind News
A
Arctic Wolf
Y
Y Combinator Blog
N
News | PayPal Newsroom
M
MIT News - Artificial intelligence
Latest news
Latest news
H
Hacker News: Front Page
Blog — PlanetScale
Blog — PlanetScale
腾讯CDC
I
Intezer
爱范儿
爱范儿
F
Fortinet All Blogs
P
Palo Alto Networks Blog
C
CERT Recently Published Vulnerability Notes

博客园 - 沐子馨

Agent Loop:从对话式 AI 到自主智能体的范式跃迁 解锁图数据建模的奥秘 基于 Neo4j 与 Milvus 的图RAG系统搭建指南 解决复杂推理难题:KG-RAG 在大模型中的应用 RAG 评估常用工具简介 RAG系统效果不好?一文看懂如何进行系统评估 写出让 AI “秒懂”的技能Skill 解锁 RAG 系统中的高级检索与重排序策略 掌握查询重构与智能路由的艺术 告别黑盒:手把手实现一个可解释、可调试的 Text2SQL 代理系统 告别简单向量搜索:RAG 中的高级查询构建与优化策略 深入理解 RAG 中的混合搜索策略 告别基础检索:掌握 RAG 中的句子窗口与递归路由策略 构建多模态检索系统:Milvus 部署、Schema 设计与混合检索实践 向量数据库原理与实战:从核心机制到 FAISS 应用 多模态向量嵌入:从文本到图像的语义统一与 RAG 应用 构建高效 RAG 的基石:深度解析 Embedding 模型原理与优化 All-in-RAG:解锁大模型“开卷考试”的终极能力 AI Agent 是如何思考的?一文读懂推理与决策引擎 终端革命:AI Agent 正在重新定义命令行 给企业装上“AI 大脑”:AgentSpace 如何从“手动检索”跨越到“智能决策”? Agentic 框架快速概览 AI 智能体交互如何带领它走出对话框,从屏幕像素迈向真实物理世界 高级提示词技巧如何带领大模型走出“一本正经胡说八道”的误区? 当 AI 学会“开疆拓土”:探索与发现模式如何从源头打破静态知识的桎梏,在陌生领域催生新洞见? 拒绝 AI “手忙脚乱”:优先级模式如何为智能体打造毫秒级的“任务调度官”? 构建生产级可信 Agent:评估与监控中的自动化审计与闭环优化机制 当 AI 学会“三思后言”:安全护栏如何从源头掐灭偏见、幻觉与恶意攻击? 拒绝“张口就来”:推理技术如何让 AI 像人类一样拆解复杂难题? 当家方知柴米贵:资源感知优化如何让 AI 智能体告别“算力浪费”? 开启多智能体互联时代:A2A 协议与 AI 协作网络的底层逻辑 为智能体装上“实时百科全书”:RAG 如何打破 AI 的知识边界? 构建负责任的AI:人类监督、干预与反馈闭环机制 当工具失效、网络中断:AI智能体的应急与自修复指南 赋予AI“北极星”:如何让智能体自主设定并追踪目标 打破“M×N”集成魔咒:MCP如何重塑AI应用架构 赋予模型“生命力”:大语言模型如何通过学习实现持续适应 赋予模型“长期记忆”:大语言模型的记忆管理机制 AI的“社会性”:深入理解多智能体协作机制 理解AI智能体的规划机制 理解大模型的工具使用 智能体的元认知:反思模式与自我优化机制 智能体并行化模式:提升复杂工作流性能的关键技术 超越线性执行:使用LLM实现智能体路由模式 解构复杂任务:深入理解提示词链(Prompt Chaining) 别再被 AI 术语唬住了!一文理清你需要了解的所有概念 不止是多写几句:谷歌如何用69页白皮书重塑提示工程 [转]长PDF文档的总结与评估策略 Context 工程:如何把正确的上下文喂给 AI 前端如何写出优秀的 AI Agent Skills mac安装python后command not found: pip keep-alive 原理剖析 如何优雅地在 React 中使用TypeScript,看这一篇就够了! react useContext React Context 详细介绍(状态共享、数据传递) 以用户为中心的性能指标【译】 element下拉框远程搜索debounce防抖控制 vue-router打开新窗口 propmise: allSettled()与all()的区别 使用 new Date() 在chrome、安卓和 IOS 中表现不同
深入理解 RAG 中的格式化生成与函数调用
沐子馨 · 2026-05-29 · via 博客园 - 沐子馨

从大语言模型(LLM)那里获得一段非结构化的文本在应用中常常不满足实际需求。为了实现更复杂的逻辑、与外部工具交互或以用户友好的方式展示数据,需要模型能够输出具有特定结构的数据,例如 JSON 或 XML。

本篇将讨论实现格式化生成的几种主流方法,包括 LangChain、LlamaIndex 等框架内置的解决方案,不依赖框架的实现思路,以及一种更强大的技术——Function Calling。

在生成阶段,提示词工程也是一个重要的部分。但是因为在前几篇中已经有了比较多的介绍,本章就不再赘述了。

一、为什么需要格式化生成?

先来看几个具体的应用场景:

  • RAG 驱动的电商客服:当用户询问“推荐几款适合程序员的键盘”时,我们希望 LLM 返回一个包含产品名称、价格、特性和购买链接的 JSON 列表,而不是一段描述性文字,以便前端直接渲染成商品卡片。
  • 自然语言转 API 调用:用户说“帮我查一下明天从上海到北京的航班”,系统需要将这句话解析成一个结构化的 API 请求,如 {"departure": "上海", "destination": "北京", "date": "2025-07-18"}
  • 数据自动提取:从一篇新闻文章中,自动抽取出事件、时间、地点、涉及人物等关键信息,并以结构化形式存入数据库。

在这些场景中,格式化生成是连接 LLM 的自然语言理解能力和下游应用程序的程序化逻辑之间的关键。

二、格式化生成的实现方法

2.1 Output Parsers

LangChain 提供了一个强大的组件——OutputParsers(输出解析器),专门用于处理 LLM 的输出,其主要思想是在发送给 LLM 的提示(Prompt)中自动注入一段关于如何格式化输出的指令,并在得到结果后将 LLM 返回的纯文本字符串解析成预期的结构化数据(如 Python 对象)。

LangChain 提供了多种开箱即用的解析器,例如:

  • StrOutputParser:最基础的输出解析器,它简单地将 LLM 的输出作为字符串返回。
  • JsonOutputParser:可以解析包含嵌套结构和列表的复杂 JSON 字符串。
  • PydanticOutputParser:通过与 Pydantic 模型结合,可以实现对输出格式最严格的定义和验证。

接下来通过一个具体的代码示例,重点分析 PydanticOutputParser 的工作原理。它通过将用户定义的 Pydantic 数据模型转换为详细的格式指令,并注入到提示词中,来引导 LLM 生成严格符合该数据结构的 JSON 输出。最后再将模型返回的 JSON 字符串安全地解析为 Pydantic 对象实例。

# (此处省略了导入和 LLM 初始化代码)

# 1. 定义期望的数据结构
class PersonInfo(BaseModel):
    """用于存储个人信息的数据结构。"""
    name: str = Field(description="人物姓名")
    age: int = Field(description="人物年龄")
    skills: List[str] = Field(description="技能列表")

# 2. 基于 Pydantic 模型,创建解析器
parser = PydanticOutputParser(pydantic_object=PersonInfo)

# 3. 创建提示模板,注入格式指令
prompt = PromptTemplate(
    template="请根据以下文本提取信息。\n{format_instructions}\n{text}\n",
    input_variables=["text"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

# 4. 创建处理链 (假定 llm 已被初始化)
chain = prompt | llm | parser

# 5. 执行调用
text = "张三今年30岁,他擅长Python和Go语言。"
result = chain.invoke({"text": text})

# 6. 打印结果
print(result)
# name='张三' age=30 skills=['Python', 'Go语言']

(1)定义数据模型:使用 Pydantic 的 BaseModel 定义 PersonInfo 类,这不仅是一个 Python 对象,更是一个清晰的数据结构规范(Schema)。Field 中的 description 描述文本将直接作为指令提供给大模型,因此其表述需要清晰准确。

(2)生成格式指令:当 PydanticOutputParser 实例化后,其 get_format_instructions() 方法会执行以下操作:

  • 调用 Pydantic 模型的 .model_json_schema() 方法,提取出该数据结构的 JSON Schema 定义。
  • 对该 Schema 进行简化,并将其嵌入到一个预设的、指导性的提示模板中。这个模板明确要求 LLM 输出一个符合该 Schema 的 JSON 对象。

(3)构建并执行调用链:通过 LangChain 表达式语言(LCEL),将 promptllmparser 链接起来。当调用链被触发时:

  • prompt 会将用户输入(text)和上一步生成的格式指令(format_instructions)组合成最终的提示,发送给 llm
  • llm 根据这个包含严格格式要求的提示,生成一个 JSON 格式的字符串。

(4)解析与验证:PydanticOutputParser 接收到 LLM 返回的字符串后,会执行一个两步解析过程:

  • 首先,它继承自 JsonOutputParser,会将 LLM 输出的文本字符串解析成一个 Python 字典。
  • 然后,最关键的一步,它会使用 PersonInfo.model_validate() 方法,用定义的数据模型来验证这个字典。如果字典的键和值类型都符合 PersonInfo 的定义,解析器就会返回一个 PersonInfo 的实例对象;如果验证失败,则会抛出一个 OutputParserException 异常。

2.2 LlamaIndex 的输出解析

LlamaIndex 的输出解析与生成过程紧密结合,主要体现在两大核心组件中,分别是响应合成(Response Synthesis)和结构化输出(Structured Output)。

在 RAG 流程中,检索器召回一系列相关的文本块(Nodes)后,并不是简单地将它们拼接起来。响应合成器(Response Synthesizer)负责接收这些文本块和原始查询,并以一种更智能的方式将它们呈现给 LLM 以生成最终答案。例如,它可以逐块处理信息并迭代地优化答案(refine 模式),或者将尽可能多的文本块压缩进单次 LLM 调用中(compact 模式)。这个阶段的默认目标是生成一段高质量的文本回答。

当需要 LLM 返回结构化数据(如 JSON)而非纯文本时,LlamaIndex 主要使用 Pydantic 程序(Pydantic Programs)。这与 LangChain 的 PydanticOutputParser 思想一致:

  • 定义 Schema:开发者首先定义一个 Pydantic 模型,明确所需输出的数据结构、字段和类型。
  • 引导生成:LlamaIndex 会将这个 Pydantic 模型转换成 LLM 能理解的格式指令。如果底层的 LLM 支持 Function Calling,LlamaIndex 会优先使用该功能以获得更可靠的结构化输出。如果不支持,它会回退到将 JSON Schema 注入到提示词中的方法。
  • 解析验证:最后,LLM 返回的输出会被自动解析并用 Pydantic 模型进行验证,确保其类型和结构完全正确,最终返回一个 Pydantic 对象实例。

2.3 不依赖框架的简单实现思路

如果不想依赖特定的框架,也可以通过提示工程的技巧来实现格式化生成。

主要思路是在提示中给出清晰、明确的指令和示例。以下是一些实用技巧:

  • 明确要求 JSON 格式:在提示中直接、强硬地要求模型“必须返回一个 JSON 对象”、“不要包含任何解释性文字,只返回 JSON”。
  • 提供 JSON Schema:在提示中给出你想要的 JSON 对象的模式(Schema),描述每个键的含义和数据类型。
  • 提供 few-shot 示例:给出 1-2 个“用户输入 -> 期望的 JSON 输出”的完整示例,让模型学习输出的格式和风格。
  • 使用语法约束:对于一些本地部署的开源模型(如通过 llama.cpp 运行的模型),可以使用 GBNF (GGML BNF) 等语法文件来强制约束模型的输出,确保其生成的每一个 token 都严格符合预定义的 JSON 语法。这是最严格也是最可靠的非 Function Calling 方法。

三、Function Calling

Function Calling(或称 Tool Calling)是近年来 LLM 领域的一个重要进展,提升了模型与外部世界交互和生成结构化数据的能力。

3.1 概念与工作流程

Function Calling 的本质是一个多轮对话流程,让模型、代码和外部工具(如 API)协同工作。其核心工作流如下:

(1)定义工具:首先,在代码中以特定格式(通常是 JSON Schema)定义好可用的工具,包括工具的名称、功能描述、以及需要的参数。

(2)用户提问:用户发起一个需要调用工具才能回答的请求。

(3)模型决策:模型接收到请求后,分析用户的意图,并匹配最合适的工具。它不会直接回答,而是返回一个包含 tool_calls 的特殊响应。这个响应相当于一个指令:“请调用某某工具,并使用这些参数”。

(4)代码执行:应用接收到这个指令,解析出工具名称和参数,然后在代码层面实际执行这个工具(例如,调用一个真实的天气 API)。

(5)结果反馈:将工具的执行结果(例如,从 API 获取的真实天气数据)包装成一个 roletool 的消息,再次发送给模型。

(6)最终生成:模型接收到工具的执行结果后,结合原始问题和工具返回的信息,生成最终的、自然的语言回答。

3.2 Function Calling 实践

接下来,直接使用 openai 的例子,来展示上述流程。

# 1. 定义工具
tools = [...] 

# 2. 用户提问
messages = [{"role": "user", "content": "杭州今天天气怎么样?"}]
message = send_messages(messages, tools=tools)

# 3. 代码执行:模拟调用天气API,并将结果添加到消息历史
if message.tool_calls:
    tool_call = message.tool_calls[0]
    messages.append(message) # 添加模型的回复
    tool_output = "24℃,晴朗" # 模拟API结果
    messages.append({
        "role": "tool", 
        "tool_call_id": tool_call.id, 
        "content": tool_output
    }) # 添加工具执行结果

    # 4. 第二次调用 (`Tool -> Model`):将工具结果返回给模型,获取最终回答
    final_message = send_messages(messages, tools=tools)
    print(final_message.content)

关键步骤:

(1)**定义 tools**:用一个列表包含了所有可用的函数定义。每个定义都是一个 JSON 对象,严格描述了函数的名称 (name)、功能 (description) 和参数 (parameters)。这个描述的质量直接决定了模型能否正确选择和使用工具。

(2)**第一次调用 (User -> Model)**:将用户的原始问题("role": "user")和 tools 列表一同发送给模型。

(3)处理 tool_calls:检查模型的响应中是否包含 tool_calls。如果包含,就说明模型决定使用工具。解析出函数名和参数,并模拟执行(在真实场景中,这里会是真实的 API 调用)。

(4)**第二次调用 (Tool -> Model)**:将原始的用户问题、模型的工具调用响应,以及模拟执行后得到的工具结果("role": "tool"),一同打包成新的对话历史,再次发送给模型。

(5)获取最终答案:模型在看到工具的执行结果后,就能用自然语言回答用户最初的问题了。

3.3 Function Calling 的优势

相比于单纯通过提示工程“请求”模型输出 JSON,Function Calling 的优势在于:

  • 可靠性更高:这是模型原生支持的能力,相比于解析可能格式不稳定的纯文本输出,这种方式得到的结构化数据更稳定、精确。
  • 意图识别:它不仅仅是格式化输出,更包含了“意图到函数的映射”。模型能根据用户问题主动选择最合适的工具。
  • 与外部世界交互:它是构建能执行实际任务的 AI 代理(Agent)的核心基础,让 LLM 可以查询数据库、调用 API、控制智能家居等。