绑定自定义工具
一、工具函数
1 2 3
| def add(a, b): print("调用了函数add:") return a + b
|
引入from langchain_core.tools import Tool,通过这个可以把函数转化为大模型能够理解的工具
1 2 3 4 5 6 7 8 9
| def add(a, b): print("调用了函数add:") return a + b
add_tools = Tool.from_function( func=add, name="add", description="两个数相加" )
|
1 2
| llm_with_tools = llm.bind_tools([add_tools]) chain = chat_prompt_template | llm_with_tools
|
四、调用大模型
1 2 3
| resp = chain.invoke(input={"role": "计算", "domain": "数学计算", "question": "100+100=?"}) print(type(resp)) print(resp)
|
结果如下:

可以看到返回的类型是一个AIMessage的类型,简单分解一下消息体resp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| content=''
additional_kwargs={ 'tool_calls': [{ 'index': 0, 'id': 'call_38caa7727617474bac274c', 'function': { 'arguments': '{"__arg1": "100", "__arg2": "100"}', 'name': 'add' }, 'type': 'function' }] }
response_metadata={ 'finish_reason': 'tool_calls', 'model_name': 'qwen-max-latest' }
id='run--7350a276-475d-443c-9233-d55bfff39686-0'
tool_calls=[{ 'name': 'add', 'args': {'__arg1': '100', '__arg2': '100'}, 'id': 'call_38caa7727617474bac274c', 'type': 'tool_call' }]
|
第一个content对象,是大模型返回的文本内容,这里是空的,先不解释,,,
然后是additional_kwargs对象,里面包含了一个tool_calls数组,数组里面有函数的名字和需要传入的参数。
所以可以看出,到这里大模型所做的事情就是选择了一个tool,并且确定了传入的参数,但是并没有去使用tool,这也就是为什么content会是空的原因。
五、让大模型调用工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| tool_dict = { "add": add }
for tool_calls in resp.tool_calls: print(tool_calls)
func_name = tool_calls["name"] print(func_name)
args = tool_calls["args"] print(args)
tool_func = tool_dict[func_name] tool_content = tool_func(int(args['__arg1']), int(args['__arg2'])) print(tool_content)
|
结果

到这里实现了一个很基础且粗糙的大模型工具调用,,,下面就是优化部分
六、优化
使用@tool装饰器生成
1 2 3 4 5 6 7 8 9
| @tool def add(a, b): """两个数相加""" print("调用了函数add:") return a + b
print(type(add)) llm_with_tools = llm.bind_tools([add])
|
这里打印add的类型是<class 'langchain_core.tools.structured.StructuredTool'>,可以看到已经是一个类似结构体的东西了
相应的调用逻辑也需要进行修改
1 2 3 4 5 6 7 8 9 10 11 12
| for tool_calls in resp.tool_calls: print(tool_calls)
func_name = tool_calls["name"] print(func_name)
args = tool_calls["args"] print(args)
tool_func = tool_dict[func_name] tool_content = tool_func.invoke(args) print(tool_content)
|
由于add已经是一个大模型可以识别的tool,所以这里直接用invoke方法,并且传入tool_calls当中的args就可以直接调用。下面是完整代码👇🏻
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| from app.bailian.commom import chat_prompt_template, llm from langchain_core.tools import Tool, tool
@tool def add(a, b): """两个数相加""" print("调用了函数add:") return a + b
llm_with_tools = llm.bind_tools([add])
chain = chat_prompt_template | llm_with_tools
resp = chain.invoke(input={"role": "计算", "domain": "数学计算", "question": "使用工具计算100+100=?"})
print(resp)
tool_dict = { "add": add }
for tool_calls in resp.tool_calls: print(tool_calls)
func_name = tool_calls["name"] print(func_name)
args = tool_calls["args"] print(args)
tool_func = tool_dict[func_name] tool_content = tool_func.invoke(args) print(tool_content)
|

2、定义数据类型
1 2 3 4 5 6 7 8 9 10 11 12
| class AddInputArgs(BaseModel): a: int = Field(description="第一个数") b: int = Field(description="第二个数")
@tool( description="两个数相加", args_schema=AddInputArgs ) def add(a, b): print("调用了函数add:") return a + b
|
在装饰器中添加一个args_schema变量,传递一个class类AddInputArgs,对a和b两个变量的类型进行了定义,并且添加了相应的描述
修改一下invoke,这里先让大模型回答了传入的两个参数,然后再调用工具,看一下执行的效果
1
| resp = chain.invoke(input={"role": "计算", "domain": "数学计算", "question": "先告诉我传入工具的参数都是什么,然后使用工具计算100+100等于多少"})
|

可以看到大模型先回答了两个参数,然后返回了工具的调用信息。
下面修改参数类型为字符串,然后再去执行
1 2 3
| class AddInputArgs(BaseModel): a: str = Field(description="第一个数") b: str = Field(description="第二个数")
|

可以看到,虽然大模型回答了参数是字符串类型,但是仍然是回答用add计算100 + 100,而tool_calls的args中参数类型是之前规定的字符串类型,调用结果也是字符串的拼接。也就是说前边装饰器当中定义的参数类型,不会因为大模型的认识而发生改变。
七、修改示例,启动本地计算器进行计算
1、工具函数
先写一个简易的调用本地计算器的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import time import pyautogui
pyautogui.hotkey('esc') pyautogui.hotkey('esc') time.sleep(1) pyautogui.hotkey('command', 'space') time.sleep(3) pyautogui.typewrite('Calculator') time.sleep(2) pyautogui.press('enter') time.sleep(3) pyautogui.typewrite('100+200') time.sleep(1) pyautogui.press('enter')
|
然后用装饰器转换为tool对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class AddInputArgs(BaseModel): a: str = Field(description="第一个数") b: str = Field(description="第二个数")
@tool( description="用计算器对两个数进行相加", args_schema=AddInputArgs ) def calculate(a, b): pyautogui.hotkey('esc') pyautogui.hotkey('esc') time.sleep(1) pyautogui.hotkey('command', 'space') time.sleep(3) pyautogui.typewrite('Calculator') time.sleep(2) pyautogui.press('enter') time.sleep(3) pyautogui.typewrite(str(a) + '+' + str(b)) time.sleep(1) pyautogui.press('enter') time.sleep(3) return "系统执行完成"
llm_with_tool = llm.bind_tools([calculate])
|
然后将大模型和tool对象进行绑定,并写入提示词
1 2 3 4 5 6 7 8 9 10 11
| chain = chat_prompt_template | llm_with_tool
llm_input = { "role": "编程", "domain": "后端开发", "question": "调用本地计算器,计算100+200的值是多少" }
resp = chain.invoke(input=llm_input)
print(resp)
|
最后调用工具
1 2 3 4 5 6 7 8 9 10 11
| tool_dict = { "calculate": calculate }
for tool_calls in resp.tool_calls: args = tool_calls["args"] func_name = tool_calls["name"]
tool_func = tool_dict[func_name] tool_content = tool_func.invoke(args) print(tool_content)
|
结果如下👇🏻