基于LangChain的Function Calling实战:文本标注与信息抽取全解析

156 阅读6分钟

前言

前面几节课里,我带大家了解了Function Calling的基础用法以及LCEL的基本概念,那这节课我将带大家深入了解其进阶用法,结合 LangChain 框架,讲解如何实现文本标签提取(Tagging)与结构化信息抽取(Extraction)

相比于传统纯文本的问答方式,Function Calling 允许我们以更结构化、更可控的方式从大模型中获取精准数据,为智能系统提供更强的上下文感知能力。

无论你是初学者,还是有一定开发经验的技术爱好者,这篇教程都将用清晰的逻辑、丰富的实例帮助你快速掌握这项关键能力。


基础知识:什么是Function Calling?

Function Calling 是 OpenAI 模型的一项高级能力,允许模型不直接生成回答,而是返回一个函数调用请求(包含函数名与参数),由开发者决定是否执行,执行后再将结果反馈回模型以继续对话流程。

这种设计的优势在于:

  • 增强可控性:我们可以检查模型的函数调用意图,避免错误调用。
  • 提升灵活性:可以加入校验机制、格式转换等操作。
  • 支持结构化输入输出:便于和已有系统对接,如数据库、API等。

Function Calling 通常和 Pydantic、LangChain 等配套使用,能够以更易维护的方式快速构建复杂的交互逻辑。那下面就让我们开始实战看看其作用吧!


实战Part1:文本标注(Tagging)

我们从一个简单的文本标注任务开始:

给定一段文本,判断其情感(正面/负面/中性)以及语言(如英文en、中文zh等)。

定义数据模型

在这一步,我们借助 Pydantic 创建一个用于情感与语言标注的数据结构,并用 LangChain 提供的工具将其转换为大模型可识别的 function 调用格式。这样,模型便能“知道”有哪些字段需要填写。

from pydantic import BaseModel, Field
class Tagging(BaseModel):
    sentiment: str = Field(description="文本的情感倾向,取值为 `pos`(正面)、`neg`(负面)或 `neutral`(中性)")
    language: str = Field(description="文本所用语言(ISO 639-1 语言代码)")

转换为函数结构后,大模型将会返回一个 JSON 格式的结构化调用请求,而不是直接输出文本:

from langchain_core.utils.function_calling import convert_to_openai_function
convert_to_openai_function(Tagging)

构建对话链条

我们使用 LangChain 的 ChatPromptTemplate 构造提示词模板,同时绑定模型并明确指定调用的函数。这样可以保证模型不产生歧义,而是直接触发我们定义好的函数结构。在使用前我们可以需要先通过pip install -U langchain langchain-openai 安装一下openai的配套库。

from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
    model="qwen-turbo",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key="api_key"# 请替换成你的 API key
)

prompt = ChatPromptTemplate.from_messages([
    ("system""请仔细思考并按照要求对文本进行标注"),
    ("user""{input}")
])

model_with_functions = model.bind(
    functions=[convert_to_openai_function(Tagging)],
    function_call={"name""Tagging"}
)

tagging_chain = prompt | model_with_functions

调用与解析结果

一切就绪后,我们调用该链条,并传入待标注的文本。模型会根据定义好的函数结构返回带有 sentiment 和 language 字段的结果:

print(tagging_chain.invoke({"input""I love langchain"}))

输出结果如下:

content='' additional_kwargs={'function_call': {'arguments': '{"sentiment""pos""language""en"}', 'name': 'Tagging'}} response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 241, 'total_tokens': 259, 'completion_tokens_details': None}, 'model_name': 'qwen-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None} id='run-6f20d16d-94a0-4a58-89ce-a95a3c8ed7cb-0'

可以看到其Function Call里面的arguments识别出来的是积极(pos)的情绪,并且也写出对应的语言英文(en)。为了进一步规范输出,我们还可以追加一个解析器,使得输出内容更清爽:

from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser
tagging_chain = prompt | model_with_functions | JsonOutputFunctionsParser()
print(tagging_chain.invoke({"input""I love langchain"}))

这样模型就只会选择出Function Call部分的内容,输出结果如下:

{'sentiment': 'pos', 'language': 'en'}

实战Part2:信息抽取(Extraction)

接下来我们尝试处理稍微复杂一点的结构化信息抽取任务:

"Joe is 30, his mom is Martha" —— 从中提取出人物姓名与年龄。

定义嵌套结构

