1. 发起查询请求
向LLM发起查询时,messages列表只有一条消息(role为user, content为用户查询内容)。另外,还需要带上tools定义。
tools定义包含如下内容:
本例中,定义了函数get_weather(location)。
我们用curl发起POST请求,body的JSON结构可参考https://platform.openai.com/docs/api-reference/chat/create
#!/bin/bash
exportOPENAI_API_BASE="https://dashscope.aliyuncs.com/compatible-mode/v1"
exportOPENAI_API_KEY="sk-xxx"# 替换为你的key
curl${OPENAI_API_BASE}/chat/completions\
-H"Content-Type: application/json"\
-H"Authorization: Bearer$OPENAI_API_KEY"\
-d'{
"model": "qwen-plus",
"messages": [
{
"role": "user",
"content": "北京和广州天气怎么样"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "location"
}
},
"required": ["location"]
}
}
}
],
"tool_choice": "auto"
}'
2. LLM返回tool_calls获取北京天气
LLM经过推理,发现需要调用函数获取北京天气,回复的消息带上tool_calls信息。
本例中,需要调用函数get_weather,参数名为location, 参数值为北京。
完整的JSON响应如下:
"id": "call_3ee91e7e0e0b420d811165", "arguments": "{\"location\": \"北京\"}" "finish_reason": "tool_calls", "object": "chat.completion", "prompt_tokens_details": { "system_fingerprint": null, "id": "chatcmpl-7c4fc4c8-92fa-90cc-aaf6-f673d7ab4220"
3. 处理函数调用获取北京天气
解析处理LLM的tool_calls获得函数名和参数列表,调用相应的API接口获得结果。
例如:通过http://weather.cma.cn/api/now/54511可获得北京的天气情况。
完整的JSON响应如下:
"windDirectionDegree": 207.0, "lastUpdate": "2025/04/20 14:25"
4. 把上下文信息以及函数调用结果发给LLM
发给LLM的messages列表有3条messages:
- 第2条role为
assistant,是LLM的tool_calls响应get_weather('北京') - 第3条role为
tool,是工具调用get_weather('北京')的结果
#!/bin/bash
exportOPENAI_API_BASE="https://dashscope.aliyuncs.com/compatible-mode/v1"
exportOPENAI_API_KEY="sk-xxx"# 替换为你的key
curl${OPENAI_API_BASE}/chat/completions\
-H"Content-Type: application/json"\
-H"Authorization: Bearer$OPENAI_API_KEY"\
-d'{
"model": "qwen-plus",
"messages": [
{
"role": "user",
"content": "北京和广州天气怎么样"
},
{
"role": "assistant",
"tool_calls": [
{
"id": "call_3ee91e7e0e0b420d811165",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"北京\"}"
}
}
]
},
{
"role": "tool",
"content": "{\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"54511\",\"name\":\"北京\",\"path\":\"中国, 北京, 北京\"},\"now\":{\"precipitation\":0.0,\"temperature\":24.3,\"pressure\":1007.0,\"humidity\":35.0,\"windDirection\":\"西南风\",\"windDirectionDegree\":207.0,\"windSpeed\":2.7,\"windScale\":\"微风\"},\"alarm\":[],\"lastUpdate\":\"2025/04/20 14:25\"}}",
"tool_call_id": "call_3ee91e7e0e0b420d811165"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "location"
}
},
"required": [
"location"
]
}
}
}
],
"tool_choice": "auto"
}'
5. LLM返回tool_calls获取广州天气
LLM经过推理,发现需要调用函数获取广州天气,回复的消息带上tool_calls信息。
本例中,需要调用函数get_weather,参数名为location, 参数值为广州。
完整的JSON响应如下:
"id": "call_4a920a1bb9d54f8894c1ac", "arguments": "{\"location\": \"广州\"}" "finish_reason": "tool_calls", "object": "chat.completion", "prompt_tokens_details": { "system_fingerprint": null, "id": "chatcmpl-5e002b5b-7220-927e-9637-554355f80658"
6. 处理函数调用获取广州天气
解析处理LLM的tool_calls获得函数名和参数列表,调用相应的API接口获得结果。
例如:通过http://weather.cma.cn/api/now/59287可获得广州的天气情况。
完整的JSON响应如下:
"windDirectionDegree": 167.0, "lastUpdate": "2025/04/20 14:25"
7. 把上下文信息以及函数调用结果发给LLM
发给LLM的messages列表有5条messages:
- 第2条role为
assistant,是LLM的tool_calls响应get_weather('北京') - 第3条role为
tool,是工具调用get_weather('北京')的结果 - 第4条role为
assistant,是LLM的tool_calls响应get_weather('广州') - 第5条role为
tool,是工具调用get_weather('广州')的结果
#!/bin/bash
exportOPENAI_API_BASE="https://dashscope.aliyuncs.com/compatible-mode/v1"
exportOPENAI_API_KEY="sk-xxx"# 替换为你的key
curl${OPENAI_API_BASE}/chat/completions\
-H"Content-Type: application/json"\
-H"Authorization: Bearer$OPENAI_API_KEY"\
-d'{
"model": "qwen-plus",
"messages": [
{
"role": "user",
"content": "北京和广州天气怎么样"
},
{
"role": "assistant",
"tool_calls": [
{
"id": "call_3ee91e7e0e0b420d811165",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"北京\"}"
}
}
]
},
{
"role": "tool",
"content": "{\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"54511\",\"name\":\"北京\",\"path\":\"中国, 北京, 北京\"},\"now\":{\"precipitation\":0.0,\"temperature\":24.3,\"pressure\":1007.0,\"humidity\":35.0,\"windDirection\":\"西南风\",\"windDirectionDegree\":207.0,\"windSpeed\":2.7,\"windScale\":\"微风\"},\"alarm\":[],\"lastUpdate\":\"2025/04/20 14:25\"}}",
"tool_call_id": "call_3ee91e7e0e0b420d811165"
},
{
"role": "assistant",
"tool_calls": [
{
"id": "call_4a920a1bb9d54f8894c1ac",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"广州\"}"
}
}
]
},
{
"role": "tool",
"content": "{\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"59287\",\"name\":\"广州\",\"path\":\"中国, 广东, 广州\"},\"now\":{\"precipitation\":0.0,\"temperature\":30.1,\"pressure\":1002.0,\"humidity\":64.0,\"windDirection\":\"东南风\",\"windDirectionDegree\":167.0,\"windSpeed\":2.4,\"windScale\":\"微风\"},\"alarm\":[],\"lastUpdate\":\"2025/04/20 14:25\"}}",
"tool_call_id": "call_4a920a1bb9d54f8894c1ac"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "location"
}
},
"required": [
"location"
]
}
}
}
],
"tool_choice": "auto"
}'
8. LLM生成最终回复
LLM生成最终的回复:
北京的当前天气状况如下:
- 温度:24.3℃
- 湿度:35%
- 风向:西南风
- 风速:微风
广州的当前天气状况如下:
- 温度:30.1℃
- 湿度:64%
- 风向:东南风
- 风速:微风
以上信息均来自最近更新,希望对你有帮助!
完整的JSON响应如下:
"content": "北京的当前天气状况如下:\n- 温度:24.3℃\n- 湿度:35%\n- 风向:西南风\n- 风速:微风\n\n广州的当前天气状况如下:\n- 温度:30.1℃\n- 湿度:64%\n- 风向:东南风\n- 风速:微风 \n\n以上信息均来自最近更新,希望对你有帮助!", "object": "chat.completion", "completion_tokens": 105, "prompt_tokens_details": { "system_fingerprint": null, "id": "chatcmpl-fd1edc89-3ddb-9e27-9029-d2be2c81f3c1"
手搓Agent代码实现Function Calling工具调用
1. 创建python环境
uv init agent
cdagent
uv venv
.venv\Scripts\activate
uvaddopenai requests python-dotenv
2. 设置API Key
创建.env,.env内容如下(注意修改OPENAI_API_KEY为您的key)
OPENAI_API_KEY=your_api_key_here
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
把.env添加到.gitignore
3. 实现Agent代码
基于openai sdk实现agent的主体代码逻辑是:在允许的迭代次数范围内,循环处理,发起chat completions直至没有tool_calls, 迭代结束,输出结果。
伪代码:
for iterSeq in range(1, maxIter+1): 构造chat completion请求(带tools列表和tool_choice) 迭代次数达到最大值,tool_choice设置为none(不再调用工具) 否则tool_choice设置为auto(根据需要调用工具) 如果chat completion结果带有tool_calls
完整的main.py代码如下:
from typing import Iterablefrom openai import OpenAIfrom openai.types.chat.chat_completion_message_param import ChatCompletionMessageParamfrom openai.types.chat.chat_completion_message_tool_call import ( ChatCompletionMessageToolCall,from openai.types.chat.chat_completion_user_message_param import ( ChatCompletionUserMessageParam,from openai.types.chat.chat_completion_tool_message_param import ( ChatCompletionToolMessageParam,from openai.types.chat.chat_completion_assistant_message_param import ( ChatCompletionAssistantMessageParam,from dotenv import load_dotenvapi_key = os.getenv("OPENAI_API_KEY")base_url = os.getenv("OPENAI_API_BASE")client = OpenAI(api_key=api_key, base_url=base_url) "description": "Get weather", "location": {"type": "string", "description": "location"} "required": ["location"],def get_weather(location: str) -> str: url = "http://weather.cma.cn/api/autocomplete?q=" + urllib.parse.quote(location) response = requests.get(url) for item in data["data"]: str_array = item.split("|") or str_array[1] + "市" == location or str_array[2] == location location_code = str_array[0] url = f"http://weather.cma.cn/api/now/{location_code}" return requests.get(url).text tool_call: ChatCompletionMessageToolCall,) -> ChatCompletionToolMessageParam: result = ChatCompletionToolMessageParam(role="tool", tool_call_id=tool_call.id) if tool_call.function.name == "get_weather": args = json.loads(tool_call.function.arguments) result["content"] = get_weather(args["location"]) result["content"] = "函数未定义" messages: Iterable[ChatCompletionMessageParam] = list() messages.append(ChatCompletionUserMessageParam(role="user", content=query)) for iterSeq in range(1, maxIter+1): print(f">> iterSeq:{iterSeq}") print(f">>> messages: {messages}") toolChoice = "auto" if iterSeq < maxIter else "none" chat_completion = client.chat.completions.create( tool_calls = chat_completion.choices[0].message.tool_calls content = chat_completion.choices[0].message.content if isinstance(tool_calls, list): ChatCompletionAssistantMessageParam( role="assistant", tool_calls=tool_calls, content="" for tool_call in tool_calls: print(f">>> tool_call: {tool_call}") result = invoke_tool(tool_call) print(f">>> tool_call result: {result}") # LLM的响应信息没有tool_calls信息,迭代结束,获取响应文本 print(f">>> final result: \n{content}")
运行代码:uv run .\main.py
输出日志如下:
>> iterSeq:1
>>> messages: [{'role': 'user', 'content': '北京和广州天气怎么样'}]
>>> tool_call: ChatCompletionMessageToolCall(id='call_db29421754a8447590d99d', function=Function(arguments='{"location": "北京"}', name='get_weather'), type='function', index=0)
>>> tool_call result: {'role': 'tool', 'tool_call_id': 'call_db29421754a8447590d99d', 'content': '{"msg":"success","code":0,"data":{"location":{"id":"54511","name":"北京","path":"中国, 北京, 北京"},"now":{"precipitation":0.0,"temperature":24.5,"pressure":1006.0,"humidity":34.0,"windDirection":"西南风","windDirectionDegree":191.0,"windSpeed":2.8,"windScale":"微风"},"alarm":[],"lastUpdate":"2025/04/20 15:35"}}'}
>> iterSeq:2
>>> messages: [{'role': 'user', 'content': '北京和广州天气怎么样'}, {'role': 'assistant', 'tool_calls': [ChatCompletionMessageToolCall(id='call_db29421754a8447590d99d', function=Function(arguments='{"location": "北京"}', name='get_weather'), type='function', index=0)], 'content': ''}, {'role': 'tool', 'tool_call_id': 'call_db29421754a8447590d99d', 'content': '{"msg":"success","code":0,"data":{"location":{"id":"54511","name":"北京","path":"中国, 北京, 北京"},"now":{"precipitation":0.0,"temperature":24.5,"pressure":1006.0,"humidity":34.0,"windDirection":"西南风","windDirectionDegree":191.0,"windSpeed":2.8,"windScale":"微风"},"alarm":[],"lastUpdate":"2025/04/20 15:35"}}'}]
>>> tool_call: ChatCompletionMessageToolCall(id='call_ae1c03437392444c869cbf', function=Function(arguments='{"location": "广州"}', name='get_weather'), type='function', index=0)
>>> tool_call result: {'role': 'tool', 'tool_call_id': 'call_ae1c03437392444c869cbf', 'content': '{"msg":"success","code":0,"data":{"location":{"id":"59287","name":"广州","path":"中国, 广东, 广州"},"now":{"precipitation":0.0,"temperature":30.4,"pressure":1001.0,"humidity":64.0,"windDirection":"东南风","windDirectionDegree":165.0,"windSpeed":2.2,"windScale":"微风"},"alarm":[],"lastUpdate":"2025/04/20 15:35"}}'}
>> iterSeq:3
>>> messages: [{'role': 'user', 'content': '北京和广州天气怎么样'}, {'role': 'assistant', 'tool_calls': [ChatCompletionMessageToolCall(id='call_db29421754a8447590d99d', function=Function(arguments='{"location": "北京"}', name='get_weather'), type='function', index=0)], 'content': ''}, {'role': 'tool', 'tool_call_id': 'call_db29421754a8447590d99d', 'content': '{"msg":"success","code":0,"data":{"location":{"id":"54511","name":"北京","path":"中国, 北京, 北京"},"now":{"precipitation":0.0,"temperature":24.5,"pressure":1006.0,"humidity":34.0,"windDirection":"西南风","windDirectionDegree":191.0,"windSpeed":2.8,"windScale":"微风"},"alarm":[],"lastUpdate":"2025/04/20 15:35"}}'}, {'role': 'assistant', 'tool_calls': [ChatCompletionMessageToolCall(id='call_ae1c03437392444c869cbf', function=Function(arguments='{"location": "广州"}', name='get_weather'), type='function', index=0)], 'content': ''}, {'role': 'tool', 'tool_call_id': 'call_ae1c03437392444c869cbf', 'content': '{"msg":"success","code":0,"data":{"location":{"id":"59287","name":"广州","path":"中国, 广东, 广州"},"now":{"precipitation":0.0,"temperature":30.4,"pressure":1001.0,"humidity":64.0,"windDirection":"东南风","windDirectionDegree":165.0,"windSpeed":2.2,"windScale":"微风"},"alarm":[],"lastUpdate":"2025/04/20 15:35"}}'}]
>>> final result:
北京的当前天气状况如下:
- 温度:24.5°C
- 湿度:34%
- 风向:西南风
- 风速:微风 (2.8 m/s)
- 最后更新时间:2025/04/20 15:35
广州的当前天气状况如下:
- 温度:30.4°C
- 湿度:64%
- 风向:东南风
- 风速:微风 (2.2 m/s)
- 最后更新时间:2025/04/20 15:35
END