用例(User Case)
抽取(Extraction)
操作指南(HowTo)
示例(Examples)

使用参考示例

提供给LLM参考示例可以提高抽取的质量。

:::⚠⚠⚠

尽管此教程重点介绍如何在调用模型的工具中使用示例,但此技术通常适用,并且也适用于更多基于JSON或提示的技术。

:::

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
 
# 定义一个自定义提示,提供说明和任何其他上下文。
# 1) 您可以将示例添加到提示模板中,以提高抽取质量
# 2) 引入其他参数以考虑上下文(例如,包含从抽取文本的文档中提取的元数据)
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "您是一个专家抽取算法。只从文本中提取相关信息。如果无法确定要提取的属性的值,请将属性的值返回为null。",
        ),
        # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        MessagesPlaceholder("examples"),  # <-- 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):
    """关于人的信息。"""
 
    # ^ 实体Person的文档字符串。
    # 此文档字符串将作为模式Person的说明发送到LLM,并可以帮助提高抽取结果。
 
    # 请注意:
    # 1. 每个字段都是“optional”的-这允许模型拒绝进行抽取!
    # 2. 每个字段都有“description”-LLM会使用此描述。良好的描述可以帮助改善抽取结果。
    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”,表示应从文本中提取的内容。

:::⚠⚠⚠

这有点复杂,请忽略如果您不理解!

示例的格式需要与所使用的API匹配(例如,调用工具或JSON模式等)。

在这里,格式化的示例将与工具调用API期望的格式匹配,因为这是我们使用的方式。

:::

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):
    """由文本输入和预期工具调用组成的示例的表示形式。
 
    对于抽取,工具调用被表示为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": {
                    # 函数名称现在对应于pydantic模型的名称
                    # 这在API中现在是隐含的,
                    # 并且将来会得到改进。
                    "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 = [
    (
        "海洋广阔而美丽。它的深度超过20000英尺。里面有很多鱼。",
        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='海洋广阔而美丽。它的深度超过20000英尺。里面有很多鱼。'), 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",
    # 记得将温度设置为0来进行提取!
    temperature=0,
)
 
runnable = prompt | llm.with_structured_output(
    schema=Data,
    method="function_calling",
    include_raw=False,
)

/Users/harrisonchase/workplace/langchain/libs/core/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: 函数 with_structured_output 处于测试阶段。它正在积极开发中,因此API可能会发生变化。 warn_beta(

没有示例 😿

请注意,尽管我们正在使用gpt-4,但它在一个非常简单的测试用例上失败了!

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

结果为:

人=[]
人=[Person(name='地球', hair_color=None, height_in_meters=None)]
人=[Person(name='地球', hair_color=None, height_in_meters=None)]
人=[]
人=[]

有示例 😻

使用参考示例可以帮助修复失败的情况!

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

结果为:

人=[]
人=[]
人=[]
人=[]
人=[]
runnable.invoke(
    {
        "text": "我叫Harrison。我有黑色的头发。",
        "examples": messages,
    }
)

结果为:

Data(people=[Person(name='Harrison', hair_color='黑色', height_in_meters=None)])