計画と自己チェック
同じ複数ステップのタスクで、そのままやる、先に計画を立ててからやる、やり終えてから自己チェックする、の三つのやり方を比べます。先に計画を立てるとお金は倍かかって効果はほぼ同じ。自己チェックは本当に出典の誤りを二つ見つけましたが、4 倍のお金がかかりました。
- 約 40 分
- 難易度:中級
- 検証:2026-09-14 deepseek-flash
コードと実行結果は実際に動かしたときのまま載せているため、コメントと出力は中国語です。
エージェントを扱う記事では、「計画」(planning)と「振り返り」(reflection)はほぼ必ず出てくる二つのテクニックです。先にモデルに計画を立てさせてから実行し、実行し終えたら自分の結果をチェックさせ、問題があれば直させる。理にかなって聞こえますし、人が複雑なことをするときもそうしています。
しかし、どちらのテクニックもモデルの呼び出しを何回か増やします。実際どれほどの改善をもたらし、そのお金に見合うのでしょうか。この課では本物の複数ステップのタスクで測ってみます。
タスク
「httpx の Client と AsyncClient は、タイムアウト、プロキシ、HTTP/2 の三つで設定方法が同じかどうかを比べてください。表にまとめ、どのマスにもドキュメントの出典(ファイル名と行番号)を明記してください。」
このタスクはこれまでの質問より複雑です。三つの異なるテーマを調べ、各テーマで二つのクライアントをそれぞれ見て、しかも出典を正確に記録しなければなりません。使うのは引き続き第 2 課のエージェントと三つのドキュメント用ツールです。
やり方一:そのままやる
タスクをそのままエージェントに渡し、どう調べるかは自分で決めさせます。
やり方二:先に計画を立てる
まずモデルを 1 回呼び出し、ツールは使わせずに計画だけを立てさせます。それから計画をタスクの後ろに付けて、エージェントに実行させます。
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 にあります。
結果
三つのやり方をそれぞれ 1 回ずつ実行しました(回答はどれも長いので、ここでは集計と要となる部分だけを載せます)。
===== 直接做
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 は同じ、タイムアウトとプロキシは推測するしかない」から、見出しで目立つように「三つとも設定方法はまったく同じ」に変わったのです。本文の最後ではタイムアウトとプロキシは推測だとまだ説明していますが、見出しの口調は根拠よりも強くなっています。チェックで出典は直ったのに、結論の言い回しはより断定的になりました。直した後の回答も、もう一度見る必要があるのです。
使う価値があるのはいつか
1 回の実験で結論は出せませんが、この結果と合わせて、私の経験ではこうです。
先に計画を立てるのは、ステップがとても多く、どこかを抜かしやすいタスクに向いています。たとえば「この 10 個のファイルを全部直して」。計画はモデルがまだ何をやっていないかを覚えておく助けになります。この課のように 3~5 ステップで終わるタスクでは、あまり価値はありません。もう一つの使い道は人に見せることです。先に計画をユーザーに見せ、方向が正しいと確認してから実行すれば、実行し終えてから方向が間違っていたと気づくよりずっと安く済みます。
やり終えてからチェックするのは、結果に細かな誤りが出やすく、しかも誤りの代償が大きいタスクに向いています。たとえば出典の引用、数字、コードです。チェックするときは必ず元の資料を渡して照らし合わせさせ、記憶に頼らせないことです。とても高くつくので、ふつうは最終結果に 1 回だけ使い、ステップごとにはチェックしません。
そのままやるのは、ほとんどのタスクで妥当な既定の選択です。まずそのままやって評価を用意し、誤りが主にどこで出ているかを見てから、狙いを定めて計画やチェックを加えましょう。
練習問題
planning_reflection.pyの SYSTEM に「中国語で答える」を加えて実行し直し、「そのままやる」の回答の最初の一文がまだ英語になるか見てください。- チェックのステップのプロンプトを、原文を渡さず回答だけを渡すもの(「この回答に誤りがないかチェックして」)に変えて実行し直してください。チェックの意見の質はどう変わりましたか。
- 三つのやり方をそれぞれ 3 回ずつ実行し、毎回のトークン数と直した内容を記録してください。1 回だけの結果と、複数回の平均値はどれくらい違いますか。
確認テスト
1. モデルに「先に計画を立ててから実行」させるとき、「計画がおかしいとわかったら調整してよい」と伝えるのはなぜですか?
計画はまだ何の資料も調べていないときに立てたもので、その中のステップは推測です。実行中に調べた情報から元の計画に問題があるとわかるかもしれず、杓子定規に従うとかえって間違った方向に進んでしまいます。
2. モデルに自分の回答をチェックさせるとき、調べた原文も一緒に渡すのはなぜですか?
回答だけを渡すと、モデルは自分の記憶で正誤を判断するしかなく、その記憶こそが誤りの原因かもしれません。前のモジュールの評価者もこの誤りを犯しました。原文を渡せば一つずつ照らし合わせられ、出典の誤りや根拠のない主張といった具体的な問題を見つけられます。
3. この課の実験では、自己チェックが本当の誤りを見つけました。では、すべてのタスクに自己チェックを加えるべきですか?
必ずしもそうではありません。この課では自己チェックに 4 倍近くのお金と 2 倍以上の時間がかかりました。誤りの代償が大きく、細かな誤りが出やすいタスクに向いていて、ふつうは最終結果に 1 回だけ行います。さらに、直した回答が新しい問題を持ち込むこともあります。この課で結論の言い回しが根拠より断定的になったように。
質問と議論
このレッスンでつまずいたところは、ここで質問してください。他の人の質問に答えるのも歓迎です。
質問で 3 ポイント、回答で 6 ポイント。審査を通過すると公開されます。
議論を読み込んでいます…