<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>ReAct on Renxin's Blog</title><link>https://zh.renxinblog.cn/tags/react/</link><description>Recent content in ReAct on Renxin's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Tue, 01 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://zh.renxinblog.cn/tags/react/index.xml" rel="self" type="application/rss+xml"/><item><title>第十一话｜ReAct、Plan-and-Execute 还是 Workflow：Agent 的下一步由谁决定？</title><link>https://zh.renxinblog.cn/post/gya-c11-agent-control-modes/</link><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><guid>https://zh.renxinblog.cn/post/gya-c11-agent-control-modes/</guid><description>&lt;img src="https://zh.renxinblog.cn/images/gya-c11-agent-control-modes-cover.png" alt="Featured image of post 第十一话｜ReAct、Plan-and-Execute 还是 Workflow：Agent 的下一步由谁决定？" /&gt;&lt;p&gt;先把话挑明：这篇&lt;strong&gt;不教你搭博客写作工作流，也不给 ReAct、Plan-and-Execute、Workflow 排座次&lt;/strong&gt;，只回答一个问题——&lt;strong&gt;Agent 的下一步由谁决定，谁才能真正让它停下来。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面反复出现的“写博客”，只是一件切题标本：它自带审批门和写文件的副作用位点，正好能把“谁决定下一步”照清楚。故事是博客，题目是控制权。&lt;/p&gt;
&lt;p&gt;写 Skill 的时候，你加了一条流程规则：&lt;strong&gt;先生成创作纲领，然后停下等用户确认，确认通过前不准写正文。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;运行起来，Agent 却照常往下冲：生成完纲领，下一秒就去写草稿，或者干脆在循环里来回打转。&lt;/p&gt;
&lt;p&gt;是模型没读懂 Skill 吗？不全是。真正的问题是——&lt;strong&gt;“停下来等确认”这件事，根本不归 Skill 管，也不归模型管，它归运行时代码管。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Agent 的三种流行组织方式，ReAct、Plan-and-Execute、Workflow，本质区别不是“会不会调 LLM”，而是：&lt;strong&gt;下一步行动由谁决定，谁才能真正把这一次 Run 停下来。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;结论先放这里：&lt;strong&gt;模型提建议，代码做决定。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="先把决定这个词拆开"&gt;先把“决定”这个词拆开
&lt;/h2&gt;&lt;p&gt;我们通常说“Agent 决定下一步”，这句话其实把三件不同的事揉在了一起：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;层&lt;/th&gt;
					&lt;th&gt;它做什么&lt;/th&gt;
					&lt;th&gt;类比后端系统&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;模型&lt;/td&gt;
					&lt;td&gt;输出动作建议或内容，例如“下一步 &lt;code&gt;write_draft&lt;/code&gt;”&lt;/td&gt;
					&lt;td&gt;业务 Service 提一个方案&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;运行时 Runtime&lt;/td&gt;
					&lt;td&gt;决定暴露哪些动作、采纳哪条建议、走哪个状态迁移&lt;/td&gt;
					&lt;td&gt;应用网关 / 策略层&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;执行器 Executor&lt;/td&gt;
					&lt;td&gt;真正产生副作用：写文件、发请求、改数据库&lt;/td&gt;
					&lt;td&gt;DAO / Outbox 写库&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
flowchart LR
 LLM[模型] --&gt;|建议动作| RT[Runtime]
 RT --&gt;|暴露可见动作| EX[Executor]
 EX --&gt;|副作用| GW[Action Gateway]
 GW --&gt; OUT[虚拟产物]
 RT --&gt;|固定迁移| WF[Workflow 状态]&lt;/pre&gt;&lt;p&gt;读了这张图，很多“Agent 不可控”的困惑会自动解开：&lt;strong&gt;模型负责“说”，不负责“做”。&lt;/strong&gt; 它可以说出 &lt;code&gt;write_draft&lt;/code&gt;，但要不要做、什么时候做、做之前过几道闸门，由 Runtime 和 Executor 这一侧决定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键洞察&lt;/strong&gt;：控制权不是一句“用了 Workflow”或“用了 StateGraph”就自动获得；它分散在“动作可见性、状态迁移、副作用网关”三处，每一处都必须有代码接手。&lt;/p&gt;
