Langchain简单快速上手教程(三)——聊天模型之工具

@
目录

前言

在讲述了如何用Langchain的接口来进行聊天模型的定义后,我们本期来具体介绍聊天模型中的工具。

一、聊天模型的工具定义

工具调用根本作用是让大语言模型(LLM)具备与外部世界交互的能力。工具调用打破了LLM因受限于其训练数据(存在滞后性)和内在的文本生成逻辑,无法执行直接计算、查询实时信息、操作数据库或调用任何外部 API的壁垒,使其获得更多权限与能力。

(一)创建工具

1、使用 @tool 装饰器创建工具

这种方式需要注意以下几点:

  • 该装饰器默认使用函数名称作为工具名称。
  • 该装饰器将使用函数的文档字符串作为工具的描述。

函数名、类型提示和文档字符串都是传递给工具 Schema 的⼀部分,不可缺失。Schema 是一个描述工具的Json串。

(1)依赖 Pydantic 类模式

python 复制代码
from langchain_core.tools import tool
from pydantic import BaseModel,Field

class AddInput(BaseModel):
    '''两数相加'''

    x : int = Field(...,description='第一个整数')
    y : int = Field(...,description='第二个整数')


@tool(args_schema=AddInput)
def add(x:int,y:int)->int:
    return x+y

值得注意的是代码中 @tool 的 args_schema 参数,其表示工具函数在未提供描述、文档字符串等需要传递给工具 Schema 的内容时,依赖 Pydantic 类使用 args_schema 参数,来定义并提供工具输⼊参数的schema,默认为 None。

(2)依赖 Annotated

依赖 Annotated 和文档字符串传递给工具 Schema。

python 复制代码
@tool
def add(
    x:Annotated[int,...,'第一个整数'],
    y:Annotated[int,...,'第二个整数'],
)->int:
    """两数相加

    Args:
        x: 第一个整数
        y: 第二个整数
    """
    return x + y


print(add.invoke({'x':1,'y':2}))
print(add.name)
print(add.description)
print(add.args)

2、使用 StructuredTool 类提供的函数创建工具

class langchain_core.tools.structured.StructuredTool 这个类使用来初始化工具的,可以用其中的 from_function 类方法通过给定的函数来创建并返回一个工具。from_function 的具体参数可查看此处

(1)常规方法

使用from_function 类方法同样要将函数名、类型提示和文档字符串传递给工具 Schema 。

python 复制代码
from langchain_core.tools import StructuredTool

def add(x:int,y:int)->int:
    """两数相加

    Args:
        x: 第一个整数
        y: 第二个整数
    """
    return x + y

add_tool = StructuredTool.from_function(func= add)
print(add_tool.invoke({'x':1,'y':2}))

(2)依赖 Pydantic 类

我们同样可以用Pydantic 类来传递所需信息传递给 Schema 。

python 复制代码
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
from pydantic_core.core_schema import arguments_schema

class addInput(BaseModel):
    x:int = Field(...,description="第一个整数")
    y:int = Field(...,description="第二个整数")

def add(x:int,y:int)->int:
    return x+y

add_tool = StructuredTool.from_function(
    func = add,
    description='两数相加',
    arguments_schema=addInput
)

print(add_tool.invoke({'x':1,'y':2}))

(3)加入 response_format 配置

设想如果我们让大模型读取content的同时,将一些用来构造 content 的原始数据保存下来,当后续有⼀些记录、分析的步骤,就可以派上用了。而部分这就是 artifact 。而artifact 通常需要使用字典 Dict 或列表 List 保存。通过artifact里存放的原生数据,我们能更有效地做日志记录、分析,或自定义后续的处理。

因此我们可以在工具区中分消息内容(content)和其他工件(artifact)

需要在定义工具时指定 response_format="content_and_artifact" 参数,并确保我们返回一个元组 (content, artifact)

python 复制代码
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field

class addInput(BaseModel):
    x:int = Field(...,description="第一个整数")
    y:int = Field(...,description="第二个整数")

def add(x:int,y:int)->tuple[str,list[int]]:
    nums = [x,y]
    content = f"{nums} 相加结果是{x+y}"
    return content,nums

add_tool = StructuredTool.from_function(
    func = add,
    description='两数相加',
    arguments_schema=addInput,
    response_format='content_and_artifact',
)

print(add_tool.invoke(
    {
        "name": "add",
        "args": {'x': 3, 'y': 6},
        "type": "tool_call",  # 必填
        'id': "111",  # 必填,用来将工具调用请求和结果关联起来
    }
))

在链(Chain)中,⼯具调用之后的其他组件或函数,可能需要工具的原始且结构化数据(即artifact )来执行特定操作。而这些大量数据不适合直接交给LLM,所以artifact 里的数据其实是为了给链中后续的组件或函数使用的。

