LangChain(四)工具调用的底层原理!给大模型按上双手吧!(新手向)

系列文章目录

LangChain(一)构建本地数据检索问答Agent,新手向-CSDN博客

LangChain(二)基础问答大模型,纯新手向-CSDN博客

LangChain(三)基础问答大模型,从LLMchain开始了解chain!纯新手向-CSDN博客

LangChain(四)工具调用的底层原理!给大模型按上双手吧!(新手向)-CSDN博客


前言

经过前面三篇的内容,我想大家对于大模型的构建、Langchain的优势、Chain的构建有了相当程度的理解(虽然只是最简单的示例,但是足够有代表性)。

后续Chain的使用将会更加丰富多彩,您会了解Langchain开发的大模型会有多么逆天的可扩展性。但今天我们先暂缓此部分,我们来讲讲Langchain里面最最最重要的功能:工具调用!

我们会先从Langchain规定的官方API开始构建,带着大家跑通一系列工具之后,从底层原理出发,为大家讲解大模型是怎么调用工具的(不必担心,非常浅显易懂,本栏目始终是新手向的)。

工具说明

在Langchain眼中,所谓的工具都只是函数而已,我们要做的就是把函数写好,并交给大模型去自主的调用。

Langchain给大模型调用的函数专门设定了一个函数装饰器: @tool

有关于函数装饰器,作为新手其实没必要理解太深刻,只需要理解装饰器给函数添加了一些功能和变量即可。而这个tool装饰器仅仅是给函数增加了几个变量而已,如下:

  • 工具名称:(tool.name)
  • 该工具是什么的描述(tool.description)
  • 输入内容的 JSON 格式 (tool.args)
  • 工具的结果是否应直接返回给用户(tool.return_direct)
@tool
def func(input:int):
    '''
    没用的函数
    '''
    return input 

print(func.name)
# 输出:func

print(func.description)
# 输出:没用的函数

print(func.args)
# 输出: {'input': {'title': 'Input', 'type': 'int'}}

print(func.return_direct)
# 输出:false

在这个装饰器中最主要的必须知晓的就是tool.nametool. description。这个是后续工具调用的基础。

  • tool.name

若无特殊设定,默认为函数名。作为新手向,该变量就不要去有额外的操作。只需要知道 tool.name == 函数名 即可。(操作更多也不会有额外的效果,还增加理解难度)

  • tool.description

若无特殊设定,默认为函数的文档字符串(即函数下方的函数说明),有关文档字符串的内容可以参考下面的博客,简单清晰。该部分作为小白直接利用该部分特性即可。有更高要求的看客可以查阅有关BaseTool类的相关知识。

Python 文档字符串(DocStrings)是个啥??-CSDN博客

大模型的工具调用(直接使用API)

在之前的项目中我们编写了有关基础大模型的相关内容,开发了第一个问答大模型以及尝试了LLMchain的相关内容,我们将在此基础上继续往前!

LangChain(二)基础问答大模型,纯新手向-CSDN博客

LangChain(三)基础问答大模型,从LLMchain开始了解chain!纯新手向-CSDN博客

其实不看也可以啦,看了理解起来会更快而已……

step1:工具定义!

该部分我们先定义需要的工具,代码如下。@tool装饰器说明这是一个工具。工具名称为“multiply”,工具描述为“Multiply two integers together.”。

from langchain_core.tools import tool

@tool
def multiply(first_int: int, second_int: int) -> int:
    """Multiply two integers together."""
    return first_int * second_int

 这部分没啥难度,其实就是你自己设定一个函数,然后前面加上@tool,函数内部首行用""" """ 定义一下函数功能描述即可。

step2:大模型定义

该部分我们定义大模型,详细内容可以参考我之前的博客内容哦。使用百度的千帆大模型


import os
from langchain_community.chat_models import QianfanChatEndpoint
 
# 设定百度千帆大模型的AK和SK-去百度千帆官网的控制台新建一个应用即可
os.environ["QIANFAN_AK"] = "your AK“"
os.environ["QIANFAN_SK"] = "your SK"
 
#创建千帆LLM模型
qianfan_chat = QianfanChatEndpoint(
    model="ERNIE-3.5-8K",
    temperature=0.2,
    timeout=30,
)

这部分依旧没啥难度,按部就班走即可。 

 step3:工具设定与绑定!

该部分我们进行工具的设定和与大模型进行绑定!

tools = [multiply]
llm_with_tools = qianfan_chat.bind_tools(tools)
tool_map = {tool.name: tool for tool in tools}

该部分必须要好好解释一下。不然大家初看之下可能会一头雾水。