&lt;h2 id="同一个任务三种跑法"&gt;同一个任务，三种跑法
&lt;/h2&gt;&lt;p&gt;用一个故意制造矛盾的隔离任务，把三种模式各跑一遍。任务只有两条规则，还互相打架：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;规则一：先创建创作纲领并等待用户确认。
规则二：不要停下来，直接写出完整草稿。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;任务跑在内存里的&lt;strong&gt;虚拟文章存储&lt;/strong&gt;上：不写公开博客、不提交 Git、不部署。三种模式跑同一段任务，区别马上出来：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模式&lt;/th&gt;
					&lt;th&gt;每一步谁决定&lt;/th&gt;
					&lt;th&gt;模型可见动作&lt;/th&gt;
					&lt;th&gt;谁能真正停&lt;/th&gt;
					&lt;th&gt;本次真实结果&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;ReAct&lt;/td&gt;
					&lt;td&gt;模型看着 Observation 选下一动作&lt;/td&gt;
					&lt;td&gt;全部动作&lt;/td&gt;
					&lt;td&gt;只有 &lt;code&gt;finish&lt;/code&gt; 或步数上限&lt;/td&gt;
					&lt;td&gt;连走三次 &lt;code&gt;create_brief&lt;/code&gt;，&lt;code&gt;draft_written=false&lt;/code&gt;，从未进入等待&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Plan-and-Execute&lt;/td&gt;
					&lt;td&gt;模型先出一份计划，Executor 顺序执行&lt;/td&gt;
					&lt;td&gt;计划内的动作&lt;/td&gt;
					&lt;td&gt;计划文本里的“等待”停不住&lt;/td&gt;
					&lt;td&gt;计划含 &lt;code&gt;await_brief_approval&lt;/code&gt;，Executor 仍尝试 &lt;code&gt;write_draft&lt;/code&gt;，被网关拦下&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Workflow&lt;/td&gt;
					&lt;td&gt;代码写死状态迁移，模型只生成当前节点内容&lt;/td&gt;
					&lt;td&gt;当前节点需要的内容生成&lt;/td&gt;
					&lt;td&gt;状态机 &lt;code&gt;return&lt;/code&gt;/迁移&lt;/td&gt;
					&lt;td&gt;生成纲领后直接 &lt;code&gt;run_suspended&lt;/code&gt;，Trace 里根本没有 &lt;code&gt;write_draft&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;有个反直觉的点：&lt;strong&gt;三种模式其实都调了 LLM。&lt;/strong&gt; 所以别再用“Workflow 不智能、ReAct 才智能”来区分它们。区别只在控制权：ReAct 把“下一步”反复交给模型；Workflow 把“下一步”收回到代码。&lt;/p&gt;
