Skip to main content
适用于自行编写的 agent,或 Failproof AI 尚无适配器的框架。无需额外配置——你只需直接发出事件。 这与四个框架适配器底层调用的 API 完全相同。适配器不过是对它的封装映射表。

安装

无额外依赖。

埋点

从上到下读,含义一目了然: 每个作用域实际发出的事件: 内部的所有内容都可以省略 session_idagent_id。作用域将身份信息绑定在上下文变量上,每次事件调用都会自动读取,无需在函数间手动传递 id。 三者均支持 async with 和普通 with 嵌套 agent 会构建树形结构。parent_id 和深度由调用栈自动计算:

作用域如何关闭

agent() 会自动处理异常: error 事件在 agent_end 之前发出,因为 dashboard 在收到 agent_end 时关闭 span,之后的事件将无法归属。取消不是失败,因此已取消的运行不会污染错误面板。异常始终会被重新抛出——作用域永远不会吞掉异常。

事件方法

六个类别,共十五个方法。大多数成对出现——你发出开启事件,再发出关闭事件,SDK 会测量两者之间的时间跨度。
在适用的场景下优先使用作用域——agent()tool_call()。它们能保证即使函数体抛出异常也会发出关闭事件。只有当控制流无法嵌套时(例如在辅助函数内的模型调用)才直接使用这些方法。
两组人工事件的方向相反。没有任何框架会发出第二对事件,因此始终需要你自己发出。
并发执行模型调用时请传入 request_id 若不传,请求和响应将按每个 agent 的到达顺序配对——并发调用会错配,将每个响应关联到错误的请求。

示例

一个基于 OpenAI API 的工具调用循环,不使用任何 agent 框架:
这将产生与适配器相同的六种事件类型。包含工具定义的完整可运行版本位于 SDK 仓库的 docs/manual/examples/ 目录下。

线程与异步

上下文变量会自动传播到 asyncio 任务中,但不会传播到新线程——线程启动时上下文为空。
不使用 propagate() 时,worker 的事件会抛出 TypeError 并提示修复方法,而不是静默地落到没有 session 的状态。这是有意为之:没有 session 的事件在摄入时会被跳过并返回 200,这正是身份层要防止的静默失败。

为没有适配器的框架添加埋点

每个 agent 框架都提供相同的三个切入点。映射它们即可获得完整的追踪——四个内置适配器也仅此而已。
1

包裹运行

2

包裹每次工具调用

在框架的工具包装器或中间件中添加。
3

配对每次模型调用

有值得观测的节点、步骤或中间件边界? 用 hook 对——hook_triggered / hook_completed——来包裹,而不是嵌套的 agent()agent_id 是低基数维度,每个节点一个条目会使其失效。Hook span 的渲染方式相同,还能提供每节点的延迟数据。
手动埋点与自动埋点可以组合使用。 在手动编写的作用域内运行的适配器会加入该 session 并以该 agent 为父节点,从而形成一棵树而非两棵——当你同时对一个框架手动埋点和使用受支持的框架时非常有用。
原因有两点,而上述三个切入点正是对这两点的解答:
  • autogen-core 自 2025 年 9 月起已停止维护。
  • AG2 没有提供等同于其他框架 hook 的全局注册点,因此要对其埋点就意味着在每个构建位置包裹每个 agent。
手动映射这三个切入点所记录的事件,与内置适配器的保真度完全相同。

深入了解

记录机制的工作原理。入门时无需了解这些内容。
每次记录的结构相同:一个 span 开启,工作嵌套在其中,每个开启事件都有对应的关闭事件。事件对是基本单元。每个关闭事件携带 SDK 从对应开启事件起测量的持续时间。以下是每个框架各一次真实运行的记录——来自 SDK 附带的示例,模型名称已统一。注意单次调用能带回多少信息。
14 events
节点转换为 hook 对,因此你可以获得每节点的延迟,而不会使 agent 列表过于拥挤。
没有 session 结束事件。 session 不是你去关闭的东西——它是一组共享同一 session_id 的事件。状态由追踪的形态推导:因此,当所有事件对都关闭时,session 即结束。适配器会为你发出 agent_end,并在拆卸时关闭所有仍打开的 span 并标记为未完成——崩溃的运行会以 done 状态结算,留下一个可见的缺口,而不是永久挂起。
这就是为什么一个 session 可以跨两次调用。LangGraph 的 interrupt() 会暂停运行,根 span 故意保持打开状态,由后续恢复调用来关闭它。两次调用属于同一个 session。
session_idagent_id 在每个事件方法中都是可选的。省略时,它们从外层作用域解析:
显式传入仍然有效且优先级更高。若既没有绑定作用域也没有传入参数,调用会抛出 TypeError 并提示修复方法,而不是发出一个没有 session 的事件(这类事件在摄入时会被跳过并返回 200)。作用域将身份信息绑定在上下文变量上。这些变量会自动传播到 asyncio 任务中,但不会传播到新线程——需要将 worker 包裹在 failproofai_sdk.propagate() 中。

