首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >LangChainGo Callback:观测每一次模型调用

LangChainGo Callback:观测每一次模型调用

作者头像
技术圈
发布2026-07-14 13:45:36
发布2026-07-14 13:45:36
730
举报

AI 应用上线后,经常会遇到一种尴尬情况:用户反馈“等了很久”,服务端日志却只有一条请求开始和一条请求结束。中间到底卡在 Prompt 渲染、Chain 编排、模型调用、工具执行,还是流式输出,日志里完全看不出来。

LangChainGo 的 Callback 可以补上这段空白。它不会改变业务逻辑,也不会让模型变聪明,但能把执行过程里的关键节点暴露出来,方便接日志、指标和链路追踪。

Handler 能观察什么

LangChainGo 的 callback 代码位于 github.com/tmc/langchaingo/callbacks 包中。核心抽象是 Handler 接口,覆盖了几类常见事件:

Chain 事件包括 HandleChainStartHandleChainEndHandleChainError;LLM 事件包括 HandleLLMStartHandleLLMGenerateContentStartHandleLLMGenerateContentEndHandleLLMError;此外还有 Tool、Agent、Retriever 和 Streaming 相关事件。

接口方法不少,直接全部实现会让示例很难读。更顺手的做法是嵌入 callbacks.SimpleHandler,只覆盖真正需要的几个方法:

代码语言:javascript
复制
   type TraceHandler struct {
    callbacks.SimpleHandler
    start time.Time
}

SimpleHandler 已经把所有方法都实现成空操作。业务侧覆盖少量方法即可,不需要写一堆空函数。

记录 Chain 整体耗时

先从最常见的需求开始:看一次 Chain 调用到底花了多久。

代码语言:javascript
复制
   func (h *TraceHandler) HandleChainStart(ctx context.Context, inputs map[string]any) {
    h.start = time.Now()
    log.Printf("chain start inputs=%d", len(inputs))
}

结束事件里计算耗时:

代码语言:javascript
复制
   func (h *TraceHandler) HandleChainEnd(ctx context.Context, outputs map[string]any) {
    log.Printf("chain end outputs=%d cost=%s", len(outputs), time.Since(h.start))
}
代码语言:javascript
复制
   func (h *TraceHandler) HandleChainError(ctx context.Context, err error) {
    log.Printf("chain error: %v", err)
}

错误日志最好带上 trace id、用户请求 id 或任务 id。只打印一条错误文本,排查链路问题时仍然不够用。完整输入也不要直接写入日志,用户问题、文档片段、访问令牌这类数据一旦落盘,后续清理会非常麻烦。

接入到 Chain 调用

这里有个容易写错的点:Chain 级事件和 LLM 级事件不是同一层。

对于 LLMChain,创建 Chain 时通过 chains.WithCallback 挂载 handler,chains.Call 执行时才会触发 Chain 开始、结束和错误事件。

代码语言:javascript
复制
   handler := &TraceHandler{}
chain := chains.NewLLMChain(llm, prompt,
    chains.WithCallback(handler))

注意,chains.WithCallback 不会把模型生成开始/结束回调自动注入所有模型调用。它在 chains.GetLLMCallOptions 里主要会把 handler 转成流式输出回调。要观察模型请求开始和结束,通常需要在具体模型构造时挂 callback。以 OpenAI 适配器为例:

代码语言:javascript
复制
   llm, err := openai.New(
    openai.WithCallback(handler),
)

自定义 Chain 还要多做一步。如果希望 chains.Call 能拿到 Chain 级 handler,需要让结构体实现 callbacks.HandlerHaver

代码语言:javascript
复制
   func (c *MyChain) GetCallbackHandler() callbacks.Handler {
    return c.handler
}

如果自定义 Chain 内部直接调用 llms.GenerateFromSinglePrompt,仍然要把 Chain 选项转换成 LLM 选项继续传下去。这样至少能传递模型名、温度、流式输出函数等调用参数。

代码语言:javascript
复制
   func (c *MyChain) Call(ctx context.Context, inputs map[string]any,
    options ...chains.ChainCallOption) (map[string]any, error) {
    opts := chains.GetLLMCallOptions(options...)
    out, err := llms.GenerateFromSinglePrompt(ctx, c.llm, prompt, opts...)
    return map[string]any{"text": out}, err
}

少了这一步,调用参数不会继续下沉;至于模型生成开始/结束事件是否触发,还取决于具体模型实例有没有配置 callback handler。

观察模型调用过程

GenerateFromSinglePrompt 会把字符串 prompt 包装成 MessageContent,再调用更通用的 GenerateContent。因此在很多模型实现里,更值得关注的是 HandleLLMGenerateContentStartHandleLLMGenerateContentEnd

开始事件适合记录消息数量、模型名和请求时间,不适合记录完整 prompt。结束事件可以读取响应对象,但落库时也应以摘要信息为主,例如输出长度、stop reason、模型名、Token 用量等。

代码语言:javascript
复制
   func (h *TraceHandler) HandleLLMGenerateContentEnd(ctx context.Context,
    res *llms.ContentResponse) {
    if res == nil {
        log.Print("llm end empty response")
        return
    }
    log.Printf("llm end choices=%d", len(res.Choices))
}

需要注意的是,不同模型实现能够提供的 GenerationInfo 不完全一致。接入指标系统时,不要假设所有 provider 都返回同样的字段。

处理流式输出

如果业务开启了流式响应,Callback 还可以观察每个输出分片。chains.GetLLMCallOptions 在没有显式设置 streaming func 且存在 callback handler 时,会把 streaming chunk 转发到 HandleStreamingFunc

代码语言:javascript
复制
   func (h *TraceHandler) HandleStreamingFunc(ctx context.Context, chunk []byte) {
    log.Printf("stream chunk bytes=%d", len(chunk))
}

这类事件适合统计首包时间、分片数量和输出速度。流式内容通常就是用户能看到的文本,直接写入日志风险很高,更合理的做法是记录字节数、字符数或 hash 摘要。

生产环境注意点

首先是隐私问题。Prompt 中可能包含用户输入、业务数据、知识库片段甚至访问凭证。默认只记录长度、数量、耗时、错误类型和必要的业务 id,更适合作为生产环境基线。

其次是并发安全。一个 handler 实例如果被多个请求复用,内部字段就可能被并发读写。前文为了简化演示,把 start 放在结构体字段中。生产代码更适合为每次请求创建独立 handler,或者把状态放到 context 里。

最后是指标粒度。Chain 级耗时回答“这次任务慢不慢”,LLM 级耗时回答“模型调用慢不慢”,Tool 和 Retriever 级耗时回答“外部依赖慢不慢”。只记录其中一种,很容易误判瓶颈。

写在最后

LangChainGo 的 Callback 不负责提升模型效果,它解决的是另一个更工程化的问题:出了问题以后,能不能快速知道问题发生在哪一步。

多模型适配解决模型切换问题,自定义 Chain 解决流程编排问题,Callback 则负责把执行过程暴露出来。当 AI 应用开始接入真实用户、真实文档和真实工具后,这部分能力会直接影响排障效率。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 技术圈子 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Handler 能观察什么
  • 记录 Chain 整体耗时
  • 接入到 Chain 调用
  • 观察模型调用过程
  • 处理流式输出
  • 生产环境注意点
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档