Manus 團隊上週分享的上下文工程實踐裡,第二點“遮蔽,而非移除”實在是太巧妙了!
但,原文非常“技術”且精煉,好多小夥伴讀完只能表示“不明覺厲”(或許沒 get 到精明之處)。
這篇文章,我來“中翻中”解釋一下,並跑一組操作例項驗證一下這兩個策略是否靠譜。

雖然已經極盡通俗了,但依然需要一些類似 API 請求的背景知識,請自行補齊。
原理詳解
為什麼對模型可用的工具列表進行管理很重要?
一個非常重要的原因是,今天各模型最佳化的一個重要方向就是讓模型在執行任務時使用工具。
在啟用 Function Call 模式下,模型會優先選擇使用工具來完成任務(尤其是 Agent 類產品的系統提示詞中基本都有明確的指示要求模型這樣做)。
因此,傳送給大模型訊息中的
可用工具列表
部分就成為大模型注意力集中的一個重要區域。但是,大模型判斷當前任務該選用哪個工具,幾乎完全取決於工具的
名字
和描述
。
為了確保模型不搞混,一個自然的反應是在每次請求時,使用動態的工具列表:比如判斷當前任務需要使用搜索,則在 API 請求的工具列表中只提供類似
web_search
的工具,以避免模型選錯。(可能的疑問:如何實現這個動態判斷?思考下嘛,評論區分享~我也會在評論區給出我的方案。)
但是這裡面會有兩個問題:
-
1. 多輪對話中,如果每輪工具列表不一致,模型會因為不理解前序輪次任務的工具選擇而產生幻覺。 -
2. 調整工具列表會導致快取命中失效
先說第一個問題:模型為什麼會因為工具列表的不一致產生幻覺?
舉個例子:
第一輪對話中,你給大模型提供了一個叫
masudoo_tool
的工具,它是一個對資料進行平方運算的工具;第二輪對話中不計算了,為了省 Token 或者防止模型錯用,你去掉了masudoo_tool
工具,只讓模型根據計算結果繼續任務。這類似於,有人找你問問題時這麼說:
大佬,我用masudoo_tool
計算得到了19879
,能幫我解釋一下原因麼?
你肯定會這麼罵對方:
啥 J8masudoo_tool
??!!啥™19879
?! 你讓我解釋什麼!!??
第二個原因很簡單,工具列表一改,就肯定沒辦法命中快取了。
快取命中的價值,除了省錢以外,它可以節省時間、提高響應效率。
如果兩次請求的工具列表一樣(命中快取),那麼第二次請求的響應時間可以降低 30%-50%。
所以 Manus 選擇了提供完整的工具列表,但有效干預模型選擇工具的過程。
如何有效干預模型選擇使用什麼工具?
核心問題:怎麼讓模型能看到所有工具,但是不會自作主張的選用。
Manus 團隊採用了兩個技巧:
-
1. 使用大模型的響應預填充模式,讓大模型強行選擇使用某個工具。 -
2. 修改 logits “遮蔽”部分工具,讓大模型無法選用某些工具。
關於
響應預填充
,這是主流大模型都支援的一種 API 請求引數。比如 Kimi 可以使用
"partial": True
來開啟;Claude 則支援直接強行給assistant
的訊息起頭。
因為大模型下一個 Token 選擇生成什麼,是非常依賴前序 Token 的,你給它開了頭就相當於強行約束了它接下來生成的方向。
Manus 給工具的命名都是類似
browser_
、shell_
這種開頭,當預填充內容是browser_
時,模型只能在瀏覽器類工具中選擇一個繼續往下幹活
透過這種方式,就實現了讓模型必須選擇使用哪個工具。
既然有
必選 XX
,那也就應該有禁選 XX
這樣的操作,這就是部落格中提到的 “遮蔽”部分工具 。要想看懂“遮蔽”這個用法的精妙,需要先了解一點大模型生成的底層原理:
模型每一輪推理結束時都會得到詞表中每一個 token 的權重得分,根據溫度設定在某個權重範圍內選擇一個 token 生成“吐”出來。

