← 返回目录

第 1 卷

第 1 / 4 卷日期:

21|Agent 的规则进化:评测与闭环

张嘉熙 · DeepSeek Harness 前沿工程实践

图 1-1
图 1-1
图 1-2
图 1-2
图 1-3
图 1-3
图 1-4
图 1-4

你好,我是张嘉熙。

上一讲,候选规则已经能靠模型从异常请求里长出来了,但长出来还不等于能上线:得先评测它拦得准不准、会不会误伤;上了线,撞上不断变化的真实流量,它还得能持续修正自己。这

一讲补的,就是长出来之后的这后半程。

要看清这一讲,回答两个问题:

候选规则怎样被评测?

规则怎样持续进化、形成闭环?

(feedMissedToEvidence)都是确定性代码,均已写进工具代码。npm run start:ch21 即可运行(示例只演示纯领域逻辑 + 真实模型,不挂后台任务、不落盘)。

本讲新用到一个 dsh 机制:ctx.setInterval(timer 插件的 effect 版定时器,即第 01 讲手动 ctx.effect 包 setInterval 的插件版),生命周期状态落盘复用第 19 讲账本

那套机制(shared/fact-log.ts),这两处都在组合包里(loop-task.ts / looplog.ts);评测、确认、状态机是纯领域逻辑。

关于示例:npm run start:ch21 一次跑通完整收尾流程,真实运行输出见正文末尾。

候选规则怎样被评测:模拟、阴影、灰度

评测不是一次性的,而是三种测试一步步逼近真实拦截,模拟测试(拿历史样本回放)、阴影测试(不影响真实环境)、灰度测试(小范围真实拦截)。

图 2-1
图 2-1

模拟测试:历史流量回放

候选长出来了,先得过评测这一关。评测要回答的是一些很具体的问题:这条规则在历史流量

先看一条具体的候选规则:第 20 讲长出的 block admin probe,它的 when 条件是 path starts_with /admin,也就是路径以 /admin 开头就算命中。模拟测试的做法,

是把历史流量文件 traffic.sample.log 里的 19 条请求(10 条正常、9 条异常)逐条交给这条规则判定:用第 18 讲的纯函数引擎对每条请求求值 when,看它命中哪些。结果它命

中 2 条 /admin 请求,两条都是异常请求。评测先看这三个数:

覆盖率(coverage):拦住了多少异常?9 条异常请求里它拦到 2 条 /admin,覆盖率 = 拦住的异常 2 / 异常总数 9 ≈ 22.2%。

精确率(precision):拦下的总数里,多少是异常?它拦下的 2 条全是异常、误伤 0 条,精确率 = 拦住的异常 2 / 拦下的总数(2 拦对的 + 0 误伤的)= 100%。

误伤率(false positive rate):误伤的正常请求占正常总数的多大比例?10 条正常请求它一条没碰,误伤率 = 误伤的正常 0 / 正常总数 10 = 0%。

这三个数怎么用?覆盖率高且误伤率低才算拦得好:覆盖率高但误伤率也高,等于“宁可错杀”:拦了真用户比漏掉攻击更易被发现,也更伤信任;覆盖率低但误伤率为零,等于“没拦住

任何请求”,也没用。

用覆盖率和误伤率这两个数看候选规则,最先暴露的是“长得像规则、实际不拦任何请求”的那些,它们的覆盖率都是 0。

block sqli:注入特征藏在查询参数的值里;回放时 ? 之后的内容被拆进 query 字段、path 只剩主干,所以它写死的 path contains "'--" 一次都匹配不到,细节见

输出块里的 ⚠(代码在小结前)。

block ua scanner:when 是按脱敏后的归类值生成的(第 20 讲脱敏把 UA 归成 scanner / browser 这类),而引擎匹配的是原始 ua 字符串(如 python-

requests/2.28),两者对不上,覆盖率同样是 0。

这三个数是怎么算出来的?我们来看 evaluate.ts 的核心(精简自源码)。四个计数器分

全站分卷索引 · 4 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