函数调用快速提取结构化数据使用技巧
一、这是什么?(概念解释)
结构化输出(Structured Output) 是让大语言模型返回固定格式数据的能力,而不是普通的文本。
在 LangChain 中,通过 with_structured_output() 方法,可以指定一个 Pydantic 模型,让 LLM 直接返回符合该模型的数据结构。
核心优势:
- 类型安全:直接得到 Python 对象,无需手动解析
- 自动验证:Pydantic 自动验证返回的数据格式
- 两种模式:
function_calling:使用函数调用方式(推荐)json_mode:使用 JSON 模式(兼容性强)
二、有什么用?(应用场景)
| 场景 | 说明 |
|---|---|
| 信息提取 | 从文本中提取姓名、日期、地址等结构化信息 |
| 数据清洗 | 将非结构化数据转换为结构化格式 |
| 表单填充 | 从对话中自动提取表单字段 |
| QA 生成 | 从文档中提取问题和答案对 |
| 实体识别 | 识别人名、地名、机构名等命名实体 |
| 分类标注 | 对文本进行分类并返回分类标签 |
| 数据转换 | 将一种格式的数据转换为另一种格式 |
三、完整示例代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import dotenv
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# ============ 第一步:定义数据结构(Pydantic 模型)============
class QAExtra(BaseModel):
"""一个问答键值对工具,传递对应的假设性问题+答案"""
question: str = Field(description="假设性问题")
answer: str = Field(description="假设性问题对应的答案")
# ============ 第二步:创建结构化输出模型 ============
llm = ChatOpenAI(model="moonshot-v1-8k")
# 方式一:使用 JSON 模式(兼容性强)
structured_llm = llm.with_structured_output(QAExtra, method="json_mode")
# 方式二:使用函数调用(推荐,如果模型支持)
# structured_llm = llm.with_structured_output(QAExtra, method="function_calling")
# ============ 第三步:创建链 ============
prompt = ChatPromptTemplate.from_messages([
("system", "请从用户传递的query中提取出假设性的问题+答案。响应格式为JSON,并携带`question`和`answer`两个字段。"),
("human", "{query}")
])
chain = {"query": RunnablePassthrough()} | prompt | structured_llm
# ============ 第四步:调用 ============
result = chain.invoke("我叫慕小课,我喜欢打篮球,游泳。")
print(result)
# 输出:QAExtra(question="慕小课喜欢什么运动?", answer="打篮球和游泳")
# 可以直接访问字段
print(f"问题:{result.question}")
print(f"答案:{result.answer}")
四、高级示例:提取多个字段
from typing import List, Optional
from pydantic import BaseModel, Field
class PersonInfo(BaseModel):
"""从文本中提取人物信息"""
name: str = Field(description="人物姓名")
age: Optional[int] = Field(description="年龄,如果没有则为None", default=None)
hobbies: List[str] = Field(description="爱好列表")
city: Optional[str] = Field(description="所在城市", default=None)
class PersonList(BaseModel):
"""人物列表"""
people: List[PersonInfo] = Field(description="提取到的人物列表")
# 创建结构化输出
structured_llm = llm.with_structured_output(PersonList, method="json_mode")
prompt = ChatPromptTemplate.from_messages([
("system", "请从文本中提取所有人物的信息,包括姓名、年龄、爱好和城市。"),
("human", "{text}")
])
chain = prompt | structured_llm
text = "我叫张三,今年25岁,喜欢编程和阅读。我的朋友李四30岁,他喜欢打篮球,住在北京。"
result = chain.invoke(text)
for person in result.people:
print(f"{person.name}, {person.age}岁, 爱好: {person.hobbies}, 城市: {person.city}")
五、流程对比图
┌─────────────────────────────────────────────────────────────────────────┐
│ 普通输出 VS 结构化输出对比 │
└─────────────────────────────────────────────────────────────────────────┘
================== 普通文本输出 ==================
用户输入 LLM 应用层
│ │ │
▼ ▼ │
"我叫慕小课..." ────────────▶│ 分析文本 │
│ │ │
│ │ 生成文本回答 │
│◀───────────────────────│ │
│ │ │
"根据文本,慕小课 │ │
喜欢打篮球和游泳" │ │
│ │ │
▼ ▼ ▼
需要手动解析字符串 ❌ 不方便
================== 结构化输出 ==================
用户输入 LLM 应用层
│ │ │
▼ ▼ │
"我叫慕小课..." ────────────▶│ 分析文本 │
│ │ │
│ │ 返回 JSON 对象 │
│◀───────────────────────│ {question: "...", │
│ │ answer: "..."} │
│ │ │
▼ ▼ ▼
直接得到 Python 对象 ✓ 类型安全
QAExtra( ✓ 自动验证
question="慕小课喜欢...", ✓ 无需解析
answer="打篮球和游泳"
)
┌─────────────────────────────────────────────────────────────────────────┐
│ 核心差异 │
└─────────────────────────────────────────────────────────────────────────┘
普通输出: 结构化输出:
- 返回字符串 - 返回 Pydantic 对象
- 需要手动解析 - 自动验证类型
- 容易出错 - 类型安全
- 不方便集成 - 直接使用字段