Skip to content

[Enhancement] 将 FieldMapping 错误统一为结构化 FieldMappingError,携带源/目标节点、字段路径与类型信息 #1027

Description

@wucm667

Is your feature request related to a problem? Please describe.

compose 在 FieldMapping 失败时抛出的大多是字符串错误,例如(来自 compose/workflow_test.go 的断言):

  • "field mapping from a struct field, but field not found. field=B"
  • "field mapping from a struct field, but field not exported."

这带来几个维护性问题:

  1. 下游 trace / 监控无法对错误分类
  2. 错误信息不含源/目标 node,定位要靠人肉
  3. compose/state_test.go:115 有 TODO「how to trace this kind of error in the goroutine of processing stream」,缺结构化错误难以推进
  4. buildFieldMappingConverter logic is incomplete #957 反映 buildFieldMappingConverter 逻辑不完整、PR fix(compose): pass through when field mapping input already matches target type #976 做 passthrough 优化,都没动错误结构

注:compose/field_mapping.go:419 已存在 typed error errInterfaceNotValidForFieldMapping,说明结构化错误是仓库认可的方向,只是没有推广。

Describe the solution you'd like

抽取统一的结构化错误,覆盖所有 FieldMapping 失败路径:

// compose/field_mapping.go
type FieldMappingError struct {
    FromNode     string
    ToNode       string
    FromField    string // 可空(passthrough 时)
    ToField      string // 可空
    Reason       FieldMappingErrorReason // FieldNotFound / FieldNotExported / TypeMismatch / InterfaceNotValid / ...
    ExpectedType reflect.Type
    ActualType   reflect.Type
    Cause        error
}

func (e *FieldMappingError) Error() string { /* 统一格式化 */ }
func (e *FieldMappingError) Unwrap() error { return e.Cause }

迁移方式:

  1. 现有 errInterfaceNotValidForFieldMapping 收敛为 FieldMappingError{Reason: InterfaceNotValid},旧 type 保留为 deprecated alias 一个版本
  2. 其它字符串错误点改为构造 *FieldMappingError
  3. Error() 输出的字符串格式保持向后兼容(用户已有的 strings.Contains 判断不会断)

Describe alternatives you've considered

  • 保持现状:错误仍是字符串,trace / IDE 无法利用。
  • 仅靠 strings.Contains 匹配错误文案:脆弱,文案一改即断,且拿不到 node 名。
  • 每个错误点各自定义 typed error:碎片化,调用方要 errors.As 一长串类型。统一一个带 Reason 字段的类型更易用。

Additional context

愿意提交 PR。


Issue 2:OTel GenAI 语义约定原生 handler

Title(中):[Proposal] 提供原生 OpenTelemetry handler,按 GenAI 语义约定为 ChatModel / Tool / Retriever / Graph / ADK 上报 span

Title(英)[Proposal] First-party OpenTelemetry handler emitting GenAI semantic-convention spans across ChatModel / Tool / Retriever / Graph / ADK

LabelsC-feature-request, D-tracing, D-callback

Is your feature request related to a problem? Please describe.

要给 eino 应用接入可观测性,目前必须自行实现 callbacks.Handler。社区生态里有 Langfuse / cozeloop 两个实现(位于 eino-ext),但它们都是面向各自后端的专有映射,无法直接对接通用的 OTel collector / APM。eino-ext#328 提出 OTel 上报,但范围只限 Indexer / Retriever。

OpenTelemetry 已经发布 GenAI Semantic Conventions(gen_ai.systemgen_ai.request.modelgen_ai.usage.input_tokensgen_ai.operation.name 等),主流框架普遍原生支持(Google ADK、Pydantic AI + Logfire、Mastra、LlamaIndex)。eino 缺一个框架自带、与后端解耦的 OTel handler。这本质上是「切面扩展」能力的标准化输出。

Describe the solution you'd like

新增 callbacks/otel 包,提供一个标准 handler:

package otel

func NewHandler(tp trace.TracerProvider, mp metric.MeterProvider, opts ...Option) callbacks.Handler

行为:

  1. RunInfo.Componentmodel / tool / retriever / ...)映射到 gen_ai.operation.namechat / execute_tool / embeddings / ...)。
  2. OnStart 注入 gen_ai.request.*OnEnd 注入 gen_ai.response.*gen_ai.usage.*
  3. 流式场景在 OnEndWithStreamOutput 内累计 token,stream 关闭时一次性落 span。
  4. Graph / ADK 节点产生父 span,Component 调用作为子 span,形成完整调用树。
  5. Metrics:gen_ai.client.token.usagegen_ai.client.operation.duration 直方图。

分期:

  • 一期:ChatModel / Tool / Embedding / Retriever / Indexer 的 span + 标准 attribute
  • 二期:Graph 节点与 ADK Agent 的父子 span 层级 + AgentEvent 拓扑
  • 三期:AgenticMessage(Responses API)的 reasoning / tool 双 span 表示

Describe alternatives you've considered

  • 继续用 Langfuse / cozeloop handler:绑定特定后端,无法对接已有的 OTel collector / Jaeger / Tempo / 各类 APM。
  • 由用户各自实现 OTel handler:每个团队重复造轮子,且很难统一遵循 GenAI semconv,trace 不可跨项目复用。
  • 只做 metrics 不做 trace:丢失调用树,无法定位多 Agent / 多节点链路。

Additional context

  • OTel GenAI Semantic Conventions: https://opentelemetry.io/docs/specs/semconv/gen-ai/
  • 现有实现可参考:eino-ext callbacks/langfuse
  • 关联:eino-ext#328(仅 indexer/retriever,范围窄)。
  • 放置位置(主仓库 callbacks/otel vs eino-ext)可讨论;倾向主仓库,因为 OTel 是行业标准、且不引入重后端依赖。

愿意在 maintainer 认可方向后承担实现。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions