前言
前面几节课里,我带大家了解了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