news 2026/10/18 8:34:43

用PID与ADRC控制论方法构建高稳定AI Agent

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用PID与ADRC控制论方法构建高稳定AI Agent

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(k+1) = 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实现的关键。我定义了三个核心状态变量:

  • 任务完成度(progress):0到1之间的连续值,用规则或小模型评估
  • 置信度(confidence):Agent对当前输出的自信程度,可以用logprob或自评估得到
  • 资源消耗率(resource_rate):单位时间内工具调用次数或token消耗量

ESO的设计如下:

import numpy as np class ESO: def __init__(self, beta1=100, beta2=300, beta3=1000, b0=1.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, dt=1.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越大,估计越快,但对噪声越敏感。我的经验值是beta1=100, beta2=300, beta3=1000,对应大约3到5步的收敛时间。

5.3 控制律设计与参数整定

控制律用最简单的形式:

class ADRCController: def __init__(self, kp=1.0, kd=0.5, b0=1.0): self.kp = kp self.kd = kd self.b0 = b0 self.eso = ESO(b0=b0) 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_hat

kp和kd的整定可以参照PID的经验,但因为有ESO补偿,参数可以设得比纯PID更激进一些。我通常从kp=1.0, kd=0.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行为。这不仅能救急,还能帮你理解控制器的盲区在哪里。我在调试初期经常用这招,比盯着日志猜原因高效多了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 0:52:02

diagram-design 图表设计体系:从协作基础设施到图表即代码的工程实践

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题第一次听到 “diagram-design” 这个词,很多人会以为它只是“画图工具”或者“设计图表”的泛称。但真正在项目里摸爬滚打过的人知道,它指的是一套完整的图表设计体系——从需求拆…

作者头像 李华
网站建设 2026/10/17 22:46:22

Python的调试技巧

415 Python的调试技巧 代码写错了怎么办?新手通常是到处加print()看结果。但专业的程序员有更高效的调试方法。 今天我们就来学学Python的调试技巧——从入门到进阶,帮你快速定位和修复bug。 一、print调试法 1.1 最基础的方法 def calculate_total(items):total = 0for…

作者头像 李华
网站建设 2026/10/16 19:12:51

Python双森林融合故障诊断:随机森林+孤立森林实战

1. 项目概述:为什么工业设备故障诊断需要“双森林”协同?在工厂产线巡检现场,我见过太多这样的场景:振动传感器数据连续三天显示“正常”,第四天凌晨设备突然抱死,轴承碎裂,整条产线停摆8小时—…

作者头像 李华
网站建设 2026/10/17 19:53:46

为Claude Code Subagent定制终端状态栏:基于JSONL日志与tmux集成

用了大半天的 Claude Code 并行任务,我一直在等一个反馈:到底还有几个 Subagent 在跑,哪个子任务快收尾了,哪个卡了很久没动静。默认界面并非没有信息,但它们都埋在日志和事件流里,你得切出去翻会话文件才知…

作者头像 李华
网站建设 2026/10/16 18:49:08

无可挑剔的交付标准:四层复查机制与落地方法

“impeccable”这个词,我最近在我们团队内部听到的频率特别高。起因是一个老同事在复盘时半开玩笑地说:客户夸你“无可挑剔”,未必是真觉得你完美,只是不想当面指出你的错。这个说法听上去扎心,但确实点中了一个长期被…

作者头像 李华
网站建设 2026/10/18 4:04:28

C#读取CSV与Excel选型指南:从编码解析到流式读取的避坑实践

简介:这份资源面向具备一定C#基础的开发者,聚焦于使用C#读取CSV与Excel文件这一常见数据处理需求,帮助解决表格数据导入、解析与界面展示中的实际问题。压缩包内共1个doc文档,约34KB,以文字讲解配合代码示例的形式呈现…

作者头像 李华