ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Inngest AI 元数据 OTLP 测试夹具:用捕获的真实 Span 锁住各家 AI 探针的提取行为

Inngest AI 元数据 OTLP 测试夹具:用捕获的真实 Span 锁住各家 AI 探针的提取行为 Inngest AI 元数据 OTLP 测试夹具:用捕获的真实 Span 锁住各家 AI 探针的提取行为【免费下载链接】inngestThe leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.项目地址: https://gitcode.com/GitHub_Trending/in/inngest本文围绕 testdata/README.md 展开,介绍 Inngest 如何用一组从真实 AI SDK 插桩(instrumentation)调用中捕获的 OTLP/JSON trace 夹具,对AIMetadata提取器做黄金文件(golden file)回归验证。读完本文,你会理解 OTLP fixture 的目录组织与调用变体划分、TestAIMetadataExtractor_CapturedFixtures的解码路径为何与线上摄取端点完全一致、四家 AI 探针(官方 OpenAI OTel、Traceloop OpenAI/Anthropic、LangSmith OTel 模式)在属性发射上的具体差异,以及如何用-update参数再生黄金文件。什么是 AI metadata OTLP 夹具库Inngest 从带插桩的 AI SDK 调用中捕获了真实的 OTLP/JSONExportTraceServiceRequest请求体,存放在 pkg/tracing/metadata/extractors/testdata/ 目录下。组织规则是:一个目录对应一套插桩(instrumentation),共 4 个:openai_otel_official ——opentelemetry/instrumentation-openaiopenai_otel_traceloop ——traceloop/instrumentation-openaiopenai_langsmith_otel ——langsmith的wrapOpenAI以 OTel 模式(initializeOTEL)运行anthropic_otel_traceloop ——traceloop/instrumentation-anthropic一个文件对应一种调用变体(fixture),仅当某套插桩确实为该变体发出 span 时才存在夹具:OpenAI 侧变体:params_chat、tools_chat、stream_chat、basic_responses、reasoning_responses、embeddingsAnthropic 侧变体:reasoning_messages各目录当前实际包含的夹具如下(仓库快照,每个夹具旁都有一个同名.out黄金文件):插桩目录夹具(变体)openai_otel_officialparams_chat、tools_chat、stream_chat、embeddingsopenai_otel_traceloopparams_chat、tools_chat、stream_chat、basic_responses、reasoning_responsesopenai_langsmith_otelparams_chat、tools_chat、basic_responses、reasoning_responsesanthropic_otel_traceloopreasoning_messages注意缺失本身就是信息:官方 OpenAI 插桩没有 Responses API 夹具(它不覆盖responses.create),LangSmith 0.7.3 的wrapOpenAI既不包装embeddings.create也不包装流式 chat completions,所以这两个目录里看不到embeddings/stream_chat夹具。夹具如何被验证:TestAIMetadataExtractor_CapturedFixtures测试入口是 aimetadata_test.go 中的TestAIMetadataExtractor_CapturedFixtures。它的核心机制有三点:1. 与线上端点同一条解码路径夹具加载函数 loadOTLPSpans 通过//go:embed testdata内嵌夹具目录,用protojson.Unmarshal解码ExportTraceServiceRequest,再拍平ResourceSpans - ScopeSpans - Spans。源码注释明确说明:这条protojson.Unmarshal路径与摄取端点 traces.go 中处理 OTLP 请求体的路径完全相同。也就是说,夹具测试验证的不是理想化的 span,而是真实探针发射、线上真实解码路径所能解析出来的 span,任何与线上新旧版本行为不一致的解码问题都会被这组测试暴露。2. 黄金文件显式渲染空字段测试并不直接用AIMetadata结构体做比对,而是映射到一个镜像结构goldenAIMetadata(aimetadata_test.go),它去掉了omitemptytag。因此某套插桩没有发射某字段这件事本身就被锁定:缺失的字符串字段渲染为,缺失的数值字段渲染为null;渲染 18 个字段,刻意排除LatencyMs(依赖 span 时间戳,不可复现)与EstimatedCost(依赖模型价格表 model_prices.json,有独立测试覆盖)。测试开头还有一道形状守卫:require.Equal(t, 20, reflect.TypeFor[extractors.AIMetadata]().NumField(), ...),即AIMetadata结构体(见 ai.go)必须是 20 个字段(18 个渲染 2 个刻意留空),一旦有人给结构体加字段而忘记更新goldenAIMetadata和黄金文件,测试会直接失败并提示去补。3. goldie 驱动的断言与再生每个 span 按文档顺序渲染:先写SPAN span name一行,再写缩进 JSON。span 名也参与比对——例如官方插桩的 span 名是chat gpt-4.1-nano,而 LangSmith 的是ChatOpenAI,这种发射方差异同样被锁进黄金文件。断言通过github.com/sebdah/goldie/v2完成,fixture 目录为testdata、后缀.out、失败时输出彩色 diff。再生黄金文件的官方命令(见 testdata/README.md):go test ./pkg/tracing/metadata/extractors -run CapturedFixtures -update四套插桩的行为差异:fixture 目录的注释文档README.md 的 Per-instrumentation notes 一节是理解这组夹具的关键。它记录的正是各家探针在属性发射上的不一致之处——提取器必须按 raw 值存储、不能替发射方修正:openai_otel_official —opentelemetry/instrumentation-openai使用标准gen_ai.*semconv 属性,每次调用一个 span。不覆盖 Responses API(responses.create),因此目录里没有相应夹具。其黄金文件字段最完整:以 params_chat.otlp.json.out 为例,temperature: 0.7、top_p: 0.9、max_tokens: 64等请求参数全部被捕获,而seed: null表示该探针没有发射 seed 属性。openai_otel_traceloop —traceloop/instrumentation-openai发射gen_ai.*属性,并直接输出gen_ai.usage.total_tokens(即 total 由 provider 侧提供,而非本地推导);finish reason 按发射方原样存储:OpenAI 原生的tool_callsfinish reason 在该探针下以单数tool_call出现,提取器不做归一;流式 chat span 不携带 usage,所以 token 计数保持 0,total_tokens也不会被推导出来(推导逻辑只在 input/output 任一非零时触发,见下文)。openai_langsmith_otel — langsmithwrapOpenAI(OTel 模式)在标准gen_ai.*集合之外还携带langsmith.*/ls_*键,因此提取走 semconv 映射即可,不需要任何 LangSmith 专属约定。两个值得注意的细节:model取的是请求时的别名(gen_ai.request.model),带日期后缀的真实模型落在response_model(例如请求gpt-4.1-nano,响应gpt-4.1-nano-2025-04-14,见 params_chat.otlp.json.out);gen_ai.response.finish_reasons以标量字符串而非 semconv 数组到达,提取器的标量回退逻辑会将其包装成单元素列表;Responses API 变体完全不发射 finish reasons;没有embeddings和stream_chat夹具:wrapOpenAI(langsmith 0.7.3)既不包装embeddings.create也不包装流式 chat completions,这些变体根本不产生 span。anthropic_otel_traceloop —traceloop/instrumentation-anthropic覆盖 Anthropic Messages API(messages.create),发射gen_ai.*属性,每次调用一个 span。与 Traceloop OpenAI 探针相同,使用当前版本的gen_ai.provider.name(anthropic),并直接发射gen_ai.usage.total_tokens(不做本地推导)。response_id为空——该探针不发射gen_ai.response.id。唯一的reasoning_messages夹具使用 adaptive extended thinking:Anthropic 没有独立的 reasoning-token 字段,thinking 被并入output_tokens(所以该夹具的output_tokens很大,见 reasoning_messages.otlp.json.out 中的 500);thinking 文本作为gen_ai.output.messages里的reasoningpart 传递,而当前提取器不映射这部分。该夹具同时展示了显式零值的锁定效果:cache_read_tokens: 0与cache_creation_tokens: 0是探针真实发射的 0,而非缺失。AIMetadata 提取链路:从 span 到黄金文件理解夹具锁定的是什么,需要看被测的提取逻辑 aimetadata.go。NewAIMetadataExtractor().ExtractSpanMetadata(ctx, span)的处理顺序是:属性扫描:extractAIMetadataFromAttributes填充AIMetadata,若未识别到任何 AI 属性则返回空结果(测试中对应渲染no AI metadata extracted);延迟计算:由 span 的EndTimeUnixNano - StartTimeUnixNano得到LatencyMs——正因为依赖时间戳,黄金文件才刻意排除该字段;total_tokens 回退推导:仅当 provider 没有直接提供TotalTokens、且 input/output 任一为正时,才用input output推导。这一条正好解释了各家差异:官方/Traceloop OpenAI 探针若直接发total_tokens就用原值,Traceloop 流式 span 无 usage 则保持零且不推导,Anthropic 探针直发total_tokens也不会被覆盖;成本回填:backfillEstimatedCost在EstimatedCost为空时按价格表估算,优先使用response_model计价、回退到request_model,且从不覆盖已有值(ai.go)。由于与价格表耦合,黄金文件同样排除该字段。AIMetadata结构体(ai.go)的设计也呼应了 README 的raw per emitter原则:ResponseModel注释说明它可能是与请求模型不同的日期快照;FinishReasons注释明确记录tool_calls/tool_call的单复数差异按发射方原样保留;Temperature、TopP、Seed等请求参数用指针类型,以便区分显式零值(如 temperature 0 或 seed 0)与属性缺失——黄金文件里temperature: 0.7与temperature: null的区别正来源于此。此外注释还记录了缓存 token 的 provider 语义差异:OpenAI 把缓存 token 报告为InputTokens的子集,而 Anthropic 是增量式报告,值均按 raw 存储、不做调和。适用前提与扩展方式适用前提:这组夹具是捕获快照,对应特定版本的插桩(例如 LangSmith 0.7.3 的wrapOpenAI覆盖面)。若升级插桩后发射行为变化,黄金文件会失败——此时应先判断差异来自插桩行为改变还是提取器回归,再决定更新提取器还是重新捕获夹具。再生黄金文件:修改提取逻辑后,用前文go test ./pkg/tracing/metadata/extractors -run CapturedFixtures -update命令重写全部.out,然后逐个人工 review diff,确认变化符合预期。新增夹具:向对应插桩目录放入新的variant.otlp.json(标准 OTLP/JSONExportTraceServiceRequest),测试会通过fs.Glob(fixtures, testdata/*/*.otlp.json)自动发现并运行。继续深入:提取器同目录下的 attributes.go 提供属性读取辅助,aimetadata_test.go中还有针对EstimateCost与 semconv 映射的独立测试;摄取端点 pkg/api/apiv1/traces.go 展示了与夹具测试共用的protojson解码入口。这组夹具的设计要点可以概括为:用真实捕获代替手工构造,用同路径解码保证与线上一致,用显式空值黄金文件把不发射什么也纳入回归范围——在多家 AI 探针属性约定不一致的现实下,这是锁定提取行为最可靠的方式。【免费下载链接】inngestThe leading workflow orchestration platform. Run stateful step functions and AI workflows on serverless, servers, or the edge.项目地址: https://gitcode.com/GitHub_Trending/in/inngest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表