本文将实践如何在 LLM(大语言模型)
调用工具函数时进行审核,即由人来确定是否应该调用工具函数。
本次使用
llama3.1
和MFDoom/deepseek-r1-tool-calling:7b
进行演练。deepseek-r1
不支持langchain
的 bind_tools 方法。
准备
在正式开始撸代码之前,需要准备一下编程环境。
-
计算机
本文涉及的所有代码可以在没有显存的环境中执行。 我使用的机器配置为:
- CPU: Intel i5-8400 2.80GHz
- 内存: 16GB
-
Visual Studio Code 和 venv 这是很受欢迎的开发工具,相关文章的代码可以在
Visual Studio Code
中开发和调试。 我们用python
的venv
创建虚拟环境, 详见:
在Visual Studio Code中配置venv。 -
Ollama 在
Ollama
平台上部署本地大模型非常方便,基于此平台,我们可以让langchain
使用llama3.1
、qwen2.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.1
和 MFDoom/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.1
和 MFDoom/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"
}
总结
通过以上的编程实践,我们发现:
- 基于
Langchain
,llama3.1
和MFDoom/deepseek-r1-tool-calling:7b
都可以处理对外部工具函数的调用; - 在 链 中增加人工审批环节很方便,可以不破坏原有 链 中的任何逻辑,很优雅。
代码
本文涉及的所有代码以及相关资源都已经共享,参见:
参考:
🪐感谢您,祝好运🪐