【Eino 框架入门】Callback 可观测性:给 Agent 装个"监控摄像头"

【Eino 框架入门】Callback 可观测性:给 Agent 装个"监控摄像头"

Agent 跑起来后,你肯定想知道:模型调了几次?Token 花了多少?哪个 Tool 最慢?

这些都是"可观测性"要解决的问题。Eino 用 Callback 机制来实现。

Callback 是什么?

打个比方:Agent 是主角(业务逻辑),Callback 是旁边的记录员。

  • 主角干活时,记录员在旁边记笔记
  • 主角开始干活 → 记录员写下"XX 开始执行"
  • 主角干完了 → 记录员写下"XX 完成,耗时 23ms"
  • 主角出错了 → 记录员写下"XX 失败:xxx"

核心特点:记录员不干扰主角干活,只是在关键节点记一笔。

五个钩子时机

Callback 在组件生命周期的 5 个固定点位触发:

时机 方法 什么时候触发
开始 OnStart 组件开始处理前
结束 OnEnd 组件成功返回后
出错 OnError 组件返回错误时
流式输入 OnStartWithStreamInput 组件接收流式输入时
流式输出 OnEndWithStreamOutput 组件返回流式输出时

一个 ChatModel 调用的流程:

markdown 复制代码
ChatModel.Generate(ctx, messages)
            ↓
    ┌───────────────┐
    │   OnStart     │  ← 记录:开始调用,输入是啥
    └───────────────┘
            ↓
    ┌───────────────┐
    │   模型处理     │
    └───────────────┘
            ↓
    ┌───────────────┐
    │    OnEnd      │  ← 记录:返回结果,Token 消耗
    └───────────────┘

快速实现一个 Callback

HandlerHelper 只注册你关心的钩子:

go 复制代码
handler := callbacks.NewHandlerHelper().
    OnStart(func(ctx context.Context, info *callbacks.RunInfo, input callbacks.CallbackInput) context.Context {
        log.Printf("[trace] %s/%s 开始", info.Component, info.Name)
        return ctx
    }).
    OnEnd(func(ctx context.Context, info *callbacks.RunInfo, output callbacks.CallbackOutput) context.Context {
        log.Printf("[trace] %s/%s 完成", info.Component, info.Name)
        return ctx
    }).
    OnError(func(ctx context.Context, info *callbacks.RunInfo, err error) context.Context {
        log.Printf("[trace] %s/%s 出错: %v", info.Component, info.Name, err)
        return ctx
    }).
    Handler()

// 全局注册,所有组件都会触发
callbacks.AppendGlobalHandlers(handler)

RunInfo 告诉你是谁触发的:

go 复制代码
type RunInfo struct {
    Name      string  // 业务名称,如 "my_agent"
    Type      string  // 实现类型,如 "OpenAI"
    Component string  // 组件类型,如 "ChatModel"
}

集成 CozeLoop

CozeLoop 是字节跳动的可观测性平台,能可视化整个调用链:

go 复制代码
import clc "github.com/cloudwego/eino-ext/callbacks/cozeloop"

client, _ := cozeloop.NewClient(
    cozeloop.WithAPIToken("your_token"),
    cozeloop.WithWorkspaceID("your_workspace"),
)

// 一行注册
callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))

然后在 CozeLoop 的 Web UI 就能看到漂亮的调用链路图,包括:

  • 每次模型调用的耗时、Token 消耗
  • 每个 Tool 的执行时间
  • 完整的调用层级关系

完整配置示例

go 复制代码
func main() {
    ctx := context.Background()

    // 配置 CozeLoop(可选)
    apiToken := os.Getenv("COZELOOP_API_TOKEN")
    workspaceID := os.Getenv("COZELOOP_WORKSPACE_ID")

    if apiToken != "" && workspaceID != "" {
        client, err := cozeloop.NewClient(
            cozeloop.WithAPIToken(apiToken),
            cozeloop.WithWorkspaceID(workspaceID),
        )
        if err != nil {
            log.Fatalf("cozeloop.NewClient failed: %v", err)
        }
        defer func() {
            time.Sleep(5 * time.Second) // 等待数据上报
            client.Close(ctx)
        }()
        callbacks.AppendGlobalHandlers(clc.NewLoopHandler(client))
        log.Println("CozeLoop tracing enabled")
    }

    // 创建 Agent 并运行...
}

运行效果:

ini 复制代码
[trace] starting session: 083d16da-6b13-4fe6-afb0-c45d8f490ce1
you> 你好
[trace] chat_model_generate: model=gpt-4.1-mini tokens=150
[trace] tool_call: name=list_files duration=23ms
[assistant] 你好!有什么我可以帮助你的吗?

注意事项

流式回调要关流OnEndWithStreamOutput 拿到的 StreamReader 必须读完并关闭,否则会 goroutine 泄漏。

不要修改 Input/Output:它们被所有下游 Handler 共享,改了会污染别人。

RunInfo 可能为 nil:顶层调用可能没有 RunInfo,用前要检查:

go 复制代码
if info != nil {
    log.Printf("[trace] %s/%s", info.Component, info.Name)
}

可观测性的三大价值

场景 能做什么
性能分析 哪个 Tool 最慢?模型调用延迟分布?
错误追踪 哪个环节崩了?完整调用链长啥样?
成本优化 Token 消耗排行?哪个对话最烧钱?

小结

Callback 是 Eino 的"旁路机制":不干扰业务,只在关键节点抽取信息。它从底层 component 到上层 adk 一以贯之,所有组件都支持。

业务代码完全不用改,注册一个 Handler 就能全局生效。这就是非侵入式设计的魅力。

相关推荐
~|Bernard|15 分钟前
五,go语言的内存管理
开发语言·后端·golang
AI人工智能+电脑小能手33 分钟前
【大白话说Java面试题 第47题】【JVM篇】第7题:Young GC 和 Full GC 分别采用什么算法?
java·jvm·后端·算法·面试
user_admin_god1 小时前
Spring Boot 3 + WebFlux 企业级流式SSE接口最佳实践
java·spring boot·后端
怪祝浙1 小时前
spring boot的启动原理以及mvc和ssm的解释
spring boot·后端·mvc
_Evan_Yao1 小时前
责任链模式在Agent编排中的应用:让AI Agent学会“踢皮球”
java·人工智能·后端·责任链模式
counting money1 小时前
MavenServlet项目文件上传
java·后端
庞轩px11 小时前
第七篇:Spring扩展点——如何优雅地介入Bean的创建流程
java·后端·spring·bean·aware·扩展点
ltl11 小时前
Q/K/V 三件套:把 Bahdanau 抽象成一个公式
后端
千叶风行13 小时前
Text-to-SQL 技术设计与注意事项
前端·人工智能·后端
阿kun要赚马内13 小时前
后端数据操作组合:Pydantic与ORM
后端·python·orm·sqlalchemy