規劃和自我檢查
同一個多步任務,比較直接做、先列計劃再做、做完之後自我檢查三種辦法。先列計劃多花了一倍的錢,效果差不多;自我檢查真的找出了兩處出處錯誤,但花了四倍的錢。
- 約 40 分鐘
- 難度:進階
- 實測:2026-09-14 deepseek-flash
程式碼和執行結果保留原樣(簡體中文),與實際執行時完全一致。
講智慧體的文章裡,"規劃"(planning)和"反思"(reflection)幾乎是必講的兩個技巧:先讓模型制定計劃再執行,執行完再讓它檢查自己的結果、發現問題就改。聽起來很合理,人做複雜的事情也是這樣。
但這些技巧都要多呼叫幾次模型。它們到底能帶來多少提升,值不值這些錢?這一課用一個真實的多步任務測一測。
任務
"對比 httpx 的 Client 和 AsyncClient 在三件事上的配置方式是否一樣:超時、代理、HTTP/2。列成一張表,每一格都註明文件出處(檔名和行號)。"
這個任務比前面的問題複雜:要查三個不同的主題,每個主題要分別看兩種客戶端,還要準確記下出處。用的還是第 2 課的智慧體和三個文件工具。
辦法一:直接做
把任務直接交給智慧體,它自己決定怎麼查。
辦法二:先列計劃
先呼叫一次模型,只讓它列計劃、不許呼叫工具。然後把計劃附在任務後面,交給智慧體執行:
def with_plan():
# 第一步:只列计划,不调用工具
plan, usage = model([{"role": "system", "content": SYSTEM}, {"role": "user", "content":
TASK + "\n\n先不要调用工具。列出你打算怎么查,编号列出每一步要找什么,不超过 6 步。"}], None)
answer, messages, stats = loop([
{"role": "system", "content": SYSTEM},
{"role": "user", "content": TASK + "\n\n按这个计划执行,执行中发现计划不对可以调整:\n" + plan.content},
])
……
"執行中發現計劃不對可以調整"這一句很重要。計劃是在還沒查任何資料的時候定的,死板地照著執行,反而會錯過查到之後才發現的線索。
辦法三:做完再檢查
先直接做,拿到回答後,把回答和智慧體在過程中查到的全部原文一起交給模型,讓它逐格檢查:
def with_reflection():
answer, messages, stats = plain()
evidence = "\n\n".join(m["content"] for m in messages if m["role"] == "tool")
review, usage = model([{"role": "user", "content": f"""下面是一份回答和查到的全部原文。逐格检查回答里的表格:
每一格的说法,原文里有没有依据?注明的出处(文件和行号)对不对?
只列出有问题的格子和原因。全部没问题就只回复"没有问题"。
回答:
{answer}
原文:
{evidence[:20000]}"""}], None)
if "没有问题" in review.content[:20]:
return answer, messages, stats
# 有问题就把检查意见交回给智能体,让它继续查、修改回答
messages += [{"role": "assistant", "content": answer},
{"role": "user", "content": "有人检查了你的回答,意见如下。需要的话继续查文档,然后给出修改后的完整回答。\n\n" + review.content}]
revised, messages, more = loop(messages)
……
檢查的依據是"查到的原文",也就是工具返回的內容,而不是模型自己的記憶。上一模組第 6 課講過,評委憑記憶判斷會出錯,所以要讓它對照材料檢查。有問題時,把檢查意見交回給智慧體,它可以繼續查文件,然後修改回答。
完整程式碼見 code/05-agents/planning_reflection.py。
結果
三種辦法各跑一次(回答都很長,這裡只貼統計和關鍵部分):
===== 直接做
4 次模型调用,9 次工具调用,13155 词元,7 秒
===== 先列计划
5 次模型调用,13 次工具调用,26086 词元,12 秒
===== 做完再检查
7 次模型调用,14 次工具调用,50132 词元,17 秒
直接做的回答質量已經不錯。HTTP/2 一行,它找到了 http2.md 第 50 行的原文 "HTTP/2 support is available on both Client and AsyncClient",明確結論是"一樣"。超時和代理兩行,它如實說明文件裡的示例都只寫了 httpx.Client,沒有 AsyncClient 的寫法,所以只能推斷兩者一致,"依據有限"。這個分寸把握得很好:它沒有把推斷當成事實。
有一點小問題:它的回答第一句是英文 "Based on the documentation, here is the comparison table"。智慧體讀了大量英文文件之後,回答的語言有時會被帶偏。在 system 提示詞裡寫明"用中文回答"可以避免。
先列計劃多呼叫了 1 次模型,工具呼叫從 9 次變成 13 次,詞元翻了一倍,時間多了 5 秒。它的計劃是這樣的:
1. 找 `Client` 的文档,定位它关于超时(timeout)的配置参数与说明。
2. 找 `AsyncClient` 的文档,定位它关于超时(timeout)的配置参数与说明,比对是否一致。
3. 分别在 `Client` 和 `AsyncClient` 文档中找代理(proxy/proxies)相关配置,比对。
4. 分别在 `Client` 和 `AsyncClient` 文档中找 HTTP/2 相关配置(如 http2 参数),比对。
5. 检查两者的基类/继承关系或 API 参考页,确认是否共用同一套初始化参数……
計劃的第 5 步帶來了一點新東西:它去看了 api.md,發現兩個類的成員列表幾乎一樣,只有 close 和 aclose 不同,並用這一點佐證"兩者共用同一套配置參數"。但它的最終結論和直接做的一樣:HTTP/2 有明確依據,超時和代理只能推斷。多花了一倍的錢,結論沒有本質的變化。
做完再檢查找到了真問題。檢查意見指出了兩處出處錯誤,智慧體繼續查了之後,在修改後的回答裡寫明瞭更正:
### 本次更正的两处(评审意见成立)
1. 原标注 `advanced/timeouts.md:66-68`("`httpx.Timeout` 细调")有误:`:66-68` 只是 `httpx.Timeout(10.0, connect=60.0)` 与 `httpx.Client(timeout=timeout)` 的示例代码。`httpx.Timeout` 的细调说明实际在 `advanced/timeouts.md:41-68`(标题 `## Fine tuning the configuration` 在 `:41`)。……
2. 原标注 `advanced/transports.md:223` 不能作为 `timeout` 构造参数的出处:该行是 `httpx.HTTPTransport(proxy=proxy, **kwargs)`,只涉及 transport 的 `proxy`/`**kwargs`,与客户端 `timeout` 无关。已删除该引用。
我對照了 timeouts.md:第 41 行確實是 "## Fine tuning the configuration" 這個標題,第 66 到 68 行確實只是示例程式碼。第一處更正是對的。它把第一版回答裡一處不相關的引用也刪掉了。這種"出處標得不準"的問題,讀的人很難自己發現,恰恰是檢查最擅長髮現的。
代價是:7 次模型呼叫,5 萬個詞元,是直接做的將近 4 倍。檢查那一步要把所有查到的原文都放進去,這是它貴的主要原因。
但修改後的回答也有一個值得注意的變化:結論從"HTTP/2 一樣,超時和代理只能推斷"變成了標題上醒目的"三件事上配置方式完全相同"。雖然正文最後仍然說明超時和代理是推斷,但標題的語氣比證據強了。檢查修正了出處,卻讓結論的措辭變得更肯定。修改之後的回答,也需要再看一遍。
什麼時候值得用
一次實驗不能下定論,但結合這個結果,我的經驗是:
先列計劃,適合步驟很多、容易漏掉某一部分的任務,比如"把這 10 個檔案都改一遍"。計劃能幫模型記住還有哪些沒做。在本課這種三五步就能完成的任務上,它的價值不大。另一個用處是給人看:把計劃先展示給使用者,確認方向對了再執行,比執行完才發現方向錯了省錢得多。
做完再檢查,適合結果容易出細節錯誤、而錯誤的代價比較高的任務,比如引用出處、數字、程式碼。檢查時一定要給它原始材料,讓它對照檢查,而不是憑記憶。它很貴,所以通常只在最終結果上用一次,不要每一步都檢查。
直接做,在大多數任務上是合理的預設選擇。先用直接做,建立評估,看看錯誤主要出在哪裡,再針對性地加規劃或者檢查。
練習
- 在
planning_reflection.py的 SYSTEM 里加上"用中文回答",重新執行,看看"直接做"的回答第一句還會不會是英文。 - 把檢查那一步的提示詞改成不給原文、只給回答("檢查這份回答有沒有錯誤"),重新執行。檢查意見的質量有什麼變化?
- 把三種辦法各跑 3 次,記錄每次的詞元數和修改的內容。一次的結果和多次的平均值相差大嗎?
自測
1. 讓模型"先列計劃再執行"時,為什麼要告訴它"發現計劃不對可以調整"?
計劃是在還沒有查任何資料時制定的,裡面的步驟是猜的。執行過程中查到的資訊可能說明原計劃有問題,死板地照著執行反而會走錯方向。
2. 讓模型檢查自己的回答時,為什麼要把查到的原文一起給它?
只給回答,模型只能憑自己的記憶判斷對錯,而它的記憶可能就是出錯的根源,前一個模組的評委就犯過這種錯。給了原文,它能逐條對照,發現出處標錯、說法沒有依據這類具體的問題。
3. 本課的實驗裡,自我檢查詢到了真正的錯誤。那是不是應該在每個任務上都加上自我檢查?
不一定。自我檢查在本課花了將近 4 倍的錢和 2 倍多的時間。它適合錯誤代價高、容易出細節錯誤的任務,而且通常只在最終結果上做一次。另外,修改後的回答也可能引入新問題,比如本課裡結論的措辭變得比證據更肯定。