cloudwego / cloudwego/eino

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

Open
#1,027 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
13k
Forks
1.1k
Avg merge
4h 6m
Merged PRs (30d)
41

Description

### 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. #957 反映 `buildFieldMappingConverter` 逻辑不完整、PR #976 做 passthrough 优化,都没动错误结构

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

### Describe the solution you'd like

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

```go
// 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

- 收益:用户可 `errors.As(err, &fme)` 拿到源/目标 node;切面 handler 可给 graph 节点补 `eino.fieldmapping.*` 属性;测试断言不再依赖字符串子串。
- 范围:改动集中在 `compose/field_mapping.go`、`compose/workflow.go`、`compose/graph.go` 的错误构造点,附带更新测试断言。
- 兼容性:不破坏现有 `errors.As(err, *errInterfaceNotValidForFieldMapping)` 用法(保留一个版本兼容期)。
- 关联:#957、PR #976、`compose/state_test.go:115` TODO。

愿意提交 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`

**Labels**:`C-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.system`、`gen_ai.request.model`、`gen_ai.usage.input_tokens`、`gen_ai.operation.name` 等),主流框架普遍原生支持(Google ADK、Pydantic AI + Logfire、Mastra、LlamaIndex)。eino 缺一个**框架自带、与后端解耦**的 OTel handler。这本质上是「切面扩展」能力的标准化输出。

### Describe the solution you'd like

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

```go
package otel

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

行为:

1. 按 `RunInfo.Component`(`model` / `tool` / `retriever` / ...)映射到 `gen_ai.operation.name`(`chat` / `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.usage`、`gen_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 认可方向后承担实现。

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.