Skip to main content
Open In ColabOpen on GitHub

构建提取链

在本教程中,我们将使用工具调用功能从非结构化文本中提取结构化信息。我们还将展示如何在这种情况下使用少样本提示来提高性能。

important

本教程需要 langchain-core>=0.3.20 并且仅适用于支持 工具调用 的模型。

设置

Jupyter 笔记本

本教程及其他教程或许在Jupyter notebooks中运行最为方便。在交互式环境中浏览指南是更好地理解它们的好方法。有关如何安装的说明,请参见这里

安装

要安装LangChain,请运行:

pip install --upgrade langchain-core

更多详情,请参阅我们的安装指南

LangSmith

使用LangChain构建的许多应用程序将包含多个步骤,涉及多次LLM调用。随着这些应用程序变得越来越复杂,能够检查链或代理内部究竟发生了什么变得至关重要。实现这一点的最佳方法是使用LangSmith

在您通过上述链接注册后,请确保设置您的环境变量以开始记录跟踪:

export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."

或者,如果在笔记本中,您可以通过以下方式设置它们:

import getpass
import os

os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()

模式

首先,我们需要描述我们想要从文本中提取的信息。

我们将使用Pydantic来定义一个示例模式以提取个人信息。

from typing import Optional

from pydantic import BaseModel, Field


class Person(BaseModel):
"""Information about a person."""

# ^ Doc-string for the entity Person.
# This doc-string is sent to the LLM as the description of the schema Person,
# and it can help to improve extraction results.

# Note that:
# 1. Each field is an `optional` -- this allows the model to decline to extract it!
# 2. Each field has a `description` -- this description is used by the LLM.
# Having a good description can help improve extraction results.
name: Optional[str] = Field(default=None, description="The name of the person")
hair_color: Optional[str] = Field(
default=None, description="The color of the person's hair if known"
)
height_in_meters: Optional[str] = Field(
default=None, description="Height measured in meters"
)

定义模式时有两个最佳实践:

  1. 记录属性模式本身:此信息发送给LLM,用于提高信息提取的质量。
  2. 不要强迫LLM编造信息!上面我们使用了Optional来允许LLM在不知道答案时输出None
important

为了获得最佳性能,请详细记录模式,并确保如果文本中没有要提取的信息,模型不会被迫返回结果。

提取器

让我们使用上面定义的模式创建一个信息提取器。

from typing import Optional

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from pydantic import BaseModel, Field

# Define a custom prompt to provide instructions and any additional context.
# 1) You can add examples into the prompt template to improve extraction quality
# 2) Introduce additional parameters to take context into account (e.g., include metadata
# about the document from which the text was extracted.)
prompt_template = ChatPromptTemplate.from_messages(
[
(
"system",
"You are an expert extraction algorithm. "
"Only extract relevant information from the text. "
"If you do not know the value of an attribute asked to extract, "
"return null for the attribute's value.",
),
# Please see the how-to about improving performance with
# reference examples.
# MessagesPlaceholder('examples'),
("human", "{text}"),
]
)

我们需要使用一个支持函数/工具调用的模型。

请查阅文档以了解所有可以与此API一起使用的模型。

pip install -qU langchain-openai
import getpass
import os

if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter API key for OpenAI: ")

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")
structured_llm = llm.with_structured_output(schema=Person)

让我们来测试一下:

text = "Alan Smith is 6 feet tall and has blond hair."
prompt = prompt_template.invoke({"text": text})
structured_llm.invoke(prompt)
Person(name='Alan Smith', hair_color='blond', height_in_meters='1.83')
important

提取是生成性的 🤯

LLMs 是生成模型,因此它们可以做一些非常酷的事情,比如即使身高是以英尺为单位提供的,也能正确提取出以米为单位的身高!

我们可以在这里查看LangSmith的跟踪记录here。请注意,跟踪记录的聊天模型部分揭示了发送给模型的消息的确切顺序、调用的工具以及其他元数据。

多个实体

大多数情况下,你应该提取一个实体列表而不是单个实体。

这可以通过在彼此内部嵌套模型来轻松实现。

from typing import List, Optional

from pydantic import BaseModel, Field


class Person(BaseModel):
"""Information about a person."""

# ^ Doc-string for the entity Person.
# This doc-string is sent to the LLM as the description of the schema Person,
# and it can help to improve extraction results.

# Note that:
# 1. Each field is an `optional` -- this allows the model to decline to extract it!
# 2. Each field has a `description` -- this description is used by the LLM.
# Having a good description can help improve extraction results.
name: Optional[str] = Field(default=None, description="The name of the person")
hair_color: Optional[str] = Field(
default=None, description="The color of the person's hair if known"
)
height_in_meters: Optional[str] = Field(
default=None, description="Height measured in meters"
)


class Data(BaseModel):
"""Extracted data about people."""

# Creates a model so that we can extract multiple entities.
people: List[Person]
important

提取结果在这里可能并不完美。继续阅读以了解如何使用参考示例来提高提取质量,并查看我们的提取操作指南以获取更多详细信息。