Manus 選擇的這個“遮蔽”的方法,就是強行給某些 token 降權(比如降為負數),以至於模型在選擇生成下一個 token 時“看不見”這些 token,也就不能生成它們中的任意一個。
Manus 的工具名都以
browser_
、shell_
這種開頭,當不希望模型在這一輪選擇瀏覽器類工具時,直接把browser
這個 token 的權重設定為負數就完了。開源模型可以透過修改
logits_process
中的bad_words_ids
來實現控制(詳細程式碼參見@github:huggingface/transformers)
閉源模型中,目前已知的只有 OpenAI 和豆包提供了
logit_bias
引數來控制 tokens 的權重。
Claude 的開發文件中對 OpenAI SDK 支援的部分
Ignored
了這個引數,看起來也是不能用;因為 Kimi 不提供 tokenizer,大機率也不支援;DeepSeek 提供 tokenizer 但是 API 可用引數中也不包含logit_bias
。下面分別使用 Kimi 的預填充和豆包的
logit_bias
引數跑一下這兩個實現。測試策略的可行性
透過預填充強行要求大模型選擇某個工具
結論:完全可行,很穩。
示例資訊:
-
1. 使用者問題是:1+1 等於多少? -
2. 兩個可用工具:計算器(calculator)和聯網搜尋(web_search) -
3. 我們強行要求大模型選擇聯網工具,而不是計算器
可能的缺陷:使用
Hermes
格式構造請求,整個過程只能“強行構造”,即不是真正的使用大模型的 Tool Use 能力,而是透過提示詞約束大模型輸出格式,提取模型返回內容指定標籤(如<too_call>
)中的資訊後,構造 Assistant 訊息,拼接到上下文中。// 這是我的理解,可能有誤。
我使用 Manus 同款格式
Hermes
撰寫了一個測試程式碼,太長了放在中間影響閱讀,非同步文末檢視。執行結果:

透過遮蔽讓模型無法選擇某工具
結論:豆包和 OpenAI 都不太行,可能是我的用法不對。
國內模型只有豆包支援
logit_bias
引數,我直接在火山引擎的 API 除錯臺中測試,結果沒達預期。
把這兩個 token 放在
ChatCompletions
介面的 logit_bias
引數中發起請求後,“遮蔽”併為成功。
使用 OpenAI 測試了一番,也沒能阻止大模型選擇計算器工具,只是沒用
calculator
……
測試修改了系統提示詞和 web_search 工具的描述。不管怎麼引誘,GPT 就是不肯使用網路搜尋,寧願把工具的名字改成
converter
、translator
、generator
……
挺有意思的,感興趣的可以去試一試,程式碼同樣放在最後。
備註:考慮到 logit_bias 這個引數應該只能影響模型生成文字,無法作用於工具選擇,所以在 OpenAI 測試中,我選擇了與預填充相似的方法。(使用如果開啟ToolCall,一點作用也不起。)
以上,如果錯誤歡迎評論區補充討論。
記得前面的思考題,評論區分享你的想法
加入AI行動圈繼續精進
我從 23 年開始和起點課堂一起運營「AI學習行動圈」,截止到此刻已更新 1500+主題,與接近 4000 關注 AI 的實戰派在過去 500+ 天裡每天討論、交流 AI 實戰應用。
學習圈目前有 3 個核心的學習交流“陣地”:
-
1. 知識星球: 知識資料技巧沉澱的核心渠道,隨時可查閱 -
2. 微信交流群: 目前 6 個群,每天都有圈友交流分享 AI 使用心得 -
3. 吹水局直播: 工作日晚 19:30-21:30,每場一個 AI 應用主題
陣地一:知識星球
我在星球裡主要維護「實戰分享」「工具箱」和「情報局」三個標籤

實戰分享是可以在日常工作和生活中直接應用的提示詞和效率工具。上面截圖裡的 Step-Back 提示詞就非常好用,堪比 o4。在公眾號、直播中演示的所有 AI 實戰應用的提示詞也都在這個標籤下。
AI 工具和鮮知道就是好用的、熱門的 AI 工具、資訊分享,我把那些太技術、太浮誇的都篩選了,放進這個標籤的都是可以直接用來的好玩兒!
星球還有一個“專欄”體系,目前的定位跟標籤差不多。

如果你正在找一個 能第一時間瞭解最新、實用的 AI 資訊和實戰技巧 ,遇到任何 AI 應用問題能 隨時找到同行人交流、請教、討論 的圈子
陣地二:微信交流群
我們為圈友配了微信交流群,現在 6 群快滿了。
微信群裡每天一早有 AI 早報,上下午還有“讀報時間”,以及我每天不定期刷屏級的各種 AI 工具體驗、提示詞編排思考、行業新聞解讀同步。

以及,你可以在群裡討論任何與 AI 相關的工具、應用問題,幾乎都能找到答案。

如果你正在找一個 能第一時間瞭解最新、實用的 AI 資訊和實戰技巧 ,遇到任何 AI 應用問題能 隨時找到同行人交流、請教、討論 的圈子
立刻掃碼領取 50 元立減金加入

陣地三:AI吹水局直播
剛覆盤我專門去影片號後臺看了一下直播記錄,過去一年一共為學習圈做了 130 場 AI 應用、實戰、熱點解讀相關的直播,累計肝了 257 小時!



