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

news2024/11/14 18:29:56

背景

经过前面三篇的内容,我想大家对于大模型的构建、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的角度出发为大家构建一个工具调用的操作,下一篇博客我们将从原理出发,直接手撸工具调用!放宽心,依旧是新手向~

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1906810.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《向量数据库指南》——Milvus Cloud生成器增强:优化RAG Pipeline的深入探索

在信息检索与生成(Retrieval-Augmented Generation, RAG)的框架下,大型语言模型(Large Language Models, LLMs)如GPT系列、T5等,通过结合外部知识库的能力,极大地扩展了它们的应用场景与准确性。然而,在实际应用中,RAG系统仍面临诸多挑战,如检索到的信息中包含噪声、…

Linux 命令历史、补全和正则表达式

1.命令历史就和windows的ctrlz一个概念,就是返回上一次的命令。 按下小键盘上下切换 ctrlc可以终止本次输入,进行下一次输入。 2.tab键可以自动补全(有点鸡肋) 3.正则表达式 类似于word的快速搜索,Linux也是用*和…

SpringSecurity中文文档(Servlet Authorize HttpServletRequests)

Authorize HttpServletRequests SpringSecurity 允许您在请求级别对授权进行建模。例如,对于 Spring Security,可以说/admin 下的所有页面都需要一个权限,而其他所有页面只需要身份验证。 默认情况下,SpringSecurity 要求对每个…

Java套红:指定位置合并文档-NiceXWPFDocument

需求:做个公文系统,需要将正文文档在某个节点点击套红按钮,实现文档套红 试了很多方法,大多数网上能查到但是实际代码不能找到关键方法,可能是跟包的版本有关系,下面记录能用的这个。 一:添加依…

《C++20设计模式》命令模式思考

文章目录 一、前言二、分析 拆解1、经典命令模式2、撤销操作3、关于Invoker类 三、实现 一、前言 哎!只要是书上写的和经典设计模式不同,我就会很伤脑筋。😩 命令模式到底是干什么的? 答:命令的发送者和接收者完全解…

“一稿多投”是学术不端,还是作者的合法权利?

【SciencePub学术】“一稿多投”一直被认为是不端的行为,但这个“规矩”是在纸质时代信息沟通不畅的情况下制定的,近年来有关取消这一观念的声音已振聋发聩! 詹启智的《一稿多投是著作权人依法享有的合法权利一一兼论一稿多发后果的规制》一文…

SpringBoot项目——送水管理系统

1、导入坐标 坐标作用pagehelper-spring-boot-startermybatis分页插件spring-boot-starter-thymeleafJSP模板引擎mybatis-spring-boot-startermybatisspring-boot-starter-webwebspring-boot-starter-testtestlombok不需要再写getter、setter或equals方法,只要有一…

建立有效的DNS性能检测机制

今天来分享如何建立有效的DNS性能监测机制,实时或定期监测关键指标。 一、建立DNS性能监测机制 (一)选择合适的监测工具 市场上有多种DNS性能监测工具可供选择,如IP数据云DNS检测功能。其具备强大的功能,能够针对多种…

简过网:快来看看你的专业能考哪个类型的事业单位?

你的专业能考哪个类型的事业单位,你知道吗?想考事业单位的姐妹,一定要在备考之前,查清楚你的专业适不适合考事业单位、考哪类事业编以及能报考哪些岗位?这个才能上岸的几率更高一些! 事业单位有5类岗位&am…

Java动态执行jar包中类的方法

动态加载执行jar包,在实际开发中经常会需要用到,尤其涉及平台和业务的关系的时候,业务逻辑部分可以独立出去交给业务方管理,业务方只需要提供jar包,就能在平台上运行。 结论 通过反射可以实现动态调用jar包中的类的方…

免费可商用的Navicat Premium Lite要不要用?小心收到律丝函!

作者公众号:霸王龙的日常 专注数据库,分享实用的项目实战经验。 上周五写了一篇关于Navicat Premium Lite的文章,有网友去官网下载,反馈当前官网Navicat Premium Lite简介和我之前文章中的介绍的有出入。 我赶紧打开网站看了下Na…

修改CentOS7.9跟Unbantu24的ip地址

修改CentOS的IP地址 ip addr 查看IP地址 cd /etc/sysconfig/network-scripts ls vi ifcfg-ens33修改ip地址跟干网关地址 TYPE"Ethernet" PROXY_METHOD"none" BROWSER_ONLY"no" BOOTPROTO"static" DEFROUTE"yes" IPV4_FA…

排序 -- 手撕归并排序(递归和非递归写法)

一、基本思想 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有…

掌上教务系统-计算机毕业设计源码84604

摘要 在数字化教育日益成为主流的今天,教务管理系统的智能化和便捷性显得尤为重要。为满足学校、教师、学生及家长对教务管理的高效需求,我们基于Spring Boot框架设计并实现了一款掌上教务系统。该系统不仅具备课程分类管理功能,使各类课程信…

软件架构之开发方法

软件架构之开发方法 第6章:开发方法6.1 软件生命周期6.2 软件开发模型6.2.1 瀑布模型6.2.2 演化模型6.2.3 螺旋模型6.2.4 增量模型6.2.5 构件组装模型 6.3 统一过程6.4 敏捷方法6.4.1 极限编程6.4.2 特征驱动开发6.4.3 Scrum6.4.4 水晶方法6.4.5 其他敏捷方法 6.5 软…

《梦醒蝶飞:释放Excel函数与公式的力量》9.5 IRR函数

9.5 IRR函数 IRR函数是Excel中用于计算内部收益率(Internal Rate of Return, IRR)的函数。内部收益率是评估投资项目盈利性的重要指标,它表示使投资项目的净现值(NPV)为零的折现率。 9.5.1 函数简介 IRR函数通过一系…

微软开源GraphRAG的使用教程-使用自定义数据测试GraphRAG

微软在今年4月份的时候提出了GraphRAG的概念,然后在上周开源了GraphRAG,Github链接见https://github.com/microsoft/graphrag,截止当前,已有6900Star。 安装教程 官方推荐使用Python3.10-3.12版本,我使用Python3.10版本安装时,在…

Java:String 类

文章目录 一、概念二、创建字符串三、字符串长度四、连接字符串五、比较字符串 一、概念 字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。 二、创建字符串 创建字符串最简单的方式如下: // 直接创…

利用Python进行数据分析PDF下载经典数据分享推荐

本书由Python pandas项目创始人Wes McKinney亲笔撰写,详细介绍利用Python进行操作、处理、清洗和规整数据等方面的具体细节和基本要点。第2版针对Python 3.6进行全面修订和更新,涵盖新版的pandas、NumPy、IPython和Jupyter,并增加大量实际案例…

什么是Common Flash Interface

目录 1. CFI概述 2. CFI的使用小结 3. CFI在车规MCU里有用吗 在看关于ifx的标准flash驱动配置时,无意中瞄到一个注灰的选项: Try to use CFI information to detect Flash Type 之前讲过CFI这个标准,但为何在IFX memtool工具里注灰&#x…