第一部分:规范调研
一、GenAI规范概述
OpenTelemetry GenAI 语义约定(Semantic Conventions for Generative AI)是一套专为生成式 AI 系统设计的可观测性标准,定义了统一的 Span、Metric 和 Event 规范,使不同 LLM 提供商和框架能够产出一致的遥测数据。
该规范目前处于 Development(开发中) 状态,尚未正式稳定(Stable),也没有明确的稳定化时间表。最初作为 OpenTelemetry Semantic Conventions 主仓库的一部分,现已迁移至独立仓库 open-telemetry/semantic-conventions-genai,使用 Weaver 工具扩展核心 OTel 规范。截至 2026 年 5 月,规范版本约为 v1.41,涵盖六大层次:Client Spans、Agent/Workflow Spans、MCP Conventions、Events、Metrics、Provider-specific Attributes。
二、Span 结构与操作类型
GenAI 规范围绕 gen_ai.operation.name 属性定义了一组标准操作名,每个操作名对应一种 Span 类型:
核心操作类型包括 chat(聊天/对话式推理,对应 Chat Completions)、text_completion(纯文本生成)、embeddings(向量嵌入计算)、chat_completions(聊天补全)。这些是 LLM 推理的最基础操作。
Agent 与工具操作类型涵盖 invoke_agent(调用 Agent,记录 Agent 的完整执行过程,可拆分为 CLIENT 和 INTERNAL 两种 Span Kind)、execute_tool(执行工具调用,Span 名称中必须包含工具名)、embeddings(RAG 场景下的向量检索)以及 chat 配合 gen_ai.tool.call.* 属性用于 Function Calling 场景。
MCP(Model Context Protocol)约定确保跨 Agent 和工具调用的 Trace 上下文连续性,使分布式 Agent 系统能够保持端到端追踪。
三、属性体系(gen_ai.* Attributes)
GenAI 属性按命名空间组织在 gen_ai.* 前缀下,以下是按功能分组的关键属性:
3.1 操作与身份信息
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.operation.name | string | 操作名称,如 chat、text_completion、embeddings |
gen_ai.provider.name | string | GenAI 提供商标识,如 openai、anthropic |
gen_ai.request.model | string | 请求使用的模型名,如 gpt-4o、claude-3.5-sonnet |
gen_ai.response.model | string | 实际生成响应的模型名(可能与请求的不同) |
3.2 请求参数
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.request.temperature | double | 采样温度 |
gen_ai.request.top_p | double | Top-p 采样参数 |
gen_ai.request.top_k | double | Top-k 采样参数 |
gen_ai.request.max_tokens | int | 最大生成 token 数 |
gen_ai.request.frequency_penalty | double | 频率惩罚 |
gen_ai.request.presence_penalty | double | 存在惩罚 |
gen_ai.request.stop_sequences | string[] | 停止序列 |
gen_ai.request.seed | int | 随机种子 |
gen_ai.request.stream | boolean | 是否流式请求 |
gen_ai.request.choice.count | int | 候选结果数量 |
gen_ai.request.encoding_formats | string[] | 嵌入编码格式 |
3.3 响应信息
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.response.id | string | 响应唯一标识 |
gen_ai.response.finish_reasons | string[] | 停止生成原因数组,如 stop、length、tool_calls |
gen_ai.response.time_to_first_chunk | double | 流式响应中首 chunk 到达时间 |
3.4 输入/输出内容
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.input.messages | any | 发送给模型的聊天历史/输入消息 |
gen_ai.output.messages | any | 模型返回的响应消息 |
gen_ai.system_instructions | any | 系统提示/指令 |
gen_ai.output.type | string | 请求的输出类型,如 text、json、image |
内容捕获支持三种模式:关闭(不记录 prompt/completion 内容)、Span 属性(直接记录在 Span 上)和外部存储引用(通过 Event API 将大体积内容记录为独立 Event,Span 上仅保留引用)。OpenTelemetry GenAI Working Group 推荐使用 Event API 而非 Span 属性来记录完整的 prompt 和 completion 内容,以避免 Span 过大。
3.5 Token 用量
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.usage.input_tokens | int | 输入(prompt)token 数 |
gen_ai.usage.output_tokens | int | 输出(completion)token 数 |
gen_ai.usage.reasoning.output_tokens | int | 推理(chain-of-thought)消耗的 token 数 |
gen_ai.usage.cache_creation.input_tokens | int | 写入提供商缓存的 token 数 |
gen_ai.usage.cache_read.input_tokens | int | 从提供商缓存读取的 token 数 |
gen_ai.token.type | string | token 类型标识(input/output) |
3.6 Agent 与工具
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.agent.id | string | Agent 唯一标识 |
gen_ai.agent.name | string | Agent 可读名称 |
gen_ai.agent.description | string | Agent 描述 |
gen_ai.agent.version | string | Agent 版本 |
gen_ai.conversation.id | string | 会话/线程唯一标识 |
gen_ai.workflow.name | string | 工作流名称 |
gen_ai.tool.name | string | 工具名称 |
gen_ai.tool.description | string | 工具描述 |
gen_ai.tool.type | string | 工具类型(function、extension、datastore) |
gen_ai.tool.definitions | any | 可用工具定义列表 |
gen_ai.tool.call.id | string | 工具调用 ID |
gen_ai.tool.call.arguments | any | 工具调用参数 |
gen_ai.tool.call.result | any | 工具调用结果 |
3.7 RAG 与检索
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.retrieval.query.text | string | 检索查询文本 |
gen_ai.retrieval.documents | any | 检索到的文档列表 |
gen_ai.data_source.id | string | 数据源标识 |
gen_ai.embeddings.dimension.count | int | 嵌入向量维度数 |
3.8 评估(Evaluation)
| 属性 | 类型 | 说明 |
|---|---|---|
gen_ai.evaluation.name | string | 评估指标名称 |
gen_ai.evaluation.score.value | double | 评估分数 |
gen_ai.evaluation.score.label | string | 评估标签,如 relevant、correct |
gen_ai.evaluation.explanation | string | 评估者的解释说明 |
3.9 已废弃属性
| 废弃属性 | 替代方案 |
|---|---|
gen_ai.system | 使用 gen_ai.provider.name |
gen_ai.prompt | 使用 Event API + gen_ai.input.messages |
gen_ai.completion | 使用 Event API + gen_ai.output.messages |
gen_ai.usage.prompt_tokens | 使用 gen_ai.usage.input_tokens |
gen_ai.usage.completion_tokens | 使用 gen_ai.usage.output_tokens |
四、Metrics 定义
GenAI 规范定义了两个核心 Metric:
gen_ai.client.token.usage:Histogram 类型,记录每次操作的 token 消耗量。通过 gen_ai.token.type(input/output)维度区分输入和输出 token,可按 model、provider 等维度聚合。这是监控 LLM 使用成本的关键指标。
gen_ai.client.operation.duration:Histogram 类型,单位为秒(s),记录 GenAI 操作的端到端耗时。可按 gen_ai.operation.name、gen_ai.request.model、gen_ai.provider.name 等维度切片分析。用于监控延迟和 SLA。
这两个 Metric 配合 Span 属性,可以实现对 LLM 应用的全面性能和成本监控。
五、Agentic AI 扩展(提案中)
针对多 Agent 协作系统,规范社区正在讨论引入更丰富的语义约定(Issue #35、#37),拟新增以下概念:
- Task(任务):最小可追踪单元,定义目标并可分解为子任务。属性包括
gen_ai.task.id、gen_ai.task.state、gen_ai.task.status、gen_ai.task.feedback等。 - Action(动作):执行机制,如工具调用或 LLM 查询,用于完成 Task。
- Agent(智能体):自主实体,编排 Action 和推理。属性包括
gen_ai.agent.*系列。 - Team(团队):Agent 协作组,共享目标。
- Artifact(制品):有形的输入/输出,如 prompt、embedding。
- Memory(记忆):持久化存储,支持跨会话上下文复用。
这些概念构成 gen_ai.task.*、gen_ai.action.* 等新的属性命名空间,目前仍在提案阶段。
六、MCP(Model Context Protocol)集成
GenAI 规范已纳入 MCP 语义约定(mcp.* 命名空间),用于标准化 Model Context Protocol 场景下的 Trace 传播。MCP 约定确保当 Agent 通过 MCP 协议调用外部工具或服务时,Trace Context 能够正确地在调用链中传递,实现跨进程、跨服务的端到端可观测性。
七、自动 Instrumentation 库
目前已有多个成熟的自动 Instrumentation 库支持 GenAI 语义约定:
- OpenAI:
opentelemetry-instrumentation-openai(Python),自动捕获 Chat Completions、Embeddings 等操作的 Span 和 Event - Anthropic:通过 OpenLLMetry 等社区库支持
- LangChain / LlamaIndex:有对应的自动 Instrumentation 包
- Azure OpenAI:通过 Azure SDK 的 OTel 集成
- MLflow:通过设置
MLFLOW_ENABLE_OTEL_GENAI_SEMCONV=true环境变量,可以将 MLflow 追踪数据以 GenAI 语义约定格式导出到 OTLP 端点
主流可观测性平台(MLflow、AgentLoop 等)已开始原生支持 OTel GenAI 语义约定的 OTLP 数据接入。
第二部分:实践接入方案
1. 整体架构
1.1 定位
LoongSuite GenAI OpenTelemetry Util for Go(包名 otelgenai)是 Python 版 loongsuite-otel-util-genai 的 Go 实现,为 Go 应用提供标准化的 GenAI 遥测采集能力。它将 LLM 调用、Agent 编排、工具执行、向量检索、文档重排、记忆操作等 AI 操作统一映射为 OpenTelemetry 遥测信号(Span、指标、事件)。
1.2 核心能力
- 统一 Span 模型:支持 12 种 Span 类型(llm / embedding / execute_tool / create_agent / invoke_agent / retrieval / rerank / entry / react_step / memory / chain / run_task),涵盖 GenAI 全链路
- Baggage 链路染色:通过 OTel Baggage 机制在 Context 中传播
gen_ai.agent.name/gen_ai.session.id/gen_ai.user.id/gen_ai.framework,实现跨 Span 上下文关联 - 多模态外置存储:Handler 集成异步上传生命周期,Start* 时启动 goroutine,End 时等待完成,将图像/音频/视频等 BlobPart/Base64BlobPart 以字节流直传到存储后端,不占用额外磁盘空间
- 指标体系:操作时长直方图 + Token 用量直方图,遵循 OTel GenAI 语义约定
- 事件发射:可选的
gen_ai.client.inference.operation.details日志事件,通过 OTel Logger API 发射 - 内容捕获策略:四种模式(NoContent / SpanOnly / EventOnly / SpanAndEvent)精细控制消息内容写入位置
- CompletionHook:完整对话内容的上传钩子机制
2. 系统架构
2.1 分层设计
┌─────────────────────────────────────────────────────────────────────┐
│ 用户应用代码 │
│ 构造 XxxInvocation → Handler.Start*(ctx, inv) → span.End()/Fail() │
└───────────────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────────────▼─────────────────────────────────────┐
│ 核心遥测层(根目录模块) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌────────────┐ ┌─────────────────┐ │
│ │ Handler │──│ XxxSpan │──│ span_attrs │──│ MetricsRecorder │ │
│ │ (入口) │ │ (生命周期) │ │ (属性设置) │ │ (指标记录) │ │
│ └──────────┘ └───────────┘ └────────────┘ └─────────────────┘ │
│ ┌──────────┐ ┌───────────┐ ┌────────────┐ │
│ │ Config │ │ types.go │ │ option.go │ │
│ │ (配置) │ │ (类型) │ │ (选项) │ │
│ └──────────┘ └───────────┘ └────────────┘ │
└────────────────┬─────────────────┬──────────────────────────────────┘
│ │
┌────────────────▼───┐ ┌─────────▼──────────────────────────────────┐
│ semconv 模块 │ │ multimodal 模块 upload 模块 │
│ │ │ │
│ attributes.go │ │ PreUploader → Uploader → Processor │
│ extended_attrs.go │ │ (异步多模态外置存储) CompletionHook │
│ (标准+扩展属性键) │ │ (完整内容上传) │
└────────────────────┘ └────────────────────────────────────────────┘
│
┌────────────────▼───┐
│ internal 模块 │
│ envutil.go │
│ jsonutil.go │
│ (环境变量/JSON工具) │
└────────────────────┘
2.2 模块划分
| 模块 | 路径 | 核心文件 | 职责 |
|---|---|---|---|
| 根目录模块 | / | genai.go, handler.go, spans.go, multimodal_upload.go, span_attrs.go, span_container.go, types.go, config.go, option.go, metrics.go | 核心遥测处理:Handler 入口、Span 生命周期管理、异步上传协调、属性序列化、指标记录、配置加载、函数选项 |
| internal | /internal | envutil.go, jsonutil.go | 内部工具:环境变量读取(GetEnvOrDefault / GetEnvBool / GetEnvInt)、JSON 序列化 |
| semconv | /semconv | attributes.go, extended_attributes.go | OTel 语义约定:标准 GenAI 属性键 + LoongSuite 扩展属性键、操作名常量、Span 类型值 |
| multimodal | /multimodal | types.go, preuploader.go, uploader.go, processor.go | 多模态数据异步外置存储:PreUploadItem → PreUploader.Prepare → UploadItem → Uploader.Upload |
| upload | /upload | completion_hook.go | 完整内容上传钩子:CompletionHook 接口 + FsCompletionHook / NoOpCompletionHook 实现 |
2.3 模块间依赖关系
┌──────────────┐
│ 用户应用 │
└──────┬───────┘
│ import otelgenai
▼
┌────────────────────────┐
│ 根目录模块 (otelgenai) │
│ Handler / Span / Config│
└──┬──────┬──────┬───────┘
│ │ │
import │ │ │ import
▼ │ ▼
┌──────────┐ │ ┌──────────┐
│ semconv │ │ │ internal │
└──────────┘ │ └──────────┘
│
可选集成 ──────┤
│
┌─────────▼─────────┐
│ │
┌──────▼──────┐ ┌───────▼──────┐
│ multimodal │ │ upload │
└─────────────┘ └──────────────┘
说明:根目录模块单向依赖
semconv(属性键常量)和internal(工具函数)。multimodal模块由 Handler 在创建时自动集成(多模态上传功能)。upload模块为独立子包,由用户应用按需集成。
3. 核心组件详解
3.1 Handler — 遥测入口
Handler 是整个库的核心入口,管理 Tracer、Meter、Logger 三大 OTel 组件的实例,并提供所有 Span 类型的启动方法。
type Handler struct {
tracer trace.Tracer // OTel Tracer,创建 Span
meter metric.Meter // OTel Meter,创建指标仪器
logger log.Logger // OTel Logger,发射事件
config *Config // 环境配置
metricsRecorder *MetricsRecorder // 指标记录器
captureMode ContentCapturingMode // 内容捕获模式
sessionID string // 全局 Session ID(Baggage 回退值)
userID string // 全局 User ID(Baggage 回退值)
framework string // 全局 Framework(Baggage 回退值)
uploadProcessor *multimodal.Processor // 多模态上传处理器(自动管理生命周期)
}
创建方式:通过 NewHandler(opts ...Option) 构造,支持函数选项模式注入自定义 Provider:
handler, err := otelgenai.NewHandler(
otelgenai.WithTracerProvider(tp),
otelgenai.WithMeterProvider(mp),
otelgenai.WithSessionID("session-123"),
otelgenai.WithMultimodalUploader(ossUploader), // 可选:注入自定义多模态 Uploader
otelgenai.WithMultimodalUploadTimeout(15 * time.Second), // 可选:上传超时
)
defer handler.Shutdown(ctx) // 确保关闭 Processor
全局单例:通过 SetGlobalHandler() / GetGlobalHandler() / MustGetGlobalHandler() 管理全局 Handler 实例,MustGetGlobalHandler() 使用 sync.Once 确保惰性初始化。
Handler 启动方法一览:
| 方法 | Span 类型 | Span Kind | Span 命名规则 |
|---|---|---|---|
StartLLM(ctx, *LLMInvocation) | LLM | Client | {operation_name} {request_model} |
StartEmbedding(ctx, *EmbeddingInvocation) | Embedding | Client | embeddings {request_model} |
StartExecuteTool(ctx, *ExecuteToolInvocation) | ExecuteTool | Internal | execute_tool {tool_name} |
StartCreateAgent(ctx, *CreateAgentInvocation) | CreateAgent | Client | create_agent {agent_name} |
StartInvokeAgent(ctx, *InvokeAgentInvocation) | InvokeAgent | Internal | invoke_agent {agent_name} |
StartRetrieval(ctx, *RetrievalInvocation) | Retrieval | Internal | retrieval |
StartRerank(ctx, *RerankInvocation) | Rerank | Internal | rerank_documents |
StartEntry(ctx, *EntryInvocation) | Entry | Internal | enter_ai_application_system |
StartReactStep(ctx, *ReactStepInvocation) | ReactStep | Internal | react step |
StartMemory(ctx, *MemoryInvocation) | Memory | Client | memory {operation} |
StartChain(ctx, *ChainInvocation) | Chain | Internal | chain {chain_name} |
StartTask(ctx, *TaskInvocation) | Task | Internal | run_task {task_name} |
3.2 XxxSpan — Span 生命周期管理器
每种 Span 类型都有对应的结构体(如 LLMSpan、EmbeddingSpan、InvokeAgentSpan 等),统一遵循以下模式:
type XxxSpan struct {
span trace.Span // 底层 OTel Span
handler *Handler // 关联的 Handler(用于指标/事件)
Invocation *XxxInvocation // 关联的调用数据对象
startTime time.Time // 启动时间戳
ctx context.Context // 关联的 Context
ended bool // 防止重复结束
}
通用 API:
Context() context.Context— 获取关联的 ContextSpan() trace.Span— 获取底层 OTel SpanEnd()— 正常结束:applyFinishAttributes → emitEvent(可选) → recordMetrics → span.End()Fail(err error)— 异常结束:applyFinishAttributes → applyErrorAttributes → emitEvent(可选) → recordMetrics → span.End()
类型特定的设置方法(示例):
| Span 类型 | 方法 |
|---|---|
LLMSpan | SetOutput(), SetTokenUsage(), SetResponseModel(), SetResponseID(), SetFinishReasons() |
EmbeddingSpan | SetDimensionCount() |
ExecuteToolSpan | SetResult() |
InvokeAgentSpan | SetOutput(), SetTokenUsage() |
RetrievalSpan | SetDocuments() |
RerankSpan | SetResults() |
ReactStepSpan | SetFinishReason() |
3.3 MetricsRecorder — 指标记录器
MetricsRecorder 封装了两个直方图仪器:
type MetricsRecorder struct {
durationHistogram metric.Float64Histogram // gen_ai.client.operation.duration
tokenHistogram metric.Int64Histogram // gen_ai.client.token.usage
}
指标定义:
| 指标名 | 类型 | 单位 | 桶边界 |
|---|---|---|---|
gen_ai.client.operation.duration | Float64Histogram | 秒 (s) | [0.01, 0.02, 0.04, ..., 81.92](14 个边界,指数递增) |
gen_ai.client.token.usage | Int64Histogram | {token} | [1, 4, 16, 64, 256, ..., 67108864](14 个边界,4 倍递增) |
记录方法:
RecordDuration(ctx, duration, attrs...)— 记录操作时长RecordTokenUsage(ctx, tokenType, count, attrs...)— 记录 Token 用量(附加gen_ai.token.type属性)recordLLMMetrics(ctx, inv, duration, errorType)— LLM 专用,构建完整属性集recordExtendedMetrics(ctx, inv, duration, errorType)— 扩展操作通用,按 Invocation 类型分发
3.4 Config — 配置管理
Config 从环境变量加载,通过 LoadConfigFromEnv() 工厂函数创建。详见 第 9 节 配置体系。
4. 数据类型体系
4.1 MessagePart 接口层次
MessagePart 是所有消息部件的接口,通过非导出方法 partType() string 密封,防止外部包随意实现:
MessagePart (interface, sealed by partType())
├── TextPart — 文本内容 {Content, Type:"text"}
├── ReasoningPart — 推理/思考内容 {Content, Type:"reasoning"}
├── BlobPart — 二进制数据 {MimeType, Modality, Content:[]byte, Type:"blob"}
├── Base64BlobPart — Base64 编码二进制 {MimeType, Modality, Content:string, Type:"base64_blob"}
├── FilePart — 文件 ID 引用 {MimeType, Modality, FileID, Type:"file"}
├── UriPart — URI 引用 {MimeType, Modality, URI, Type:"uri"}
├── ToolCallPart — 工具调用请求 {Arguments, Name, ID, Type:"tool_call"}
├── ToolCallResponsePart — 工具调用结果 {Response, ID, Type:"tool_call_response"}
├── CompactionPart — 对话压缩 {Content, ID, Type:"compaction"}
├── GenericPart — 通用扩展 {Type, Properties map[string]interface{}, Type:动态}
├── ServerToolCallPart — 服务端工具调用 {Name, Arguments, ID, Type:"server_tool_call"}
└── ServerToolCallResponsePart — 服务端工具响应 {Response, ID, Type:"server_tool_call_response"}
Modality 枚举:ModalityImage("image") / ModalityVideo("video") / ModalityAudio("audio") / ModalityDocument("document")
4.2 消息模型
type InputMessage struct {
Role string // "user", "system", "assistant" 等
Parts []MessagePart // 消息部件列表
Name string // 可选,消息发送者名称
}
type OutputMessage struct {
Role string // 通常为 "assistant"
Parts []MessagePart
Name string // 可选,消息发送者名称
FinishReason string // "stop", "length", "tool_calls", "error", "content_filter"
}
ToolDefinition:联合类型,可以是 FunctionToolDefinition(函数工具)或 GenericToolDefinition(通用工具)。
4.3 Invocation 类型族
Invocation 是调用数据的载体,每种 Span 类型对应一个 Invocation 结构体:
| Invocation | 关键字段 | 特征 |
|---|---|---|
| LLMInvocation | RequestModel, InputMessages, OutputMessages, SystemInstruction, ToolDefinitions, Token 计数, 延迟参数 | 最完整的 Invocation,包含全部 LLM 请求/响应参数和 LoongSuite 扩展字段(缓存 Token、首 Token 延迟、推理时间等) |
| EmbeddingInvocation | RequestModel, DimensionCount, EncodingFormats, InputTokens | 向量嵌入操作 |
| ExecuteToolInvocation | ToolName, ToolCallArguments, ToolCallResult, Skill* | 工具执行,包含技能关联信息 |
| CreateAgentInvocation | AgentName, AgentID, Provider | Agent 创建 |
| InvokeAgentInvocation | AgentName, InputMessages, OutputMessages, Token 计数 | Agent 调用,类似 LLM 但有 Agent 特有字段 |
| RetrievalInvocation | Query, Documents, DataSourceID, TopK | 文档检索,包含 RetrievalDocument 列表 |
| RerankInvocation | Query, TopK, InputDocuments, OutputDocuments, 各种重排参数 | 文档重排,支持 LLM/Cohere/HuggingFace 多种重排器参数 |
| EntryInvocation | SessionID, UserID, InputMessages, OutputMessages | AI 系统入口,注入 Baggage |
| ReactStepInvocation | FinishReason, Round | ReAct 单步迭代 |
| MemoryInvocation | Operation, UserID, AgentID, 分页参数, 记忆参数 | 记忆操作(add/search/update/delete 等) |
| ChainInvocation | ChainName, InputValue, OutputValue, UserTimeToFirstToken | 链式编排 |
| TaskInvocation | TaskName, InputValue, InputMimeType, OutputMimeType | 自定义任务 |
所有 Invocation 都包含 Attributes map[string]interface{} 字段用于自定义 Span 属性,部分包含 MetricAttributes 用于自定义指标属性。
5. 核心数据流
5.1 LLM 调用完整流程
① 构造 LLMInvocation
│
▼
② handler.StartLLM(ctx, inv)
┌─────────┴──────────┐
│ injectGlobalBaggage │ ← 注入 handler 级 session/user/framework
│ tracer.Start(CLIENT)│ ← 创建 OTel Span
│ injectBaggageAttrs │ ← 从 Baggage 读取 → Span 属性
│ applyStartAttributes│ ← 设置启动属性 (operation.name, model, provider...)
│ startAsyncUpload │ ← 扫描 BlobPart/Base64BlobPart,启动异步上传 goroutine
│ return LLMSpan │ (包含 uploadFuture)
└─────────┬──────────┘
│
▼
③ 业务处理 (调用 LLM API)
↑ 与后台上传 goroutine 并行执行
span.SetOutput(messages)
span.SetTokenUsage(in, out)
span.SetResponseModel(model)
│
▼
④ span.End() 或 span.Fail(err)
┌─────────┴──────────────────────────────┐
│ │
│ a. 捕获 duration │
│ └─ duration = now - startTime │
│ (不含后续上传等待时间) │
│ │
│ b. waitAndReplace (若有 uploadFuture) │
│ ├─ 等待上传完成(带超时,默认 30s) │
│ └─ BlobPart 替换为 UriPart │
│ │
│ c. applyLLMFinishAttributes │
│ ├─ 更新 Span 名称 │
│ ├─ 设置 Common + Request + Response │
│ │ 属性 (model, tokens, latency...) │
│ ├─ 按 ContentCapturingMode 序列化 │
│ │ 消息内容 → Span 属性 │
│ │ (消息中已是 URI,而非原始字节) │
│ └─ 设置自定义 Attributes │
│ │
│ d. emitLLMEvent (若 EmitEvent=true) │
│ └─ logger.Emit → LogRecord │
│ (包含 input/output messages) │
│ │
│ e. recordLLMMetrics │
│ ├─ durationHistogram.Record(秒) │
│ ├─ tokenHistogram.Record(input) │
│ └─ tokenHistogram.Record(output) │
│ (使用步骤 a 捕获的纯 LLM duration)│
│ │
│ f. span.End() │
│ └─ TracerProvider 导出 Span │
│ │
└────────────────────────────────────────┘
5.2 异常结束流程(Fail)
Fail 与 End 流程基本相同,但有重要区别:
- 不等待上传完成:
Fail(err)不会等待异步上传 goroutine 完成,上传在后台继续执行但不影响错误路径 applyErrorAttributes:设置span.SetStatus(Error)、span.RecordError(err)、error.type属性- 指标记录时附加
error.type维度
5.3 扩展操作通用流程
除 LLM 外的 11 种扩展操作(Embedding、ExecuteTool、InvokeAgent 等)遵循统一模式:
handler.StartXxx(ctx, inv) → XxxSpan
↓
业务处理 + span.SetXxx(结果)
↓
span.End() / span.Fail(err):
1. applyXxxFinishAttributes → span.SetAttributes
2. [可选] applyErrorAttributes
3. recordExtendedMetrics → durationHistogram + tokenHistogram(仅部分类型)
4. span.End()
注意:扩展操作不发射事件(无 emitEvent),仅 LLM 操作支持事件发射。
6. Baggage 传播机制
6.1 概述
Baggage 是 OTel 的跨进程上下文传播机制。本库利用 Baggage 在 Span 链路中传播 GenAI 业务标识,使子 Span 能自动继承父级上下文信息。
6.2 传播的 Baggage 键
| Baggage Key | 来源 | 用途 |
|---|---|---|
gen_ai.agent.name | StartInvokeAgent 写入 | 标识当前调用链所属 Agent |
gen_ai.session.id | StartEntry 写入 / Handler 级回退 | 标识会话 |
gen_ai.user.id | StartEntry 写入 / Handler 级回退 | 标识终端用户 |
gen_ai.framework | Handler 级回退 | 标识框架名称 |
6.3 写入时机
1. Handler 级全局注入(injectGlobalBaggage)
在每次 Start*() 调用时执行。将 Handler 构造时设置的 sessionID / userID / framework 作为回退值写入 Baggage,仅当 Baggage 中对应键为空时才写入(不覆盖已有值)。
// injectGlobalBaggage 的优先级逻辑:
// 已有 Baggage 值 > Handler 全局配置值
if bag.Member("gen_ai.session.id").Value() == "" && h.sessionID != "" {
bag.SetMember("gen_ai.session.id", h.sessionID)
}
2. Entry 入口注入
StartEntry 从 EntryInvocation 的 SessionID / UserID 字段直接写入 Baggage:
if inv.SessionID != "" {
bag.SetMember("gen_ai.session.id", inv.SessionID)
}
if inv.UserID != "" {
bag.SetMember("gen_ai.user.id", inv.UserID)
}
3. InvokeAgent Agent 名称注入
StartInvokeAgent 将 inv.AgentName 写入 gen_ai.agent.name,使该 Agent 下游的所有子 Span(如 LLM 调用、工具执行)都能自动关联 Agent 名称。
6.4 读取时机(injectBaggageAttributes)
在大多数 Start*() 方法中,创建 Span 后调用 injectBaggageAttributes(ctx, nil),从 Context 的 Baggage 中提取四个已知键并转为 Span 属性:
bag := baggage.FromContext(ctx)
if m := bag.Member("gen_ai.agent.name"); m.Value() != "" {
attrs = append(attrs, semconv.GenAIAgentNameKey.String(m.Value()))
}
// ... 类似处理 session.id, user.id, framework
6.5 传播示例
Entry(SessionID="s1", UserID="u1")
└─ Baggage: {gen_ai.session.id=s1, gen_ai.user.id=u1}
│
└─ InvokeAgent(AgentName="assistant")
└─ Baggage: {..., gen_ai.agent.name=assistant}
│
├─ LLM(model="qwen-max")
│ Span attrs: {gen_ai.agent.name=assistant,
│ gen_ai.session.id=s1, gen_ai.user.id=u1}
│
└─ ExecuteTool(name="search")
Span attrs: {gen_ai.agent.name=assistant,
gen_ai.session.id=s1, gen_ai.user.id=u1}
7. 多模态异步处理
7.1 架构概览
多模态模块负责将 LLM 请求/响应中的二进制数据(图像、音频、视频)异步外置到存储系统,避免大体积数据占用 Span 属性空间。
在新版本中,Handler 已集成 Processor 的完整生命周期管理。用户只需通过 WithMultimodalUploader 注入自定义 Uploader,Handler 会在内部自动完成 Processor 的创建、启动和关闭。
┌───────────────────────────────────────────────────┐
│ Handler (自动管理) │
│ │
│ Start*() ──► 扫描 BlobPart/Base64BlobPart │
│ Base64 解码为字节流 │
│ 创建子 Span: gen_ai.multimodal.upload│
│ 启动后台 goroutine 异步上传 │
│ │
│ goroutine: │
│ PreUploader.Prepare() ──► 生成目标路径 │
│ Uploader.Upload() ──► 字节流直传返回 URI │
│ │
│ End() ──► 等待上传完成(带超时) │
│ BlobPart 替换为 UriPart │
│ 记录 LLM duration(排除上传等待) │
└───────────────────────────────────────────────────┘
7.2 异步上传流程(三路分支路由模型)
Handler 在每次 Start*() 调用中自动编排异步上传:
① handler.StartLLM(ctx, inv)
│ 扫描 InputMessages/OutputMessages 中的 BlobPart/Base64BlobPart/UriPart
│ 构建 PreUploadItem[] 提交给 Processor
▼
② Processor.Execute(ctx, items) → 返回 UploadFuture
│
├─ Phase 1: PreUploader.Prepare(ctx, items)
│ ├─ 处理内联字节数据 / 文件路径
│ ├─ 计算 MD5 哈希、生成 TargetPath
│ └─ 生成 []UploadItem(每个标记 ShouldUpload 和 ResolvedURI)
│
└─ Phase 2: 有界并发(semaphore, 默认 maxConcurrency=8)
├─ Branch 1: ShouldUpload=true
│ └─ 获取 semaphore → Uploader.Upload(ctx, item) → 返回 URI
├─ Branch 2: ShouldUpload=false + ResolvedURI 非空
│ └─ 直接使用 ResolvedURI 作为结果
└─ Branch 3: ShouldUpload=false + 无 ResolvedURI
└─ 跳过,不产出 result
│
▼
③ 用户调用 span.End()
│ 先捕获 duration = now - startTime(排除上传等待)
│ 等待 UploadFuture.Wait() 完成(带超时,默认 30s)
│ 将 BlobPart 替换为 UriPart(MimeType/Modality 保留)
│ 然后执行 applyFinishAttributes / emitEvent / recordMetrics
▼
④ span.End(trace.WithTimestamp(endTime)) → TracerProvider 导出
7.3 容错机制
- 上传失败降级:单个 part 上传失败不中断其余 part,失败时保留原始 BlobPart 不变
- 超时降级:
End()等待超时后保留原始内容,记录警告日志,不阻塞 Span 结束 - Fail 路径跳过等待:
Fail(err)结束时不等待上传完成,上传 goroutine 在后台继续执行
8. 语义约定
8.1 组织方式
semconv 包将属性键分为两个文件:
attributes.go** — 标准 OTel GenAI 属性**(遵循 OpenTelemetry Semantic Conventions):
| 类别 | 属性键示例 |
|---|---|
| 系统 | gen_ai.system |
| 模型 | gen_ai.request.model, gen_ai.response.model |
| 操作 | gen_ai.operation.name |
| 请求参数 | gen_ai.request.temperature, gen_ai.request.top_p, gen_ai.request.max_tokens, gen_ai.request.seed, gen_ai.request.stop_sequences, gen_ai.request.frequency_penalty, gen_ai.request.presence_penalty, gen_ai.request.top_k, gen_ai.request.choice.count |
| 响应 | gen_ai.response.id, gen_ai.response.finish_reasons |
| Token 用量 | gen_ai.usage.input_tokens, gen_ai.usage.output_tokens |
| 网络 | server.address, server.port |
| 会话 | gen_ai.conversation.id, gen_ai.output.type |
| 消息内容 | gen_ai.input.messages, gen_ai.output.messages, gen_ai.system.instructions |
extended_attributes.go** — LoongSuite 扩展属性**:
| 类别 | 属性键示例 |
|---|---|
| Agent | gen_ai.agent.name, gen_ai.agent.id, gen_ai.agent.description |
| Tool | gen_ai.tool.name, gen_ai.tool.call.id, gen_ai.tool.call.arguments, gen_ai.tool.call.result, gen_ai.tool.type, gen_ai.tool.description, gen_ai.tool.definitions |
| Skill | gen_ai.skill.name, gen_ai.skill.id, gen_ai.skill.description, gen_ai.skill.version |
| 检索 | gen_ai.retrieval.query.text, gen_ai.retrieval.documents |
| 重排 | gen_ai.rerank.documents.count, gen_ai.rerank.scoring_prompt, gen_ai.rerank.input_documents, gen_ai.rerank.output_documents 等 |
| Embedding | gen_ai.embeddings.dimension.count, gen_ai.request.encoding_formats |
| 缓存 Token | gen_ai.usage.cache_creation.input_tokens, gen_ai.usage.cache_read.input_tokens |
| 延迟 | gen_ai.response.time_to_first_token, gen_ai.response.reasoning_time, gen_ai.latency.time_in_model_prefill, gen_ai.latency.time_in_model_decode, gen_ai.latency.time_in_model_inference |
| 入口 | gen_ai.session.id, gen_ai.user.id |
| ReAct | gen_ai.react.round, gen_ai.react.finish_reason |
| Memory | gen_ai.memory.operation, gen_ai.memory.user_id, gen_ai.memory.agent_id 等 15+ 属性 |
| Chain/Task | input.value, output.value, input.mime_type, output.mime_type, gen_ai.task.name |
| 框架 | gen_ai.framework |
| Span Kind | gen_ai.span.kind(LLM / AGENT / TOOL / EMBEDDING / RETRIEVER / RERANKER / ENTRY / STEP / MEMORY / CHAIN / TASK) |
8.2 操作名常量
| 常量 | 值 | 对应 Span |
|---|---|---|
OperationChat | "chat" | LLM |
OperationEmbedding | "embeddings" | Embedding |
OperationExecuteTool | "execute_tool" | ExecuteTool |
OperationCreateAgent | "create_agent" | CreateAgent |
OperationInvokeAgent | "invoke_agent" | InvokeAgent |
OperationRetrieval | "retrieval" | Retrieval |
OperationRerank | "rerank_documents" | Rerank |
OperationEntry | "enter" | Entry |
OperationReactStep | "react" | ReactStep |
OperationMemory | "memory" | Memory |
OperationChain | "chain" | Chain |
OperationRunTask | "run_task" | Task |