一架梯子,一头程序猿,仰望星空!
使用LangChain提取结构化数据 > 内容正文

使用参考示例


使用参考示例

通常,通过向LLM提供参考例子,可以改善数LLM的据提取的质量。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你是一种专家级的数据提取算法。"
            "只从文本中提取相关信息。"
            "如果您不知道被要求提取的属性的值,"
            "则返回该属性值的null值。",
        ),
        MessagesPlaceholder("examples"),  # <-- 示例占位符!
        ("human", "{text}"),
    ]
)

测试模板:

from langchain_core.messages import (
    HumanMessage,
)

prompt.invoke(
    {"text": "这是一些文本", "examples": [HumanMessage(content="测试 1 2 3")]}
)
ChatPromptValue(messages=[SystemMessage(content="您是一种专家级的提取算法。只从文本中提取相关信息。如果您不知道被要求提取的属性的值,则返回该属性值的null值。"), HumanMessage(content='测试 1 2 3'), HumanMessage(content='这是一些文本')])

定义需要提取的数据模型

让我们从快速开始中重新使用人物模型。

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]

定义参考示例

示例可以被定义为输入-输出对的列表。

每个示例包含一个示例输入文本和一个示例输出,显示应从文本中提取什么。

import uuid
from typing import Dict, List, TypedDict

from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    HumanMessage,
    SystemMessage,
    ToolMessage,
)
from langchain_core.pydantic_v1 import BaseModel, Field


# 定义参考例子的数据结构,用来保存例子数据样本
class Example(TypedDict):
    """每一个样本数据,由文本输入和预期的工具调用结果组成。
    """

    input: str  # 这是用户输入的问题样例
    tool_calls: List[BaseModel]  # 这里就是LLM模型应该返回的正确数据,我们这里使用 pydantic 模型的实例表示


def tool_example_to_messages(example: Example) -> List[BaseMessage]:
    """将示例转换为可以输入到 LLM 中的消息列表,目的就是格式化参考示例数据,方便传给LLM模型。

    此代码是一个适配器,将我们的示例转换为可以输入到聊天模型中的消息列表。

    每个示例的消息列表对应于:

    1) HumanMessage:包含应该被提取的内容,也就是用户输入的问题。
    2) AIMessage:包含模型中提取的信息,就是LLM应该返回的数据结构
    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 [
        "您正确地调用了这个工具。"
    ] * 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),
    ),
    (
        "Fiona 远离法国到达了西班牙。",
        Person(name="Fiona", height_in_meters=None, hair_color=None),
    ),
]


messages = []

for text, tool_call in examples:
    messages.extend(
        tool_example_to_messages({"input": text, "tool_calls": [tool_call]})
    )

让我们测试一下提示

prompt.invoke({"text": "这是一些文本", "examples": messages})
ChatPromptValue(messages=[SystemMessage(content="您是一位专家级的提取算法。只需从文本中提取相关信息。如果您不知道要提取的属性的值,可以将该属性的值返回为null。"), HumanMessage(content="海洋辽阔而湛蓝。它的深度超过20,000英尺。里面有许多鱼。"), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'c75e57cc-8212-4959-81e9-9477b0b79126', 'type': 'function', 'function': {'name': 'Person', 'arguments': '{"name": null, "hair_color": null, "height_in_meters": null}'}}]}), ToolMessage(content='您已正确调用了这个工具。', tool_call_id='c75e57cc-8212-4959-81e9-9477b0b79126'), HumanMessage(content='Fiona从法国远赴西班牙。'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': '69da50b5-e427-44be-b396-1e56d821c6b0', 'type': 'function', 'function': {'name': 'Person', 'arguments': '{"name": "Fiona", "hair_color": null, "height_in_meters": null}'}}]}), ToolMessage(content='您已正确调用了这个工具。', tool_call_id='69da50b5-e427-44be-b396-1e56d821c6b0'), HumanMessage(content='这是一些文本')])

创建提取器实现数据提取

在这里,我们将使用 gpt-4 来创建一个提取器。

llm = ChatOpenAI(
    model="gpt-4-0125-preview",
    temperature=0,
)


runnable = prompt | llm.with_structured_output(
    schema=Data,
    method="function_calling",
    include_raw=False,
)

没有示例的情况

请注意,尽管我们在使用 gpt-4,但它在一个非常简单的测试案例上失败了,本例子不应该提取到数据!

for _ in range(5):
    text = "太阳系很大,但地球只有一个月亮。"
    print(runnable.invoke({"text": text, "examples": []}))

下面输出,表示提取了两条错误的数据

people=[]
people=[Person(name='earth', hair_color=None, height_in_meters=None)]
people=[Person(name='earth', hair_color=None, height_in_meters=None)]
people=[]
people=[]

带有示例

使用参考示例有助于修复失败!

for _ in range(5):
    text = "太阳系很大,但地球只有一个月亮。"
    print(runnable.invoke({"text": text, "examples": messages}))

这次修复了错误

people=[]
people=[]
people=[]
people=[]
people=[]
runnable.invoke(
    {
        "text": "我叫哈里森。我的头发是黑色的。",
        "examples": messages,
    }
)
Data(people=[Person(name='Harrison', hair_color='black', height_in_meters=None)])


关联主题