1. 从“脆弱的聪明”说起为什么AI Agent总在关键时刻掉链子做AI Agent的人大概都有过这种体验demo阶段惊艳四座一旦放到真实环境里跑就开始各种离谱操作。明明昨天还稳定完成任务的智能体今天突然开始胡言乱语或者陷入死循环又或者在多步推理的第五步突然“忘记”了第一步的约束条件。你调了prompt换了模型加了few-shot示例甚至把温度调到0.1它还是会在某个意想不到的角落给你来一下。这个问题的本质其实不是模型不够聪明而是系统缺乏稳定性保障机制。我们花了大量精力在提升Agent的“智商”——更强的推理能力、更丰富的工具调用、更长的上下文窗口却很少有人在架构层面认真思考一个问题当外部环境扰动、内部状态漂移、任务目标动态变化时这个系统靠什么维持稳定这个问题在控制论里已经被研究了大半个世纪。从最早的PID控制器到后来的自抗扰控制ADRC工程师们一直在解决同一类问题如何让一个动态系统在不确定环境中保持鲁棒输出。而AI Agent本质上就是一个高度非线性的、带有时滞和噪声的动态系统。它的输入是用户意图和环境状态输出是动作序列中间经过的推理链路就是它的“被控对象”。把控制论的视角引入AI Agent的可靠性设计不是赶时髦而是因为这套方法论已经被验证了几十年。PID让蒸汽机稳定运转ADRC让四旋翼在强风里悬停这些经验完全可以迁移到智能体的行为控制上。我最近在几个实际项目里尝试用这套思路重构Agent的决策回路效果比单纯堆prompt工程要扎实得多。这篇文章适合两类人看一类是正在从0到1搭建AI Agent、被稳定性问题折磨的开发者另一类是对控制算法有基础认知、想看看这套老方法在新场景下怎么用的工程师。我会从PID讲到ADRC从理论逻辑讲到代码实现把“让Agent从脆弱变鲁棒”这件事拆开揉碎讲清楚。2. 控制论视角下的AI Agent一个被忽视的映射关系2.1 Agent决策回路与闭环控制的同构性先把映射关系说清楚。一个典型的AI Agent工作流程是这样的接收用户输入设定值理解意图并规划步骤控制器计算调用工具或生成内容执行器动作观察执行结果传感器反馈根据反馈调整下一步误差修正。这个循环和工业控制里的闭环反馈系统在结构上几乎一模一样。PID控制器处理的是连续量比如温度、速度、位置。AI Agent处理的是离散的语义状态比如“任务完成度”、“上下文一致性”、“工具调用成功率”。但数学结构是相通的都有一个目标状态都有一个当前状态都需要根据两者之间的偏差来决定下一步动作。我在实际项目中习惯把Agent的每一步输出看作一次“控制动作”把任务完成度看作“被控量”。这样一转换很多控制论里的工具就能直接拿来用了。比如用超调量来衡量Agent是否“用力过猛”——有些Agent为了确保任务完成会反复确认、重复调用工具这就是典型的超调用稳态误差来衡量它是否“差一口气”——任务做到90%就停住了怎么催都不动。2.2 为什么纯Prompt工程解决不了稳定性问题Prompt工程本质上是一种开环控制。你精心设计了一段提示词希望模型按照预期行为输出但模型实际输出什么你无法在生成过程中实时干预。这就像没有反馈回路的恒温器你设定了一个温度但房间实际温度是多少、加热器功率够不够、外面是不是开了窗系统一概不知。开环控制的致命缺陷是对扰动没有抵抗力。模型版本更新了prompt效果可能完全变了用户输入稍微偏一点Agent就跑到完全不同的轨道上工具接口返回格式变了整个链路就断了。这些问题不是靠写更长的prompt能解决的因为它们的根源在于系统架构缺少反馈修正机制。我见过太多团队把大量时间花在“调prompt”上今天加一句“请确保输出格式正确”明天加一句“如果遇到错误请重试”结果prompt越来越长效果越来越不稳定。这就像用开环控制去开飞机全靠事先算好每一步一旦有风就完蛋。2.3 从PID到ADRC控制思想的演进路径PID的核心思想很简单用误差的比例项快速响应用积分项消除稳态误差用微分项抑制振荡。三个参数调好了大部分线性系统都能控制得不错。但PID有两个硬伤一是对非线性系统效果有限二是对强扰动敏感。AI Agent恰恰是一个强非线性、强扰动的系统。用户意图可能前后矛盾工具返回可能格式错误模型本身还有随机性。这些扰动在PID框架下很难被有效抑制因为PID假设扰动是“小信号”可以通过反馈慢慢修正。但Agent遇到的扰动往往是大信号一步错就可能步步错。ADRC自抗扰控制的思路就不一样了。它不区分“内部动态”和“外部扰动”而是把所有不确定因素打包成一个“总扰动”然后用扩张状态观测器ESO去实时估计这个总扰动并在控制量里把它抵消掉。这个思路对AI Agent特别有启发与其试图精确建模Agent的每一个行为不如把模型不确定性、环境噪声、工具异常统统看作“总扰动”用一个观测器去实时估计并补偿。3. 用PID思路搭建Agent的基础反馈回路3.1 比例项快速响应任务偏差比例项的作用是“偏差越大修正力度越大”。在Agent场景里这对应的是根据任务完成度动态调整推理深度和工具调用频率。具体怎么做我给Agent设一个“任务完成度”的实时评估函数可以用规则打分也可以用一个小模型来评估。每一步动作后计算当前完成度和目标完成度的偏差。如果偏差大就增加推理步数、扩大工具调用范围如果偏差小就收敛输出。比如一个信息检索Agent目标是从多个来源找到特定数据。如果第一步搜索返回的结果相关性很低比例项就会触发“加大搜索力度”的动作——换关键词、扩大搜索范围、调用更多数据源。如果第一步就命中了高相关结果比例项就不会过度反应避免浪费资源。这里的关键是比例增益的设定。增益太大Agent会过度反应一个小的偏差就触发大量工具调用造成资源浪费和响应延迟增益太小Agent反应迟钝任务快失败了还在慢悠悠地试。我的经验是初始增益设在“中等偏差触发一次额外工具调用”的水平然后根据实际运行数据微调。3.2 积分项消除长期累积误差积分项解决的是“一直差一点”的问题。在Agent场景里这对应的是对反复出现的同类错误的累积修正。举个例子一个客服Agent在处理退款请求时总是忘记确认订单号。每次用户提到退款它就直接进入退款流程结果因为缺少订单号而失败。单次失败可能被比例项修正但如果这个错误反复出现就需要积分项来“记住”这个偏差并在后续交互中主动补偿。实现上我会给Agent维护一个“错误累积表”记录每类错误的出现频率和严重程度。当某类错误的累积值超过阈值时就在系统提示里动态插入一条针对性约束。比如“过去10次退款请求中有7次因为缺少订单号失败请在进入退款流程前务必确认订单号”。积分项的关键是防止积分饱和。如果Agent一直无法完成任务积分项会不断累积导致系统过度修正表现为Agent陷入死循环或者过度谨慎。我的做法是给积分项设一个上限超过上限就触发“任务降级”或“人工介入”而不是继续累积。3.3 微分项抑制振荡与过度修正微分项看的是偏差的变化率作用是“提前刹车”。在Agent场景里这对应的是检测行为模式的突变防止Agent在多个策略之间反复横跳。我遇到过一种典型情况Agent在调用工具A失败后切换到工具B工具B也失败又切回工具A如此反复。这就是典型的振荡。微分项的作用是检测到这种高频切换时强制Agent停下来重新评估策略而不是继续在几个失败选项之间打转。实现上我会监控Agent最近N步的动作序列计算动作变化的频率。如果频率超过阈值就触发“策略冻结”——强制Agent在当前策略上多执行几步收集更多信息后再决定是否切换。这相当于给系统加了阻尼防止它因为过度敏感而振荡。3.4 三个参数在Agent场景下的整定经验PID参数整定在控制工程里是一门手艺在Agent场景下同样如此。我总结了一个适合大多数Agent任务的整定流程步骤操作观察指标调整方向1只开比例项积分和微分置零任务完成率、响应步数增大比例增益直到出现轻微振荡2加入积分项从很小值开始稳态误差、任务完成率缓慢增大积分增益直到稳态误差消除3加入微分项从很小值开始动作切换频率、振荡幅度增大微分增益直到振荡被抑制4整体微调综合评分根据实际业务指标权衡注意Agent场景下的“振荡”不一定表现为数值波动更多表现为行为模式的反复切换。观察Agent的思考链和工具调用序列比看最终输出更能发现振荡。4. ADRC登场把模型不确定性当作“总扰动”来处理4.1 为什么PID在复杂Agent任务中不够用PID的三个参数是固定的但Agent面临的环境是动态变化的。一个在简单问答任务上调好的PID参数放到多步推理任务上可能完全不好用。更麻烦的是Agent的“被控对象”本身就在变化——模型在变、工具在变、用户意图在变。固定参数的PID控制器面对这种时变系统要么响应不足要么过度振荡。我在一个多Agent协作项目里深刻体会到了这一点。三个Agent分别负责信息收集、分析和报告生成用PID思路给每个Agent设了固定的反馈参数。结果在简单任务上表现很好一旦任务复杂度上来信息收集Agent就开始过度调用搜索工具分析Agent则陷入反复验证的循环。固定参数无法适应任务复杂度的变化。4.2 扩张状态观测器ESO的核心思想ADRC的核心创新是扩张状态观测器。它的思路是不去区分哪个是内部动态、哪个是外部扰动而是把所有不确定因素打包成一个“总扰动”然后用观测器实时估计这个总扰动的值。具体到Agent场景总扰动包括模型推理的不确定性、工具返回的噪声、用户输入的模糊性、上下文窗口的截断效应、多步推理中的误差累积。这些东西很难单独建模但可以用一个观测器来统一估计。ESO的实现不复杂。它本质上是一个状态估计器用系统的输入和输出反推内部状态和扰动。在Agent里输入是当前的动作指令输出是任务完成度的变化。通过比较“预期完成度变化”和“实际完成度变化”就能估计出总扰动的大小。4.3 把Agent的“模型不确定性”映射为总扰动映射关系是这样的Agent的推理链路可以看作一个动态系统它的“标称模型”是我们期望的理想推理过程。实际推理过程中由于模型能力限制、上下文噪声、工具异常等原因实际输出会偏离标称模型。这个偏离量就是总扰动。我通常用一个简单的状态空间模型来描述x(k1) f(x(k), u(k)) d(k)其中x是任务状态比如完成度、置信度u是控制量比如推理步数、工具调用次数f是标称动态d是总扰动。ESO的任务就是实时估计d(k)。估计出d(k)之后控制量就可以设计为u(k) u0(k) - d_hat(k)/b其中u0是基础控制量可以用PID算b是控制增益d_hat是扰动估计值。这样就把扰动补偿掉了剩下的系统就接近标称模型控制起来容易得多。4.4 自抗扰控制在Agent调度中的实际效果我在一个任务调度Agent上做了对比实验。任务是从多个数据源收集信息并生成报告数据源有快有慢返回格式也不统一。用纯PID控制时Agent在数据源响应慢的时候会频繁切换数据源导致大量重复请求在数据源返回格式异常时会卡住需要人工干预。任务完成率大约75%平均耗时12分钟。换成ADRC框架后ESO实时估计“数据源响应延迟”和“格式异常”这两个总扰动分量并在调度决策中提前补偿。Agent不再频繁切换数据源而是根据扰动估计值动态调整等待时间和重试策略。任务完成率提升到92%平均耗时降到8分钟。关键改进在于ADRC让Agent学会了“预期扰动”。当ESO估计到某个数据源当前扰动较大时Agent会主动降低对该数据源的依赖权重而不是等到失败后再被动切换。5. 手把手实现给AI Agent加上ADRC控制层5.1 环境准备与基础框架选型我用的技术栈比较轻量Python 3.10 OpenAI API或任何兼容接口的模型 一个简单的状态管理库。不需要额外的控制论库ESO和控制器都可以用NumPy手写总共不到200行代码。基础框架选的是ReAct模式因为它的“思考-行动-观察”循环天然适合嵌入反馈控制。每个循环结束后控制层会计算当前状态、估计扰动、调整下一步的推理参数。如果你用的是LangChain或类似的框架控制层可以作为callback嵌入在每次LLM调用前后执行。如果是从零搭建那就更简单了直接在循环里加一个控制模块就行。5.2 状态定义与观测器设计状态定义是ADRC实现的关键。我定义了三个核心状态变量任务完成度progress0到1之间的连续值用规则或小模型评估置信度confidenceAgent对当前输出的自信程度可以用logprob或自评估得到资源消耗率resource_rate单位时间内工具调用次数或token消耗量ESO的设计如下import numpy as np class ESO: def __init__(self, beta1100, beta2300, beta31000, b01.0): self.beta1 beta1 self.beta2 beta2 self.beta3 beta3 self.b0 b0 self.z1 0.0 # 状态估计 self.z2 0.0 # 状态变化率估计 self.z3 0.0 # 总扰动估计 def update(self, y, u, dt1.0): e self.z1 - y self.z1 dt * (self.z2 - self.beta1 * e) self.z2 dt * (self.z3 - self.beta2 * e self.b0 * u) self.z3 dt * (-self.beta3 * e) return self.z3三个beta参数决定观测器的收敛速度。beta越大估计越快但对噪声越敏感。我的经验值是beta1100, beta2300, beta31000对应大约3到5步的收敛时间。5.3 控制律设计与参数整定控制律用最简单的形式class ADRCController: def __init__(self, kp1.0, kd0.5, b01.0): self.kp kp self.kd kd self.b0 b0 self.eso ESO(b0b0) def compute(self, target, current, current_rate, u_prev): # 扰动估计 d_hat self.eso.update(current, u_prev) # 基础控制量PD形式 e target - current u0 self.kp * e - self.kd * current_rate # 扰动补偿 u (u0 - d_hat) / self.b0 return u, d_hatkp和kd的整定可以参照PID的经验但因为有ESO补偿参数可以设得比纯PID更激进一些。我通常从kp1.0, kd0.5开始根据实际响应调整。5.4 与Agent主循环的集成方式集成方式很直接。在Agent的每一步推理后调用控制器计算下一步的“控制量”这个控制量可以映射为推理步数上限工具调用超时时间重试次数温度参数上下文窗口大小def agent_step(task_state, controller, target_progress): # 获取当前状态 current task_state.progress current_rate task_state.progress_rate u_prev task_state.last_control # 计算控制量 u, d_hat controller.compute(target_progress, current, current_rate, u_prev) # 映射到Agent参数 max_steps int(np.clip(5 u * 2, 3, 20)) timeout np.clip(30 u * 10, 10, 120) temperature np.clip(0.3 d_hat * 0.1, 0.1, 0.9) # 执行Agent推理 result llm_reason(task_state, max_steps, timeout, temperature) # 更新状态 task_state.update(result) return result提示控制量的映射关系需要根据具体任务调整。关键是保持单调性——控制量增大时Agent应该更“用力”地推进任务。6. 实战踩坑记录那些文档里不会写的经验6.1 观测器带宽与噪声放大的权衡ESO的带宽越高扰动估计越快但对噪声也越敏感。我在一个项目里把beta3设到了5000结果观测器把模型输出的正常随机性当成了扰动导致控制量剧烈波动Agent行为变得极不稳定。后来我把带宽降下来同时在前端加了一个简单的低通滤波情况就好多了。经验是观测器带宽应该略高于系统的主要扰动频率但不要超过噪声频率。对于大多数Agent任务beta3在500到2000之间比较合适。6.2 控制量饱和与积分限幅的处理控制量饱和是另一个常见问题。当任务特别难或者特别简单时控制器算出的控制量可能超出合理范围。如果不做限幅Agent要么被强制跑太多步要么被限制得太死。我的做法是双管齐下一是对控制量做硬限幅二是对ESO的扰动估计做软限幅。硬限幅防止极端情况软限幅防止观测器发散。具体限幅值根据任务类型定一般推理步数限在3到20步超时限在10到120秒。6.3 多Agent场景下的耦合振荡问题多Agent协作时每个Agent都有自己的控制器如果参数不协调会出现耦合振荡。我遇到过两个Agent互相等待对方输出结果都卡住的情况。解决办法是在控制器之间加一个协调层共享扰动估计信息。当检测到多个Agent同时进入高扰动状态时协调层会降低所有Agent的控制增益让系统先稳定下来再继续推进。6.4 常见问题速查表现象可能原因排查方法解决思路Agent反复调用同一工具微分项不足或ESO估计滞后检查动作序列的切换频率增大微分增益或提高ESO带宽任务做到一半停住积分项饱和或控制量限幅过紧检查积分累积值和控制量历史放宽限幅或加入任务降级逻辑响应时间波动大比例增益过高或扰动估计噪声大观察控制量方差降低比例增益或加低通滤波多Agent互相等待控制器耦合振荡检查各Agent的扰动估计相关性加入协调层或降低控制增益简单任务过度推理比例增益过高或目标设定不合理对比任务复杂度与控制量调整目标完成度曲线或降低增益7. 从控制论到工程落地一些个人体会这套方法我在三个项目里实际用过最长的跑了六个月。最大的感受是控制论提供的不是现成的解决方案而是一种思考框架。它强迫你去定义什么是“稳定”、什么是“扰动”、什么是“反馈”而这些定义本身就是对系统理解的深化。PID和ADRC的具体实现可以很轻量但背后的系统思维很重。当你开始用“被控对象”、“设定值”、“扰动”、“反馈”这些概念去描述Agent时很多之前模糊的问题会变得清晰。比如“Agent为什么不稳定”这个问题会变成“哪个扰动分量没有被有效估计和补偿”排查起来就有方向了。另外一点体会是不要追求一步到位。先加比例项让Agent有基本的反馈能力再加积分项消除系统性偏差最后加微分项和ESO处理复杂扰动。每一步都验证效果每一步都保留回退空间。控制系统的调试本身就是迭代过程Agent也一样。最后分享一个实用技巧给控制器加一个“手动模式”开关。当自动控制效果不好时可以切到手动人工设定控制量观察Agent行为。这不仅能救急还能帮你理解控制器的盲区在哪里。我在调试初期经常用这招比盯着日志猜原因高效多了。