提取解析与结构化输出
- 提取结构化输出
- 概述
- 提取方式
- 使用函数
- 配置Schema模式、架构
- 提取器
- 执行
- 多个实体
- 使用create_extraction_chain
- 使用输出解析器
- 使用参考示例
- 配置Schema模式、架构
- 定义参考示例
- 创建一个提取器
- 执行测试
- 提取解析输出指南
提取结构化输出
概述
从原始LLM生成结构化输出可能是非常困难的,尤其在需要特定格式时。但是LLM只需适当说明和示例,就可以快速适应提取任务。
对于大模型输出的结构化数据,是非常重要、非常有用的,例如可以有以下应用:
提取要插入数据库的结构化行
提取API参数
提取用户查询的不同部分,执行语义提取
提取方式
函数方式:
某些LLM可以调用函数来从LLM响应中提取任意实体,比解析器更通用。
解析方式:
输出解析器是用于结构化LLM响应的类,能精确提取在Schema中定义的列属性。
基于提示:
LLM可以很好地遵循指示,可以指示以所需格式生成文本,从而实现结构化数据的输出。
使用函数
需要使用支持函数/工具调用的模型来从文本中提取信息
配置Schema模式、架构
Pydantic是一个用于Python的数据验证和设置管理库。它允许创建具有自动验证的属性的数据类。
定义一个带有类型注释的属性的类,描述想要从文本中提取哪些信息
from typing import Optional
from langchain_core.pydantic_v1 import BaseModel, Field
class Person(BaseModel):
"""
关于一个人的信息。
创建实体 Person 的文档字符串, 并作为模式 Person 的描述发送到 LLM,它可以帮助提高提取结果。
注意:
1. 每个字段都是“可选”——这允许模型拒绝提取它!
2. 每个字段都有一个“描述”——该描述由LLM使用。
良好的描述有助于改善提取结果。
"""
name: Optional[str] = Field(default=None, description="人名")
hair_color: Optional[str] = Field(default=None, description="头发的颜色")
height_in_meters: Optional[str] = Field(default=None, description="身高以米为单位测量")
两种最佳实践:
记录属性和模式本身:此信息被发送到 LLM 并用于提高信息提取的质量。
请勿强行LLM编造信息!上面使用 Optional 作为属性,允许 LLM 在不知道答案时输出 None 。
提取器
使用定义的Schema模式创建一个信息提取器
from langchain_core.prompts import ChatPromptTemplate
# 定义自定义提示以提供说明和任何其他上下文。
# 1) 可以在提示模板中添加示例以提高提取质量
# 2) 引入额外的参数以考虑上下文(例如,包括从中提取文本的文档元数据)。
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"您是提取算法专家。只从文本中提取相关信息。如果您不知道要求提取的属性的值,返回 null 作为属性值。",
),
("human", "{text}"),
]
)
执行
from langchain_openai import ChatOpenAI
# 初始化大模型
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 返回格式化以匹配给定模式的输出的模型包装器
# with_structured_output API处于测试阶段,未来可能会改变。
runnable = prompt | llm.with_structured_output(schema=Person)
# 执行
text = "Alan Smith is 6 feet tall and has blond hair."
res = runnable.invoke({"text": text})
print(res)
执行输出结果如下:
name='Alan Smith' hair_color='blond' height_in_meters='1.83'
多个实体
在大多数情况下,应该提取实体列表而不是单个实体,这可以通过使用 pydantic 将模型相互嵌套来轻松实现。
from typing import Optional, List
from langchain_core.pydantic_v1 import BaseModel, Field
class Person(BaseModel):
"""
关于一个人的信息。
创建实体 Person 的文档字符串, 并作为模式 Person 的描述发送到 LLM,它可以帮助提高提取结果。
注意:
1. 每个字段都是“可选”——这允许模型拒绝提取它!
2. 每个字段都有一个“描述”——该描述由LLM使用。
良好的描述有助于改善提取结果。
"""
name: Optional[str] = Field(default=None, description="人名")
hair_color: Optional[str] = Field(default=None, description="头发的颜色")
height_in_meters: Optional[str] = Field(default=None, description="身高以米为单位测量")
class Data(BaseModel):
"""提取有关人员的数据。"""
# 创建一个模型,以便我们可以提取多个实体。
people: List[Person]
from langchain_core.prompts import ChatPromptTemplate
# 定义自定义提示以提供说明和任何其他上下文。
# 1) 可以在提示模板中添加示例以提高提取质量
# 2) 引入额外的参数以考虑上下文(例如,包括从中提取文本的文档元数据)。
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"您是提取算法专家。只从文本中提取相关信息。如果您不知道要求提取的属性的值,返回 null 作为属性值。",
),
# Please see the how-to about improving performance with
# reference examples.
# MessagesPlaceholder('examples'),
("human", "{text}"),
]
)
from langchain_openai import ChatOpenAI
# 初始化大模型
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 返回格式化以匹配给定模式的输出的模型包装器
# with_structured_output API处于测试阶段,未来可能会改变。
runnable = prompt | llm.with_structured_output(schema=Data)
# 执行
text = "My name is Jeff, my hair is black and i am 6 feet tall. Anna has the same color hair as me."
res = runnable.invoke({"text": text})
print(res)
执行输出结果如下:
people=[
Person(name='Jeff', hair_color='black', height_in_meters='1.83'),
Person(name='Anna', hair_color='black', height_in_meters=None)
]
使用create_extraction_chain
OpenAI函数是一种提取的方式。定义一个模式,指定从LLM输出中提取的属性,然后使用
create_extraction_chain
和OpenAI函数
调用来提取所需模式。
from langchain.chains import create_extraction_chain
from langchain_openai import ChatOpenAI
# 模式
schema = {
# 人的属性
"properties": {
"name": {"type": "string"},
"height": {"type": "integer"},
"hair_color": {"type": "string"},
},
# 允许模型只返回的属性
"required": ["name", "height"],
}
# 输入
inp = """亚历克斯身高 5 英尺。克劳迪娅比亚历克斯高 1 英尺,并且跳得比他更高。克劳迪娅是黑发女郎,亚历克斯是金发女郎。"""
# 初始化大模型
llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo")
# 构建链
chain = create_extraction_chain(schema, llm)
# 执行
res = chain.invoke(inp)
print(res)
{'input': '亚历克斯身高 5 英尺。克劳迪娅比亚历克斯高 1 英尺,并且跳得比他更高。克劳迪娅是黑发女郎,亚历克斯是金发女郎。',
'text': [
{'name': '亚历克斯', 'height': 5},
{'name': '克劳迪娅', 'height': 1, 'hair_color': '黑发'}
]}
使用输出解析器
使用输出解析器实现提取解析与结构化输出,输出解析器是帮助结构化语言模型响应的类。
from typing import Optional, List
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
class Person(BaseModel):
"""
关于一个人的信息。
创建实体 Person 的文档字符串, 并作为模式 Person 的描述发送到 LLM,它可以帮助提高提取结果。
注意:
1. 每个字段都是“可选”——这允许模型拒绝提取它!
2. 每个字段都有一个“描述”——该描述由LLM使用。
良好的描述有助于改善提取结果。
"""
name: Optional[str] = Field(default=None, description="人名")
hair_color: Optional[str] = Field(default=None, description="头发的颜色")
height_in_meters: Optional[str] = Field(default=None, description="身高以米为单位测量")
class Data(BaseModel):
"""提取有关人员的数据。"""
people: List[Person]
from langchain_core.prompts import PromptTemplate
from langchain_openai import OpenAI
query = """亚历克斯身高 5 英尺。克劳迪娅比亚历克斯高 1 英尺,并且跳得比他更高。克劳迪娅是黑发女郎,亚历克斯是金发女郎。"""
# 设置解析器 + 将指令注入提示模板。
parser = PydanticOutputParser(pydantic_object=Data)
# 提示
prompt = PromptTemplate(
template="回答用户查询。\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# 格式化提示
promptValue = prompt.format_prompt(query=query)
# 初始化大模型并执行
llm = OpenAI(temperature=0)
output = llm.invoke(promptValue.to_string())
# 执行输出解析
res = parser.parse(output)
print(res)
people=[
Person(name='Alex', hair_color='Blonde', height_in_meters='1.524'),
Person(name='Claudia', hair_color='Black', height_in_meters='1.8288')
]
使用参考示例
可以通过提供的参考示例来提高提取的质量LLM。
配置Schema模式、架构
定义一个带有类型注释的属性的类,描述想要从文本中提取哪些信息
from typing import List, Optional
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
class Person(BaseModel):
"""有关一个人的信息。"""
name: Optional[str] = Field(..., description="人名")
hair_color: Optional[str] = Field(..., description="眼睛的颜色")
height_in_meters: Optional[str] = Field(..., description="身高(米)")
class Data(BaseModel):
"""提取有关人员的数据。"""
people: List[Person]
定义参考示例
示例可以定义为输入-输出对的列表。每个示例都包含一个示例input文本和一个output 显示应从文本中提取内容的示例。
import uuid
from typing import List, TypedDict
from langchain_core.messages import (
AIMessage,
BaseMessage,
HumanMessage,
ToolMessage,
)
from langchain_core.pydantic_v1 import BaseModel
class Example(TypedDict):
"""由文本输入和预期工具调用组成的示例表示.
对于提取,工具调用被表示为 pydantic 模型的实例。
"""
input: str # 这是示例文本
tool_calls: List[BaseModel] # 应提取的 pydantic 模型实例
def tool_example_to_messages(example: Example) -> List[BaseMessage]:
"""将示例转换为可以输入 LLM 的消息列表。
此代码是一个适配器,它将我们的示例转换为消息列表 可以将其输入聊天模型中。
每个示例的消息列表对应于:
1) HumanMessage:包含应从中提取内容的内容。
2)AIMessage:包含从模型中提取的信息
3) ToolMessage:包含对模型的确认,表明模型正确地请求了工具。
ToolMessage 是必需的,因为某些聊天模型针对代理进行了超级优化 而不是用于提取用例。
"""
messages: List[BaseMessage] = [HumanMessage(content=example["input"])]
openai_tool_calls = []
for tool_call in example["tool_calls"]:
openai_tool_calls.append(
{
"id": str(uuid.uuid4()),
"type": "function",
"function": {
"name": tool_call.__class__.__name__,
"arguments": tool_call.json(),
},
}
)
messages.append(
AIMessage(content="", additional_kwargs={"tool_calls": openai_tool_calls})
)
tool_outputs = example.get("tool_outputs") or ["You have correctly called this tool."] * len(openai_tool_calls)
for output, tool_call in zip(tool_outputs, openai_tool_calls):
messages.append(ToolMessage(content=output, tool_call_id=tool_call["id"]))
return messages
定义示例,然后将它们转换为消息格式。
examples = [
(
"海洋广阔而蔚蓝。它的深度超过 20,000 英尺。里面有很多鱼。",
Person(name=None, height_in_meters=None, hair_color=None),
),
(
"菲奥娜从法国长途跋涉来到西班牙。她是 1.75 米高,而且她的眼睛是深色的。",
Person(name="菲奥娜", height_in_meters=1.75, hair_color="深色"),
),
]
messages = []
for text, tool_call in examples:
messages.extend(
tool_example_to_messages({"input": text, "tool_calls": [tool_call]})
)
创建一个提取器
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# 定义自定义提示以提供说明和任何其他上下文。
# 1) 可以在提示模板中添加示例以提高提取质量
# 2) 引入额外的参数以考虑上下文(例如,包括从中提取文本的文档元数据)。
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"您是提取算法专家。只从文本中提取相关信息。如果您不知道要求提取的属性的值,返回 null 作为属性值。",
),
MessagesPlaceholder("examples"),
("human", "{text}"),
]
)
text = "Jack来自美国,他1.75 米高"
res = prompt.invoke({"text": text, "examples": messages})
# print(res)
执行测试
初始化大模型,创建执行链
llm = ChatOpenAI(
model="gpt-4-0125-preview",
temperature=0,
)
runnable = prompt | llm.with_structured_output(
schema=Data,
method="function_calling",
include_raw=False,
)
向LLM输入语句,测试添加参考示例对输出结果的影响
print(runnable.invoke({"text": "太阳系很大,但地球只有1个月亮。", "examples": []}))
print(runnable.invoke({"text": "太阳系很大,但地球只有1个月亮。", "examples": messages}))
print(runnable.invoke({"text": "Jack来自美国,他1.75 米高", "examples": messages}))
从执行日志可知,添加参考示例后,模型输出回答的更好
people=[Person(name='Earth', hair_color='Blue', height_in_meters='1')]
people=[Person(name='太阳系', hair_color=None, height_in_meters=None)]
people=[Person(name='Jack', hair_color='null', height_in_meters='1.75')]
提取解析输出指南
提取结果的质量取决于许多因素,参考以下建议,帮助从模型中获得最佳性能
将模型温度设置为 0
改进提示。提示应该准确、切题
记录架构:确保记录架构以便为 LLM 提供更多信息
提供参考例子!各种示例都可以提供帮助,包括不应提取任何内容的示例。
如果您有很多示例,请使用检索器来检索最相关的示例
具有最佳可用 LLM/聊天模型(例如 gpt-4、claude-3 等)的基准
如果架构非常大,请尝试将其分解为多个较小的架构,运行单独的提取并合并结果
确保架构允许模型拒绝提取信息。如果没有的话,模特就会被强制补信息
添加验证/更正步骤(要求 LLM 更正或验证提取结果)