附录 E:上线与运维清单
Agent 从 demo 走到生产,真正考验的不是“能不能回答一次”,而是“能不能长期、可控、可回滚地服务用户”。这份清单把全书的生产化内容压成一个上线前后都能用的 playbook。
它的用法和正文不同:正文是讲原理,这份附录是给你勾的。建议把它复制进你团队的 Wiki,每次发布前对照过一遍;每个条目都尽量给了“为什么这么做、具体怎么做、常见坑”,而不只是一句口号。全篇以一个跨境电商场景贯穿:一个帮卖家做选品与竞品调研的 Research Agent——它会抓竞品链接、查关键词热度、算利润空间、写调研简报,跨多个工具、跑几分钟甚至几十分钟,还要面对多个卖家租户。这类 Agent 恰好把上线运维的所有难点都占全了:长任务、高成本、外部依赖多、多租户。
上线前:先定运行形态
先回答它以什么方式运行——形态决定了你后面所有的隔离、鉴权、成本策略:
| 形态 | 适合场景 | 重点风险 |
|---|---|---|
| 本地 CLI | 个人/小团队、编码 Agent、内部工具 | 凭据散落、危险命令、不可复现 |
| Web/RPC 服务 | SaaS、后台任务、远程驱动 | 鉴权、会话隔离、断线恢复 |
| 后台 Agent | 长时间调研、定时巡检、异步处理 | 成本失控、任务挂死、状态不可见 |
| 多租户平台 | 多客户、多团队共享 | 数据隔离、资源公平、审计合规 |
pi 的优势是同一套运行时可以长出 CLI、Web/RPC 和后台形态;但越靠近公网和多租户,越要把安全、预算、可观测性前置。
为什么形态要先定? 因为它决定了“错一次的代价”。本地 CLI 里一个 Research Agent 跑飞了,最多烧掉你自己的 key;同样的逻辑放到多租户 SaaS 上,一个租户的调研任务写坏了共享缓存,可能污染所有卖家的选品结果。我们的 Research Agent 大概率是“Web/RPC 服务 + 后台 Agent”的组合:卖家在前端点“帮我调研这个类目”,请求进入队列,后台 Agent 异步跑几分钟再回填结果。这意味着从第一天起就要面对断线恢复(卖家关了浏览器,任务不能死)和成本失控(一次调研可能触发几十次模型调用和上百次抓取)。后台形态的细节见第 33 章。
踩坑:最常见的错误是“demo 用 CLI 形态写,上线直接套一层 HTTP 就发”。CLI 里默认信任本机、凭据放环境变量、危险命令随便跑——这些假设一旦暴露到公网就是漏洞。形态切换时,安全与隔离假设必须重新过一遍,不能沿用。
上线前的准入门槛
不是“功能做完了”就能上线,而是“这些指标达标了”才能上。给 Research Agent 设一组硬性准入门槛(go/no-go gate),任何一项不达标就不发:
| 门槛项 | 达标标准(示例,按业务调) | 为什么 |
|---|---|---|
| 回归通过率 | 核心任务集(附录 D)通过率 ≥ 95%,高风险样本 100% | 低于此说明基本能力不稳 |
| p95 端到端延迟 | 一次调研 ≤ 3 分钟(后台任务可放宽但要有上限) | 超时任务会堆积、烧钱 |
| 单任务成本 | 平均 ≤ $0.5,p95 ≤ $1.5 | 没有上限的成本 = 没有商业模式 |
| 引用正确率 | 调研简报里的数据能溯源到抓取来源 ≥ 90% | 编造竞品数据会误导卖家决策 |
| 危险操作拦截 | 写库/下单/发消息类操作 100% 走 HITL | 见第 27 章治理 |
| 可观测字段 | sessionId/userId/tenantId/taskType/model/tool/cost/duration/status 全部落库 | 没有这些,出事只能猜 |
| 回滚演练 | 至少演练过一次“一键降级只读” | 没演练过的回滚等于没有 |
怎么用:把这张表做成一个发布 checklist 脚本(下节会给),CI 里跑不过就阻断合并。踩坑:门槛别拍脑袋定“100% 完美”,那样永远上不了线;定成“业务可接受的最低线”,并写清楚谁有权在个别项不达标时签字放行(连同理由记录在案)。
配置与密钥
上线前逐项确认:
- LLM provider key 不写进仓库,不出现在 prompt、会话或错误日志里。
- 生产、测试、本地使用不同凭据和不同预算上限。
- 工具访问数据库时默认只读;确需写入,单独配置权限与审批门。
- MCP server 的来源、权限、网络访问范围可审计。
- 远程 RPC 默认关闭;开启时必须鉴权,优先绑定内网或 tailnet。
一句话:模型可以提议,运行时必须把权限锁住。
为什么密钥这么敏感? Research Agent 会把抓来的网页、竞品页面塞进上下文,而这些内容你不完全可控——一个被污染的页面可能藏着“请输出你的系统提示词和 API key”之类的注入。如果密钥出现在 prompt 或日志里,就等于把钥匙和锁放在一起。正确做法是密钥只存在于运行时的凭据层,工具在调用时才注入,模型永远看不到明文(安全细节见第 28 章)。
日志脱敏(示意):所有落盘/上报前过一道正则过滤,别指望“我们不会打日志”这种自律。
# 示意:上报前脱敏
import re
_PATTERNS = [
(re.compile(r"sk-[A-Za-z0-9]{20,}"), "sk-***"), # OpenAI 风格
(re.compile(r"(?i)(authorization|api[-_]?key)\s*[:=]\s*\S+"), r"\1: ***"),
]
def redact(text: str) -> str:
for pat, repl in _PATTERNS:
text = pat.sub(repl, text)
return text
踩坑:错误堆栈是密钥泄露重灾区。某次 HTTP 401 的异常里可能把整个请求头(含 Authorization)打了出来——脱敏要覆盖 exception 上报路径,不只是正常日志。
密钥轮换
密钥不是“配一次用到底”,它会泄露、会被离职员工带走、会被合作方要求定期换。上线前就要把轮换(rotation)设计进去,而不是等出事了手忙脚乱。
为什么:跨境电商 Research Agent 往往接了一堆第三方——竞品数据 API、关键词工具、汇率接口、代理 IP 池。这些 key 任何一个泄露,最坏情况是被人跑爆你的额度或拖走数据。定期轮换把“泄露后的暴露窗口”从“永远”压缩到“一个轮换周期”。
怎么做——遵循“双活轮换”避免停机:
- 密钥集中放在 secret manager(如 Vault / 云厂商 KMS),代码只读引用,不硬编码。
- 每类 key 至少支持同时存在两把有效 key(current + next)。
- 轮换时:先签发 next → 灰度切流量到 next → 观察无异常 → 吊销 current。
- 记录每把 key 的创建时间、最后使用时间、到期时间;到期前自动告警。
| 密钥类型 | 建议轮换周期 | 触发立即轮换的信号 |
|---|---|---|
| LLM provider key | 90 天 | 成本异常飙升、疑似泄露 |
| 第三方数据 API | 90 天 | 供应商通知、额度异常 |
| 数据库/内部服务凭据 | 30–90 天 | 员工离职、权限变更 |
| 任意 key | 立即 | 出现在日志/仓库/工单截图里 |
踩坑:最疼的是“轮换时忘了某个后台任务还在用旧 key”,导致跑了一半的调研任务集体 401。所以吊销 current 前,务必确认没有正在运行的长任务还持有它——这也是为什么要记录“最后使用时间”。
依赖与模型版本管理
Agent 的行为不只取决于你的代码,还取决于它依赖的模型和外部服务——而这些你往往控制不了。模型供应商悄悄更新了版本、某个 MCP server 改了返回格式、竞品网站改版导致抓取失效,都会让“昨天还好好的” Agent 今天翻车。
为什么要钉版本:如果你的 prompt 是针对 some-model-2026-06 调优的,供应商把 latest 指向了新版本,你的调研简报格式可能一夜之间就变了样。永远钉具体版本号,不要用 latest。
怎么做:
- 模型:钉到带日期的具体版本;升级新版本走灰度(见下节),并在评测集(附录 D)上先跑一遍回归。
- 外部依赖:给每个 MCP server / 第三方 API 记录版本或 commit,并写契约测试——用固定输入验证返回结构没变。
- 抓取目标:竞品页面结构会变,抓取器要能检测“解析失败率突增”并告警,而不是默默返回空数据。
# 示意:pin 依赖清单,纳入版本控制
models:
research_writer: anthropic/claude-x-2026-06-01 # 不用 latest
cheap_summarizer: some-provider/mini-2026-05-10
mcp_servers:
keyword-tool: { version: "1.4.2", contract_test: tests/keyword_contract.py }
competitor-scraper: { version: "0.9.0", contract_test: tests/scrape_contract.py }
踩坑:模型升级往往“能力更强但格式微变”。别假设新模型一定更好——它可能更啰嗦、更爱加免责声明,把你精心设计的结构化输出撑破。升级必须过评测,不能凭“听说新版更强”就直接切。
发布前检查
| 类别 | 检查项 | 对应章节 |
|---|---|---|
| Context | 长会话会压缩吗?压缩摘要保留关键事实吗? | 第 9 章 |
| Memory | 长期记忆有提炼、遗忘和用户可控入口吗? | 第 10 章 |
| RAG | 检索有来源、版本、score,回答会引用依据吗? | 第 11 章 |
| State | 会话/任务状态可恢复、可寻址、可回放吗? | 第 11、23 章 |
| Tools | 工具最小权限、参数 schema 清楚、错误结构化吗? | 第 4 章 |
| HITL | 高风险动作会暂停并请求人工确认吗? | 第 7、26 章 |
| Observability | 会话、token、成本、工具调用都可追踪吗? | 第 25 章 |
| Budget | 单会话、单用户、单租户有预算上限吗? | 第 26 章 |
| Security | 不可信执行有容器/微 VM/受限环境吗? | 第 28 章 |
| Multi-tenant | 数据、执行、资源、配置四层隔离了吗? | 第 29 章 |
| Evaluation | 核心任务集与高风险样本跑过回归吗? | 附录 D |
这张表应成为第 35 章综合实战的发布门槛。把它做成一个能在 CI 里跑的脚本,比贴在墙上更可靠:
#!/usr/bin/env bash
# 示意:发布前门禁脚本,任一失败即退出非零,阻断发布
set -euo pipefail
echo "== 1. 回归评测 =="
npm run eval:core # 附录 D 核心任务集,通过率写入 report.json
node scripts/check_threshold.js report.json --pass-rate 0.95
echo "== 2. 密钥扫描 =="
git grep -nE 'sk-[A-Za-z0-9]{20,}' && { echo "发现疑似密钥!"; exit 1; } || true
echo "== 3. 模型版本已 pin =="
grep -q 'latest' config/models.yaml && { echo "禁止使用 latest"; exit 1; } || true
echo "== 4. 回滚开关就绪 =="
node scripts/check_kill_switch.js # 确认降级开关可用且已演练
echo "全部通过,可以发布 ✅"
踩坑:检查项写在文档里没人看,写成人工 checklist 会被“这次赶时间先跳过”。凡是能自动化的门禁,尽量做进 CI;实在要人工判断的(如“这次改动风险有多大”),也要留签字记录。
灰度与金丝雀发布
新版本别一次切 100% 流量。Research Agent 尤其危险——它的问题往往不是“崩溃”(那反而好发现),而是悄悄变差:简报开始编数据、成本悄悄涨、某类目调研突然跑偏。这些只有在真实流量下才暴露,所以要用金丝雀(canary)小流量先探路。
怎么做——按比例放量,每一档都设自动回滚条件:
| 阶段 | 流量比例 | 观察时长 | 放行标准 | 自动回滚条件 |
|---|---|---|---|---|
| 金丝雀 | 1% | 30 min | 无错误率上升、成本平稳 | 错误率 > 基线 2×,或单任务成本 > 基线 1.5× |
| 小灰度 | 10% | 2 h | 引用正确率、延迟不劣化 | 引用正确率下降 > 5pp |
| 大灰度 | 50% | 半天 | 各租户表现一致 | 任一租户错误率异常 |
| 全量 | 100% | 持续观察 | — | 触发即降级到旧版本 |
关键:灰度要能按租户/用户维度切,而不只是按请求随机切。把大客户放在后面的档位,先用内部账号和小卖家试。同时保证同一用户在观察期内落在同一版本,否则体验会闪烁、指标也没法归因。
// 示意:按 tenant 做稳定分桶的金丝雀路由
function pickVersion(tenantId: string, canaryPct: number): "new" | "old" {
const bucket = hashToUnit(tenantId); // 0..1 稳定哈希
return bucket < canaryPct ? "new" : "old"; // 同租户始终同版本
}
踩坑:金丝雀期太短是常见错误。Agent 的成本和质量问题有滞后性——一个“偶尔多绕三轮”的 bug,1% 流量跑 5 分钟看不出来,要跑够样本量才显形。宁可慢一点。
上线前明确谁对能力结果负责
Agent 上线不是工程团队把服务交给运维就结束了。一项进入经营流程的 AI 能力,需要一个明确的 Capability Owner(能力负责人),持续协调业务、产品、工程、数据和风控,并对“是否继续运行、何时扩量、何时降级”作出决定。
| 角色 | 持续责任 |
|---|---|
| 业务负责人 | 确认经营目标、流程边界和结果基线,判断业务结果是否成立。 |
| 能力负责人 | 维护路线图、指标、自动化等级和扩量节奏,组织定期复盘。 |
| 工程与平台团队 | 保障运行时、上下文、工具、观测、成本和发布质量。 |
| 数据 / 知识负责人 | 维护数据口径、知识版本、更新频率与访问权限。 |
| 风控与安全 | 审批高风险动作的策略、权限、审计和事故响应机制。 |
建立固定的能力复盘节奏
上线初期可按周复盘,稳定后按月复盘。每次至少检查五组信息:经营结果、流程效率、AI 质量、风险事件、单位有效结果成本。复盘输出不是一份汇报,而是明确的动作:新增回归样本、调整上下文、修改策略、降低或提高自动化等级、暂停某类任务,或扩展到下一个流程。
没有负责人和复盘节奏,Agent 只是一项会上线也会逐渐失真的软件功能;有了持续运营机制,它才可能成为企业长期积累的 AI 能力。
Day-2 运维:该看哪些信号
上线后真正要盯的是这些信号:
- 成本:按用户、租户、任务、模型、工具聚合。
- 延迟:模型调用、工具调用、RAG 检索、多 Agent 子任务分别看。
- 失败率:工具错误、模型限流、策略拒绝、人工审批超时分开统计。
- 质量:核心任务评测分数、RAG 引用正确率、用户重试率。
- 安全:被阻止的危险工具调用、越权访问尝试、异常租户流量。
- 队列:后台 Agent 的等待时间、运行时长、取消/重试次数。
如果只能先做一件事,就先把 sessionId/userId/tenantId/taskType/model/tool/cost/duration/status 这些字段打通。没有这些维度,出了事只能猜。
Agent 特有的、传统监控看不到的信号,尤其要盯:
- 单任务工具调用次数 / 推理轮数:Research Agent 正常一次调研调 10–20 次工具就够了。如果某类任务突然平均调 80 次,多半是陷入了循环或反复重试——这是成本失控最早的征兆,比看账单快得多。
- 上下文 token 增长曲线:长任务的上下文会不会无限膨胀?压缩(第 9 章)有没有真的生效?
- 工具错误的“结构”:是外部依赖挂了(抓取超时),还是模型传了非法参数(schema 不匹配)?前者是运维问题,后者是 prompt/工具描述问题,处理路径完全不同。
- “看似成功”的失败:任务 status=success 但产出是空简报或全是免责声明。这类最阴险,靠 status 发现不了,得靠质量评测和用户重试率兜底。
怎么做:把这些聚合成两三块看板——一块成本/延迟(给运维盯),一块质量/安全(给产品和风控盯),一块队列健康(给后台任务盯)。可观测的完整方案见第 25 章。踩坑:只加了 dashboard 没加告警,等于让人 24 小时盯屏——没人做得到。看板是用来事后排查的,实时靠告警。
监控告警:阈值该怎么定
告警的艺术在于既不漏报也不吵。一份可直接抄的起步阈值表(数值按你的基线调):
| 信号 | 告警阈值(示例) | 级别 | 建议动作 |
|---|---|---|---|
| 单任务成本 | > p95 × 2,或 > $2 | 高 | 检查是否循环;必要时熔断该任务类型 |
| 小时总成本 | > 日预算 / 10 | 高 | 触发成本护栏,见下节 |
| 工具错误率 | 5 min 内 > 10% | 高 | 看是外部依赖还是 schema 问题 |
| 模型限流(429) | 5 min 内 > 5% | 中 | 降级到备用 provider / 排队 |
| p95 端到端延迟 | > 基线 × 1.5 | 中 | 查队列积压、外部依赖变慢 |
| HITL 审批积压 | 待审 > 20 或最久等待 > 30 min | 中 | 通知审批人,防任务饿死 |
| 引用正确率 | 日均 < 85% | 中 | 质量回归,考虑回滚模型版本 |
| 被拦截危险操作 | 单租户 5 min 内 > 3 次 | 高 | 疑似注入攻击或异常租户,见第 28 章 |
| 队列等待时长 | p95 > 5 min | 中 | 扩容 worker 或限流入口 |
为什么要分级:高(P1)要电话叫醒人,中(P2)进工作时段处理,别把所有告警都设成 P1——那样很快就没人当回事(告警疲劳)。怎么定阈值:先用相对基线(“× 2”“× 1.5”),跑一两周积累数据后再收紧。踩坑:绝对阈值(“成本 > $2”)在业务量波动时容易误报或漏报,尽量用“相对基线 + 绝对兜底”两条一起。
成本护栏与熔断
Research Agent 是“烧钱型”Agent——一次调研 = 多次模型调用 × 大上下文 + 大量抓取。没有护栏,一个 bug 或一次滥用就能在几小时内烧掉一个月预算。护栏要分层,且能自动动手,不能只告警。
四层预算上限(预算体系见第 26 章):
| 层级 | 上限(示例) | 超限动作 |
|---|---|---|
| 单任务 | $2 / 任务 | 硬停该任务,标记 budget_exceeded,返回已有的部分结果 |
| 单用户 | $20 / 天 | 该用户后续任务排队或拒绝,提示升级套餐 |
| 单租户 | $500 / 天 | 该租户降级到便宜模型 + 限并发 |
| 全局 | $5000 / 天 | 触发全局熔断,只保留付费大客户 |
熔断(circuit breaker)示意——超限时自动降级而非直接报错:
# 示意:任务执行前的成本护栏检查
def before_task(ctx):
if spent(ctx.task_id) > TASK_CAP:
raise BudgetExceeded(partial=collect_partial_result(ctx)) # 返回半成品,别白烧
if spent_today(ctx.tenant_id) > TENANT_CAP:
ctx.model = CHEAP_MODEL # 降级模型
ctx.max_tool_calls = 8 # 收紧循环
if global_spent_today() > GLOBAL_CAP:
if not ctx.is_priority_tenant:
raise CircuitOpen("全局成本熔断,非优先租户暂停")
为什么要返回半成品:一次跑到 $2 才被砍的调研任务,如果直接丢弃就是纯亏损;把“已抓到的竞品数据 + 已完成的部分分析”返回给卖家,钱至少没白花。踩坑:熔断一定要有手动恢复开关和冷却期——别做成“熔断→自动重试→立刻又熔断”的死循环。恢复要人工确认根因已解决。
回滚与降级
Agent 系统的回滚不只是回代码,还包括:
- 回滚 prompt / instructions 版本。
- 回滚工具描述、schema 或权限配置。
- 回滚模型 provider 或模型版本。
- 回滚 RAG 索引版本。
- 暂停高风险工具,只保留只读能力。
- 把自动执行降级为 human-in-the-loop。
- 关闭后台任务入口,只保留交互式模式。
每一次上线都应该知道:“如果 10 分钟后成本暴涨或错误率上升,我先关哪个开关?”
为什么 Agent 回滚比普通服务难:普通服务回滚就是回代码镜像;Agent 的“行为”分散在代码、prompt、工具描述、模型版本、RAG 索引五处,任何一处变了都可能翻车,所以要能分别回滚。把这些都做成运行时可切的开关(feature flag / 配置中心),而不是每次都重新发版——出事时你没时间等 CI。
降级开关表(一份能贴在事故 runbook 里的对照表):
| 开关 | 打开后的效果 | 何时打开 |
|---|---|---|
readonly_mode | 禁用所有写操作,只保留调研/查询 | 疑似数据被写坏、越权 |
force_hitl | 所有敏感操作强制人工确认 | 质量下滑、疑似注入 |
cheap_model_only | 全量切便宜模型 | 成本告警、贵模型限流 |
disable_background | 关后台任务入口,只留交互模式 | 队列雪崩、成本失控 |
pin_prev_prompt | prompt/instructions 回退上一版 | 新 prompt 导致行为异常 |
pin_prev_model | 模型版本回退 | 新模型版本质量劣化 |
freeze_rag_index | RAG 锁回上一个索引版本 | 新索引导致检索跑偏 |
回滚触发条件(配合灰度和告警,能自动触发的就别靠人反应):
- 金丝雀阶段错误率 > 基线 2×,或单任务成本 > 基线 1.5× → 自动回滚版本。
- 引用正确率日均跌破 85% → 回退模型/prompt 版本,同时开
force_hitl。 - 全局小时成本 > 日预算 1/8 → 开
cheap_model_only+disable_background。 - 被拦截危险操作在单租户内激增 → 对该租户开
readonly_mode并告警风控。
踩坑:没演练过的回滚等于没有回滚。 上线前务必真的按一次每个开关,确认它生效、且不会引发次生故障(比如切便宜模型后 schema 输出撑破反而错更多)。这也是准入门槛里“回滚演练”那一项存在的原因。
事故响应与 Runbook
告警响了、开关有了,还缺一样:出事时按什么流程走。别指望临场发挥——凌晨三点被叫醒的人需要一份能照着做的 runbook。
通用响应流程(示意,贴进值班文档):
1. 止血(分钟级):先按降级开关表把伤害止住,不急着找根因。
—— 成本失控 → cheap_model_only + disable_background
—— 行为异常 → force_hitl 或 pin_prev_prompt / pin_prev_model
—— 疑似安全 → readonly_mode,通知风控(第 28 章)
2. 定级:判断 P1(影响付费大客户/资金/数据)还是 P2,拉对应的人。
3. 定位:用会话回放(第 24 章)找到出错的那一步——是模型、工具、
还是外部依赖?看是哪次上线/版本引入的。
4. 修复 + 灰度重新放量:别直接全量,走一遍金丝雀。
5. 复盘:写事故报告,产出可执行的改进项(新告警、新护栏、新回归样本)。
为什么止血优先于定位:卖家的调研任务在批量编造数据、成本在飙升,这时先关阀门比“搞清楚为什么”重要得多。定位可以事后慢慢做,损失止不住就是真金白银。
Runbook 里每类典型事故都该有一页,至少覆盖:成本暴涨、模型限流/供应商宕机、抓取依赖大面积失效、疑似 prompt 注入、某租户数据疑似泄露。每页写清:征兆是什么、先按哪个开关、找谁、怎么验证已恢复。踩坑:runbook 写完就烂在文档里没人更新。规定每次事故复盘必须回头改 runbook,否则下次还是抓瞎;而复盘产出的新回归样本要加进附录 D 的评测集,让同一个坑不会踩第二次。
实验清单
读完全书后,可以用这 10 个小实验检验自己是否真的掌握了 Harness 思路:
- 给 pi 注册一个只读业务工具,并写清工具描述与参数 schema。
- 接一个 MCP server,让 Agent 使用外部通用工具。
- 写一个 Skill,把你的领域流程封装成
SKILL.md。 - 用 Hook 拦截危险命令,要求人工确认。
- 做一个
search_knowledgeRAG 工具,返回text/source/score。 - 跑一次长会话,观察 compaction 前后上下文变化。
- 用会话文件回放一次失败任务,定位是哪一步出错。
- 给同一个任务跑便宜模型和贵模型,比较成本与质量。
- 把一个会话通过 RPC 远程驱动,并验证断线恢复。
- 挑 20 个真实任务样本,做成附录 D 风格的回归集。
完成这些实验,比背 API 更重要。API 会变,生产 Agent 的控制点不会变。
一条总原则
上线不是把 Agent 放到服务器上,而是把它放进一套能观测、能限制、能回滚、能持续评测的运行体系里。
没有这套体系,demo 越聪明,生产风险越大。再浓缩成一句可以背下来的操作口诀:先能看见(可观测),才敢放量(灰度);先能踩刹车(护栏与降级),才敢上路(发布);先演练过回滚,才算真的上线。