本地大模型编程实战(11)与外部工具交互(2)

server/2025/2/13 23:59:36/

文章目录

    • 准备
    • 定义工具方法
    • 创建提示词
    • 生成工具方法实参
    • 以 `json` 格式返回实参
      • 自定义 `JsonOutputParser`
      • 返回 `json`
    • 调用工具方法
      • 定义通用方法
      • 用 链 返回结果
      • 返回结果中包含工具输入
    • 总结
    • 代码


在使用 LLM(大语言模型) 时,经常需要调用一些自定义的工具方法完成特定的任务,比如:执行一些特殊算法、查询天气预报、旅游线路等。
很多大模型都具备使用这些工具方法的能力,Langchain 也为这些调用提供了便利。

之前的文章介绍了 llama3.1 与工具方法交互的实际例子,不过可惜 langchaindeepseek 支持还不够,导致:

  • llm.bind_tools 根据用户问题生成的工具方法签名与 llama3.1 不同,在后续在调用工具方法时报错
  • deepseek 返回的结果中包含了思考过程内容,显然 Langchain 还不能正确解析出最终结果,这会导致 langchain 的很多方法不能正常运行

这次我们将尝试通过以下两种方法解决 Langchain 使用 deepseek 时产生的上述问题:

  • 使用提示词让大模型推理调用工具的方法名称和参数
  • 使用自定义的 JsonOutputParser 处理 deepseek 返回的信息

这里使用 llama3.1deepseek 等不同模型做对比,并不是为了说明孰优孰劣,而是仅仅为了技术演示需要。

准备

在正式开始撸代码之前,需要准备一下编程环境。

  1. 计算机
    本文涉及的所有代码可以在没有显存的环境中执行。 我使用的机器配置为:

    • CPU: Intel i5-8400 2.80GHz
    • 内存: 16GB
  2. Visual Studio Code 和 venv
    这是很受欢迎的开发工具,相关文章的代码可以在 Visual Studio Code 中开发和调试。 我们用 pythonvenv 创建虚拟环境, 详见:
    在Visual Studio Code中配置venv。

  3. Ollama
    Ollama 平台上部署本地大模型非常方便,基于此平台,我们可以让 langchain 使用 llama3.1qwen2.5 等各种本地大模型。详见:
    在langchian中使用本地部署的llama3.1大模型 。

定义工具方法

下面定义了两个简单的工具方法:计算加法和乘法:

python">def create_tools():"""创建tools"""@tooldef add(x: int, y: int) -> int:"""计算a和b的和。"""print (f"add is called...{x}+{y}")return x + y@tooldef multiply(x: int, y: int) -> int:"""计算a和b的乘积。"""print (f"multiply is called...{x}*{y}")return x * ytools = [add, multiply]for t in tools:print("--")print(t.name)print(t.description)print(t.args)return toolstools = create_tools()

上述代码执行后,会打印出 tools 的 名称 、描述 和 形参 :

--
add
计算a和b的和。
{'x': {'title': 'X', 'type': 'integer'}, 'y': {'title': 'Y', 'type': 'integer'}}
--
multiply
计算a和b的乘积。
{'x': {'title': 'X', 'type': 'integer'}, 'y': {'title': 'Y', 'type': 'integer'}}

创建提示词

python">rendered_tools = render_text_description(tools)
print(rendered_tools)system_prompt = f"""\
您是一名助理,有权使用以下工具集。
以下是每个工具的名称和说明:{rendered_tools}根据用户输入,返回要使用的工具的名称和输入。
以 JSON blob 形式返回您的响应,其中包含“name”和“arguments”键。“arguments”应该是一个字典,其中的键对应于参数名称,值对应于请求的值。
"""prompt = ChatPromptTemplate.from_messages([("system", system_prompt), ("user", "{input}")]
)

render_text_description 方法生成了 tools 的描述:

add(x: int, y: int) -> int - 计算a和b的和。
multiply(x: int, y: int) -> int - 计算a和b的乘积。

这些描述在后面添加到提示词中,LLM 应该能通过这个完整的提示词生成工具方法的 参数(即:实参)了,我们后面试试看。

生成工具方法实参

定义测试方法:

python">def too_call(model_name,query):llm = ChatOllama(model=model_name,temperature=0.1,verbose=True)chain = prompt | llmmessage = chain.invoke({"input": query})print(f'response: \n{message.content}') 

lamma3.1deepseek 返回的结果为:

  • lamma3.1
{"name": "multiply","arguments": {"x": 3,"y": 12}
}
<think>
好,我现在需要解决用户的问题:“3乘以12等于多少?” 用户希望我作为助理,使用提供的工具来计算。首先,我要理解用户的需求是什么。...最后,我需要将这些信息整合成一个JSON对象,并确保语法正确,避免任何错误导致返回失败。这样,当用户调用这个工具时,就能得到正确的结果了。
</think>```json
{"name": "multiply","arguments": {"x": 3,"y": 12}
}
```

llama3.1deepseek-r1 都正确生成了工具方法的实参,只是 deepseek 包含了 <think>...</think> 块,后面需要将其中的 json 部分提取出来。

json 格式返回实参

自定义 JsonOutputParser

一般来说,结构化的数据在 链 中才好处理, JsonOutputParser 用于将结果转换为 json 格式,我们先自定义一个类,它继承自 JsonOutputParser 并能处理 deepseek 的返回文本。

python">class ThinkJsonOutputParser(JsonOutputParser):def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:"""将 LLM 调用的结果解析为 JSON 对象。支持deepseek。Args:result: LLM 调用的结果。partial: 是否解析 partial JSON 对象。If True, 输出将是一个 JSON 对象,其中包含迄今为止已返回的所有键。If False, 输出将是完整的 JSON 对象。默认值为 False.Returns:解析后的 JSON 对象。Raises:OutputParserException: 如果输出不是有效的 JSON。"""text = result[0].texttext = text.strip()# 判断是否为 deepseek生成的内容,如果是的话,提取其中的json字符串if '<think>' in text and '</think>' in text:  match = re.search(r'\{.*\}', text.strip(), re.DOTALL)if match:text = match.group(0)result[0].text = textreturn super().parse_result(result, partial=partial)

上述方法使用正则表达式将 deepseek 返回的 json 内容提取出来,这样处理后,deepseek 就可以加入 langchain 中了。

返回 json

python">query = "3 * 12等于多少?"def too_call_json(model_name,query):"""以json格式输出"""llm = ChatOllama(model=model_name,temperature=0.1,verbose=True)chain = prompt | llm | ThinkJsonOutputParser()message =chain.invoke({"input": query})print(f'JsonOutputParser: \n{message}')

我们用两个大模型分别测试,这次返回的结果一样:

{'name': 'multiply', 'arguments': {'x': 3, 'y': 12}}

调用工具方法

定义通用方法

我们先定义一个通用的调用工具方法的方法:

python">class ToolCallRequest(TypedDict):"""invoke_tool 函数使用的参数格式。"""name: strarguments: Dict[str, Any]def invoke_tool(tool_call_request: ToolCallRequest, config: Optional[RunnableConfig] = None
):"""执行工具调用的函数。Args:tool_call_request: 包含键名和参数的字典。`name` 必须与已存在的工具名称匹配。`arguments` 是工具函数的参数。config: 这是 LangChain 使用的配置信息,其中包含回调、元数据等内容。Returns:requested tool 的输出"""tool_name_to_tool = {tool.name: tool for tool in tools}name = tool_call_request["name"]requested_tool = tool_name_to_tool[name]return requested_tool.invoke(tool_call_request["arguments"], config=config)

用 链 返回结果

现在我们可以用 langchain 整合以上成果:

python">def invoke_chain(model_name,query):llm = ChatOllama(model=model_name,temperature=0.1,verbose=True)chain = prompt | llm | ThinkJsonOutputParser() | invoke_toolresult =chain.invoke({"input": query})print(f'invoke_chain:\n{result}')

调用此方法,我们会发现 llama3.1deepseek-r1 都返回了简单的结果:

36

返回结果中包含工具输入

返回工具输出和工具输入都很有帮助。我们可以通过 RunnablePassthrough.assign 输出,这将获取 RunnablePassthrough 组件的输入并为其添加一个键,同时仍传递当前输入中的所有内容。

python">def invoke_chain_with_input(model_name,query):llm = ChatOllama(model=model_name,temperature=0.1,verbose=True)from langchain_core.runnables import RunnablePassthroughchain = (prompt | llm | ThinkJsonOutputParser() | RunnablePassthrough.assign(output=invoke_tool))result = chain.invoke({"input": query})print(f'invoke_chain with input:\n{result}')

调用此方法,我们会发现 llama3.1deepseek-r1 返回了同样的结果:

{'name': 'multiply', 'arguments': {'x': 3, 'y': 12}, 'output': 36}

完美!

总结

在这篇文章里,我们通过直接使用提示词和自定义 json解析类 的方法,让 deepseek-r1 也完美的嵌入到 langchain 中,从而完成了对 工具方法 的调用。

代码

本文涉及的所有代码以及相关资源都已经共享,参见:

  • github
  • gitee

参考:

  • How to add ad-hoc tool calling capability to LLMs and Chat Models

🪐祝好运🪐


http://www.ppmy.cn/server/167465.html

相关文章

【大语言模型】最新ChatGPT、DeepSeek等大语言模型助力高效办公、论文与项目撰写、数据分析、机器学习与深度学习建模等科研应用

ChatGPT、DeepSeek等大语言模型助力科研应用 随着人工智能技术的快速发展&#xff0c;大语言模型如ChatGPT和DeepSeek在科研领域的应用正在为科研人员提供强大的支持。这些模型通过深度学习和大规模语料库训练&#xff0c;能够帮助科研人员高效地筛选文献、生成论文内容、进行数…

【工业场景】用YOLOv8实现烟雾识别

烟雾识别任务的应用场景主要主要体现在以下几个方面: 火灾预警:烟雾是火灾的早期信号,通过烟雾识别技术可以及时发现火灾并及时采取应急措施,减少火灾对人员和财产的损失。 工业安全监测:在工业生产过程中,烟雾可能是一些危险化学品泄露或燃烧产生的,通过烟雾识别可以实…

大模型被偷家?CNN结合多模态!

2025深度学习发论文&模型涨点之—— CNN多模态 卷积神经网络是一种特殊类型的神经网络&#xff0c;其主要结构包括卷积层、池化层、全连接层和输出层。卷积层通过卷积操作学习图像的特征&#xff0c;池化层通过下采样操作减少参数数量&#xff0c;全连接层和输出层通过分类…

DeepSeek服务器繁忙之谜:原因与解决方案

文章来源于百家号&#xff1a;GPU服务器厂家 DeepSeek&#xff0c;作为一款备受欢迎的人工智能平台&#xff0c;凭借其强大的功能和免费开放的策略&#xff0c;吸引了大量普通用户和开发者。然而&#xff0c;许多用户在使用过程中经常遇到“服务器繁忙&#xff0c;请稍后再试”…

IPC 共享通俗讲解及其安全风险

IPC 共享&#xff0c;指的是进程间通信&#xff08;IPC&#xff0c;Inter-Process Communication&#xff09;中的数据共享方式。简单来说&#xff0c;它允许多个进程&#xff08;程序&#xff09;相互交换信息或共享数据。 为什么需要 IPC 共享&#xff1f; 在计算机中&#…

深度学习-利用预训练的 ResNet 和 DenseNet 模型进行医学影像诊断

下面将分别展示基于 PyTorch 框架&#xff0c;利用预训练的 ResNet 和 DenseNet 模型进行医学影像诊断以提高准确率的代码实现。以肺炎 X 光影像诊断为例&#xff0c;假设数据集已经按照训练集和测试集划分好&#xff0c;每个类别存放在不同文件夹中。 1. 安装必要的库 pip i…

Ai无限免费生成高质量ppt教程(deepseek+kimi)

第一步&#xff1a;打开deepseek官网&#xff08;DeepSeek) 1.如果deepseek官网网络繁忙&#xff0c;解决方案如下&#xff1a; (1)超算互联网:超算互联网 (2)秘塔AI搜索:https://metaso.cn/(开启长思考&#xff09; (3)纳米ai:https://bot.n.cn/ (4)使用easychat官网&#xff…

蓝桥杯算法日记|贪心、双指针

3412 545 2928 2128 贪心学习总结&#xff1a; 1、一般经常用到sort&#xff08;a&#xff0c;an&#xff09;&#xff1b;【a[n]】排序&#xff0c;可以给整数排&#xff0c;也可以给字符串按照字典序排序 2、每次选最优 双指针 有序数组、字符串、二分查找、数字之和、反转字…