各 id 的生成者

适配器如何解析 session_id

按优先级,第一个匹配生效:
  1. 显式传入的 session_id 选项
  2. 每次调用的元数据
  3. 外层的 session() 作用域
  4. 框架元数据
  5. 框架自身的运行 id
在上述任一来源存在时,绝不会凭空生成——合成的 id 会将一次运行拆分到多个 session 中。

保持 agent_id 的低基数

它是每个 dashboard 界面的主要维度,对应一个 LowCardinality(String) 列。每次运行一个值会降低该列的效能,并使过滤下拉菜单中充斥着每次运行的单独条目。适配器会为你维护这个列:真实 id 保存在 fw_agent_id / fw_run_id 上,在那里仍可查询,但不作为维度。
此保护仅作用于框架自动选择的标签。 你自己传入的 agent_id——无论是传给 event.* 还是 failproofai_sdk.agent(...)——都会原样记录。静默改写显式参数带来的危害比它所防止的基数问题更大,因此请自行为 span 命名。
基于上述运行数据,各框架记录的事件:横线表示该框架没有此概念。human_pausehuman_interrupt 描述的是人对 agent 采取操作,没有任何框架会发出这类信号——需要你自己发出。
事件从不单独出现。一个开启,一个关闭,关闭事件携带 SDK 从开启事件起测量的持续时间。
有开启事件但没有对应关闭事件,意味着一个永远不会结束的 span。session 会显示为仍在运行,且活跃时长持续增长。这是手动埋点时需要注意的失败模式。

关联规则

  • 在匹配的完成事件中复用相同的 tool_call_idhook_idpause_idinput_id
  • SDK 为 tool_resulthook_completedagent_resumehuman_input 计算 duration_ms。向这些方法传入 duration_ms 会抛出 ValueError
  • duration_ms 可以传给 model_response,因为只有调用方才知道真实的提供商延迟。它必须是整数——浮点数会在调用处抛出 ValueError,因为服务端将该列读取为无符号 32 位整数,其他类型会存储为 NULL。
  • 关联键按类型和 session 作用域,因此工具调用和 hook 可以安全地共享 id,两个并发 session 也可以复用相同的 id 而不冲突。关联键不按 agent 作用域:在一个 agent 下开启、在另一个 agent 下关闭的事件对仍然能正确关联,这在多 agent 框架中是常见情况。
  • request_idmodel_requestmodel_response 配对。若不传,模型事件按每个 agent 的顺序配对,并发调用会错配。
  • 跨进程拆分的事件对在下游仍能关联,但 SDK 无法计算其进程内持续时间。
  • 待匹配映射最多保存 10,000 个开启事件,满后会驱逐最旧的条目。
