AI Agent Note (绑定自定义工具)
2025-12-03 00:00:00 # AI

绑定自定义工具

一、工具函数

1
2
3
def add(a, b):
print("调用了函数add:")
return a + b

二、将工具函数转换为LangChain Tool对象

引入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="两个数相加"
)

三、大模型和Tool对象绑定

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)

结果如下:

image-20251203102953163

可以看到返回的类型是一个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)

结果

image-20251203110347321

到这里实现了一个很基础且粗糙的大模型工具调用,,,下面就是优化部分


六、优化

1、tool 对象转换

使用@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)

image-20251203142700953

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,对ab两个变量的类型进行了定义,并且添加了相应的描述

修改一下invoke,这里先让大模型回答了传入的两个参数,然后再调用工具,看一下执行的效果

1
resp = chain.invoke(input={"role": "计算", "domain": "数学计算", "question": "先告诉我传入工具的参数都是什么,然后使用工具计算100+100等于多少"})

image-20251203151732815

可以看到大模型先回答了两个参数,然后返回了工具的调用信息。

下面修改参数类型为字符串,然后再去执行

1
2
3
class AddInputArgs(BaseModel):
a: str = Field(description="第一个数")
b: str = Field(description="第二个数")

image-20251203152241176

可以看到,虽然大模型回答了参数是字符串类型,但是仍然是回答用add计算100 + 100,而tool_callsargs中参数类型是之前规定的字符串类型,调用结果也是字符串的拼接。也就是说前边装饰器当中定义的参数类型,不会因为大模型的认识而发生改变。

七、修改示例,启动本地计算器进行计算

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)

结果如下👇🏻