ARTICLE DETAIL

资讯详情

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

AgentOps OTel Collector Builder:用 OTTL 在遥测管道中实时计算 LLM Token 成本

AgentOps OTel Collector Builder:用 OTTL 在遥测管道中实时计算 LLM Token 成本 AgentOps OTel Collector Builder用 OTTL 在遥测管道中实时计算 LLM Token 成本【免费下载链接】agentopsPython SDK for AI agent monitoring, LLM cost tracking, benchmarking, and more. Integrates with most LLMs and agent frameworks including CrewAI, Agno, OpenAI Agents SDK, Langchain, Autogen, AG2, and CamelAI项目地址: https://gitcode.com/GitHub_Trending/ag/agentopsAgentOps 的builder包负责把tokencost包中的模型价格数据转换成 OpenTelemetry Collector 的 YAML 配置让 LLM 应用的 Trace 在流经 Collector 管道时自动获得gen_ai.usage.prompt_cost/gen_ai.usage.completion_cost成本属性。本文基于 builder 包文档 展开并结合 配置模板、Dockerfile 与 集成测试 等源码完整讲解这套“构建期注入价格、运行期 OTTL 计算”的实时成本归因机制读完你可以掌握模型价格如何被加载与归一化、Jinja2 模板如何渲染出 transform processor 配置、OTTL 的 span cache 与 where 条件如何协同工作以及如何在 Docker 构建流程中复用该机制并验证结果。1. builder 包解决什么问题文档明确列出了该包的目标从tokencost包的 JSON 模型价格数据库model_prices.json中提取 Token 成本数据将其转换为 OpenTelemetry collector-contrib 中 transform processor 可用的 YAML 配置生成完整的 Collector 配置文件对带有gen_ai.usage.completion_tokens和/或gen_ai.usage.prompt_tokens属性的 span 应用 OTTLOpenTelemetry Transformation Language转换通过 span 的模型属性把成本匹配到具体模型从而实现在 LLM API span 上的实时成本归因。整体工作流在文档中概括为五步并与源码一一对应builder 从tokencost包的model_prices.json加载 Token 定价数据——对应 load_model_costs()数据被解析并转换为合适的十进制精度格式——对应ModelCost的 Pydantic 校验器config目录中的模板文件被处理注入模型成本——对应 template.py生成 OpenTelemetry Collector 的 YAML 配置配置中包含 OTTL 转换语句识别携带模型属性的 span 并套用对应成本数据。包结构上builder由 conf.py 定义关键路径模板目录为app/opentelemetry-collector/config默认输出目录为app/opentelemetry-collector/out。依赖方面pyproject.toml 声明了tokencost0.1.26、jinja23.1.6、pydantic2.11.4与agentops0.4.10要求 Python 3.12——agentops被用于导入SpanAttributes语义约定常量见第 3 节。2. 模型价格从 tokencost JSON 到 Decimal 精度成本数据的入口是 costs/init.py。其核心是一个 Pydantic 模型ModelCost字段包括model模型名、provider可选来自 tokencost 的litellm_provider字段、input、output默认Decimal(0)以及可选的cached、reasoningfrom_tokencost_json()L50-L68把 tokencost 的 JSON 字段映射过来input_cost_per_token→input、output_cost_per_token→output、cache_read_input_token_cost→cached、reasoning_cost_per_token→reasoning。两个实现细节保证了“货币计算”的可靠性float → Decimal 的精确转换L35-L47字段校验器先把浮点数转成字符串再构造Decimal源码注释明确指出“先转字符串可避免转换过程中的浮点噪声fp noise”值为0会被转换为None注释说明“零值不应进入上游计算”——这样免费模型不会产生误导性的 0 成本。输出格式控制L20-L33model_dump()把 Decimal 格式化为最多 10 位小数并去掉末尾零保证写入 YAML 的价格字面量干净例如0.00001而不是0.0000100000。价格库的加载位置由get_tokencost_path()L71-L95确定先通过importlib.util.find_spec(tokencost)查找本地安装的包再回退到importlib.metadata.distribution(tokencost)查找 site-packages找不到则返回None最终load_model_costs()会抛出带路径信息的FileNotFoundError。这些行为有对应单测覆盖见 tests/test_model_cost.py例如test_init_with_mixed_data验证 float/str 输入统一转为 Decimal、reasoning0被归一为Nonetest_model_dump_formats_decimals验证 dump 后的字符串格式。3. Jinja2 模板渲染把价格注入 transform processorotelcollector_config/template.py 是渲染层其工作方式模板环境jinja2.Environment以CONFIG_DIR即config/目录为FileSystemLoader并开启trim_blocks/lstrip_blocks让渲染结果保持缩进整洁L22-L26共享上下文L29-L51_get_shared_context()返回一个SEMCONV字典把模板变量映射到 AgentOps 的语义约定常量agentops.semconv.SpanAttributesToken 用量类LLM_USAGE_PROMPT_TOKENS、LLM_USAGE_COMPLETION_TOKENS、LLM_USAGE_TOTAL_TOKENS、缓存/推理/流式 Token 等、模型类LLM_REQUEST_MODEL、LLM_RESPONSE_MODEL、项目元信息AGENTOPS_PROJECT_IDagentops.project.id以及两个成本属性gen_ai.usage.prompt_cost/gen_ai.usage.completion_cost。模板中因此不硬编码任何属性名而是通过{{ SEMCONV.* }}占位符引用两个渲染入口render_base_config()直接把base.yaml拷贝到输出目录该文件不含动态内容L74-L86render_processors_config()先调用load_model_costs()取全部模型价格再把[m.model_dump() for m in model_costs]作为MODEL_COSTS传入processors.yaml.tpl渲染L89-L103。输出文件名会去掉.tpl后缀即生成processors.yaml。processors.yaml.tpl 中有三类 processor 定义resource把 JWT 认证上下文中的auth.project_idupsert 到ProjectId与agentops.project.id两个资源属性上实现多项目数据隔离resourcedetection/system从操作系统探测主机名transform核心成本计算见下节。配套的 config/base.yaml 定义了接收器OTLP HTTP 4318 / gRPC 4317均挂jwtauthextension做 JWT 鉴权fluentforward 24224 仅接 logs、batchsend_batch_size: 100000、timeout: 5s、memory_limiterlimit_mib: 1800以及clickhouse/otel_traces导出器endpoint/库名/表名/TTL 全部来自环境变量create_schema: false。注意 traces 管道是三条管道中唯一挂transform的traces: receivers: [otlp] processors: [memory_limiter, resourcedetection/system, resource, transform, batch] exporters: [clickhouse/otel_traces]即成本计算只作用于 Trace 数据且位于batch之前逐 span 生效。4. OTTL 转换详解span cache where 条件这是整个机制的精髓。文档给出的 OTTL 示例以简化的模型属性展示如下processors: transform: trace_statements: # 使用根级 trace_statements使 cache 在整个管道的 # 所有 transform 之间共享scope: span 会每次新建 cache # 成本数据在容器构建期动态填充 # 对 tokencost 数据库中的每个模型写入输入/输出成本 # 示例 # - set(span.cache[_input_costs][gpt-4], 0.00001) # - set(span.cache[_output_costs][gpt-4], 0.00003) # 对同时具备 prompt tokens 和已知模型的 span 写入 prompt 成本 - set(span.attributes[gen_ai.usage.prompt_cost], Double(span.attributes[gen_ai.usage.prompt_tokens]) * span.cache[_input_costs][span.attributes[gen_ai.request.model]]) where ( span.attributes[gen_ai.usage.prompt_tokens] ! nil and span.attributes[gen_ai.request.model] ! nil and span.cache[_input_costs][span.attributes[gen_ai.request.model]] ! nil) # 对具备 completion tokens 和已知模型的 span 写入 completion 成本 - set(span.attributes[gen_ai.usage.completion_cost], Double(span.attributes[gen_ai.usage.completion_tokens]) * span.cache[_output_costs][span.attributes[gen_ai.request.model]]) where ( span.attributes[gen_ai.usage.completion_tokens] ! nil and span.attributes[gen_ai.request.model] ! nil and span.cache[_output_costs][span.attributes[gen_ai.request.model]] ! nil)对照 processors.yaml.tpl 的实际模板成本填充段由 Jinja2 循环生成——对MODEL_COSTS中每个模型输出两条set语句{% for cost in MODEL_COSTS %} - set(span.cache[_input_costs][{{ cost.model }}], {{ cost.input }}) - set(span.cache[_output_costs][{{ cost.model }}], {{ cost.output }}) {% endfor %}渲染后tokencost 价格库中的每个模型都会变成两行span.cache赋值语句这正是文档所说“实际 processor 配置包含 tokencost 数据库中所有模型的成本数据通过构建期的 Jinja2 模板填充”。几个值得注意的设计要点成本数据放在 span cache 而非 span 属性中_input_costs/_output_costs两个字典作为价格“内存表”不会污染导出的 span 属性——只有最终算出的prompt_cost/completion_cost才写入span.attributes。根级trace_statements让 cache 跨语句共享文档特别强调若写成- scope: span形式每条转换会各自新建 cache先填充的价格就查不到了模板注释中也保留了这一说明。Double()函数做类型转换Token 数在 span 属性中通常是整数OTTL 用Double(...)转浮点后与单价相乘。三层 nil 检查的 where 子句Token 属性存在、模型属性存在、且该模型在 cache 中有价格三者同时成立才执行成本计算。未知模型不会报错也不会写入成本属性只是被静默跳过——这降低了管道对长尾模型的脆弱性。模型匹配属性的一处演进文档示例中以gen_ai.request.model作为匹配键从当前源码看模板实际使用的是SEMCONV.LLM_RESPONSE_MODEL即gen_ai.response.modeltemplate.py L48-L49 的成本上下文与 tpl 文件 的 where 条件均以LLM_RESPONSE_MODEL为准集成测试发送的 span 也使用gen_ai.response.model作为模型标识。以response.model匹配还有一个隐含好处它来自 LLM 实际响应的模型名能覆盖请求别名/路由改名的情况。文档归纳的“成本数据四大特性”同样值得记住构建期动态生成、仅存内存、按需使用、管道内统一应用。所用到的 OTTL 能力包括 where 条件子句、span cache、Double()函数、nil 检查、set()函数与字典下标访问文档说明该实现遵循 OpenTelemetry 的 AI/ML 语义约定gen_ai.*属性族。5. 使用方式与 Docker 构建集成5.1 命令行生成配置文档给出的用法# 默认输出目录 python -m builder build_configs # 自定义输出目录 python -m builder build_configs -o /path/to/output/directory一个需要留意的事实性差异main.py 中实际注册的子命令是generate_configs-o/--output-dir默认OUT_DIRDockerfile 中调用的也是它python -m builder generate_configs -o /app/out不传子命令时打印帮助传入未知子命令会报错退出。5.2 两阶段 Docker 构建文档指出“该流程也用于 Docker 构建过程在构建期生成配置”。Dockerfile 完整体现了这一点# 第一阶段builder 生成配置文件 FROM python:3.12-slim AS builder WORKDIR /app COPY builder ./builder COPY config ./config RUN pip install -e builder RUN python -m builder generate_configs -o /app/out # 第二阶段collector 服务 FROM agentopsai/otelcontribcol:latest COPY --frombuilder /app/out/* /etc/config/ EXPOSE 1888 8888 8889 13133 4317 4318 55679 # otelcollector 内部会合并多个配置文件 CMD [--config/etc/config/base.yaml, --config/etc/config/processors.yaml]含义是模型价格库被“烘焙”进镜像——每次重新构建镜像即刷新成本数据运行时 Collector 同时加载base.yaml管道骨架与processors.yaml含全量价格与 transform 语句两个配置。这与文档“成本数据在构建期动态生成”的表述一致也意味着价格时效性由镜像重建频率决定。5.3 本地运行与依赖的环境变量compose.yaml 提供 otelcollector ClickHouseclickhouse/clickhouse-server:24.12的本地组合Collector 端口的默认值即 ClickHouse 连接http://clickhouse:8123库otel_2traces 表otel_tracesTTL 默认12h与JWT_SECRET。曝光端口除 OTLP4317/4318外还包括 pprof1888、health_check13133、zpages55679与 fluentforward24224/tcpudp。发送数据时需要携带 JWTbase.yaml中 OTLP 接收器挂了jwtauthextension密钥来自JWT_SECRET。仓库提供了两个手动验证脚本01-send-span.sh 发送一个不带 Token 属性的测试 span02-send-span-with-model.sh 则发送带模型与 Token 用量的 span 以触发成本计算——二者都是curl POST http://localhost:4318/v1/tracesAuthorization 头读取本地.jwt文件。6. 测试如何验证成本归因6.1 单元测试价格归一化tests/test_model_cost.py 围绕ModelCost验证完整/部分/混合类型数据的初始化、float 与字符串统一转 Decimal、零值归一为None以及model_dump()的十进制格式化输出。6.2 集成测试端到端成本计算tests/integration/test_collector_cost.py 是一套完整的端到端验证流程用docker-compose up -d拉起 compose.yaml 中的服务并用 app/clickhouse/migrations 下的 SQL 迁移初始化 ClickHouse 的otel_2库生成 HS256 JWTpayload 含project_id、api_key、exp、aud与 Collector 的JWT_SECRET配套完成 OTLP 接收端鉴权create_span_data()构造 OTLP span属性包含gen_ai.response.model、gen_ai.usage.prompt_tokens、gen_ai.usage.completion_tokensPOST 到http://localhost:4318/v1/traces轮询 ClickHouse 的otel_traces表断言SpanAttributes[gen_ai.usage.prompt_cost]与completion_cost。三个用例分别覆盖已知模型gpt-4prompt_tokens100、completion_tokens1000断言两个成本属性均存在且大于 0L255-L298另一个已知模型claude-3-opus-20240229验证 cache 中多模型价格均可命中未知模型unknown-model-xyz断言 span 上不存在gen_ai.usage.prompt_cost/completion_cost属性L349-L408精确对应第 4 节 where 子句的第三个 nil 条件。运行该集成测试需要 Docker 环境dev 依赖含docker7.0.0、PyJWT。7. 小结这套机制的设计取舍回看 文档 与源码可以提炼出几个自洽的设计取舍计算位置成本计算放在 Collector 侧而非 SDK 侧SDK 只需上报gen_ai.usage.*与模型属性管道统一补全成本对所有接入方一致生效价格来源复用tokencost的价格库并做 Decimal 精度处理先转字符串消除浮点噪声、零值转 None保证货币字面量精确注入方式Jinja2 模板在构建期展开全量模型价格到span.cache运行期只存内存、不污染 span 属性安全边界三层 where 条件 nil 检查未知模型静默跳过管道不因价格缺失而失败可验证性从ModelCost单测到 docker-compose 端到端集成测试价格归一化与成本写入都有明确断言。适用前提与限制该流程依赖tokencost已安装构建环境通过pip install -e builder引入、价格时效取决于镜像重建频率、transform仅挂接在 traces 管道上且 OTLP 入口要求 JWT 鉴权JWT_SECRET。理解了这些你就掌握了 AgentOps 实时 LLM 成本归因从“价格库 → 模板 → OTTL → ClickHouse”的完整链路。【免费下载链接】agentopsPython SDK for AI agent monitoring, LLM cost tracking, benchmarking, and more. Integrates with most LLMs and agent frameworks including CrewAI, Agno, OpenAI Agents SDK, Langchain, Autogen, AG2, and CamelAI项目地址: https://gitcode.com/GitHub_Trending/ag/agentops创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表