当前位置:   article > 正文

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

langchain4

系列文章目录

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)
  1. @tool
  2. def func(input:int):
  3. '''
  4. 没用的函数
  5. '''
  6. return input
  7. print(func.name)
  8. # 输出:func
  9. print(func.description)
  10. # 输出:没用的函数
  11. print(func.args)
  12. # 输出: {'input': {'title': 'Input', 'type': 'int'}}
  13. print(func.return_direct)
  14. # 输出: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.”。

  1. from langchain_core.tools import tool
  2. @tool
  3. def multiply(first_int: int, second_int: int) -> int:
  4. """Multiply two integers together."""
  5. return first_int * second_int

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

step2:大模型定义

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

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

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

 step3:工具设定与绑定!

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

  1. tools = [multiply]
  2. llm_with_tools = qianfan_chat.bind_tools(tools)
  3. 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:实际运行!

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

  1. def call_tools(msg: AIMessage) -> Runnable:
  2. """Simple sequential tool calling helper."""
  3. tool_calls = msg.tool_calls.copy()
  4. for tool_call in tool_calls:
  5. tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])
  6. return tool_calls
  7. chain = llm_with_tools | call_tools
  8. chain.invoke(
  9. "What's 25 times 11?"
  10. )

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 的实际输出!

我们运行下面的代码:

  1. query = "25 * 11 = ?"
  2. messages = [HumanMessage(query)]
  3. print("messages1 = ", messages)
  4. ai_msg = llm_with_tools.invoke(messages)
  5. print("ai_msg = ", ai_msg)

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

  1. messages1 = [HumanMessage(content='25 * 11 = ?')]
  2. ai_msg =
  3. content=''
  4. additional_kwargs={
  5. 'finish_reason': 'function_call',
  6. 'request_id': 'as-y3xqr3j5b5',
  7. 'object': 'chat.completion',
  8. 'search_info': [],
  9. 'function_call': {
  10. 'name': 'Multiply',
  11. 'arguments': '{"a":25,"b":11}'},
  12. 'tool_calls': [
  13. {'type': 'function',
  14. 'function': {'name': 'Multiply', 'arguments': '{"a":25,"b":11}'},
  15. 'id': '07eeb8f7-56b0-42f0-b828-4c7d4b17c850'}]}
  16. response_metadata={
  17. 'token_usage': {'prompt_tokens': 51, 'completion_tokens': 24, 'total_tokens': 75},
  18. 'model_name': 'ERNIE-3.5-8K',
  19. 'finish_reason': 'function_call',
  20. 'id': 'as-y3xqr3j5b5',
  21. 'object': 'chat.completion',
  22. 'created': 1720421110,
  23. 'result': '',
  24. 'is_truncated': False,
  25. 'need_clear_history': False,
  26. 'function_call': {
  27. 'name': 'Multiply',
  28. 'thoughts': '用户需要进行乘法运算,我可以使用工具Multiply来完成这个任务。',
  29. 'arguments': '{"a":25,"b":11}'},
  30. 'usage': {'prompt_tokens': 51, 'completion_tokens': 24, 'total_tokens': 75}}
  31. id='run-082b9676-4902-4bf6-af1b-545f4a095001-0'
  32. tool_calls=[{
  33. 'name': 'Multiply',
  34. 'args': {'a': 25, 'b': 11},
  35. '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调用该工具。

  1. def call_tools(msg: AIMessage) -> Runnable:
  2. """Simple sequential tool calling helper."""
  3. tool_calls = msg.tool_calls.copy()
  4. for tool_call in tool_calls:
  5. tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])
  6. 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

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

  1. import os
  2. from langchain_community.chat_models import QianfanChatEndpoint
  3. from langchain_core.tools import tool
  4. from langchain_core.messages import AIMessage
  5. from langchain_core.runnables import Runnable
  6. # 设定百度千帆大模型的AK和SK
  7. os.environ["QIANFAN_AK"] = " your AK"
  8. os.environ["QIANFAN_SK"] = " your SK"
  9. # 定义千帆大模型
  10. qianfan_chat = QianfanChatEndpoint(
  11. model="ERNIE-3.5-8K",
  12. temperature=0.2,
  13. timeout=30,
  14. )
  15. # 设定两个函数,记得描述函数功能,这很重要
  16. @tool
  17. def func1():
  18. ''' useless function '''
  19. return 0
  20. @tool
  21. def Multiply(a: int, b: int) -> int:
  22. """Multiplies a and b."""
  23. return a * b
  24. # 工具集合
  25. tools = [Multiply, func1]
  26. # 工具与大模型绑定,构建函数选择模块
  27. llm_with_tools = qianfan_chat.bind_tools(tools)
  28. # 构建一一对应的map
  29. tool_map = {tool.name: tool for tool in tools}
  30. # 工具函数执行
  31. def call_tools(msg: AIMessage) -> Runnable:
  32. """Simple sequential tool calling helper."""
  33. tool_calls = msg.tool_calls.copy()
  34. for tool_call in tool_calls:
  35. tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])
  36. return tool_calls
  37. # 构建链
  38. chain = llm_with_tools | call_tools
  39. print(chain.invoke("What's 25 times 11?")[0]["output"])

 输出:275

总结

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/988823
推荐阅读
相关标签
  

闽ICP备14008679号