本次任务需要抽取多个人物信息,因此我们将构建一个嵌套的结构体:外层是包含多个 Person 的 Information,内层是每个人的 name 和可选的 age 字段。

from typing import List, Optional
from pydantic import BaseModel, Field

class Person(BaseModel):
    name: str = Field(description="人物姓名")
    age: Optional[int] = Field(description="人物年龄,可缺失")

class Information(BaseModel):
    people: List[Person] = Field(description="人物信息列表")

这一步是关键,Pydantic 允许我们通过这种嵌套结构清晰表达所需数据的层级关系。

注册为Function并绑定模型

有了数据结构后,我们仍需将其注册为函数形式供大模型调用,并指定函数名称:

info_fn = convert_to_openai_function(Information)
extraction_model = model.bind(functions=[info_fn], function_call={"name""Information"})

这样,模型收到输入后就会依据我们预设的结构,返回 people 字段对应的所有人物信息。

构造Prompt并执行链条

我们设置提示词,指导模型在抽取信息时不进行猜测,只输出明确表达的信息:

prompt = ChatPromptTemplate.from_messages([
    ("system""提取文本中所有人物姓名和年龄"),
    ("user""{input}")
])

extraction_chain = prompt | extraction_model | JsonOutputFunctionsParser()

执行调用:

print(extraction_chain.invoke({"input""Joe is 30, his mom is Martha"}))

输出结构如下:

{'people': [{'name': 'Joe', 'age'30}, {'name': 'Martha', 'age': None}]}

可以看到大模型成功提取出了两个人的信息,一个是Joe,对应是30岁,另一个是Martha,没有年龄信息。假如我们只想要对应的人物信息而不需要people的话,我们也可以使用 JsonKeyOutputFunctionsParser(key_name="people") 获取更简洁的列表格式结果。

from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser

extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="people")

print(extraction_chain.invoke({"input""Joe is 30, his mom is Martha"}))

输出结构如下:

[{'name': 'Joe', 'age': 30}, {'name': 'Martha', 'age': None}]

这样我们就成功完成提取任务信息的任务啦!

完整代码如下所示:

from langchain_openai import ChatOpenAI
from langchain_core.utils.function_calling import convert_to_openai_function
from pydantic import BaseModel, Field
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser
from typing import List, Optional
from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser

model = ChatOpenAI(
    model="qwen-turbo",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key="api_key"# 你的 API key
)

# class Tagging(BaseModel):
#    sentiment: str = Field(description="文本的情感倾向,取值为 `pos`(正面)、`neg`(负面)或 `neutral`(中性)")
#    language: str = Field(description="文本所用语言(ISO 639-1 语言代码)")
    
# convert_to_openai_function(Tagging)

# prompt = ChatPromptTemplate.from_messages([
#     ("system", "请仔细思考并按照要求对文本进行标注"),
#     ("user", "{input}")
# ])

# model_with_functions = model.bind(
#     functions=[convert_to_openai_function(Tagging)],
#     function_call={"name": "Tagging"}
# )

# tagging_chain = prompt | model_with_functions
# print(tagging_chain.invoke({"input": "I love langchain"}))

# tagging_chain = prompt | model_with_functions | JsonOutputFunctionsParser()
# print(tagging_chain.invoke({"input": "I love langchain"}))

# --------------------------

class Person(BaseModel):
    name: str = Field(description="人物姓名")
    age: Optional[int] = Field(description="人物年龄,可缺失")

class Information(BaseModel):
    people: List[Person] = Field(description="人物信息列表")

info_fn = convert_to_openai_function(Information)
extraction_model = model.bind(functions=[info_fn], function_call={"name""Information"})
prompt = ChatPromptTemplate.from_messages([
    ("system""提取文本中所有人物姓名和年龄"),
    ("user""{input}")
])

extraction_chain = prompt | extraction_model | JsonOutputFunctionsParser()
print(extraction_chain.invoke({"input": "Joe is 30, his mom is Martha"}))

extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="people")

print(extraction_chain.invoke({"input": "Joe is 30, his mom is Martha"}))

总结

通过这篇教程,我们基于LangChain与OpenAI的Function Calling机制,完成了两个典型任务:

  • 文本标签识别(Tagging)
  • 嵌套结构信息抽取(Extraction)

你不仅学会了如何设计Pydantic模型、注册为函数、绑定模型、执行调用,还掌握了如何使用解析器解析函数结果,极大提升了对大模型调用逻辑的理解。

原文地址:https://mp.weixin.qq.com/s/gbMAixnSy9joM3ojbISm6Q