一文徹底搞懂智能體Agent基于ReAct的工具調(diào)用
前言
AI智能體是指具備一定自主性、能感知環(huán)境并通過智能決策執(zhí)行特定任務(wù)的軟件或硬件實(shí)體。它結(jié)合了人工智能技術(shù)(如機(jī)器學(xué)習(xí)、自然語言處理、計(jì)算機(jī)視覺等),能夠獨(dú)立或協(xié)作完成目標(biāo)。
基于大語言模型(LLM)的Function Calling可以令智能體實(shí)現(xiàn)有效的工具使用和與外部API的交互。支持Function Calling的模型(如gpt-4,qwen-plus等)能夠檢測(cè)何時(shí)需要調(diào)用函數(shù),并輸出調(diào)用函數(shù)的函數(shù)名和所需參數(shù)的JSON格式結(jié)構(gòu)化數(shù)據(jù)。
但并非所有的LLM模型都支持Function Calling(如deepseek-v3)。對(duì)于不支持Function Calling的模型,可通過ReAct的相對(duì)較為復(fù)雜的提示詞工程,要求模型返回特定格式的響應(yīng),以便區(qū)分不同的階段(思考、行動(dòng)、觀察)。
工具調(diào)用主要有兩個(gè)用途:
獲取數(shù)據(jù):
例如根據(jù)關(guān)鍵字從知識(shí)庫檢索內(nèi)容、通過特定API接口獲取業(yè)務(wù)數(shù)據(jù)執(zhí)行行動(dòng):
例如通過API接口修改業(yè)務(wù)狀態(tài)數(shù)據(jù)、執(zhí)行預(yù)定業(yè)務(wù)操作
本文包含如下內(nèi)容:
- ReAct基礎(chǔ)
- 詳細(xì)介紹基于ReAct的工具調(diào)用流程和涉及的交互消息
- 手搓Agent代碼實(shí)現(xiàn)基于ReAct的工具調(diào)用
ReAct基礎(chǔ)
ReAct源于經(jīng)典論文: REACT: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS
(鏈接:https://arxiv.org/pdf/2210.03629)
基于ReAct的智能體為了解決問題,需要經(jīng)過幾個(gè)階段
- Thought: 思考推理
- Action:作出行動(dòng),決定要調(diào)用的工具和參數(shù)
- Observation:行動(dòng)的結(jié)果(工具輸出)
以上3個(gè)階段可能迭代多次,直到問題得到解決或者達(dá)到迭代次數(shù)上限。
基于ReAct的工具調(diào)用依賴于復(fù)雜的提示詞工程。系統(tǒng)提示詞參考langchain的模板:
Answer the following questions as best you can. You have access to the following tools:
{tool_strings}
The way you use the tools is by specifying a json blob.
Specifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).
The only values that should be in the "action" field are: {tool_names}
The $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:
```
{{{{
"action": $TOOL_NAME,
"action_input": $INPUT
}}}}
```
ALWAYS use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action:
```
$JSON_BLOB
```
Observation: the result of the action
... (this Thought/Action/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin! Reminder to always use the exact characters `Final Answer` when responding.
基于ReAct的工具調(diào)用流程和交互消息
我們以查詢北京和廣州天氣為例,LLM采用阿里云的deepseek-v3
。查詢天氣的流程如下圖:
1. 發(fā)起查詢請(qǐng)求
向LLM發(fā)起查詢時(shí),messages列表有2條messages:
- 第1條role為
system
,定義了系統(tǒng)提示詞(含工具定義) - 第2條role為
user
,包含如下內(nèi)容:
Question: 北京和廣州天氣怎么樣
我們用curl發(fā)起POST請(qǐng)求,body的JSON結(jié)構(gòu)可參考https://platform.openai.com/docs/api-reference/chat/create 。
請(qǐng)求里的
stop
字段需要設(shè)置為Observation:
,否則LLM會(huì)直接輸出整個(gè)Thought/Action/Observation流程并給出虛構(gòu)的最終答案。我們僅需要LLM輸出Thought/Action即可
#!/bin/bash
export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
export OPENAI_API_KEY="sk-xxx" # 替換為你的key
curl ${OPENAI_BASE_URL}/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "deepseek-v3",
"messages": [
{
"role": "system",
"content": "\nAnswer the following questions as best you can. You have access to the following tools:\n{\"name\": \"get_weather\", \"description\": \"Get weather\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"the name of the location\"}}, \"required\": [\"location\"]}}\n\n\nThe way you use the tools is by specifying a json blob.\nSpecifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).\n\nThe only values that should be in the \"action\" field are: get_weather\n\nThe $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:\n\n```\n{{\n\"action\": $TOOL_NAME,\n\"action_input\": $INPUT\n}}\n```\n\nALWAYS use the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction:\n```\n$JSON_BLOB\n```\nObservation: the result of the action\n... (this Thought/Action/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\n\nBegin! Reminder to always use the exact characters `Final Answer` when responding. \n"
},
{
"role": "user",
"content": "Question: 北京和廣州天氣怎么樣\n\n"
}
],
"stop": "Observation:"
}'
2. LLM返回Action獲取北京天氣
LLM經(jīng)過推理,發(fā)現(xiàn)需要先調(diào)用函數(shù)獲取北京天氣。
Thought: 我需要獲取北京和廣州的天氣信息。首先,我將獲取北京的天氣。
Action:
```
{
"action": "get_weather",
"action_input": {
"location": "北京"
}
}
```
完整的JSON響應(yīng)如下:
{
"choices": [
{
"message": {
"content": "Thought: 我需要獲取北京和廣州的天氣信息。首先,我將獲取北京的天氣。\n\nAction:\n```\n{\n \"action\": \"get_weather\",\n \"action_input\": {\n \"location\": \"北京\"\n }\n}\n```",
"role": "assistant"
},
"finish_reason": "stop",
"index": 0,
"logprobs": null
}
],
"object": "chat.completion",
"usage": {
"prompt_tokens": 305,
"completion_tokens": 49,
"total_tokens": 354
},
"created": 1745651748,
"system_fingerprint": null,
"model": "deepseek-v3",
"id": "chatcmpl-697b0627-4fca-975b-954c-7304386ac224"
}
3. 處理函數(shù)調(diào)用獲取北京天氣
解析處理LLM的Action獲得函數(shù)名和參數(shù)列表,調(diào)用相應(yīng)的API接口獲得結(jié)果。
例如:通過http://weather.cma.cn/api/now/54511
可獲得北京的天氣情況。
完整的JSON響應(yīng)如下:
{
"msg": "success",
"code": 0,
"data": {
"location": {
"id": "54511",
"name": "北京",
"path": "中國, 北京, 北京"
},
"now": {
"precipitation": 0.0,
"temperature": 23.4,
"pressure": 1005.0,
"humidity": 43.0,
"windDirection": "西南風(fēng)",
"windDirectionDegree": 216.0,
"windSpeed": 2.7,
"windScale": "微風(fēng)",
"feelst": 23.1
},
"alarm": [],
"jieQi": "",
"lastUpdate": "2025/04/26 15:00"
}
}
4. 把上下文信息以及函數(shù)調(diào)用結(jié)果發(fā)給LLM
發(fā)給LLM的messages列表有2條messages:
- 第1條role為
system
,定義了系統(tǒng)提示詞(含工具定義) - 第2條role為
user
,包含如下內(nèi)容:
Question: 北京和廣州天氣怎么樣
Thought: 我需要獲取北京和廣州的天氣信息。首先,我將獲取北京的天氣
Action: {"action":"get_weather","action_input":{"location":"北京"}}
Observation: 工具調(diào)用get_weather('北京')
的結(jié)果
#!/bin/bash
export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
export OPENAI_API_KEY="sk-xxx" # 替換為你的key
curl ${OPENAI_BASE_URL}/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "deepseek-v3",
"messages": [
{
"role": "system",
"content": "\nAnswer the following questions as best you can. You have access to the following tools:\n{\"name\": \"get_weather\", \"description\": \"Get weather\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"the name of the location\"}}, \"required\": [\"location\"]}}\n\n\nThe way you use the tools is by specifying a json blob.\nSpecifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).\n\nThe only values that should be in the \"action\" field are: get_weather\n\nThe $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:\n\n```\n{{\n\"action\": $TOOL_NAME,\n\"action_input\": $INPUT\n}}\n```\n\nALWAYS use the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction:\n```\n$JSON_BLOB\n```\nObservation: the result of the action\n... (this Thought/Action/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\n\nBegin! Reminder to always use the exact characters `Final Answer` when responding. \n"
},
{
"role": "user",
"content": "Question: 北京和廣州天氣怎么樣\n\nThought: 我需要獲取北京和廣州的天氣信息。首先,我將獲取北京的天氣。\n\nAction:\n```\n{\n\"action\": \"get_weather\",\n\"action_input\": {\n\"location\": \"北京\"\n}\n}\n```\nObservation: {\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"54511\",\"name\":\"北京\",\"path\":\"中國, 北京, 北京\"},\"now\":{\"precipitation\":0.0,\"temperature\":23.4,\"pressure\":1005.0,\"humidity\":43.0,\"windDirection\":\"西南風(fēng)\",\"windDirectionDegree\":216.0,\"windSpeed\":2.7,\"windScale\":\"微風(fēng)\",\"feelst\":23.1},\"alarm\":[],\"jieQi\":\"\",\"lastUpdate\":\"2025/04/26 15:00\"}}\n"
}
],
"stop": "Observation:"
}'
5. LLM返回Action獲取廣州天氣
LLM經(jīng)過推理,發(fā)現(xiàn)還需要調(diào)用函數(shù)獲取廣州天氣。
Thought: 我已經(jīng)獲取了北京的天氣信息。接下來,我將獲取廣州的天氣信息。
Action:
```
{
"action": "get_weather",
"action_input": {
"location": "廣州"
}
}
```
完整的JSON響應(yīng)如下:
{
"choices": [
{
"message": {
"content": "Thought: 我已經(jīng)獲取了北京的天氣信息。接下來,我將獲取廣州的天氣信息。\n\nAction:\n```\n{\n\"action\": \"get_weather\",\n\"action_input\": {\n\"location\": \"廣州\"\n}\n}\n```\nObservation",
"role": "assistant"
},
"finish_reason": "stop",
"index": 0,
"logprobs": null
}
],
"object": "chat.completion",
"usage": {
"prompt_tokens": 472,
"completion_tokens": 46,
"total_tokens": 518
},
"created": 1745651861,
"system_fingerprint": null,
"model": "deepseek-v3",
"id": "chatcmpl-a822b8d7-9105-9dc2-8e98-4327afb50b3a"
}
6. 處理函數(shù)調(diào)用獲取廣州天氣
解析處理LLM的Action獲得函數(shù)名和參數(shù)列表,調(diào)用相應(yīng)的API接口獲得結(jié)果。
例如:通過http://weather.cma.cn/api/now/59287
可獲得廣州的天氣情況。
完整的JSON響應(yīng)如下:
{
"msg": "success",
"code": 0,
"data": {
"location": {
"id": "59287",
"name": "廣州",
"path": "中國, 廣東, 廣州"
},
"now": {
"precipitation": 0.0,
"temperature": 24.2,
"pressure": 1005.0,
"humidity": 79.0,
"windDirection": "東北風(fēng)",
"windDirectionDegree": 31.0,
"windSpeed": 1.3,
"windScale": "微風(fēng)",
"feelst": 27.1
},
"alarm": [],
"jieQi": "",
"lastUpdate": "2025/04/26 15:00"
}
}
7. 把上下文信息以及函數(shù)調(diào)用結(jié)果發(fā)給LLM
發(fā)給LLM的messages列表有2條messages:
- 第1條role為
system
,定義了系統(tǒng)提示詞(含工具定義) - 第2條role為
user
,包含如下內(nèi)容:
Question: 北京和廣州天氣怎么樣
Thought: 我需要獲取北京和廣州的天氣信息。首先,我將獲取北京的天氣
Action: {"action":"get_weather","action_input":{"location":"北京"}}
Observation: 工具調(diào)用get_weather('北京')
的結(jié)果
Thought: 現(xiàn)在我已經(jīng)獲取了北京的天氣信息,接下來我將獲取廣州的天氣信息。
Action: {"action":"get_weather","action_input":{"location":"廣州"}}
Observation: 工具調(diào)用get_weather('廣州')
的結(jié)果
#!/bin/bash
export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
export OPENAI_API_KEY="sk-xxx" # 替換為你的key
curl ${OPENAI_BASE_URL}/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "deepseek-v3",
"messages": [
{
"role": "system",
"content": "\nAnswer the following questions as best you can. You have access to the following tools:\n{\"name\": \"get_weather\", \"description\": \"Get weather\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"the name of the location\"}}, \"required\": [\"location\"]}}\n\n\nThe way you use the tools is by specifying a json blob.\nSpecifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).\n\nThe only values that should be in the \"action\" field are: get_weather\n\nThe $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:\n\n```\n{{\n\"action\": $TOOL_NAME,\n\"action_input\": $INPUT\n}}\n```\n\nALWAYS use the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction:\n```\n$JSON_BLOB\n```\nObservation: the result of the action\n... (this Thought/Action/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\n\nBegin! Reminder to always use the exact characters `Final Answer` when responding. \n"
},
{
"role": "user",
"content": "Question: 北京和廣州天氣怎么樣\n\nThought: 我需要獲取北京和廣州的天氣信息。首先,我將獲取北京的天氣。\n\nAction:\n```\n{\n\"action\": \"get_weather\",\n\"action_input\": {\n\"location\": \"北京\"\n}\n}\n```\nObservation: {\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"54511\",\"name\":\"北京\",\"path\":\"中國, 北京, 北京\"},\"now\":{\"precipitation\":0.0,\"temperature\":23.4,\"pressure\":1005.0,\"humidity\":43.0,\"windDirection\":\"西南風(fēng)\",\"windDirectionDegree\":216.0,\"windSpeed\":2.7,\"windScale\":\"微風(fēng)\",\"feelst\":23.1},\"alarm\":[],\"jieQi\":\"\",\"lastUpdate\":\"2025/04/26 15:00\"}}\nThought: 現(xiàn)在我已經(jīng)獲取了北京的天氣信息,接下來我將獲取廣州的天氣信息。\n\nAction:\n```\n{\n\"action\": \"get_weather\",\n\"action_input\": {\n\"location\": \"廣州\"\n}\n}\n```\nObservation\nObservation: {\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"59287\",\"name\":\"廣州\",\"path\":\"中國, 廣東, 廣州\"},\"now\":{\"precipitation\":0.0,\"temperature\":24.2,\"pressure\":1005.0,\"humidity\":79.0,\"windDirection\":\"東北風(fēng)\",\"windDirectionDegree\":31.0,\"windSpeed\":1.3,\"windScale\":\"微風(fēng)\",\"feelst\":27.1},\"alarm\":[],\"jieQi\":\"\",\"lastUpdate\":\"2025/04/26 15:00\"}}\n"
}
],
"stop": "Observation:"
}'
8. LLM生成最終回復(fù)
LLM生成最終的回復(fù):
Thought: 我已經(jīng)獲取了北京和廣州的天氣信息,現(xiàn)在可以回答用戶的問題了。
Final Answer: 北京的天氣溫度為23.4°C,濕度為43%,風(fēng)向?yàn)槲髂巷L(fēng),風(fēng)速為2.7米/秒。廣州 的天氣溫度為24.2°C,濕度為79%,風(fēng)向?yàn)闁|北風(fēng),風(fēng)速為1.3米/秒。
完整的JSON響應(yīng)如下:
{
"choices": [
{
"message": {
"content": "Thought: 我已經(jīng)獲取了北京和廣州的天氣信息,現(xiàn)在可以回答用戶的問題了。\n\nFinal Answer: 北京的天氣溫度為23.4°C,濕度為43%,風(fēng)向?yàn)槲髂巷L(fēng),風(fēng)速為2.7米/秒。廣州 的天氣溫度為24.2°C,濕度為79%,風(fēng)向?yàn)闁|北風(fēng),風(fēng)速為1.3米/秒。",
"role": "assistant"
},
"finish_reason": "stop",
"index": 0,
"logprobs": null
}
],
"object": "chat.completion",
"usage": {
"prompt_tokens": 641,
"completion_tokens": 79,
"total_tokens": 720
},
"created": 1745652025,
"system_fingerprint": null,
"model": "deepseek-v3",
"id": "chatcmpl-d9b85f31-589e-9c6f-8694-cf813344e464"
}
手搓Agent代碼實(shí)現(xiàn)基于ReAct的工具調(diào)用
1. 創(chuàng)建python環(huán)境
uv init agent
cd agent
uv venv
.venv\Scripts\activate
uv add openai requests python-dotenv
2. 設(shè)置API Key
創(chuàng)建.env,.env內(nèi)容如下(注意修改OPENAI_API_KEY為您的key)
OPENAI_API_KEY=your_api_key_here
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
把.env添加到.gitignore
3. 實(shí)現(xiàn)Agent代碼
基于openai sdk實(shí)現(xiàn)ReAct agent的偽代碼主體邏輯如下:
maxIter = 5 # 最大迭代次數(shù)
agent_scratchpad = "" # agent思考過程(Thought/Action/Observation)
for iterSeq in range(1, maxIter+1):
構(gòu)造chat completion請(qǐng)求
messages有2條
第1條為系統(tǒng)提示詞消息(含工具定義)
第2條為用戶消息:Question + agent思考過程(Thought/Action/Observation)
stop參數(shù)設(shè)置為"Observation:"
獲取chat completion結(jié)果
如果chat completion結(jié)果帶有"Final Answer:"
返回最終答案
如果chat completion結(jié)果帶有Action
解析并調(diào)用相應(yīng)函數(shù)
更新agent思考過程:把本次LLM的輸出(Though/Action)和工具調(diào)用結(jié)果(Observation)添加到agent_scratchpad
繼續(xù)迭代
完整的main.py代碼如下:
import json
import re
import requests
import urllib.parse
from typing import Iterable
from openai import OpenAI
from openai.types.chat.chat_completion_message_param import ChatCompletionMessageParam
from openai.types.chat.chat_completion_user_message_param import (
ChatCompletionUserMessageParam,
)
from openai.types.chat.chat_completion_system_message_param import (
ChatCompletionSystemMessageParam,
)
# 加載環(huán)境變量
from dotenv import load_dotenv
load_dotenv()
client = OpenAI()
model = "deepseek-v3"
# 工具定義
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "location"}
},
"required": ["location"],
},
},
}
]
# 系統(tǒng)提示詞
def get_system_prompt():
tool_strings = "\n".join([json.dumps(tool["function"]) for tool in tools])
tool_names = ", ".join([tool["function"]["name"] for tool in tools])
systemPromptFormat = """
Answer the following questions as best you can. You have access to the following tools:
{tool_strings}
The way you use the tools is by specifying a json blob.
Specifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).
The only values that should be in the "action" field are: {tool_names}
The $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:
```
{{{{
"action": $TOOL_NAME,
"action_input": $INPUT
}}}}
```
ALWAYS use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action:
```
$JSON_BLOB
```
Observation: the result of the action
... (this Thought/Action/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin! Reminder to always use the exact characters `Final Answer` when responding.
"""
return systemPromptFormat.format(tool_strings=tool_strings, tool_names=tool_names)
# 實(shí)現(xiàn)獲取天氣
def get_weather(location: str) -> str:
url = "http://weather.cma.cn/api/autocomplete?q=" + urllib.parse.quote(location)
response = requests.get(url)
data = response.json()
if data["code"] != 0:
return "沒找到該位置的信息"
location_code = ""
for item in data["data"]:
str_array = item.split("|")
if (
str_array[1] == location
or str_array[1] + "市" == location
or str_array[2] == location
):
location_code = str_array[0]
break
if location_code == "":
return "沒找到該位置的信息"
url = f"http://weather.cma.cn/api/now/{location_code}"
return requests.get(url).text
# 實(shí)現(xiàn)工具調(diào)用
def invoke_tool(toolName: str, toolParamaters) -> str:
result = ""
if toolName == "get_weather":
result = get_weather(toolParamaters["location"])
else:
result = f"函數(shù){toolName}未定義"
return result
def main():
query = "北京和廣州天氣怎么樣"
systemMsg = ChatCompletionSystemMessageParam(
role="system", cnotallow=get_system_prompt()
)
maxIter = 5 # 最大迭代次數(shù)
agent_scratchpad = "" # agent思考過程
action_pattern = re.compile(r"\nAction:\n`{3}(?:json)?\n(.*?)`{3}.*?$", re.DOTALL)
for iterSeq in range(1, maxIter + 1):
messages: Iterable[ChatCompletionMessageParam] = list()
messages.append(systemMsg)
messages.append(
ChatCompletionUserMessageParam(
role="user", cnotallow=f"Question: {query}\n\n{agent_scratchpad}"
)
)
print(f">> iterSeq:{iterSeq}")
print(f">>> messages: {json.dumps(messages)}")
# 向LLM發(fā)起請(qǐng)求,注意需要設(shè)置stop參數(shù)
chat_completion = client.chat.completions.create(
messages=messages,
model=model,
stop="Observation:",
)
content = chat_completion.choices[0].message.content
print(f">>> content:\n{content}")
final_answer_match = re.search(r"\nFinal Answer:\s*(.*)", content)
if final_answer_match:
final_answer = final_answer_match.group(1)
print(f">>> 最終答案: {final_answer}")
return
action_match = action_pattern.search(content)
if action_match:
obj = json.loads(action_match.group(1))
toolName = obj["action"]
toolParameters = obj["action_input"]
print(f">>> tool name:{toolName}")
print(f">>> tool parameters:{toolParameters}")
result = invoke_tool(toolName, toolParameters)
print(f">>> tool result: {result}")
# 把本次LLM的輸出(Though/Action)和工具調(diào)用結(jié)果(Observation)添加到agent_scratchpad
agent_scratchpad += content + f"\nObservation: {result}\n"
else:
print(">>> ERROR: detect invalid response")
return
print(">>> 迭代次數(shù)達(dá)到上限,我無法得到最終答案")
main()
運(yùn)行代碼:uv run .\main.py
輸出日志如下:
>> iterSeq:1
>>> messages: [{"role": "system", "content": "\nAnswer the following questions as best you can. You have access to the following tools:\n{\"name\": \"get_weather\", \"description\": \"Get weather\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"the name of the location\"}}, \"required\": [\"location\"]}}\n\n\nThe way you use the tools is by specifying a json blob.\nSpecifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).\n\nThe only values that should be in the \"action\" field are: get_weather\n\nThe $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:\n\n```\n{{\n\"action\": $TOOL_NAME,\n\"action_input\": $INPUT\n}}\n```\n\nALWAYS use the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction:\n```\n$JSON_BLOB\n```\nObservation: the result of the action\n... (this Thought/Action/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\n\nBegin! Reminder to always use the exact characters `Final Answer` when responding. \n"}, {"role": "user", "content": "Question: \u5317\u4eac\u548c\u5e7f\u5dde\u5929\u6c14\u600e\u4e48\u6837\n\n"}]
>>> content:
Thought: 我需要獲取北京和廣州的天氣信息。首先,我將獲取北京的天氣。
Action:
```
{
"action": "get_weather",
"action_input": {
"location": "北京"
}
}
```
>>> tool name:get_weather
>>> tool parameters:{'location': '北京'}
>>> tool result: {"msg":"success","code":0,"data":{"location":{"id":"54511","name":"北京","path":"中國, 北京, 北京"},"now":{"precipitation":0.0,"temperature":23.4,"pressure":1005.0,"humidity":43.0,"windDirection":"西南風(fēng)","windDirectionDegree":216.0,"windSpeed":2.7,"windScale":"微風(fēng)","feelst":23.1},"alarm":[],"jieQi":"","lastUpdate":"2025/04/26 15:00"}}
>> iterSeq:2
>>> messages: [{"role": "system", "content": "\nAnswer the following questions as best you can. You have access to the following tools:\n{\"name\": \"get_weather\", \"description\": \"Get weather\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"the name of the location\"}}, \"required\": [\"location\"]}}\n\n\nThe way you use the tools is by specifying a json blob.\nSpecifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).\n\nThe only values that should be in the \"action\" field are: get_weather\n\nThe $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:\n\n```\n{{\n\"action\": $TOOL_NAME,\n\"action_input\": $INPUT\n}}\n```\n\nALWAYS use the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction:\n```\n$JSON_BLOB\n```\nObservation: the result of the action\n... (this Thought/Action/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\n\nBegin! Reminder to always use the exact characters `Final Answer` when responding. \n"}, {"role": "user", "content": "Question: \u5317\u4eac\u548c\u5e7f\u5dde\u5929\u6c14\u600e\u4e48\u6837\n\nThought: \u6211\u9700\u8981\u83b7\u53d6\u5317\u4eac\u548c\u5e7f\u5dde\u7684\u5929\u6c14\u4fe1\u606f\u3002\u9996\u5148\uff0c\u6211\u5c06\u83b7\u53d6\u5317\u4eac\u7684\u5929\u6c14\u3002\n\nAction:\n```\n{\n\"action\": \"get_weather\",\n\"action_input\": {\n\"location\": \"\u5317\u4eac\"\n}\n}\n```\nObservation: {\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"54511\",\"name\":\"\u5317\u4eac\",\"path\":\"\u4e2d\u56fd, \u5317\u4eac, \u5317\u4eac\"},\"now\":{\"precipitation\":0.0,\"temperature\":23.4,\"pressure\":1005.0,\"humidity\":43.0,\"windDirection\":\"\u897f\u5357\u98ce\",\"windDirectionDegree\":216.0,\"windSpeed\":2.7,\"windScale\":\"\u5fae\u98ce\",\"feelst\":23.1},\"alarm\":[],\"jieQi\":\"\",\"lastUpdate\":\"2025/04/26 15:00\"}}\n"}]
>>> content:
Thought: 現(xiàn)在我已經(jīng)獲取了北京的天氣信息,接下來我將獲取廣州的天氣信息。
Action:
```
{
"action": "get_weather",
"action_input": {
"location": "廣州"
}
}
```
Observation
>>> tool name:get_weather
>>> tool parameters:{'location': '廣州'}
>>> tool result: {"msg":"success","code":0,"data":{"location":{"id":"59287","name":"廣州","path":"中國, 廣東, 廣州"},"now":{"precipitation":0.0,"temperature":24.2,"pressure":1005.0,"humidity":79.0,"windDirection":"東北風(fēng)","windDirectionDegree":31.0,"windSpeed":1.3,"windScale":"微風(fēng)","feelst":27.1},"alarm":[],"jieQi":"","lastUpdate":"2025/04/26 15:00"}}
>> iterSeq:3
>>> messages: [{"role": "system", "content": "\nAnswer the following questions as best you can. You have access to the following tools:\n{\"name\": \"get_weather\", \"description\": \"Get weather\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"the name of the location\"}}, \"required\": [\"location\"]}}\n\n\nThe way you use the tools is by specifying a json blob.\nSpecifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).\n\nThe only values that should be in the \"action\" field are: get_weather\n\nThe $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:\n\n```\n{{\n\"action\": $TOOL_NAME,\n\"action_input\": $INPUT\n}}\n```\n\nALWAYS use the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction:\n```\n$JSON_BLOB\n```\nObservation: the result of the action\n... (this Thought/Action/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\n\nBegin! Reminder to always use the exact characters `Final Answer` when responding. \n"}, {"role": "user", "content": "Question: \u5317\u4eac\u548c\u5e7f\u5dde\u5929\u6c14\u600e\u4e48\u6837\n\nThought: \u6211\u9700\u8981\u83b7\u53d6\u5317\u4eac\u548c\u5e7f\u5dde\u7684\u5929\u6c14\u4fe1\u606f\u3002\u9996\u5148\uff0c\u6211\u5c06\u83b7\u53d6\u5317\u4eac\u7684\u5929\u6c14\u3002\n\nAction:\n```\n{\n\"action\": \"get_weather\",\n\"action_input\": {\n\"location\": \"\u5317\u4eac\"\n}\n}\n```\nObservation: {\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"54511\",\"name\":\"\u5317\u4eac\",\"path\":\"\u4e2d\u56fd, \u5317\u4eac, \u5317\u4eac\"},\"now\":{\"precipitation\":0.0,\"temperature\":23.4,\"pressure\":1005.0,\"humidity\":43.0,\"windDirection\":\"\u897f\u5357\u98ce\",\"windDirectionDegree\":216.0,\"windSpeed\":2.7,\"windScale\":\"\u5fae\u98ce\",\"feelst\":23.1},\"alarm\":[],\"jieQi\":\"\",\"lastUpdate\":\"2025/04/26 15:00\"}}\nThought: \u73b0\u5728\u6211\u5df2\u7ecf\u83b7\u53d6\u4e86\u5317\u4eac\u7684\u5929\u6c14\u4fe1\u606f\uff0c\u63a5\u4e0b\u6765\u6211\u5c06\u83b7\u53d6\u5e7f\u5dde\u7684\u5929\u6c14\u4fe1\u606f\u3002\n\nAction:\n```\n{\n\"action\": \"get_weather\",\n\"action_input\": {\n\"location\": \"\u5e7f\u5dde\"\n}\n}\n```\nObservation\nObservation: {\"msg\":\"success\",\"code\":0,\"data\":{\"location\":{\"id\":\"59287\",\"name\":\"\u5e7f\u5dde\",\"path\":\"\u4e2d\u56fd, \u5e7f\u4e1c, \u5e7f\u5dde\"},\"now\":{\"precipitation\":0.0,\"temperature\":24.2,\"pressure\":1005.0,\"humidity\":79.0,\"windDirection\":\"\u4e1c\u5317\u98ce\",\"windDirectionDegree\":31.0,\"windSpeed\":1.3,\"windScale\":\"\u5fae\u98ce\",\"feelst\":27.1},\"alarm\":[],\"jieQi\":\"\",\"lastUpdate\":\"2025/04/26 15:00\"}}\n"}]
>>> content:
Thought: 我已經(jīng)獲取了北京和廣州的天氣信息,現(xiàn)在可以回答用戶的問題了。
Final Answer: 北京的天氣情況為:溫度23.4°C,濕度43%,西南風(fēng),風(fēng)速2.7米/秒,微風(fēng)。廣州的天氣情況為:溫度24.2°C,濕度79%,東北風(fēng),風(fēng)速1.3米/秒,微風(fēng)。
>>> 最終答案: 北京的天氣情況為:溫度23.4°C,濕度43%,西南風(fēng),風(fēng)速2.7米/秒,微風(fēng)。廣州的天氣情況為:溫度24.2°C,濕度79%,東北風(fēng),風(fēng)速1.3米/秒,微風(fēng)。
總結(jié)
基于Function Calling和基于ReAct的工具調(diào)用有各自的優(yōu)缺點(diǎn):
1. Function Calling
- 無需設(shè)定系統(tǒng)提示詞,LLM根據(jù)tools定義即可觸發(fā)工具調(diào)用,token消耗較少
- 模型參數(shù)量相對(duì)較大。模型的訓(xùn)練數(shù)據(jù)必須包含F(xiàn)unction Calling相關(guān)的內(nèi)容,以確保模型能夠理解和生成結(jié)構(gòu)化輸出,結(jié)構(gòu)化輸出更穩(wěn)定
- 輸出結(jié)果較為容易處理
- 隱藏了推理過程,缺乏可解釋性
2. ReAct
- 需要設(shè)置復(fù)雜的系統(tǒng)提示詞,token消耗較多
- 對(duì)模型參數(shù)要求較低
- 輸出結(jié)果處理比Function Calling復(fù)雜
- 推理過程可見,更高的可解釋性