在大模型应用开发中,从非结构化文本中提取结构化信息是高频需求。今天我们就来聊聊如何通过 LangChain 与 DeepSeek 模型的结合,实现精准的信息提取,全程附带代码实战,新手也能轻松上手。
一、定义清晰的提取 schema:用 Pydantic 构建数据模型
我们先需要告诉模型 “要提取什么”。通过 Pydantic 定义数据模型是最佳实践,来看这个 Person 类的设计:
python
from typing import Optional
from pydantic import BaseModel, Field
class Person(BaseModel):
"""人物信息提取模型。
用于从文本中提取人物相关属性,若信息未知或无法提取,字段值可为空。
DeepSeek模型请严格按照以下字段结构返回JSON,允许字段值为null。"""
name: Optional[str] = Field(
default=None,
description="人物的姓名(如:张三、Alice),若文本未提及则返回null"
)
hair_color: Optional[str] = Field(
default=None,
description="人物的头发颜色(如:黑色、金色、棕色),若文本未提及头发颜色则返回null"
)
height_in_meters: Optional[str] = Field(
default=None,
description="人物的身高(单位:米,如:1.75、1.80),需保留两位小数,若未提及身高则返回null"
)
这里有几个关键设计点:
- 使用
Optional
类型允许字段为空,避免模型编造信息 Field
描述中用具体示例(如 “1.75 米”)明确格式要求- 保留两位小数的细节规定,确保输出一致性
当需要提取多个实体时,我们可以用容器模型封装:
python
from typing import List, Optional
class ExtractedData(BaseModel):
"""多人物信息提取容器
用于封装多个Person实体,形成结构化列表输出
DeepSeek模型请严格按照此结构返回JSON数组"""
people: List[Person] = Field(
description="从文本中提取的人物信息列表,每个元素需符合Person模型结构,无匹配信息时返回空数组"
)
这种层级结构让模型清楚:输出应该是包含多个 Person 对象的列表,没有匹配时返回空数组。
二、构建提示模板:让模型明确任务边界
接下来需要告诉模型 “如何处理输入”。我们通过ChatPromptTemplate
构建系统提示:
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt_template = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一个专业的信息提取算法。"
"仅从文本中提取相关信息。"
"如果你不知道被要求提取的属性的值,请将该属性的值设为 null。"
),
("human", "{text}")
]
)
这里的 system prompt 做了三个关键约束:
- 明确角色定位:专业的信息提取工具
- 划定任务边界:仅从输入文本提取,不做额外联想
- 错误处理规范:未知信息设为 null
这种明确的指令能有效减少模型的 “幻觉” 问题,比如不会把 “身高八尺” 强行转换为米制单位,而是返回 null。
三、工具调用实战:从单实体到多实体提取
初始化 LLM 时我们指定 DeepSeek 模型,并通过with_structured_output
绑定数据 schema:
python
from langchain_deepseek import ChatDeepSeek
llm = ChatDeepSeek(
model="deepseek-chat",
temperature=0, # 固定输出,避免随机性
max_tokens=None,
timeout=None,
max_retries=2,
api_key="sk-***",
)
structured_llm = llm.with_structured_output(schema=ExtractedData)
来看实际提取效果:
当输入 “张飞身高 1.8 米,有一头黑发。” 时,模型能准确提取:
python
{
"people": [
{
"name": "张飞",
"hair_color": "黑色",
"height_in_meters": "1.80"
}
]
}
处理多实体场景 “张飞的头发是黑色的,身高 1.8 米。李逵的头发和张飞的颜色一样。” 时,模型会:
- 提取张飞的完整信息
- 推断李逵的头发颜色(通过 “颜色一样”)
- 身高字段因未提及设为 null
最终输出符合预期的结构化列表,这说明模型能理解上下文关联信息。
四、少样本提示:用示例教会模型理解规则
当模型对复杂逻辑或特定格式的理解不够精准时,少样本提示就像 “教学手册”—— 通过少量「用户输入 - 工具输出 - 最终反馈」的完整示例,让模型快速掌握业务规则。我们结合具体实现,拆解这个关键技术环节。
4.1 消息序列的完整构建过程
我们先定义两个典型示例:
python
examples = [
(
"txt", # 空文本输入,无人物信息
ExtractedData(people=[]),
),
(
"张飞的头发是黑色的,身高1.8米。李逵的头发和张飞的颜色一样。", # 多人物文本,包含隐含信息
ExtractedData(people=[
Person(name='张飞', hair_color='黑色', height_in_meters='1.80'),
Person(name='李逵', hair_color='黑色', height_in_meters=None)
]),
),
]
通过tool_example_to_messages
生成的完整消息序列(以 DeepSeek 格式为例),实际是这样的 “教学对话”:
json
[
{
"role": "user",
"content": "txt" // 第一条用户输入:空文本,测试无信息场景
},
{
"role": "assistant",
"tool_call": {
"tool": {
"name": "extract_person", // 工具名由schema自动生成,固定为模型可识别的处理函数
"parameters": {"text": "txt"} // 传入用户输入文本作为处理参数
}
}
},
{
"role": "tool",
"content": "{\"people\": []}" // 工具返回空数组,明确“无匹配信息时输出空列表”
},
{
"role": "assistant",
"content": "未检测到人物信息." // 助手总结,建立“空数据→明确反馈”的映射
},
]
4.2 消息序列的三层核心教学目标
第一层:教会模型 “如何调用工具”(格式标准化)
- 痛点:不同模型的工具调用语法不同(如 OpenAI 用
function call
,DeepSeek 用tool
字段),手动编写易出错。 - 解决方案:
tool_example_to_messages
自动根据所选模型(此处为 DeepSeek)生成合规的tool_call
结构:python
# DeepSeek要求的工具调用格式 { "tool": { "name": "extract_person", // 工具名由schema名称自动生成,无需手动定义 "parameters": {"text": 输入文本} // 固定传入待处理的text参数 } }
开发者无需记忆复杂格式,模型也能通过示例明确 “遇到用户文本,就调用 extract_person 工具,传入 text 参数”。
第二层:教会模型 “如何输出数据”(结构严格化)
- 关键设计:通过 Pydantic 模型
ExtractedData
和Person
,给模型划定 “输出红线”:- 必须包含
people
字段,且是Person
对象的列表(如[]
或[{...}, {...}]
) - 每个
Person
必须包含name
、hair_color
、height_in_meters
,允许null
但不能缺失字段
- 必须包含
- 示例作用:
第二个示例的工具输出中,李逵的height_in_meters
为null
而非忽略,明确 “未提及字段必须保留并设为 null”;张飞的身高是"1.80"
而非"1.8"
,强调 “必须保留两位小数”。这些细节通过具体案例传递,比文字描述更直观。
第三层:教会模型 “如何反馈结果”(逻辑链条化)
- 消息序列的隐性逻辑:
plaintext
用户输入文本 → 助手调用工具处理 → 工具返回结构化数据 → 助手根据数据有无给出总结
第一层示例(空文本)展示 “无数据时,助手回复‘未检测到人物信息’”;
第二层示例(多人物)展示 “有数据时,助手回复‘检测到人物信息’”。
这种 “输入 - 处理 - 反馈” 的完整链路,让模型学会在实际处理时,自动遵循 “先工具提取,再总结反馈” 的流程,避免跳过关键步骤。
4.3 少样本提示的核心优势:从 “模糊指令” 到 “具体示范”
假设我们只用文字指令告诉模型:“遇到人物信息,提取姓名、发色、身高,未提及的设为 null”,模型可能会困惑:
- “身高的单位是米还是厘米?需要保留几位小数?”
- “‘发色相同’这种隐含关系要不要处理?怎么处理?”
而少样本提示通过具体示例,让模型 “看到” 正确做法:
- 格式规范:身高必须是字符串类型,保留两位小数(如
"1.80"
) - 逻辑处理:通过 “颜色一样” 推断李逵的发色,但身高未提及时严格设为
null
- 边界情况:无匹配信息时返回空数组,而非报错或乱填
这种 “示例驱动” 的学习方式,比单纯依赖自然语言指令更高效,尤其适合处理:
- 字段格式有严格要求(如日期、金额)
- 存在上下文关联(如代词指代、隐含关系)
- 模型容易产生 “幻觉”(如编造未提及的信息)
4.4 示例设计的黄金法则
- 覆盖核心场景:至少包含 “单实体”“多实体”“无匹配” 三类示例,确保模型能处理 80% 的常见情况。
- 突出差异点:每个示例重点展示一个规则,如示例 1 强调 “空输入→空数组”,示例 2 强调 “隐含信息提取 + 字段完整性”。
- 控制数量:2-3 个高质量示例优于 10 个重复示例,避免模型因信息过载导致规则混淆。
五、避坑指南:确保提取准确性的关键细节
在实践中需要注意这些细节:
- 数据类型一致性:身高字段定义为字符串而非浮点型,避免模型返回科学计数法等格式
- 单位严格性:明确要求 “米” 为单位,防止将 “180cm” 直接转换导致的精度问题
- 未知处理:所有字段默认值设为 None,配合 system prompt 的 null 返回要求,避免模型猜测
- 温度控制:设置 temperature=0,确保输出确定性,避免同一输入产生不同结果
当遇到复杂文本时,建议分步骤处理:
- 先通过简单示例验证单个字段提取
- 逐步增加实体数量和关联关系
- 对边界情况(如完全无匹配文本)进行鲁棒性测试
总结:打造可靠的信息提取流水线
通过今天的实践,我们掌握了从数据建模到模型调用的完整流程:
- 用 Pydantic 定义清晰的提取 schema,明确 “要什么”
- 通过提示模板约束模型行为,规范 “怎么做”
- 利用少样本示例强化特定规则,解决 “怎么做好”
这套方法适用于各类垂直领域的信息提取任务,无论是简历解析、医疗文本处理还是电商商品信息抓取,核心思路都是相通的。关键是要:
- 提前定义好完整的数据模型
- 用明确的指令约束模型行为
- 通过示例让模型理解业务规则
现在就可以将这些代码整合到你的项目中,试试从自己的文本数据中提取结构化信息吧!如果在实践中遇到问题,欢迎在评论区交流 —— 记得点击关注,后续会分享更多大模型应用开发的实战技巧~
觉得有帮助的话,别忘了收藏这篇文章,方便后续查阅。也欢迎点赞让更多开发者看到