structured_llm = 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."
prompt = prompt_template.invoke({"text": text})
structured_llm.invoke(prompt)
Data(people=[Person(name='Jeff', hair_color='black', height_in_meters='1.83'), Person(name='Anna', hair_color='black', height_in_meters=None)])
tip

当模式适应提取多个实体时,如果文本中没有相关信息,它也允许模型通过提供一个空列表来提取无实体

这通常是一件事!它允许在实体上指定必需的属性,而不必强制模型检测此实体。

我们可以在这里看到LangSmith的追踪here

参考示例

LLM应用程序的行为可以通过使用少样本提示来引导。对于聊天模型,这可以采取一系列输入和响应消息对的形式,展示期望的行为。

例如,我们可以通过交替使用userassistant消息来传达符号的含义:

messages = [
{"role": "user", "content": "2 🦜 2"},
{"role": "assistant", "content": "4"},
{"role": "user", "content": "2 🦜 3"},
{"role": "assistant", "content": "5"},
{"role": "user", "content": "3 🦜 4"},
]

response = llm.invoke(messages)
print(response.content)
7

结构化输出通常使用工具调用作为底层机制。这通常涉及生成包含工具调用的AI消息,以及包含工具调用结果的工具消息。在这种情况下,消息序列应该是什么样子的?

不同的聊天模型提供商对有效消息序列有不同的要求。有些会接受以下形式的(重复)消息序列:

  • 用户消息
  • 带有工具调用的AI消息
  • 带有结果的工具消息

其他情况需要包含某种响应的最终AI消息。

LangChain 包含一个实用函数 tool_example_to_messages,它将为大多数模型提供商生成有效的序列。它通过仅需要相应工具调用的 Pydantic 表示来简化结构化少样本示例的生成。

让我们试试这个。我们可以将输入字符串和所需的Pydantic对象对转换为可以提供给聊天模型的消息序列。在底层,LangChain会将工具调用格式化为每个提供者所需的格式。

注意:此版本的 tool_example_to_messages 需要 langchain-core>=0.3.20

from langchain_core.utils.function_calling import tool_example_to_messages

examples = [
(
"The ocean is vast and blue. It's more than 20,000 feet deep.",
Data(people=[]),
),
(
"Fiona traveled far from France to Spain.",
Data(people=[Person(name="Fiona", height_in_meters=None, hair_color=None)]),
),
]


messages = []

for txt, tool_call in examples:
if tool_call.people:
# This final message is optional for some providers
ai_response = "Detected people."
else:
ai_response = "Detected no people."
messages.extend(tool_example_to_messages(txt, [tool_call], ai_response=ai_response))

检查结果,我们看到这两个示例对生成了八条消息:

for message in messages:
message.pretty_print()
================================ Human Message =================================

The ocean is vast and blue. It's more than 20,000 feet deep.
================================== Ai Message ==================================
Tool Calls:
Data (d8f2e054-7fb9-417f-b28f-0447a775b2c3)
Call ID: d8f2e054-7fb9-417f-b28f-0447a775b2c3
Args:
people: []
================================= Tool Message =================================

You have correctly called this tool.
================================== Ai Message ==================================

Detected no people.
================================ Human Message =================================

Fiona traveled far from France to Spain.
================================== Ai Message ==================================
Tool Calls:
Data (0178939e-a4b1-4d2a-a93e-b87f665cdfd6)
Call ID: 0178939e-a4b1-4d2a-a93e-b87f665cdfd6
Args:
people: [{'name': 'Fiona', 'hair_color': None, 'height_in_meters': None}]
================================= Tool Message =================================

You have correctly called this tool.
================================== Ai Message ==================================

Detected people.

让我们比较一下有和没有这些消息的性能。例如,让我们传递一个我们不打算提取任何人的消息:

message_no_extraction = {
"role": "user",
"content": "The solar system is large, but earth has only 1 moon.",
}

structured_llm = llm.with_structured_output(schema=Data)
structured_llm.invoke([message_no_extraction])
Data(people=[Person(name='Earth', hair_color='None', height_in_meters='0.00')])

在这个例子中,模型可能会错误地生成人的记录。

因为我们的少量示例包含“负面”示例,我们鼓励模型在这种情况下表现正确:

structured_llm.invoke(messages + [message_no_extraction])
Data(people=[])
tip

LangSmith 的运行跟踪揭示了发送到聊天模型的消息的确切顺序、生成的工具调用、延迟、令牌计数和其他元数据。

请参阅本指南以获取有关提取工作流程的更多详细信息,包括如何整合提示模板和自定义示例消息的生成。

下一步

现在你已经了解了使用LangChain进行提取的基础知识,你可以继续阅读其余的指南:

  • 添加示例: 更多关于使用参考示例来提高性能的详细信息。
  • 处理长文本: 如果文本不适合LLM的上下文窗口,你应该怎么做?
  • 使用解析方法: 使用基于提示的方法来提取不支持工具/函数调用的模型。

这个页面有帮助吗?