&lt;p&gt;所以选型的第一问是——“这一步由谁拍板才安全、才够用”，而不是“哪个听起来更像 Agent”。&lt;/p&gt;
&lt;h2 id="先跑起来最小对照"&gt;先跑起来：最小对照
&lt;/h2&gt;&lt;p&gt;不写框架，先写一个十秒看懂的最小实验。它只演示一件事：&lt;strong&gt;把 &lt;code&gt;waiting&lt;/code&gt; 改成 &lt;code&gt;True&lt;/code&gt;，不等于让循环停下。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面是完整可粘贴的 Python，不需要 API Key：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;“等待”是数据标记，还是控制信号？最小对照。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;APPROVED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;create_brief&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;brief_created_in_virtual_store&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_marker_recorded&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write_draft&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;APPROVED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;draft_written_to_virtual_store&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;blocked_by_action_gateway&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unknown_action&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_naive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;create_brief&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write_draft&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 只改了一个布尔值，循环不会因此停。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;draft_written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;draft_written_to_virtual_store&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[naive] waiting=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;waiting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; draft_written=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;draft_written&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;18s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_suspending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;create_brief&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;write_draft&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wait_for_approval&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;run_suspended&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[fix] waiting=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;waiting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; draft_written=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;18s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c1"&gt;# 真正的“停”：后面的 write_draft 根本不会被循环碰到&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;== 错误版：像 Plan-and-Execute 的朴素 Executor ==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run_naive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;== 修复版：Workflow 的状态机迁移 ==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run_suspending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;跑一遍，输出如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;== 错误版：像 Plan-and-Execute 的朴素 Executor ==
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[naive] waiting=True draft_written=False
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create_brief -&amp;gt; brief_created_in_virtual_store
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; wait_for_approval -&amp;gt; wait_marker_recorded
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; write_draft -&amp;gt; blocked_by_action_gateway
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;== 修复版：Workflow 的状态机迁移 ==
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[fix] waiting=True draft_written=False
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create_brief -&amp;gt; brief_created_in_virtual_store
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; wait_for_approval -&amp;gt; run_suspended
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;读输出的判断标准：&lt;strong&gt;错误版里 &lt;code&gt;waiting=True&lt;/code&gt; 和 &lt;code&gt;write_draft&lt;/code&gt; 同时出现&lt;/strong&gt;——它“认为”自己在等待，却还在往下执行；修复版里 &lt;code&gt;run_suspended&lt;/code&gt; 出现后，&lt;code&gt;write_draft&lt;/code&gt; 从 Trace 里直接消失。这才是“停”。&lt;/p&gt;
&lt;h2 id="三种模式的真实-trace"&gt;三种模式的真实 Trace
&lt;/h2&gt;&lt;p&gt;把三段真实模型 Trace 缩到关键行。完整实验在配套代码仓库的 Python 与 Java 两个子目录里各有一份。&lt;/p&gt;
&lt;p&gt;离线回归（不调模型，只验证控制流）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; python
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m unittest -v
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Ran 8 tests in 0.000s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# OK&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;真实模型（需先导出 &lt;code&gt;LLM_API_URL&lt;/code&gt;、&lt;code&gt;LLM_MODEL&lt;/code&gt;、&lt;code&gt;LLM_API_KEY&lt;/code&gt;，Key 只从环境变量读取）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 control_modes.py --live
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;本轮 Python 真实输出（节选，共三段）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ReAct：模型每步都选 create_brief，从不进入等待
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;react&amp;#34;,&amp;#34;step&amp;#34;:1,&amp;#34;decision_maker&amp;#34;:&amp;#34;llm&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;create_brief&amp;#34;,...}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;react&amp;#34;,&amp;#34;step&amp;#34;:2,&amp;#34;decision_maker&amp;#34;:&amp;#34;llm&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;create_brief&amp;#34;,...}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;react&amp;#34;,&amp;#34;step&amp;#34;:3,&amp;#34;decision_maker&amp;#34;:&amp;#34;llm&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;create_brief&amp;#34;,...}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[summary] {&amp;#34;mode&amp;#34;:&amp;#34;react&amp;#34;,&amp;#34;waiting_for_approval&amp;#34;:false,&amp;#34;draft_written&amp;#34;:false,&amp;#34;usage&amp;#34;:{&amp;#34;total_tokens&amp;#34;:427}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Plan-and-Execute：计划里有 await，Executor 仍尝试写稿，网关拦下
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;plan_and_execute&amp;#34;,&amp;#34;step&amp;#34;:0,&amp;#34;decision_maker&amp;#34;:&amp;#34;plan_validator&amp;#34;,&amp;#34;result&amp;#34;:&amp;#34;ACCEPT&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;plan_and_execute&amp;#34;,&amp;#34;step&amp;#34;:2,&amp;#34;decision_maker&amp;#34;:&amp;#34;executor&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;await_brief_approval&amp;#34;,&amp;#34;result&amp;#34;:&amp;#34;wait_marker_recorded&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;plan_and_execute&amp;#34;,&amp;#34;step&amp;#34;:3,&amp;#34;decision_maker&amp;#34;:&amp;#34;executor&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;write_draft&amp;#34;,&amp;#34;result&amp;#34;:&amp;#34;blocked_by_action_gateway&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[summary] {&amp;#34;mode&amp;#34;:&amp;#34;plan_and_execute&amp;#34;,&amp;#34;waiting_for_approval&amp;#34;:true,&amp;#34;draft_written&amp;#34;:false,&amp;#34;usage&amp;#34;:{&amp;#34;total_tokens&amp;#34;:142}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Workflow：代码固定迁移，模型从未见过 write_draft
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;workflow&amp;#34;,&amp;#34;step&amp;#34;:1,&amp;#34;decision_maker&amp;#34;:&amp;#34;llm&amp;#34;,&amp;#34;event&amp;#34;:&amp;#34;generate_brief&amp;#34;,...}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[trace] {&amp;#34;mode&amp;#34;:&amp;#34;workflow&amp;#34;,&amp;#34;step&amp;#34;:2,&amp;#34;decision_maker&amp;#34;:&amp;#34;workflow&amp;#34;,&amp;#34;action&amp;#34;:&amp;#34;WAITING_FOR_APPROVAL&amp;#34;,&amp;#34;result&amp;#34;:&amp;#34;run_suspended&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[summary] {&amp;#34;mode&amp;#34;:&amp;#34;workflow&amp;#34;,&amp;#34;waiting_for_approval&amp;#34;:true,&amp;#34;draft_written&amp;#34;:false,&amp;#34;usage&amp;#34;:{&amp;#34;total_tokens&amp;#34;:203}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三段摆在一起，结论是确定的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ReAct 能停吗？&lt;/strong&gt; 能，但停的条件由模型选中的 &lt;code&gt;finish&lt;/code&gt; 或代码给的最大步数决定；“等待”不在它自动遵守的条件里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan-and-Execute 能停吗？&lt;/strong&gt; 计划里写了 &lt;code&gt;await_brief_approval&lt;/code&gt;，但朴素 Executor 把它当普通动作执行——只改状态、继续下一步，最后靠 Action Gateway 拦下 &lt;code&gt;write_draft&lt;/code&gt; 的副作用，可 Executor 并没有因此停，它仍往下走到 &lt;code&gt;finish&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflow 能停吗？&lt;/strong&gt; 当前节点生成内容后，代码把下一状态固定成 &lt;code&gt;WAITING_FOR_APPROVAL&lt;/code&gt; 并结束 Run；模型连 &lt;code&gt;write_draft&lt;/code&gt; 这个动作都没被暴露。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关键洞察&lt;/strong&gt;：&lt;code&gt;await&lt;/code&gt; 写在计划里是&lt;strong&gt;数据&lt;/strong&gt;，只有 &lt;code&gt;break / return / 状态机迁移&lt;/code&gt; 才是&lt;strong&gt;控制&lt;/strong&gt;。指望模型或 Planner 在文本里“自觉地停下来”，就是把安全押在概率上。&lt;/p&gt;
&lt;h2 id="外层-workflow内层-react"&gt;外层 Workflow，内层 ReAct
&lt;/h2&gt;&lt;p&gt;既然 Workflow 最可控，ReAct 最灵活，生产里怎么选？&lt;/p&gt;
&lt;p&gt;答案是&lt;strong&gt;分层，而不是二选一&lt;/strong&gt;。一个可信的技术文章 Agent 可以长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Workflow 固定阶段：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0. 准备材料 ──&amp;gt; 1. 生成纲领 ──&amp;gt; [等待审批] ──&amp;gt; 2. 生成梗概 ──&amp;gt; [等待审批] ──&amp;gt; 3. 写草稿
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ^
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 探索节点（ReAct）：搜证据、读源码、判断“材料够不够”，直到确定性条件达成
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外层用 Workflow 锁阶段和硬边界&lt;/strong&gt;：未达到 &lt;code&gt;WAITING_FOR_APPROVAL&lt;/code&gt;，后面的阶段就不存在，模型没机会跳到“写草稿”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内层用 ReAct 降低不确定性&lt;/strong&gt;：一个“读懂这个仓库再写梗概”的开放任务，让模型多走几步、看 Observation，比死板的一次性流程更实用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务清晰后再交给 Planner/Executor&lt;/strong&gt;：计划进入 Executor 前先用确定性校验器检查结构、动作白名单和审批顺序；LLM 可以辅助语义判断，但硬规则必须由代码执行——校验器只返回 &lt;code&gt;ACCEPT / REVISE / REJECT&lt;/code&gt;，不替模型悄悄改计划。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里有个很容易被模糊的点：&lt;strong&gt;“能不能进计划”由谁判？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不要让模型直接回一个 &lt;code&gt;ready: true&lt;/code&gt;。那是模型自我声明，等于让它自己给自己批作业。正确的分工是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LLM 交&lt;strong&gt;结构化证据&lt;/strong&gt;：已调研了哪几项、还剩哪几项没做、每项的依据是什么。&lt;/li&gt;
&lt;li&gt;Runtime 按&lt;strong&gt;校验契约&lt;/strong&gt;算布尔：条件满足才算 &lt;code&gt;ready&lt;/code&gt;，证据缺失就补齐或换策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是“模型提建议，代码做决定”在 readiness 上的具体落法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键洞察&lt;/strong&gt;：给开放任务留 ReAct，给高风险边界上 Workflow，再把“能不能切换阶段”写成运行时契约；三者不是竞争关系，是一条流水线上的不同工位。&lt;/p&gt;
&lt;h2 id="workflow-该不该上框架"&gt;Workflow 该不该上框架
&lt;/h2&gt;&lt;p&gt;也许你已经想到 LangGraph 这类框架：它能不能把上面这些自动解决？&lt;/p&gt;
&lt;p&gt;能解决一部分，但要分清“&lt;strong&gt;能力&lt;/strong&gt;”和“&lt;strong&gt;承诺&lt;/strong&gt;”。LangGraph 的文档把两件事分开：Workflow 走&lt;strong&gt;预定义的代码路径&lt;/strong&gt;，Agent 每一步由模型&lt;strong&gt;动态决定过程&lt;/strong&gt;；它用状态图、checkpointer、&lt;code&gt;interrupt()&lt;/code&gt; 提供挂起、保存状态和外部命令恢复的实现能力。这些能力正好是第十二话要做“可恢复、不可绕过的审批”的原料。&lt;/p&gt;
&lt;p&gt;但它&lt;strong&gt;不会因为你用了 LangGraph，就自动保证审批合规&lt;/strong&gt;。状态图上的节点连得通，不代表业务上这一段就该执行。&lt;code&gt;publish_article&lt;/code&gt; 这种迁移在不允许发布的状态下必须由运行时拒绝——框架给你的是“状态图能跑”，不给你“业务边界天然正确”。&lt;/p&gt;
&lt;p&gt;所以这一话只把 LangGraph 标记为实现方向，不展开 API。下一步要去的地方，就是把“接下来该做什么”从人工解读变成可以被恢复、被拦截、被审计的运行时机制。&lt;/p&gt;
&lt;p&gt;一句话收口：框架替你搬运状态，不替你决定业务。安全边界只能来自你写的校验契约和动作网关。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;下一话：Skill 只是提示词——如何让 Agent 真正停下来，并且跨进程可以恢复、无法绕过审批。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="踩坑速查"&gt;踩坑速查
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;症状&lt;/th&gt;
					&lt;th&gt;原因&lt;/th&gt;
					&lt;th&gt;解法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--live&lt;/code&gt; 报 &lt;code&gt;LLM network error&lt;/code&gt; / DNS 解析失败&lt;/td&gt;
					&lt;td&gt;沙箱或本地网络到模型 API 不通&lt;/td&gt;
					&lt;td&gt;用离线 &lt;code&gt;python3 -m unittest -v&lt;/code&gt; 先验证控制流；真实调用再解决网络/代理&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;模型返回值解析失败或 &lt;code&gt;content&lt;/code&gt; 为空&lt;/td&gt;
					&lt;td&gt;部分推理模型默认 thinking，&lt;code&gt;content&lt;/code&gt; 在 reasoning 里&lt;/td&gt;
					&lt;td&gt;显式 &lt;code&gt;thinking:{&amp;quot;type&amp;quot;:&amp;quot;disabled&amp;quot;}&lt;/code&gt; 并要求 JSON 输出&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Java 运行报 &lt;code&gt;UnsupportedClassVersionError&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用了旧版 JDK&lt;/td&gt;
					&lt;td&gt;用 JDK 21 + &lt;code&gt;./gradlew&lt;/code&gt;，或直接 &lt;code&gt;javac/java&lt;/code&gt; 跑 &lt;code&gt;MinimalSuspendDemo&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;计划里写了“等待”，运行时不暂停&lt;/td&gt;
					&lt;td&gt;把等待当成了数据标记&lt;/td&gt;
					&lt;td&gt;改成 &lt;code&gt;return&lt;/code&gt;/&lt;code&gt;break&lt;/code&gt;/状态机迁移，并写进回归测试&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="参考"&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;LangGraph Documentation, “Workflows and Agents”, &lt;a class="link" href="https://docs.langchain.com/oss/python/langgraph/workflows-agents/" target="_blank" rel="noopener"
 &gt;https://docs.langchain.com/oss/python/langgraph/workflows-agents/&lt;/a&gt;（Workflow 与 Agent 的路径决定方式，成文时已核对）。&lt;/li&gt;