tools = [multiply]

        由于在实际开发过程中,不可能只有一个工具,我们常常会调用多个工具,那么和大模型进行绑定难道要每个工具函数都绑定一次吗?咋可能对不对。这部分就是把所有需要调用的函数打造成一个列表,列表内保存的是各个函数(不是函数名!函数名是string,函数就是函数,本质上是个对象,这里理解不了跳过即可,我还记得这是个新手向的博客~~~)。

llm_with_tools = qianfan_chat.bind_tools(tools)

        这一行是把大模型和工具进行一个绑定,构建一个工具选择模块(一个 agent)大模型就是通过该模块进行的工具选择,具体的原理在下一篇博客会详细讲解,此部分我们先暂缓跳过~。

tool_map = {tool.name: tool for tool in tools}

        这一行是把函数名称(string)和函数(对象)作为一个字典保存。

        key:函数名称,value:函数

        这个变量大家先留意一下,现在可能看不出用途,后面就有用了。

step4:实际运行!

接下来我们把后面的代码一次性和盘托出! 

def call_tools(msg: AIMessage) -> Runnable:
    """Simple sequential tool calling helper."""
    tool_calls = msg.tool_calls.copy()
    for tool_call in tool_calls:
        tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])
    return tool_calls


chain = llm_with_tools | call_tools

chain.invoke(
    "What's 25 times 11?"
)

I know,I know,突然信息量就上来了对不对。没事,我们一个一个来! 我们先跳过call_tools的函数定义,我们先看下面:

  • chain = llm_with_tools | call_tools

        对于chain还不理解的同学可以先看我之前的博客,链接在上面!看了我之前博客的同学想必依旧有疑惑,我们只是使用过LLMchain,怎么就变成这样了?

        实际上Langchain确实有很多已经定义好的chain,只需要调用即可,但是在实际开发中,最实用的依旧是自己定义的chain,个性化的定义才能满足个性化的需求嘛。

        Langchain官方自然有可以让我们自己个性化定义chain的方式。该处就是一个典型。

        该处的chain是如何工作的呢?作为小白我们不需要去理解源码。从高维去俯瞰它。步骤如下:

  • 用户输入给到 llm_with_tools(该部分有大模型)
  • llm_with_tools 获取用户输入和函数名称与描述,大模型进行处理并返回需要的函数名和对应的输入变量,记为“AIMessage”(这就是上面call_tools的参数哦~)。
  • call_tools获取上一个步骤输出的参数,并帮助大模型调用对应的函数,并返回结果。

llm_with_tools 的实际输出!

我们运行下面的代码:

query = "25 * 11 = ?"

messages = [HumanMessage(query)]

print("messages1 = ", messages)

ai_msg = llm_with_tools.invoke(messages)

print("ai_msg = ", ai_msg)

可得输出如下(手动标准格式了下):

messages1 =  [HumanMessage(content='25 * 11 = ?')]
ai_msg =  
content='' 
additional_kwargs={
    'finish_reason': 'function_call', 
    'request_id': 'as-y3xqr3j5b5', 
    'object': 'chat.completion', 
    'search_info': [], 
    'function_call': {
        'name': 'Multiply', 
        'arguments': '{"a":25,"b":11}'}, 
    'tool_calls': [
        {'type': 'function', 
        'function': {'name': 'Multiply', 'arguments': '{"a":25,"b":11}'}, 
        'id': '07eeb8f7-56b0-42f0-b828-4c7d4b17c850'}]} 
response_metadata={
    'token_usage': {'prompt_tokens': 51, 'completion_tokens': 24, 'total_tokens': 75},  
    'model_name': 'ERNIE-3.5-8K', 
    'finish_reason': 'function_call', 
    'id': 'as-y3xqr3j5b5', 
    'object': 'chat.completion', 
    'created': 1720421110, 
    'result': '', 
    'is_truncated': False, 
    'need_clear_history': False, 
    'function_call': {
        'name': 'Multiply', 
        'thoughts': '用户需要进行乘法运算,我可以使用工具Multiply来完成这个任务。', 
        'arguments': '{"a":25,"b":11}'}, 
        'usage': {'prompt_tokens': 51, 'completion_tokens': 24, 'total_tokens': 75}}     
id='run-082b9676-4902-4bf6-af1b-545f4a095001-0' 
tool_calls=[{
    'name': 'Multiply', 
    'args': {'a': 25, 'b': 11}, 
    'id': '07eeb8f7-56b0-42f0-b828-4c7d4b17c850'}]

大家先别慌!别着急!重点其实很少~。听我细细说来。

第一行的 messages1中的HumanMessage,仅仅只是告诉大模型这是用户发出的信息而已,至少在现在这个阶段不是重点,不用管他!

