大模型ReAct框架——打造AI Agent的代碼實(shí)現(xiàn)——基于LLM + Function Call構(gòu)建Agent 原創(chuàng)
“ Agent通過(guò)大模型的推理和規(guī)劃,使得大模型真正實(shí)現(xiàn)類(lèi)似人類(lèi)的能力”
AI Agent也就是AI智能體,是通過(guò)把大模型作為“大腦”,通過(guò)利用大模型的推理和規(guī)劃能力,然后調(diào)用外部工具來(lái)完成復(fù)雜任務(wù)的一種方式。
簡(jiǎn)單來(lái)說(shuō),Agent就是一種讓大模型自己思考和分析問(wèn)題,選擇合適的工具,最終解決問(wèn)題的一種方法,其背后原理就來(lái)自于ReAct。
ReAct是Reasoning And Acting的縮寫(xiě),意思是LLM可以根據(jù)邏輯推理(Reson),構(gòu)建完整系列行動(dòng)(Act),從而達(dá)到期望目標(biāo)。
LLM的靈感來(lái)源于人類(lèi)和推理之間的協(xié)同關(guān)系,人類(lèi)根據(jù)這種協(xié)同關(guān)系學(xué)習(xí)新的知識(shí),做出決策,然后執(zhí)行。
什么是ReAct框架?
從本質(zhì)上來(lái)說(shuō),智能體的作用就是模仿人類(lèi)的思維和處理復(fù)雜問(wèn)題的方式。
基于LLM 和 Function Call實(shí)現(xiàn)Agent
ReAct的作用就是協(xié)同LLM和外部的信息獲取,與其它功能交互,如果說(shuō)LLM模型是大腦,那么ReAct框架就是這個(gè)大腦的手腳和五官。
下面我們就用代碼來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的具有自主規(guī)劃功能的Agent,需要的東西也很簡(jiǎn)單:
Python開(kāi)發(fā)環(huán)境 python 版本用到3.12.1 版本沒(méi)有強(qiáng)制要求
支持Function Call 工具的大模型(可以是自己部署的大模型或者第三方模型)。
使用第三方模型需要自己申請(qǐng)并獲取其API-KEY,代碼中還用到了tavily搜索,這個(gè)也需要自己去申請(qǐng)。
下圖是Agent根據(jù)任務(wù)要求輸出的結(jié)果,任務(wù)要求是
請(qǐng)幫我制定一份理財(cái)計(jì)劃,你可以通過(guò)網(wǎng)絡(luò)搜索的方式來(lái)收集一定的參考資料,并把最終的計(jì)劃內(nèi)容寫(xiě)入到理財(cái)計(jì)劃.txt文件中
Agent實(shí)現(xiàn)的核心有三點(diǎn)
- 大模型的質(zhì)量
- 外部工具集
- 提示詞的質(zhì)量
大模型的質(zhì)量問(wèn)題直接影響到Agent表現(xiàn)的好壞,推理能力強(qiáng),知識(shí)豐富的大模型會(huì)表現(xiàn)更好。
而外部工具集就是提供給大模型使用的工具可以根據(jù)不同的業(yè)務(wù)場(chǎng)景提供不同的工具集(API)。如果使用一些第三方API可能需要自己申請(qǐng),比如百度或谷歌搜索,高德和百度的地圖接口等。
提示詞是最重要的一個(gè)環(huán)節(jié),我們知道大模型的能力是一方面,但怎么發(fā)揮大模型的能力是由提示詞的質(zhì)量決定的。
# 約束
constraints = [
"僅使用下面列出的動(dòng)作",
"你只能主動(dòng)行動(dòng),在計(jì)劃行動(dòng)時(shí)需要考慮到這一點(diǎn)",
"你無(wú)法與物理對(duì)象交互,如果對(duì)于完成任務(wù)或目標(biāo)是絕對(duì)必要的,則必須要求用戶為你完成,如果用戶拒絕,并且沒(méi)有其它方法實(shí)現(xiàn)目標(biāo),則直接終止,避免浪費(fèi)時(shí)間和精力"
]
# 資源
resources = [
"提供搜索和信息搜集的互聯(lián)網(wǎng)接入",
"讀取和寫(xiě)入文件的能力",
"你是一個(gè)大語(yǔ)言模型,接受了大量的文本訓(xùn)練,包括大量的事實(shí)知識(shí),利用這些知識(shí)來(lái)避免不必要的信息收集"
]
# 最佳實(shí)踐說(shuō)明
best_practices = [
"不斷地回顧和分析你的行為,確保發(fā)揮出你最大的能力",
"不斷地進(jìn)行建設(shè)性的自我批評(píng)",
"反思過(guò)去的決策和策略,完善你的方案",
"每個(gè)動(dòng)作執(zhí)行部分代價(jià),所以要聰明高效,目的是用最少的步驟完成任務(wù)"
]
prompt_template = """
你是一個(gè)問(wèn)答專家,你必須始終獨(dú)立做出決策,無(wú)需尋求用戶的幫助,發(fā)揮你作為L(zhǎng)LM的優(yōu)勢(shì),追求簡(jiǎn)單的策略,不要涉及法律問(wèn)題
任務(wù):
{query}
限制條件說(shuō)明:
{constraints}
動(dòng)作說(shuō)明:這是你唯一可以使用的作用,你的任何操作都必須通過(guò)以下操作實(shí)現(xiàn):
{actions}
資源說(shuō)明:
{resources}
最佳實(shí)踐的說(shuō)明:
{best_practices}
agent_scratch:
{agent_scratch}
你應(yīng)該只以json格式響應(yīng),響應(yīng)格式如下:
{response_format_prompt}
確保響應(yīng)結(jié)果可以由python json.loads解析
"""
response_format_prompt = """
{
"action":{
"name": "action name",
"args": {
"answer": "任務(wù)的最終結(jié)果"
}
},
"thoughts": {
"plan": "簡(jiǎn)短的描述短期和長(zhǎng)期的計(jì)劃列表",
"criticism": "建設(shè)性的自我批評(píng)",
"speak": "當(dāng)前步驟,返回給用戶的總結(jié)",
"reasoning": "推理"
},
"observation": "觀察當(dāng)前任務(wù)的整體進(jìn)度"
}
"""
在這個(gè)提示詞中加入了工具列表,資源說(shuō)明,任務(wù)需求等;并且約定了大模型的輸出格式,以便于進(jìn)行解析,大模型就可以根據(jù)這提示詞對(duì)任務(wù)進(jìn)行思考和推理。
并且根據(jù)推理結(jié)果,選擇合適的工具來(lái)完成對(duì)應(yīng)的任務(wù),比如調(diào)用搜索工具完成信息收集,調(diào)用文件寫(xiě)入工具把結(jié)果寫(xiě)入到文件中。
下圖是大模型的思考,推理和工具調(diào)用的過(guò)程,從圖中可以看出大模型經(jīng)過(guò)多次規(guī)劃才完成的任務(wù)。
其次,就是工具集的構(gòu)建,簡(jiǎn)單來(lái)說(shuō)就是一些python函數(shù),用來(lái)給大模型進(jìn)行調(diào)用:
"""
1. 寫(xiě)文件
2. 讀文件
3. 追加
4. 網(wǎng)絡(luò)搜索
"""
def get_workdir_root():
workdir_root = os.environ.get("WORKDIR_ROOT", './data/llm_result')
return workdir_root
WORKDIR_ROOT = get_workdir_root()
def read_file(filename):
if not os.path.exists(filename):
return f"{filename} not exist, please check file exist before read"
with open(filename, "r") as f:
return "\n".join(f.readline())
def append_to_file(filename, content):
filename = os.path.join(WORKDIR_ROOT, filename)
if not os.path.exists(filename):
return f"{filename} not exist, please check file exist before read"
with open(filename, 'a') as f:
f.write(content)
return "append content to file success"
def write_to_file(filename, content):
filename = os.path.join(WORKDIR_ROOT, filename)
if not os.path.exists(WORKDIR_ROOT):
os.makedirs(WORKDIR_ROOT)
with open(filename, 'w') as f:
f.write(content)
return "write content to file success"
def search(query):
tavily = TavilySearchResults(max_results=5)
try:
ret = tavily.invoke(input=query)
"""
ret:
[{
"content": "",
"url": ""
}]
"""
print("搜索結(jié)果", ret)
content_list = [obj["content"] for obj in ret]
return "\n".join(content_list)
except Exception as err:
return "search err: {}".format(err)
tools_info = [
{
"name": "read_file",
"description": "read file from agent generate, should write file before read.",
"args": [
{
"name": "filename",
"type": "string",
"description": "read file name"
}
]
},
{
"name": "append_to_file",
"description": "append llm content to file, should write file before read.",
"args": [
{
"name": "filename",
"type": "string",
"description": "file name"
},
{
"name": "filename",
"type": "string",
"description": "append to file content"
}
]
},
{
"name": "write_to_file",
"description": "write llm content to file",
"args": [
{
"name": "filename",
"type": "string",
"description": "file name"
},
{
"name": "filename",
"type": "string",
"description": "write to file content"
}
]
},
{
"name": "search",
"description": "this is a search engine, you can gain additional knowledge though this search engine when you are unsure of what large model return",
"args": [
{
"name": "query",
"type": "string",
"description": "search query to lookup"
}
]
},
{
"name": "finish",
"description": "完成用戶目標(biāo)",
"args": [{
"name": "answer",
"type": "string",
"description": "最后的目標(biāo)結(jié)果"
}]
}
]
tools_map = {
"read_file": read_file,
"append_to_file": append_to_file,
"write_to_file": write_to_file,
"search": search
}
def gen_tools_desc():
tools_desc = []
for idx, t in enumerate(tools_info):
args_desc = []
for info in t['args']:
args_desc.append({
"name": info['name'],
"description": info["description"],
"type": info['type']
})
args_desc = json.dumps(args_desc, ensure_ascii=False)
tool_desc = f"{idx + 1}. {t['name']}: {t['description']}, args: {args_desc}"
tools_desc.append(tool_desc)
tools_prompt = "\n".join(tools_desc)
return tools_prompt
最后兩個(gè)就是大模型的調(diào)用模塊和業(yè)務(wù)的解析模塊,大模型的調(diào)用模塊相對(duì)比較簡(jiǎn)單,這里就不仔細(xì)說(shuō)了,感興趣的可以直接看代碼。
解析模塊說(shuō)簡(jiǎn)單也簡(jiǎn)單,說(shuō)復(fù)雜也復(fù)雜;因?yàn)楫?dāng)前的功能比較簡(jiǎn)單,因此只需要使用大模型本身的能力即可,然后完成對(duì)大模型每次思考和規(guī)劃數(shù)據(jù)的解析即可。
而如果后續(xù)需要開(kāi)發(fā)更加復(fù)雜的業(yè)務(wù)功能,比如說(shuō)金融行業(yè)的投資分析,需要非常復(fù)雜的業(yè)務(wù)分析等環(huán)節(jié),這時(shí)只依靠大模型本身的能力就不行了。
比如說(shuō),由于大模型在垂直領(lǐng)域的表現(xiàn)不佳,直接使用可能會(huì)帶來(lái)幻覺(jué)等問(wèn)題;還有就是Agent的記憶模塊,在大量的復(fù)雜業(yè)務(wù)分析中,需要增加外部存儲(chǔ)模塊來(lái)記錄歷史記憶功能,這樣才能更好地完成復(fù)雜的任務(wù)處理。
"""
todo:
環(huán)境變量的設(shè)置
工具的引入
prompt模板
模型的初始化
"""
# 初始化模型
mp = ModelProvider()
# 解析大模型的響應(yīng)
def parse_thoughts(response):
try:
thoughts = response.get("thoughts")
observation = response.get("observation")
plan = thoughts.get("plan")
reasoning = thoughts.get("reasoning")
criticism = thoughts.get("criticism")
prompt = f"plan: {plan}\n reasoning: {reasoning}\n criticism: {criticism}\nobservation: {observation}"
print("thoughts: ", prompt)
return prompt
except Exception as err:
print("parse thoughts err: {}".format(err))
return "".format(err)
def agent_execute(query, max_request_time=10):
cur_request_time = 0
# 大模型記憶 包括短期記憶和長(zhǎng)期記憶
chat_history = []
# agent 反思 規(guī)劃等
agent_scratch = ''
while cur_request_time < max_request_time:
cur_request_time += 1
"""
如果返回結(jié)果達(dá)到預(yù)期,則直接返回
"""
# 提示詞模板
"""
prompt包含的功能:
1. 任務(wù)描述
2. 工具描述
3. 用戶的輸入user_msg
4. assistant_msg
5. 限制
6. 給出更好實(shí)踐的描述
"""
prompt = gen_prompt(query, agent_scratch)
start_time = time.time()
print("--------------------------{}, 開(kāi)始調(diào)用大模型LLM------------".format(cur_request_time), flush=True)
# 調(diào)用大模型 直接返回json格式數(shù)據(jù)
"""
sys_prompt:
user_msg, assistant, history
"""
if cur_request_time < 3:
print("prompt: ", prompt)
# response = call_llm()
response = mp.chat(prompt, chat_history=chat_history)
end_time = time.time()
print("--------------------------{}, 調(diào)用大模型結(jié)束,耗時(shí):{}---------------".format(cur_request_time, end_time-start_time), flush=True)
if not response or not isinstance(response, dict):
print("調(diào)用大模型錯(cuò)誤, 即將重試: ", response)
continue
"""
大模型返回格式約定
response:
{
"action":{
"name": "action name",
"args": {
"args name": "args values"
}
},
"thoughts": {
"text": "thought",
"plan": "plan",
"criticism": "criticism",
"speak": "當(dāng)前步驟,返回給用戶的總結(jié)",
"reasoning": ""
}
}
"""
action_info = response.get("action")
action_name = action_info.get("name")
action_args = action_info.get("args")
print("當(dāng)前action name: ", action_name, action_args)
if action_name == "finish":
final_answer = action_args.get("answer")
print("final_answer: ", final_answer)
break
observation = response.get("observation")
try:
"""
action_name 到函數(shù)的映射, map -> { action_name: func }
"""
# todo: tools_map 的實(shí)現(xiàn)
# tools_map = {}
func = tools_map.get(action_name)
call_func_result = func(**action_args)
# { action_name: func }
except Exception as err:
print("調(diào)用工具異常: ", err)
call_func_result = "{}".format(err)
agent_scratch = agent_scratch + "\n:observation:{}\n execute action result: {}".format(observation, call_func_result)
# 由于大模型沒(méi)有記憶功能,因此需要把大模型之前的輸入和輸出加入到history中,這也是Agent四大塊中的記憶模塊 由于目前的業(yè)務(wù)并不復(fù)雜,因此不需要外部記憶模塊 如果業(yè)務(wù)比較復(fù)雜可能還需要外部模塊進(jìn)行存儲(chǔ)
assistant_msg = parse_thoughts(response)
chat_history.append([user_prompt, assistant_msg])
if cur_request_time == max_request_time:
print("很遺憾,本次任務(wù)失敗")
else:
print("恭喜你,任務(wù)完成")
def main():
# 支持用戶多次交互 最大規(guī)劃次數(shù)設(shè)置為10次 當(dāng)超過(guò)10次時(shí) 則說(shuō)明任務(wù)失敗 用戶可以根據(jù)自己的情況進(jìn)行調(diào)整
max_request_time = 10
while True:
query = input("請(qǐng)輸入你的目標(biāo):")
if query == "exit":
return
agent_execute(query, max_request_time=max_request_time)
if __name__ == "__main__":
main()
最后,這里只是為了實(shí)現(xiàn)Agent的實(shí)現(xiàn)流程,并且由于大模型的質(zhì)量問(wèn)題,也不能保證每次都能成功。
受限于大模型的性能問(wèn)題,大模型在推理和規(guī)劃方面表現(xiàn)還不盡人意,因此怎么才能讓大模型更好實(shí)現(xiàn)復(fù)雜推理規(guī)劃是一個(gè)值得研究的問(wèn)題。
里面主要涉及兩個(gè)第三方模塊,一個(gè)是第三方大模型的申請(qǐng),個(gè)人使用的是阿里的通義千問(wèn);第二個(gè)是Travily的搜索接口,官網(wǎng)地址:https://app.tavily.com用戶也可以自己去申請(qǐng)或者使用其它搜索工具,比如百度搜索等。
Agent學(xué)習(xí)參考
??https://www.bilibili.com/video/BV1Sz421m7Rr?p=1&vd_source=f2f15e671adda1cc1e5512694f310ebd??
用戶可以在公眾號(hào)回復(fù):Agent源碼 獲取 用戶只需要在env文件中把參數(shù)改成自己的參數(shù),然后啟動(dòng)agent_main.py文件即可。
此代碼只是用來(lái)學(xué)習(xí)使用,并不能完成復(fù)雜的業(yè)務(wù)邏輯,用戶如果想實(shí)現(xiàn)更加復(fù)雜的業(yè)務(wù)功能,則需要對(duì)提示詞和工具進(jìn)行添加和調(diào)整,比如做旅行規(guī)劃就需要添加地圖工具和酒店預(yù)定的API等。
本文轉(zhuǎn)載自公眾號(hào)AI探索時(shí)代 作者:DFires
原文鏈接:??https://mp.weixin.qq.com/s/KV1hp5SmdOAoyFoJX9vmmQ??
