AI模型的回調(diào)能力的理解和實現(xiàn)
前言
BigTall最近把RAG和Agent的原理想通了,對于“一切都是提示詞”的理解又更多了一些。本文把我的理解大致整理了一下,給出BigTall自己的一個實驗。希望能夠?qū)Υ蠹矣杏谩?/p>
關(guān)于大模型
現(xiàn)在大模型很熱,尤其是春節(jié)時候DeepSeek的出圈,直接讓大模型跳出了IT圈,跟普通人產(chǎn)生了聯(lián)系。最近的豆包相關(guān)的“我要去世了”文章,讓大模型加上了一層人性與情感的光環(huán)。北京的機器人馬拉松則讓人看到了大模型真正落地進入人們生活的一個可能,讓更多的人明白了“未來已來,只是尚未流行”。
我們每一分每一秒,都在踏步進入科幻時代。
但是大模型依然神秘。
大模型的出現(xiàn)提供了什么以前缺乏的能力?我思考的答案是:真正處理“模糊”的能力!以往用代碼編寫出來的“模糊”都是用基于精確匹配的改良:要么用偏移量多匹配幾次,要么轉(zhuǎn)化為數(shù)學(xué)相似度做一個范圍取值。但是真正能夠基于“語義”上的模糊匹配能力,而且可以直接使用的,大模型是唯一。
對于傳統(tǒng)軟件開發(fā)者來說,大模型更多是一種DSL(Domain Specific Language)領(lǐng)域驅(qū)動語言,是一種“功能的粘合劑”。本質(zhì)上跟你嵌入一個JavaScript引擎沒多大區(qū)別,只不過大模型可以支持自然語言和模糊處理而已,但對于程序來說,都是提供功能調(diào)用入口,讓DSL進行調(diào)用而已。
作為一個語言模型,文字是大模型唯一的輸入和輸出形式。我們是怎么做到讓大模型去控制外部的設(shè)備的?我們是怎么做到大模型可以自主在網(wǎng)絡(luò)上搜尋信息并向我們展示的?它又是如何讀懂我給他的文檔,幫我進行內(nèi)容解釋的?
提示詞就是一切!
有了提示詞的魔法,我們可以簡單一句“請幫我規(guī)劃到麗江的旅游”,驅(qū)動大模型幫我們?nèi)シ纸馊蝿?wù),尋找景點、規(guī)劃線路、尋找住宿、尋找合適的交通工具、詢價甚至直接訂票。
魔法一樣的技術(shù)背后,是多輪的提示詞往來。
RAG應(yīng)用
RAG應(yīng)用的基本原理是把文檔放向量數(shù)據(jù)庫里,用戶詢問的問題會也向量化,跟向量數(shù)據(jù)庫的內(nèi)容做相似度查找,找到的前10條結(jié)果會組合成提示詞以文字形式送給背后的AI渲染。背后的提示詞類似:【問題xxx找到abcd等幾個相似文檔,文檔該要和匹配片段分別是xyz,請分析并給出答案】。
智能體
Agent就是智能體,所謂的智能體就是一段特定任務(wù)相關(guān)的提示詞,智能體背后可以是同一個AI,也可以是不同的AI。你可以認(rèn)為智能體就是對應(yīng)一組提示詞的化身,通過把某個能力相關(guān)(例如訂票)的一組提示詞放在一個“智能體”中,實現(xiàn)任務(wù)的專有性。也符合軟件開發(fā)領(lǐng)域【高內(nèi)聚低耦合】的原理。
多個智能體之間的合作,就等價于多個提示詞之間的合作,有任務(wù)分解的、有對結(jié)果監(jiān)督的、有驅(qū)動過程的不同的提示詞對應(yīng)的Agent。
例如【我要去桂林旅行】,就可以分解為幾個智能體:
- 將文字整理成任務(wù)片段的:理解文字內(nèi)容,并將文字分解成步驟和動作,負(fù)責(zé)總控和協(xié)調(diào)其他專有
Agent的執(zhí)行。 - 查機票火車票的:通過
RAG的方式,大模型生成參數(shù),調(diào)用外部的查詢接口查到各種交通工具的信息,提供其他Agent使用。 - 整理行程的:負(fù)責(zé)從知識庫或者網(wǎng)絡(luò)查詢檢索獲得一組行程,并根據(jù)用戶的需求挑選出最合適的行程。
- 查桂林景點的:負(fù)責(zé)從知識庫或者網(wǎng)絡(luò)查詢獲得桂林的景點,并根據(jù)用戶需求挑選出最合適的景點。
- 整理旅游計劃的:將以上的信息綜合,組合成旅游計劃。
這個只是簡單的說明,實際上智能體之間的關(guān)系是一個網(wǎng)狀關(guān)系,非常復(fù)雜。所以智能體組合的運行速度其實很慢。
大模型回調(diào)
所謂的大模型和本地程序的回調(diào),等價于提示詞:【我現(xiàn)在有什么任務(wù),這些是可以調(diào)用的本地接口定義如下123,請回復(fù)問題位置中插入適當(dāng)?shù)谋镜亟涌谡{(diào)用】。然后,你問ai,今天早上我要穿什么衣服?然后AI就會返回一個帶占位符的文字【今天溫度是${weather(2025-5-17)},請告訴我要穿什么衣服】,你的程序會把${}中的函數(shù)計算并嵌入結(jié)果,結(jié)果AI收到【今天溫度25度,請告訴我要穿什么衣服】,然后ai再返回結(jié)果【穿短袖】。
大致原理就是這樣,只不過支持回調(diào)的大模型都經(jīng)過專門訓(xùn)練,可以使用json結(jié)構(gòu)來定義回調(diào)函數(shù),并且他們的回調(diào)準(zhǔn)確率也更高。
MCP和A2A協(xié)議
說到這里,大家應(yīng)該明白了,大模型離不開和外部的協(xié)作。所以需要一個大模型控制外部設(shè)施的協(xié)議。另外,剛才講智能體協(xié)作的時候,很明顯智能體之間存在通訊關(guān)系,因此也需要一個通訊協(xié)議。
MCP(Model Context Protocol)模型上下文協(xié)議由Anthropic(Claude 模型的開發(fā)公司)提出,OpenAI后續(xù)跟隨采用讓它成為事實上的標(biāo)準(zhǔn)。該協(xié)議就是讓大模型和外部協(xié)作的。有人可能會說,大模型那么智能,我們讓大模型學(xué)會各個系統(tǒng)的交互,就可以不要什么MCP了。有道理,但是大模型要記住這些的話,需要記憶,花這么多代價去訓(xùn)練怎么跟外部交互是不是很浪費?!未來還會有更多的新系統(tǒng)出現(xiàn),難道不間斷去訓(xùn)練嗎?所以合理的方式就是讓每個想和大模型協(xié)作的外部程序都學(xué)會“普通話”,能夠和大模型對話,接受大模型的指令。
A2A(Agent to Agent)由Google主導(dǎo)開發(fā),定位為跨平臺、跨廠商的AI 智能體之間的通訊協(xié)議。等于讓智能體都加入了一個群,智能體們可以在群里互相@進行交流。大家看剛才旅游訂票的例子就可以看到,智能體之間的交互其實并不比智能體和大模型之間的交互要來的少多少。
QWen3 0.6B的回調(diào)實驗
無論是MCP還是A2A,還是普通的大模型功能調(diào)用,本質(zhì)都是一回事:提示詞輸入通過大模型變成答案輸出,答案又變成下一次的輸入。來來回回,就把事情辦完了。
這次我們的實驗,就是把不支持回調(diào)的本地小模型改造成可以支持回調(diào)。讓小模型也可以和代碼進行交互。
實驗環(huán)境
實驗環(huán)境首先需要安裝Ollama,一個決心要像使用docker一樣使用大模型的環(huán)境。大家去官網(wǎng) https://ollama.com/ 下載安裝,并且啟動運行(確保任務(wù)欄里有白色羊駝的圖標(biāo))。
ollama pull qwen3:0.6b
另外,需要安裝python3。我機器是MacBook Pro,所以用brew安裝,其他操作系統(tǒng)請自己安裝環(huán)境。
brew install python mkdir ~/qwen3-callback cd ~/qwen3-callback python3 -m venv . source ./bin/activate python3 -m pip install requests
代碼
把以下的源代碼放到文件callback.py中。
1 import requests 2 import re 3 import json 4 5 # Ollama API 配置 6 OLLAMA_API_URL = "http://localhost:11434/api/generate" 7 MODEL_NAME = "qwen3:0.6b" 8 9 # 模擬的天氣函數(shù) 10 def weather(date): 11 # 實際場景可調(diào)用天氣 API,這里模擬返回 25 度 12 return 35 13 14 def find_goods(item): 15 # 模擬返回商品信息 16 return f""" 17 List {item} brand: 18 - Apple: MacBook Pro, MacBook Air 19 - Dell: XPS, Inspiron 20 - HP: Spectre, Envy 21 - Lenovo: ThinkPad, IdeaPad 22 - Microsoft: Surface 23 - Razer: Blade 24 - Samsung: Galaxy Book 25 - Sony: VAIO 26 """ 27 28 def find_goods_prices(item): 29 # 模擬返回商品價格 30 return f"{item} 5999.00元" 31 32 33 # 調(diào)用 Ollama API 的函數(shù) 34 def call_ollama(prompt, model=MODEL_NAME): 35 payload = { 36 "model": model, 37 "prompt": prompt, 38 "stream": False, 39 "temperature": 1 40 } 41 response = requests.post(OLLAMA_API_URL, json=payload) 42 if response.status_code == 200: 43 return json.loads(response.text)["response"] 44 else: 45 raise Exception(f"Ollama API error: {response.text}") 46 47 # 提取 </think> 之后的文本 48 def extract_post_think_text(text): 49 markers = ["</think>", "提示詞", "提示詞**", "輸出:", "輸出:"] 50 last_pos = -1 51 last_marker = None 52 53 # Find the last occurrence of any marker 54 for marker in markers: 55 try: 56 pos = text.rindex(marker) # Get the last index of the marker 57 if pos > last_pos: 58 last_pos = pos 59 last_marker = marker 60 except ValueError: 61 continue # Marker not found, skip 62 63 # If a marker was found, return text after it 64 if last_pos != -1: 65 return text[last_pos + len(last_marker):].strip() 66 67 # If no markers found, return original text 68 return text.strip() 69 70 71 # 解析占位符并執(zhí)行函數(shù)調(diào)用 72 def process_placeholder(text): 73 # 查找 ${function(args)} 模式的占位符 74 placeholder_pattern = r'\${([^}]+)\}' 75 matches = re.findall(placeholder_pattern, text) 76 77 if not matches: 78 return text # 沒有占位符,直接返回原文 79 80 for match in matches: 81 # 處理函數(shù)調(diào)用格式:${function(arg)} 82 if '(' in match and ')' in match: 83 func_name = match.split('(')[0].strip() 84 func_arg = match.split('(')[1].rstrip(')').strip() 85 86 if func_name == "weather": 87 result = weather(func_arg) 88 text = text.replace(f"${{{match}}}", f"{result}") 89 elif func_name == "find_goods": 90 result = find_goods(func_arg) 91 text = text.replace(f"${{{match}}}", f"{result}") 92 elif func_name == "find_goods_prices": 93 result = find_goods_prices(func_arg) 94 text = text.replace(f"${{{match}}}", f"${result}") 95 else: 96 text = text.replace(f"${{{match}}}", "[Unknown function]") 97 else: 98 text = text.replace(f"${{{match}}}", "[Invalid placeholder]") 99 100 return text 101 102 # 主邏輯 103 def main(): 104 # 用戶輸入 105 # user_input = "今天我要穿什么衣服?" 106 # user_input = "請給我推薦一臺性價比高的電腦" 107 # user_input = "我想買一個蘋果筆記本電腦,需要準(zhǔn)備多少預(yù)算?" 108 user_input = "列出中國歷朝歷代的名稱" 109 print(f"用戶輸入: {user_input}") 110 print("======================") 111 112 # 第一次調(diào)用模型,生成帶占位符的提示詞 113 initial_prompt = f""" 114 今天是2025年5月17日。 115 用戶問題:{user_input} 116 可用函數(shù): 117 - weather('2025-5-17'): 返回當(dāng)天的攝氏溫度。 118 - find_goods(item): 返回商品信息(item可以是商品類別、產(chǎn)品名、型號等,如“mobile”或“Samsung”貨“Galaxy S”,基于用戶問題的主關(guān)鍵詞)。 119 - find_goods_prices(item): 返回指定商品的價格(item同上) 120 推理:回答需要什么信息?選擇一個函數(shù)。若問題未指定具體item,從問題中提取主關(guān)鍵詞(如“電腦”)作為item。 121 生成提示詞,僅輸出:${{function(arg)}}提供的信息,請回答:{{用戶問題}}。禁止附加任何說明或邏輯。 122 例如: 123 - 輸入:“今天熱不熱?”,輸出:“${{weather('2025-5-17')}}提供的信息,請回答:今天熱不熱?” 124 - 輸入:“推薦一臺筆記本電腦?”,輸出:“${{find_goods('laptop')}}提供的信息,請回答:推薦一臺筆記本電腦?” 125 - 輸入:“襯衫多少錢?”,輸出:“${{find_goods_prices('shirt')}}提供的信息,請回答:襯衫多少錢? 126 - 輸入:“愛因斯坦的生平”,輸出:“請回答:愛因斯坦的生平 127 """ 128 print(f"初始提示詞: {initial_prompt}") 129 130 response = call_ollama(initial_prompt) 131 print("======================") 132 print(f"模型第一次響應(yīng): {response}") 133 134 # 提取 </think> 之后的文本 135 post_think_text = extract_post_think_text(response) 136 print("======================") 137 print(f"</think> 后的文本: {post_think_text}") 138 139 # 處理占位符,調(diào)用外部函數(shù) 140 processed_response = process_placeholder(post_think_text) 141 print("======================") 142 print(f"處理占位符后的文本: {processed_response}") 143 144 # 第二次調(diào)用模型,使用回填后的提示詞 145 final_prompt = f""" 146 根據(jù)以下信息回答用戶的問題: 147 {processed_response} 148 """ 149 print("======================") 150 print(f"最終提示詞: {final_prompt}") 151 152 final_response = call_ollama(final_prompt) 153 print("======================") 154 print(f"最終回答: {final_response}") 155 156 if __name__ == "__main__": 157 main()
運行結(jié)果
整個程序運行完成之后是這樣的輸出內(nèi)容:
$ python3 ./callback.py
用戶輸入: 今天天氣適合穿什么衣服?
======================
初始提示詞:
今天是2025年5月17日。
用戶問題:今天天氣適合穿什么衣服?
可用函數(shù):
- weather('2025-5-17'): 返回當(dāng)天的攝氏溫度。
- find_goods(item): 返回商品信息(item可以是商品類別、產(chǎn)品名、型號等,如“mobile”或“Samsung”貨“Galaxy S”,基于用戶問題的主關(guān)鍵詞)。
- find_goods_prices(item): 返回指定商品的價格(item同上)
推理:回答需要什么信息?選擇一個函數(shù)。若問題未指定具體item,從問題中提取主關(guān)鍵詞(如“電腦”)作為item。
生成提示詞,僅輸出:${function(arg)}提供的信息,請回答:{用戶問題}。禁止附加任何說明或邏輯。
例如:
- 輸入:“今天熱不熱?”,輸出:“${weather('2025-5-17')}提供的信息,請回答:今天熱不熱?”
- 輸入:“推薦一臺筆記本電腦?”,輸出:“${find_goods('laptop')}提供的信息,請回答:推薦一臺筆記本電腦?”
- 輸入:“襯衫多少錢?”,輸出:“${find_goods_prices('shirt')}提供的信息,請回答:襯衫多少錢?
- 輸入:“愛因斯坦的生平”,輸出:“請回答:愛因斯坦的生平
======================
模型第一次響應(yīng): <think>
好的,用戶的問題是今天天氣適合穿什么衣服,需要回答。首先,我需要確定可用的函數(shù)。用戶的問題中提到了天氣,所以應(yīng)該使用weather函數(shù)來獲取溫度信息。然后,根據(jù)問題中的主關(guān)鍵詞“天氣”,可能需要提取這個關(guān)鍵詞作為item,但問題中并沒有具體商品類別,所以可能直接使用天氣數(shù)據(jù)。接下來,生成提示詞,只輸出函數(shù)調(diào)用的結(jié)果,不需要其他說明。例如,用戶的問題中沒有指定商品,所以主關(guān)鍵詞是“天氣”,所以調(diào)用weather函數(shù),然后給出回答。確保不添加任何其他說明,只輸出指定的格式。
</think>
${weather('2025-5-17')}提供的信息,請回答:今天天氣適合穿什么衣服?
======================
</think> 后的文本: ${weather('2025-5-17')}提供的信息,請回答:今天天氣適合穿什么衣服?
======================
處理占位符后的文本: '2025-5-17' 的天氣是 35°C提供的信息,請回答:今天天氣適合穿什么衣服?
======================
最終提示詞:
根據(jù)以下信息回答用戶的問題:
'2025-5-17' 的天氣是 35°C提供的信息,請回答:今天天氣適合穿什么衣服?
======================
最終回答: <think>
好的,用戶的問題是根據(jù)“2025-5-17”當(dāng)天的天氣35°C,回答今天適合穿什么衣服。首先,我需要確認(rèn)用戶提供的天氣信息是否正確,然后根據(jù)溫度來判斷合適的服裝。
首先,用戶給出的信息是35°C,這通常意味著白天的溫度在35度左右,可能還有傍晚的溫度稍低。不過,具體天氣情況可能還需要參考其他數(shù)據(jù),比如是否有風(fēng)、濕度等。但用戶只提供了溫度,所以需要假設(shè)白天是35度,可能在下午或晚上有降溫。
接下來,考慮適合穿什么衣服。通常,白天的溫度較高,所以可能會穿短袖、T恤、短褲和長袖襯衫。同時,考慮到天氣可能比較熱,建議選擇透氣、吸汗的面料,比如棉質(zhì)或透氣面料。另外,可能還需要考慮防曬,所以帽子和太陽鏡也很重要。
不過,用戶的問題可能只需要根據(jù)給出的信息直接回答,不需要考慮其他因素。所以綜合來看,最合適的回答應(yīng)該是推薦短袖、T恤、短褲和長袖襯衫,同時考慮防曬措施。
需要檢查是否有其他可能的因素,比如是否有雨天,但用戶只提到了溫度,所以可能不需要考慮其他天氣情況。因此,最終回答應(yīng)基于溫度和常見天氣模式,給出具體的穿衣建議。
</think>
根據(jù)今天的天氣情況,35°C的高溫天氣適合穿短袖、T恤、短褲和長袖襯衫。建議搭配防曬用品(如帽子和太陽鏡)以保持舒適和安全。
總結(jié)
不知道有多少人會看到這里。
操控AI模型其實沒什么難度,最大的難度還是在“提示詞的調(diào)試”,你需要不停的試錯,不停地和模型的理解能力和幻覺做斗爭。模型參數(shù)量越小,這個調(diào)試的難度就越大,因為小模型本來就很笨。
這次調(diào)試多虧了Grok,否則要拿捏住0.6B的小模型不被它蠢哭,還真不容易。至于Big Tall是怎么跟Grok對話最終降伏小模型的?
不告訴你!
提示詞才是AI時代最大的秘密!

公眾號:老翅寒暑
浙公網(wǎng)安備 33010602011771號