《AI Agent 高级编程》10.1 统一可观测:三框架 trace 归一 OTel
pydantic-ai 原生就吐 OTel span,smolagents 与 minagent 却只有各自的内部轨迹。本节在装了 opentelemetry-sdk 1.44.0 的环境里真跑:把三种来源的 trace 归一到一张中间表示,再统一 emit 成 GenAI 语义约定的 OTel span 树,并给出三框架 span 字段到 OTel 属性的逐项映射表。
category
pydantic-ai 原生就吐 OTel span,smolagents 与 minagent 却只有各自的内部轨迹。本节在装了 opentelemetry-sdk 1.44.0 的环境里真跑:把三种来源的 trace 归一到一张中间表示,再统一 emit 成 GenAI 语义约定的 OTel span 树,并给出三框架 span 字段到 OTel 属性的逐项映射表。
灰度让新旧两版代码同时在跑,于是 schema 变更不能一删了事。本节讲 expand–contract 五阶段迁移法,并在本机 Postgres 17.11 上用 psycopg 真跑一遍:加新列、双写、回填、切读、删旧列,每阶段查询验证新旧两列一致,另演示 DDL 锁表与 CREATE INDEX CONCURRENTLY。
Agent 服务的发布对象不只是代码,提示词、模型 ID、工具集、检索参数都是可发布的配置,改提示词的风险不低于改代码。本节设计按流量比例的灰度与自动回滚条件,真跑一个灰度路由器:新版正常时放量、新版劣化时按成功率/成本/P95 三个条件触发一键回滚,并演示影子流量。
本节把 ResearchCopilot 的 CI 串成一条分阶段流水线:静态检查 → 单元测试 → 评测质量门 → 构建镜像,按「快先跑、贵后跑」排序。真跑 compileall、pytest、评测夹具回放与 docker build,打印每段真实退出码,给出一键脚本与 GitHub Actions 形态。
客户端用 fetch 发 POST,检查 HTTP 状态与 Content-Type,再逐字节解码并交给有状态 SSEParser。原生 EventSource 的 GET 接口不适合这个 POST 演示。空行才派发事件;流结束时 flush TextDecoder,未终止的残帧按规范丢弃。心跳注释忽略,帧长度与总请求时间有上限;finally 释放 reader。协议中的 id/retry 与自动重连尚未实现。
本节固定 FakeModel 只回答预设加法,验证 /run、/stream 与 OpenAPI 接线,不理解任意问题。HTTP/SDK 都是核心之外的可选环境。StreamingResponse 按帧传输不等于模型增量流;complete 阶段仍等完整响应。原生 EventSource 是 GET,本节 POST 用 fetch。同步线程不因连接断开就被杀死,真实服务需另设计异步调用和取消。
本节把 minagent 从「一堆模块」整理成「一个库」:用 __init__.py 收敛出稳定的公开 API 面,用 __all__ 与下划线前缀划清公开契约与内部实现的边界,并定下 SemVer 版本策略——破坏性变更何时允许、哪些名字属于契约。最后用 import minagent as ma 真跑一段只碰公开面的稳定性示例。
训模型不是默认答案。本节给出一张「何时该训、何时调提示」的成本收益决策清单,把调提示、换模型、加工具、微调、RL 五种干预按成本与收益排开,说清每种手段的前置条件与失效场景;并配一个可运行的决策打分小脚本,对「冷启动无标注」与「有 5k 可验证轨迹」两种场景真跑排序,帮你在动手训之前先算清楚这笔账。
SFT 教会模型「格式」,RL 才教会它「取舍」。本节讲清从监督微调到强化学习的完整流程,聚焦工具使用与多步推理:对比偏好奖励与可验证奖励,说明为什么「工具调用成功、引用可回溯」这类可自动判分的信号特别适合 Agent,并给出一个真跑的离线策略打分例子——给定若干轨迹与奖励函数,算出每条的分数并选出最优。
Agent 的强化学习不是给分类任务换个 loss。本节把 Agent 的多步交互形式化成轨迹(状态、动作、奖励序列),讲清奖励设计与信用分配——到底是哪一步的功劳。配一段可运行的轨迹与奖励数据结构,以及一个按折扣回报做信用归因、再按总回报给轨迹排序的离线小例子,真跑贴输出;全程不训练模型,不编造任何实验曲线。
16.2 把服务编排起来了,但容器编排还有三件「看不见却致命」的事:配置怎么注入、健康检查怎么区分 liveness 与 readiness、收到 SIGTERM 后怎么优雅退出。本节讲清用环境变量注入配置、两条健康线各自的取舍、以及「停止接新请求→等在途 run 收尾→关连接池→退出」四步。用 uvicorn 0.54.0 + lifespan 真发 SIGTERM,贴出完整日志序列与参数表。
上一节做出了 ResearchCopilot 的镜像,但一个镜像撑不起一个服务——它还要数据库、缓存和后台 worker。本节写 docker-compose.yml 编排这四个服务,讲清 depends_on 不等于「依赖就绪」(须配 healthcheck)、同一镜像靠 command 扮演 api/worker 两种角色。真跑 config 与 ps 起栈验证。
本节是 TS 落点三的上半场:把 ResearchCopilot 打成 Python + Node 双栈镜像。用多阶段构建分别产出 TS 前端产物与 Python 依赖,只把产物拷进运行时镜像;再叠加 .dockerignore、--no-cache-dir、slim 基础镜像与 digest 固定四个瘦身杠杆。Dockerfile 在本机真构建,贴出镜像体积、分层与容器内验证的真实输出。
demo_ab.py 复用同一个评测模块,比较 max_steps 和人工设置的两版脚本。期望与评分规则保持相同,结果变化来自固定脚本行为。真实 A/B 应固定测试集和评分口径,随机分配配置、覆盖多次运行、记录模型版本与不确定性。一次一个变量有助归因,但不能代替随机化和统计检验。
本节提供完整 Case、Score、Report 和 evaluate。修订示例使用两条固定用例:检索用例与已知失败用例,不再引用旧六条统计。权重非负且和为一,评测集不能空,case id 不能重复。AgentLoopError 保留部分失败轨迹;脚本耗尽属于运行失败,其他配置错误继续向上暴露。
本节使用标准库 unittest,不要求 pytest。测试应验证工具顺序、模型实际收到的消息、受控停止和错误回灌,而不是只检查最终一句话。四个 BookTests 是教学入门用例;完整项目另有基础和扩展回归,覆盖并行、预算、恢复、批准、幂等与日志。测试通过只针对这些路径,不承诺不误报或没有其他缺陷。
8.1 的 A2A 端是 Python 写的,可真实团队里另一半服务可能是 TypeScript。协议的价值正在于此:只要双方说同一种「线上语言」,语言就不重要。本节用 TypeScript(Node 26,只用可擦除语法)实现一个 A2A 端,与 8.1 的 Python 端跨语言真跑一次双向协作:TS 服务端被 Python 客户端调用,TS 客户端又反过来调用 Python 服务端。
上一节把 A2A 跑通了,可很多人第一反应是:这跟 MCP 有什么区别?答案是它们解决的根本不是同一件事——MCP 是 Agent 连工具,A2A 是 Agent 连 Agent。本节用一张对照表把「解决什么、谁连谁、典型对象、有无状态、怎么发现、什么时候用哪个」逐项讲清,再真跑 MCP 与 A2A 两端的对象与调用,给出一张「该用哪个」的决策表与两者的组合形态。
前七章讲的是单个 Agent 怎么用框架写得更好,可真实系统往往有多个 Agent。它们怎么互相发现、怎么把活儿交出去、怎么拿回结果?这正是 A2A(Agent2Agent)协议要解决的。本节先讲清 Agent Card、Task、Message、Artifact 四个核心对象与任务状态机,再用 Python 标准库起一个 A2A 服务端、由客户端发现并委派任务,把一次真实的任务往返贴出来。
本节讲清审计日志与普通日志的区别:普通日志可丢可采样,审计日志必须完整、必须能证明没被改过。用追加式加哈希链实现审计日志,每条记录带前一条的哈希,改任何一条都会让链断掉。真跑:写 5 条记录、校验链完整、篡改中间一条再校验,打印「第 N 条校验失败」,并给出审计字段表与保留策略表。