最主要的是下面的ai_msg,有三个重要模块

  • “additional_kwargs”:额外信息,对新手没啥用
  • “response_metadata”:正式的响应信息,一堆没啥用的信息之外,thoughts该字段反映了大模型是如何思考的。并且格式化返回了需要调用的相关函数名称(string)和函数的参数。
  • “tool_calls”:最重要的信息,单独提出来单纯只是降低层级而已,你可以看到上面几个字段都有一样的信息。

总而言之,在本篇工具调用栏目看来,最重要的就是tool_calls字段,其他直接忽略。函数选择器的详细原理将会放置下一篇博客详细讲解,本文仅说Langchain的API调用的步骤和思路。毕竟是新手向嘛~

综上 函数选择器 的功能输出正式讲解完毕,其实大家只需要知道函数选择器就是用来选择函数的,最重要的功能就是输出的tool_calls字段,其中保存大模型想要调用的函数名称(string)和对应的参数。

call_tools函数的原理和操作!

以防大家往上翻太烦,再粘贴一次。这个函数的主要作用就是获取AI想要调用的工具,并帮AI调用该工具。

def call_tools(msg: AIMessage) -> Runnable:
    """Simple sequential tool calling helper."""
    tool_calls = msg.tool_calls.copy()
    for tool_call in tool_calls:
        tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])
    return tool_calls

该部分的输入参数就是上一步函数选择器的输出:AImessage(不知道大家有没有注意到,这和HumanMessage正好是对应关系,其实就是一个是用户的信息,一个是AI的信息而已,仅仅是对信息做一个标识,其实没啥用) 

后面的 --> Runnable 请忽略,新手直接跳过即可,想了解可以自行了解。 接下来让我们分行说明!

  • tool_calls = msg.tool_calls.copy()

        养成好习惯,直接copy,解耦互不影响,尤其在流式场景下。

  • for tool_call in tool_calls

        因为AI可能需要调用多个函数,所以对每一个AI想要调用的函数都需要处理。我不知道为什么我要解释这个……

  • tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])

        最重要的是这一行,不知道大家还记不记得 tool_map 这个变量,在上文提过,截图如下。这一行我们慢慢来,对于当前需要调用的tool_call,有一个字段“name”保存着需要调用函数的函数名称。用tool_map访问该名称,返回该函数名称(string)对应的函数(对象)!这下大家终于理解为什么我需要强调这多次了吧~。即:

  • tool_map[tool_call["name"]] == multiply
  • tool_call["args"] == {'a': 25, 'b': 11}

这一行 == multiply.invoke({'a': 25, 'b': 11}) == multiply('a': 25, 'b': 11) == 275,此时tool_call多了一个字段“output”,value = 275

到此就结束了,我们终于实现了大模型调用工具的基础操作。大家安心,上面代码好像很多,好像很复杂,我们最后复习一下,看一下全部的代码,你会发现没什么难的其实。

import os
from langchain_community.chat_models import QianfanChatEndpoint
from langchain_core.tools import tool
from langchain_core.messages import AIMessage
from langchain_core.runnables import Runnable

# 设定百度千帆大模型的AK和SK
os.environ["QIANFAN_AK"] = " your AK"
os.environ["QIANFAN_SK"] = " your SK"

# 定义千帆大模型
qianfan_chat = QianfanChatEndpoint(
    model="ERNIE-3.5-8K",
    temperature=0.2,
    timeout=30,
)

# 设定两个函数,记得描述函数功能,这很重要
@tool
def func1():
    ''' useless function '''
    return 0

@tool
def Multiply(a: int, b: int) -> int:
    """Multiplies a and b."""
    return a * b

# 工具集合
tools = [Multiply, func1]
# 工具与大模型绑定,构建函数选择模块
llm_with_tools = qianfan_chat.bind_tools(tools)
# 构建一一对应的map
tool_map = {tool.name: tool for tool in tools}

# 工具函数执行
def call_tools(msg: AIMessage) -> Runnable:
    """Simple sequential tool calling helper."""
    tool_calls = msg.tool_calls.copy()
    for tool_call in tool_calls:
        tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])
    return tool_calls

# 构建链
chain = llm_with_tools | call_tools

print(chain.invoke("What's 25 times 11?")[0]["output"])

 输出:275

总结

有一说一,工具调用会了,世界上还有什么功能实现不了?

但是本篇博客是从API的角度出发为大家构建一个工具调用的操作,下一篇博客我们将从原理出发,直接手撸工具调用!放宽心,依旧是新手向~

由于小博主依旧是个卑微的打工人,只能上班摸鱼的时候写写博客,后续的博客将保持一周一篇的频率~ 敬请期待~

  • 35
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千天夜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值