前文
在業務中如果使用大模型的 tools 方式來解決問題,肯定會遇到一個問題,就是 tools 的數量太多了,tools 本身的描述加上引數的描述整個 token 的數量會超出大模型的輸入 token 限制,這種情況需要我們想辦法去解決。本文就是討論遇到這類問題的解決方案。
基本認識
首先我們要有以下幾點基本的認識:
大模型的最強的最本質的能力是
理解能力
,理解能力是個很強的能力,理論上大模型什麼都可以幹,就像大腦
一樣。tools 本質上就是將遇到的問題進行
分類
,再往簡單說就是將業務上可能碰到的問題進行if-else
劃分,每個 tool 會寫好具體的實現邏輯、入參、返回結果
,我們只需要讓大模型幹兩件事情:第一步就是先讓大模型理解使用者的問題可以使用哪個 tool 來解決,第二個問題就是使用大模型從使用者的問題中抽取出適合該 tool 的入參。這樣可以保證在遇到 tools 可以解決的問題集合的時候,輸出都是穩定的。避免了原生的大模型的幻覺等原生缺點。
基礎思路
如果是通常情況下 tools 的數量比較少,其中的引數及其描述也比較少,使用最常見的方式進行即可,我這裏用 qwen 的 api 展示效果。可以看到我的 tools 中只有兩個:
一個是獲取當前的時間
get_current_time
,其工具描述為“當你想知道現在的時間時非常有用”
,這個 tool 沒有引數,直接返回結果即可。另一個是獲取某地的天氣
get_current_weather
,其工具描述為“當你想查詢指定城市的天氣時非常有用”
,這個 tool 需要從問題中獲取引數location
。
tools = [ # 工具1 獲取當前時刻的時間 { "type": "function", "function": { "name": "get_current_time", "description": "當你想知道現在的時間時非常有用。", "parameters": {} # 因為獲取當前時間無需輸入引數,因此parameters為空字典 } }, # 工具2 獲取指定城市的天氣 { "type": "function", "function": { "name": "get_current_weather", "description": "當你想查詢指定城市的天氣時非常有用。", "parameters": { # 查詢天氣時需要提供位置,因此引數設定為location "type": "object", "properties": { "location": { "type": "string", "description": "城市或縣區,比如北京市、杭州市、餘杭區等。" } } }, "required": [ "location" ] } } ] messages = [ { "content": '杭州天氣如何', "role": "user" } ] response = Generation.call( model='qwen-max', messages=messages, tools=tools, seed=random.randint(1, 10000), result_format='message' )
我們這裏問的問題是杭州天氣如何
,那麼大模型就會同時去 tools 中檢視,自己透過理解問題選擇合適的工具為 get_current_weathe
r ,並且提取出了引數“杭州”
。日誌結果如下:
{ "status_code": 200, "request_id": "bd803417-56a7-9597-9d3f-a998a35b0477", "code": "", "message": "", "output": { "text": null, "finish_reason": null, "choices": [ { "finish_reason": "tool_calls", "message": { "role": "assistant", "content": "", "tool_calls": [ { "function": { "name": "get_current_weather", "arguments": "{"properties": {"location": "杭州"}, "type": "object"}" }, "id": "", "type": "function" } ] } } ] }, "usage": { "input_tokens": 222, "output_tokens": 27, "total_tokens": 249 } }
進階思路
從上面的日誌結果中我們看到,裡面有一項是 input_tokens
,這個表示我們向大模型中傳入的 prompt 的 token 數量,包括了 tools 中的所有文字描述以及你的問題描述的 token 總和。 qwen-max
的上限是 6k
,而我在業務中需要用到的 tools 數量很大,而且引數的數量及其介紹也很多,這樣上面的基礎思路就明顯無法滿足我們的需求,因為一旦將所有的 tools 傳入大模型,肯定會報錯超出大模型的輸入限制。我們可以換一個思路,大模型接收所有的 tools 內容自動做了選 tool 和提引數這兩件事情,我可以手動控制分步做這兩件事:
第一步就是使用一個簡略的 tools ,這個裡面只有工具的
名字和描述
,沒有引數及其描述,先將這個 tools 傳入大模型讓大模型能夠先選擇出正確的 tool 。第二步再將這個 tool 改造成長度只有 1 的 tools ,傳入大模型,讓其按照這個 tool 的預定引數來從問題中抽取入參即可。
這樣分兩步即可解決 tools 太多的問題。其實還可以擴充套件開來,我們將所有的 tools 進行多級分類,這樣前面的幾級只負責對問題識別和路由的過程,到最後的某個區域性分子樹或者葉子節點的時候對應的少量的 tools ,再解決問題就很簡單了。
我這裏簡單舉例,大家懂邏輯即可。首先我先簡單寫了一個 tool_choose_prompt 來讓大模型針對問題選擇合適的 tool 名字,然後根據名字再使用大模型做具體的任務。
tool_choose_prompt = [system_message( "你是一個擅長解析問題的助手,你可以根據問題解析出解決這個問題要用哪一類工具,具體有以下幾種工具:" "gxssln_tool、 dxtdpy_tool、 plan_tool," "其中,gxssln_tool 用於解決管線問題、 dxtdpy_tool 用於解決地下通道問題、 plan_tool 用於解決計劃、專案、工程的問題。" "再返回結果時你只需要返回工具名即可。"), user_message(f"我現在的問題是【{question}】") ] first_response = dashscope.Generation.call(model="qwen-max", messages=tool_choose_prompt, result_format='message') content = str(first_response["output"]["choices"][0]["message"]["content"]) response = "" if content == "gxssln_tool": response = dashscope.Generation.call(model="qwen-max", messages=tool_prompt, result_format='message', tools=gxssln_tool) elif content == "dxtdpy_tool": response = dashscope.Generation.call(model="qwen-max", messages=tool_prompt, result_format='message', tools=dxtdpy_tool) elif content == "plan_tool": response = dashscope.Generation.call(model="qwen-max", messages=tool_prompt, result_format='message', tools=plan_tool) print(response['output']['choices'][0]['message']['tool_calls'])