(二)绑定与调用工具

为了实际将这些⼯具绑定到聊天模型,可以使用聊天模型的 .bind_tools() 方法。

python 复制代码
@tool
def add(
        x: Annotated[int,...,'第一个整数'],
        y: Annotated[int,...,'第二个整数'],
)->int:
    """两数相加"""
    return x + y

@tool
def multiply(
        x:Annotated[int,...,'第一个整数'],
        y:Annotated[int,...,'第二个整数'],
)->int:
    """两数相乘"""
    return x * y

model = ChatOpenAI(
    model = "deepseek-v4-flash",
    api_key=os.environ.get('DEEPSEEK_API_KEY'),
    base_url="https://api.deepseek.com",
)

# 绑定工具
tools = [add, multiply]
model_with_tools = model.bind_tools(tools=tools)

同时.bind_tools()方法所返回的是一个Runnable实例,所以我们可以用调用 .invoke() 方法,完成工具的调用。工具调用 的⼀个关键原则是,模型根据输入的相关性决定何时使用⼯具。模型并不总是需要调用工具。所以如果给定一个不相关的输入,模型不会调用该工具

当然如果我们想让模型强制调用工具,那就需要在绑定工具时,设置 tool_choice="any" ,表示强制调用至少一个工具。

python 复制代码
model_with_tools = model.bind_tools(tools=tools,tool_choice="any")

此时可通过两种不同的工具绑定方式来让模型来调用工具,看一下效果。

python 复制代码
print(model_with_tools.invoke("2乘以3等于多少?"))
# print(model_with_tools.invoke("who are you?"))
# 模型在没有强制选择工具的情况下,会根据问题来决定是否使用工具

(三)工具与聊天模型的交互

在这之前,我们都是将工具与聊天模型进行绑定后,让聊天模型对工具进行调用,但大模型没有使用到工具的结果来进行问题的处理。此时就需要将工具输出传递给聊天模型。

1、工具的属性

在将工具输出传递给聊天模型之前,我们要了解工具的属性以及工具的具体输出是什么。

我们知道.invoke()的输出结果是一个 AIMessage,如果调用了工具,则 输出结果里将具有一个 tool_calls 属性。此属性包括执行该工具所需的一切,包括工具名称和输入参数。

2、将工具输出传递给聊天模型

要将工具输出传递给聊天模型,就要包括 HumanMessage、AIMessage (工具调用)、ToolMessage。而聊天模型会根据以上消息输入,将最终结果 AIMessage 返回。

python 复制代码
import os
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from typing_extensions import Annotated
from langchain_core.tools import tool


@tool
def add(
        x: Annotated[int,...,'第一个整数'],
        y: Annotated[int,...,'第二个整数'],
)->int:
    """两数相加"""
    return x + y

@tool
def multiply(
        x:Annotated[int,...,'第一个整数'],
        y:Annotated[int,...,'第二个整数'],
)->int:
    """两数相乘"""
    return x * y

model = ChatOpenAI(
    model = "deepseek-v4-flash",
    api_key=os.environ.get('DEEPSEEK_API_KEY'),
    base_url="https://api.deepseek.com",
)

# 绑定工具
tools = [add, multiply]
model_with_tools = model.bind_tools(tools=tools)

# 定义消息列表,添加要传递给聊天模型的消息
messages = [
    HumanMessage("2乘以3等于多少?6加3等于多少?")
]
ai_msg = model_with_tools.invoke(messages)
# print(ai_msg)
messages.append(ai_msg)

# 构造ToolMessage,并添加到消息列表中去
for tool_call in ai_msg.tool_calls:
    selected_tool = {"add":add,"multiply":multiply}[tool_call['name'].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    messages.append(tool_msg)

print(messages)
print(model_with_tools.invoke(messages).content)

通过运行上面的代码,我们发现实际上我们调用了两次聊天模型。第一次仅将【 HumanMessage 】发送给聊天模型进行处理,结果返回了【包含⼯具调用的AIMessage】,然后执行工具,得到 ToolMessage 。第二次将【HumanMessage + AIMessage + ToolMessage 】消息记录发送给聊天模型进行处理,结果返回了【包含结果的 AIMessage 】。

(四)LangChain 提供的工具

我们除了可以自己写的工具,LangChain 官⽅也已经给我们提供了很多现成的工具(Tool)和工具包(Toolkit),具体见官网此处。同时我们也可以去官网查找里面工具的具体说明与使用方法,具体见官网此处

结语

以上就是在Langchain中如何具体给聊天模型绑定工具与调用工具的内容了。如果喜欢我的内容,请点赞、收藏加关注,多多支持我,以及欢迎各位在评论区讨论交流,并指出我的不足,谢谢大家。