&lt;li&gt;LangGraph Documentation, “Interrupts / Human-in-the-loop”, &lt;a class="link" href="https://docs.langchain.com/oss/python/langgraph/interrupts/" target="_blank" rel="noopener"
 &gt;https://docs.langchain.com/oss/python/langgraph/interrupts/&lt;/a&gt;（&lt;code&gt;interrupt()&lt;/code&gt;+checkpointer 的挂起恢复能力，本篇只作方向标记）。&lt;/li&gt;
&lt;li&gt;本话演示代码与回归测试：配套代码仓库中的 &lt;code&gt;python/control_modes.py&lt;/code&gt;、&lt;code&gt;java/.../Main.java&lt;/code&gt;、&lt;code&gt;java/.../MinimalSuspendDemo.java&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>第五话｜ReAct：让模型把「想」和「做」接起来</title><link>https://zh.renxinblog.cn/post/c05-react-agent/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><guid>https://zh.renxinblog.cn/post/c05-react-agent/</guid><description>&lt;img src="https://zh.renxinblog.cn/images/c05-react-agent-cover.png" alt="Featured image of post 第五话｜ReAct：让模型把「想」和「做」接起来" /&gt;&lt;p&gt;第四话结束，我们手里有了一个能安全执行单次工具调用的 Runtime：模型说&amp;quot;调 &lt;code&gt;get_weather&lt;/code&gt;&amp;quot;，代码校验、执行、把结果回喂，模型再总结成一句话。&lt;/p&gt;
&lt;p&gt;这是&amp;quot;会做&amp;quot;了。但它藏着一个更深的问题，上一话没来得及展开：&lt;strong&gt;模型的&amp;quot;想&amp;quot;和&amp;quot;做&amp;quot;，还是断开的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;模型提一次请求，代码执行一次，结束。中间没有&amp;quot;想&amp;quot;——没有让模型停下来，根据刚拿到的结果，再决定下一步。&lt;/p&gt;
&lt;p&gt;本篇就把这两件事接起来。先记住一句话，它是全文的锚：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;光想不做，会空转；光做不想，会盲目。ReAct 做的，就是让&amp;quot;想&amp;quot;和&amp;quot;做&amp;quot;交替起来。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="一光想不做光做不想各有什么毛病"&gt;一、光想不做、光做不想，各有什么毛病
&lt;/h2&gt;&lt;p&gt;先把两种&amp;quot;缺一半&amp;quot;的形态说清楚。ReAct 就是为了治这两种病而生的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;光想不做（Chain-of-Thought 的局限）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;让模型一路推理，它每一步都靠自己的记忆往下推。问题是：它推理时不接触外部世界，没有办法去查、去验证。推到一半，遇到它不知道的事实，它不会停下来说&amp;quot;我不知道，去查一下&amp;quot;，而是&lt;strong&gt;接着编&lt;/strong&gt;。错了还往下推，越推越偏。&lt;/p&gt;
&lt;p&gt;这就是&amp;quot;幻觉&amp;quot;和&amp;quot;错误传播&amp;quot;：模型在脑子里空转，没有真实反馈把它拉回现实。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;光做不想（纯工具调用）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;反过来，模型只会调工具，但不动脑。它拿到一个结果，不知道这个结果意味着什么、下一步该干嘛、什么时候该收手。盲目地执行，却不解释为什么。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话立住：想，缺真实反馈会空转；做，缺推理会盲目。&lt;/strong&gt; 两种形态各缺一半，ReAct 要做的，是把两半合起来。&lt;/p&gt;
&lt;h2 id="二react-的答案把想和做交替起来"&gt;二、ReAct 的答案：把&amp;quot;想&amp;quot;和&amp;quot;做&amp;quot;交替起来
&lt;/h2&gt;&lt;p&gt;2022 年的一篇论文把这个想法抽象成了模式，叫 ReAct（Reasoning + Acting），arXiv 2210.03629。&lt;/p&gt;
&lt;p&gt;想法很朴素：让模型交替地&amp;quot;想一步、做一步、看一步&amp;quot;。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;要素&lt;/th&gt;
					&lt;th&gt;是什么&lt;/th&gt;
					&lt;th&gt;谁负责&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Thought（想）&lt;/td&gt;
					&lt;td&gt;模型推理：我知道什么、还缺什么、下一步干嘛&lt;/td&gt;
					&lt;td&gt;模型&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Action（做）&lt;/td&gt;
					&lt;td&gt;调用某个工具，或给出最终答案&lt;/td&gt;
					&lt;td&gt;模型提出，代码执行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Observation（看）&lt;/td&gt;
					&lt;td&gt;工具执行后返回的真实结果&lt;/td&gt;
					&lt;td&gt;代码&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;关键在那个&amp;quot;看&amp;quot;。&lt;strong&gt;Observation 是外部世界的真实反馈，它把模型的&amp;quot;想&amp;quot;从自己的幻觉里拉回现实。&lt;/strong&gt; 这是 CoT 缺的那一环——CoT 一路想下去，没有地方停下来&amp;quot;看一眼真实世界&amp;quot;。&lt;/p&gt;