安装 failproofai-sdk 会安装全部内容,包含四个适配器。extras 拉取的是框架,而不是适配器。
import failproofai_sdk 承诺零依赖,通过以下测试强制保证:一个在不带 --no-deps 的情况下安装构建 wheel 的测试,以及另一个证明没有框架进入 sys.modules 的测试。
不存在 failproofai_sdk.crewai 属性。适配器故意不在顶层包上暴露:访问它会作为属性访问的副作用导入框架,破坏零依赖承诺。请使用 instrument()
自动检测读取 sys.modules 而不是已安装的包列表,因此已安装但从未导入的框架不会被埋点,也不会被代为导入。查看已连接的内容:
在没有安装 CrewAI 的机器上调用 instrument("crewai") 不会抛出异常。 它会记录一条警告并返回 (),因此一个缺失的框架不会拖垮同时埋点其他框架的进程。警告中包含底层的 ImportError,该消息会指出确切的安装命令——修复方法在你的日志里,不会被隐藏。
设置 FAILPROOFAI_SDK_STRICT=1 可改为抛出异常。该标志只读取一次并缓存,因此请在进程启动前导出它,而不是在运行途中设置。
instrument() 必须在框架导入之后调用。 自动检测读取 sys.modules,因此在导入之前的裸调用什么都找不到,不会安装任何内容,并返回 ()
如果出错,进程会在 SDK 已导入、适配器看似已安装的情况下运行,但不会发出任何事件。它会记录一条明确说明此情况的警告——因此当某次运行没有记录任何内容时,请先检查日志。
spool 是确保安全的关键:你的 agent 永远不会因网络而阻塞,Cloud 故障只会导致目录增大,而不是事件丢失。每次刷新写入一个批次文件,先写 .tmp,然后 fsync,再原子重命名:
守护进程只处理 .jsonl 文件,因此永远不会读到写入一半的文件。文件名包含时间戳、进程 id 和序列号,因此两个进程在同一毫秒内刷新也不会冲突。队列最多容纳 10,000 个事件,超出后会丢弃最旧的并记录日志。
collector.redact 不适用于你的 SDK 事件。 它根本看不到这些事件。
守护进程发送你的批次。它不打开也不重写这些批次。脱敏在守护进程写入自身事件的地方运行——而不是在批次发送的地方。因此,包含 API 密钥的 prompt 或工具参数在到达时仍然包含该密钥。这是有意为之。这些是你自己的埋点调用,在传输过程中改写它们意味着你收到的事件与你发出的不一致。
你在源头控制载荷,有两种方式:
  • 在适配器上关闭内容捕获。选项名称各不相同,且有一个适配器没有此选项——这不是一个统一的开关:
    • LangChain / LangGraph、Pydantic AI——capture_content=False
    • LlamaIndex——capture_messages=False
    • CrewAI——完全没有内容开关;它只读取 session_id 选项,因此 prompt 和补全内容始终会被记录。
    instrument() 会丢弃适配器不读取的选项,因此传入错误的名称不会报错,也不会有任何效果。
  • 一开始就不要将敏感信息传给 input=
collector.redact 不能替代上述两种方式。
spool 目录为空才是健康状态。 不要用它来检查事件是否已送达。
守护进程在发送批次后的毫秒内就会将其删除,因此 ls 命令会与收集器产生竞争,只能看到你实际发出内容的一小部分——与 SDK 什么都没记录的情况无法区分。要确认事件已成功落地,请检查 dashboard。要观察 spool 的填充过程,请先停止守护进程。
每个回调都运行在一个包装器内,其唯一职责是重新抛出异常,因此你的调用恰好位于一个 try 中,SDK 的所有操作都在其外部进行。默认行为在生产环境中是正确的,在调试时是错误的,因为它只能证明”没有崩溃”。设置 FAILPROOFAI_SDK_STRICT=1 可让被吞掉的失败变得明显。

常见问题

某个开启事件没有对应的关闭事件:model_request 没有 model_response,或 tool_use 没有 tool_result。请使用作用域,它们能保证即使函数体抛出异常也会发出事件对。如果直接调用事件方法,请使用 tryfinally
持续时间由对应的开启事件测量,因此在 tool_resulthook_completedagent_resumehuman_input 上传入 duration_ms 会被拒绝。在 model_response 上可以接受,因为只有你知道真实的提供商延迟,且必须是整数。
该线程从未继承上下文。请将可调用对象包裹在 failproofai_sdk.propagate() 中。参见线程与异步
额外字段最后合并,因此与真实字段(如 modeloutcome)同名的字段会覆盖它,改变存储的列值。请为你的字段加命名空间前缀;适配器使用 fw_ 前缀。
agent_id 是低基数维度,而你在其中放入了运行 id。请使用角色或节点名称,将真实 id 放入载荷字段。

下一步

工作原理

事件对、id、session 生命周期与事件投递。

读取追踪记录

在刚捕获的 session 中沿因果链路追溯。

框架适配器

LangGraph、CrewAI、LlamaIndex 和 Pydantic AI。