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

本文将实践如何在 LLM(大语言模型) 调用工具函数时进行审核,即由人来确定是否应该调用工具函数。

本次使用 llama3.1MFDoom/deepseek-r1-tool-calling:7b 进行演练。 deepseek-r1 不支持 langchain 的 bind_tools 方法。

准备

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

  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():
    @tool
    def count_emails(last_n_days: int) -> int:
        """计算电子邮件数量的函数。"""
        print(f'count_emails is called:{last_n_days}')
        return last_n_days * 2

    @tool
    def send_email(message: str, recipient: str) -> str:
        """发送电子邮件的函数。"""
        print(f'send_email is called:{recipient}:{message}')
        return f"邮件已经成功发送至:{recipient}."


    tools = [count_emails, send_email]

    return tools

tools = create_tools()

现在,我们给出一个问题,看看 llama3.1MFDoom/deepseek-r1-tool-calling:7b 能否推理出调用这些工具的 tool_calls。如果能够正确推理出来,那就意味着 LLM 可以正确的调用工具方法。

python 复制代码
query = "我过去7天收到了多少封电子邮件?"

def test_tool_call(model_name,query):
    """测试tool_call,看看输出内容"""
    llm = ChatOllama(model=model_name,temperature=0.1,verbose=True)
    llm_with_tools = llm.bind_tools(tools)

    messages = [HumanMessage(query)]
    ai_msg = llm_with_tools.invoke(messages)
    print(f' tool_calls is:\n{ai_msg.tool_calls}')

执行上述测试代码后,我发现两个模型都可以正确的推理出 tool_calls:

json 复制代码
[
    {'name': 'count_emails', 'args': {'last_n_days': 7}, 'id': 'f0790c9b-2b33-4de5-aacf-ec722c3458b9', 'type': 'tool_call'}
]

直接调用工具函数

下面我们定义调用工具的一个方法,然后测试一下 LLM 直接调用工具的结果。

python 复制代码
query = "我过去7天收到了多少封电子邮件?"

def call_tools(msg: AIMessage) -> List[Dict]:
    """调用工具的通用方法。"""

    tool_map = {tool.name: tool for tool in tools}
    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

def tool_call(model_name,query):
    """使用chain调用tools很方便。这里直接输出json格式的结果"""

    llm = ChatOllama(model=model_name,temperature=0.1,verbose=True)
    llm_with_tools = llm.bind_tools(tools)
    chain = llm_with_tools | call_tools
    result = chain.invoke(query)
    print(f'chain.invoked:\n{result}')

用两个大模型执行上述定义的 tool_call 后,生成了同样的结果:

json 复制代码
[
    {'name': 'count_emails', 'args': {'last_n_days': 7}, 'id': '6ae71225-12f5-4ec9-b4ec-fa1f5d4650c0', 'type': 'tool_call', 'output': 14}
]

最终结果在 output 中。

增加人工审批环节

上面我们定义了一个 链, 它可以调用外部工具生成结果,langchain 很灵活,我们只需要定义一个审批方法,并把它加入到 链 中,就可以支持人工审批了。

定义人工审批方法

python 复制代码
class NotApproved(Exception):
    """自定义异常。"""
    print(f'Not approved:{Exception}')


def human_approval(msg: AIMessage) -> AIMessage:
    """负责传递其输入或引发异常。

    Args:
        msg: 聊天模型的输出

    Returns:
        msg: 消息的原始输出
    """
    tool_strs = "\n\n".join(
        json.dumps(tool_call, indent=2) for tool_call in msg.tool_calls
    )
    input_msg = (
        f"您是否同意以下工具调用\n\n{tool_strs}\n\n"
        "除'Y/Yes'(不区分大小写)之外的任何内容都将被视为否。\n >>>"
    )
    resp = input(input_msg)
    if resp.lower() not in ("yes", "y"):
        print("主人没有批准。")
        raise NotApproved(f"未批准使用工具:\n\n{tool_strs}")
    print("主人已批准。")
    return msg

此方法与 call_tools 的输入参数都是 AIMessage

当人工审核通过时,该方法返回原始消息,允许 链 继续进行处理;否则引发异常,终端 链 的执行。

测试人工审核

现在我们将审核环节加入 链 ,测试一下效果:

python 复制代码
query = "我过去7天收到了多少封电子邮件?"

def approval(model_name,query):
    """由人类批准是否使用工具"""

    llm = ChatOllama(model=model_name,temperature=0.1,verbose=True)
    llm_with_tools = llm.bind_tools(tools)

    chain = llm_with_tools | human_approval | call_tools

    try:
        result = chain.invoke(query)
        print(f'human-in-the-loop chain.invoke:{result}')
    except NotApproved as e:
        print(f'Not approved:{e}')

使用 llama3.1MFDoom/deepseek-r1-tool-calling:7b 执行的结果是相同的。

首先,系统给出提示:

text 复制代码
您是否同意以下工具调用

{
  "name": "count_emails",
  "args": {
    "last_n_days": 7
  },
  "id": "17bb61ad-422e-441f-ad97-f719943c5e71",
  "type": "tool_call"
}

除'Y/Yes'(不区分大小写)之外的任何内容都将被视为否。
 >>>

如果输入 y ,则返回:

text 复制代码
主人已批准。
count_emails is called:7
human-in-the-loop chain.invoke:[{'name': 'count_emails', 'args': {'last_n_days': 7}, 'id': '17bb61ad-422e-441f-ad97-f719943c5e71', 'type': 'tool_call', 'output': 14}]

其中的 output 字段给出了结果。

如果输入 n ,则返回:

text 复制代码
主人没有批准。
Not approved:未批准使用工具:

{
  "name": "count_emails",
  "args": {
    "last_n_days": 7
  },
  "id": "7bb74fd9-6db7-4aa9-b0e7-1d72afe7ab61",
  "type": "tool_call"
}

总结

通过以上的编程实践,我们发现:

  • 基于 Langchainllama3.1MFDoom/deepseek-r1-tool-calling:7b 都可以处理对外部工具函数的调用;
  • 在 链 中增加人工审批环节很方便,可以不破坏原有 链 中的任何逻辑,很优雅。

代码

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

参考:

🪐感谢您,祝好运🪐

相关推荐
deephub9 分钟前
FANformer:融合傅里叶分析网络的大语言模型基础架构
人工智能·语言模型·傅立叶分析
斑鸠喳喳10 分钟前
模块系统 JPMS
java·后端
kunge201312 分钟前
【手写数字识别】之数据处理
后端
SimonKing14 分钟前
Redis7系列:百万数据级Redis Search 吊打 ElasticSearch
后端
飞哥数智坊16 分钟前
Cursor实战:1小时集成天地图
人工智能·cursor
uhakadotcom17 分钟前
Python应用中的CI/CD最佳实践:提高效率与质量
后端·面试·github
ianozo21 分钟前
[GHCTF 2025]UPUPUP【.htaccess绕过 XBM/WBMP】
图像处理·人工智能
大囚长38 分钟前
deepseek+ansible实现AI自动化集群部署
人工智能·自动化·ansible
程序边界38 分钟前
AI+游戏开发:如何用 DeepSeek 打造高性能贪吃蛇游戏
人工智能·游戏