LangChain之提取解析与结构化输出

提取结构化输出

概述

从原始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_chainOpenAI函数调用来提取所需模式。

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 更正或验证提取结果)
  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeDevMaster

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值