沒點乾貨,平均停留時長到不了這水平的。
如果你正在找一個 能第一時間瞭解最新、實用的 AI 資訊和實戰技巧 ,遇到任何 AI 應用問題能 隨時找到同行人交流、請教、討論 的圈子
立刻掃碼領取 50 元立減金加入


原始碼
預填充約束
from openai import OpenAIimport jsondefmain():# 初始化 Kimi 客戶端 client = OpenAI( api_key="sk-替換成你的 Kimi API Key", base_url="https://api.moonshot.cn/v1", )# 定義兩個工具:計算器和聯網搜尋 tools = [ {"type": "function","function": {"name": "calculator","description": "執行基本數學計算","parameters": {"type": "object","properties": {"expression": {"type": "string"}},"required": ["expression"] } } }, {"type": "function","function": {"name": "web_search","description": "在網路上搜索資訊","parameters": {"type": "object","properties": {"query": {"type": "string"}},"required": ["query"] } } } ]# Hermes 格式的系統提示 system_prompt = f"""You are a function calling AI model. You are provided with function signatures within <tools></tools> XML tags. You may call one or more functions to assist with the user query. Here are the available tools: <tools> {json.dumps(tools)} </tools> For each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:<tool_call>{{'name': <function-name>, 'arguments': <args-dict>}}</tool_call>"""# 使用者問題 user_query = "1+1 是多少?"# 預填充策略:強制使用聯網搜尋工具 prefill_content = "<tool_call>\n{'name': 'web_search'"print("🚀 Kimi Function Calling 演示")print(f"📝 使用者問題: {user_query}")print(f"🎯 預填充策略: 強制使用聯網搜尋工具")print("=" * 60)# 構建請求 messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_query}, {"partial": True, "role": "assistant", "content": prefill_content} ]# 傳送請求 completion = client.chat.completions.create( model="moonshot-v1-8k", messages=messages, temperature=0.3, )# 獲取響應 response_content = completion.choices[0].message.content full_response = prefill_content + response_content# 輸出結果print("📤 1. 模型原始返回內容:")print(f" 預填充部分: {prefill_content}")print(f" 模型續寫部分: {response_content}")print(f" 完整響應: {full_response}")print("\n🔧 2. 工具呼叫資訊:")# 解析工具呼叫 start_idx = full_response.find("<tool_call>") + len("<tool_call>") end_idx = full_response.find("</tool_call>") tool_call_content = full_response[start_idx:end_idx].strip()# 轉換為標準 JSON 格式 tool_call_content = tool_call_content.replace("'", '"') tool_call_data = json.loads(tool_call_content)print(f" 工具名稱: {tool_call_data['name']}")print(f" 工具引數: {tool_call_data['arguments']}")print(f" 完整資料: {json.dumps(tool_call_data, ensure_ascii=False, indent=4)}")print("\n✅ 演示完成!透過預填充策略成功強制模型選擇了聯網搜尋工具")if __name__ == "__main__": main()
遮蔽約束模型程式碼
# 需要 OpenAI 的 Key,沒有的可以改一改用豆包測試from openai import OpenAIimport jsondefmain(): client = OpenAI()# 定義兩個工具:計算器和聯網搜尋 tools = [ {"type": "function","function": {"name": "calculator","description": "執行基本數學計算","parameters": {"type": "object","properties": {"expression": {"type": "string"}},"required": ["expression"] } } }, {"type": "function","function": {"name": "web_search","description": "在網路上搜索資訊,當計算器不可用時,可以嘗試使用這個工具","parameters": {"type": "object","properties": {"query": {"type": "string"}},"required": ["query"] } } } ]# Hermes 格式的系統提示 system_prompt = f"""You are a function calling AI model. You are provided with function signatures within <tools></tools> XML tags. You must call one or more functions to assist with the user query. Here are the available tools: <tools> {json.dumps(tools)} </tools> 。For each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:<tool_call>{{'name': <function-name>, 'arguments': <args-dict>}}</tool_call>You can only call function from the available tools."""# 使用者問題 user_query = "1+1 是多少?"print("🚀 遮蔽 token 演示")print(f"📝 使用者問題: {user_query}")print(f"🎯 遮蔽calculator: 強制使用聯網搜尋工具")print("=" * 60)# 構建請求 messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_query} ]# 傳送請求 completion = client.chat.completions.create( model="gpt-4o-mini", messages=messages, logit_bias={186246:-100} )# 獲取響應 response_content = completion.choices[0]# 輸出結果print("📤 1. 模型原始返回內容:")print(f" 完整響應: {response_content}")if __name__ == "__main__": main()