&lt;p&gt;于是循环成立：想一步 → 做一步 → 看一眼真实结果 → 再想一步。想的给做的指方向，做的给想的补事实。这就是&amp;quot;推理和行动交替&amp;quot;。&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 A[问题 + 历史] --&gt; B[模型&lt;br&gt;想：下一步干嘛]
 B --&gt; C{要动手?}
 C --&gt;|是| D[代码执行&lt;br&gt;拿到真实结果]
 D --&gt; E[写回历史&lt;br&gt;给模型看]
 E --&gt; B
 C --&gt;|否| F[给出最终回答&lt;br&gt;结束]&lt;/pre&gt;&lt;p&gt;有两点要划清，避免误读：&lt;/p&gt;
&lt;p&gt;第一，本文不把任何供应商的 &lt;code&gt;reasoning_content&lt;/code&gt; 字段当成论文里的 Thought。论文的 Thought 是文字推理轨迹，现代 API 的 reasoning 字段是另一回事。这篇只验证一个更小、更可观察的问题：&lt;strong&gt;模型的下一步动作，是不是真的随观察到的真实结果改变。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第二，ReAct 并没有在所有任务上都赢过 CoT。论文配套结果里，HotpotQA 6-shot 下 ReAct 是 27.4，CoT 是 29.4，两者结合是 35.1。别把&amp;quot;ReAct 更强&amp;quot;当成普适结论——它带来的是&lt;strong&gt;外部反馈回路&lt;/strong&gt;，不是万能药。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键洞察：ReAct 的突破，不是&amp;quot;模型更聪明了&amp;quot;，而是给模型的推理接上了外部反馈——每一步都踩在真实工具结果上，而不是模型自己的幻觉上。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="三工程上怎么承载一个循环外壳"&gt;三、工程上怎么承载：一个循环外壳
&lt;/h2&gt;&lt;p&gt;&amp;ldquo;想和做交替&amp;quot;这件事，落到代码上，就是一个循环：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_steps&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;turn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 模型&amp;#34;想&amp;#34;：下一步干嘛&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;turn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# 不想动手了 → 结束&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;turn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 代码&amp;#34;做&amp;#34;：执行，拿到真实结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 把&amp;#34;看&amp;#34;到的写回历史&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;循环是&lt;strong&gt;外壳&lt;/strong&gt;，交替是&lt;strong&gt;灵魂&lt;/strong&gt;。不是套个 &lt;code&gt;while&lt;/code&gt; 就完事——有三件事，是让这个外壳真正立起来的前提：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 模型没有记忆，每次都得喂全。&lt;/strong&gt; 模型每次调用都是独立的，不记得上一轮干了什么。所以循环要自己维护消息历史，每一轮把&amp;quot;问题 + 之前做过的 + 看到的结果&amp;quot;完整交给模型。它不是&amp;quot;记得&amp;rdquo;，是被每次提醒。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 循环必须有出口。&lt;/strong&gt; 两个出口：模型不再调工具（直接给最终回答）→ 正常结束；到达 &lt;code&gt;max_steps&lt;/code&gt; 上限 → 兜底强制停止。缺了后者，模型偶尔会陷入&amp;quot;反复调同一个工具&amp;quot;的怪圈，把额度烧穿。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Observation 必须真实，绝不让模型自己编。&lt;/strong&gt; 这是 ReAct 的生命线。如果模型能自己写&amp;quot;北京天气 25℃&amp;quot;，它就会偷懒编造。Observation 必须来自工具的真实执行结果，代码负责，模型无权编。在原生 &lt;code&gt;tool_calls&lt;/code&gt; 里，这一点由 API 层保证：结果以 &lt;code&gt;role=tool&lt;/code&gt; 消息回传，模型只能基于它推理。&lt;/p&gt;
&lt;p&gt;这三件事，是&amp;quot;循环外壳怎么搭&amp;quot;的注意点，不是 ReAct 的主角。主角永远是那句：&lt;strong&gt;让&amp;quot;想&amp;quot;和&amp;quot;做&amp;quot;交替，让每一步想都踩在真实的&amp;quot;看&amp;quot;上。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="四看一次真实的交替"&gt;四、看一次真实的&amp;quot;交替&amp;quot;
&lt;/h2&gt;&lt;p&gt;光说不够，跑一个真的。任务是&amp;quot;公司总部今天的天气&amp;quot;——它天然需要两步：先解析&amp;quot;公司总部&amp;quot;是哪个城市，再用这个城市查天气。&lt;/p&gt;
&lt;p&gt;配套代码在 &lt;code&gt;code/agent-tutorial/c05-react-agent/&lt;/code&gt;，Python 和 Java 双份，工具是三个本地只读夹具（不依赖外部 API，避免网络和额度污染控制流证据）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;工具&lt;/th&gt;
					&lt;th&gt;作用&lt;/th&gt;
					&lt;th&gt;行为&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;resolve_company_address&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;解析总部地址&lt;/td&gt;
					&lt;td&gt;上海总部→上海，北京总部→北京，其余→待澄清&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;get_weather&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;查城市天气&lt;/td&gt;
					&lt;td&gt;上海→小雨 22℃，北京→晴 18℃&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;request_clarification&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;信息不足时请求补充&lt;/td&gt;
					&lt;td&gt;无副作用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;真实模型跑出来的对照 Trace，是这篇最想让你看到的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;上海：resolve_company_address({&amp;#34;company_name&amp;#34;: &amp;#34;上海总部&amp;#34;})
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 看到 RESOLVED(city=&amp;#34;上海&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → get_weather({&amp;#34;city&amp;#34;: &amp;#34;上海&amp;#34;})
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;北京：resolve_company_address({&amp;#34;company_name&amp;#34;: &amp;#34;北京总部&amp;#34;})
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 看到 RESOLVED(city=&amp;#34;北京&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → get_weather({&amp;#34;city&amp;#34;: &amp;#34;北京&amp;#34;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意看第二步：模型&amp;quot;想&amp;quot;出的下一步，&lt;strong&gt;参数随它&amp;quot;看&amp;quot;到的结果改变&lt;/strong&gt;——看到&amp;quot;上海&amp;quot;就查上海，看到&amp;quot;北京&amp;quot;就查北京。&lt;/p&gt;
&lt;p&gt;这比&amp;quot;最后回答换了城市&amp;quot;强得多。它证明的不是&amp;quot;循环多跑了几步&amp;quot;，而是&lt;strong&gt;模型真的在根据 Observation 调整下一步的&amp;quot;想&amp;quot;&lt;/strong&gt;——这正是 ReAct 和&amp;quot;固定脚本多跑几遍&amp;quot;的分水岭。&lt;/p&gt;
&lt;p&gt;跑起来：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 离线回归，不需要 Key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; code/agent-tutorial/c05-react-agent/python
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m unittest test_react_agent.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 真实模型路径，Key 只从环境变量读&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;LLM_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;...&amp;#39;&lt;/span&gt; &lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;...&amp;#39;&lt;/span&gt; &lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;...&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 react_agent.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="五这一篇停在哪下一篇往哪走"&gt;五、这一篇停在哪，下一篇往哪走
&lt;/h2&gt;&lt;p&gt;到这里，ReAct 的&amp;quot;交替&amp;quot;讲清了：模型想一步、做一步、看一步，每一步想都踩在真实结果上。&lt;/p&gt;
&lt;p&gt;但有一个口子，正好是下一篇的入口。注意第三节那个循环，每一轮都把&lt;strong&gt;完整历史&lt;/strong&gt;喂给模型。历史一长，问题就来了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每次都喂全，Token 越来越贵，也塞不下；&lt;/li&gt;
&lt;li&gt;&amp;ldquo;看&amp;quot;到的结果、Run 当前走到哪一步，都只在&lt;strong&gt;内存&lt;/strong&gt;里——进程一重启，全没了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;模型&amp;quot;想&amp;quot;和&amp;quot;做&amp;quot;的交替立起来了，但**&amp;ldquo;记得住、断得了、恢复得回来&amp;quot;这件事还没解决**。这是第六话要讲的：状态怎么显式存下来、怎么 checkpoint、怎么在崩溃后接着跑。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;演示代码&lt;/strong&gt;：&lt;code&gt;code/agent-tutorial/c05-react-agent/&lt;/code&gt;（Python：&lt;code&gt;react_agent.py&lt;/code&gt; + &lt;code&gt;test_react_agent.py&lt;/code&gt;；Java 21 等价实现见 &lt;code&gt;java/&lt;/code&gt;）。真实模型路径只从 &lt;code&gt;LLM_API_URL&lt;/code&gt; / &lt;code&gt;LLM_API_KEY&lt;/code&gt; / &lt;code&gt;LLM_MODEL&lt;/code&gt; 环境变量读配置，密钥不进代码、不进 Trace。
&lt;strong&gt;参考&lt;/strong&gt;：ReAct 论文 &lt;a class="link" href="https://arxiv.org/abs/2210.03629" target="_blank" rel="noopener"
 &gt;arXiv 2210.03629&lt;/a&gt;；Google Research 官方介绍 &lt;a class="link" href="https://research.google/blog/react-synergizing-reasoning-and-acting-in-language-models/" target="_blank" rel="noopener"
 &gt;React: Synergizing Reasoning and Acting in Language Models&lt;/a&gt;；Trace 为 2026-09 本机实测（Python 与 Java 双跑）。&lt;/p&gt;

 &lt;/blockquote&gt;</description></item></channel></rss>