Phase 19: Capstone Projects

石28课:使用OTel GenAI跨度和普罗梅斯指标可观察性

无可观察的代理带是花钱的黑盒子.本课程手动滚动一个跨度构造器,它发出符合OpenTelemetry GenAI语义公约的记录,将它们写入一个JSON-Lines文件,每行一个跨度,并暴露了Prometheus文本格式的计数器和历史图.整个东西是Stdlib Python,并运行离线.

Type: Build

Languages: Python (stdlib)

Prerequisites: Phase 19 · 25 (verification gates), Phase 19 · 26 (sandbox), Phase 19 · 27 (eval harness), Phase 13 · 20 (OpenTelemetry GenAI), Phase 14 · 23 (OTel GenAI conventions)

Time: ~90 minutes

学习目标

  • 建立一个以OpenTelemetry GenAI语义公约为形状的跨度数据类.
  • 实现一个JSONL出口程序,每行写出一个独立的跨度.
  • 建立标签和Prometheus文本格式曝光的计数器和历史图.
  • 包装任何调用器在一个记录时间,状态和例外的跨度环境管理器中.
  • 检查发射的跨度是否回路通过json.loads并且与规格的形状相匹配.

问题

在生产中,编码代理每次生产三类文物:模型调用,工具执行和验证门决策.

首先是失败模式是缺失的痕迹.周二发生了一些错误,但唯一记录是500行聊天日志.没有记录哪个工具运行,需要多长时间,多少代币进入提示,或者门是否拒绝任何东西.代理作者必须猜测.

其他方法是: 子写了跨度,但使用了自己的专用字段名称.Grafana,Honeycomb,Jaeger或本地CLI中没有任何东西能读取它们.团队堆中存在的任何工具都会浪费,因为跨度是不标准的.

第三种故障模式是未聚合的指标.你可以看到一个缓慢的工具调用在追踪中,但你不能回答"在上个小时内读_文件调用的p95延迟是什么?"因为没有指标,只有痕迹.

开放Telemetry GenAI语义公约是为了这个.它们定义了一个小组标准属性,在LLM框架中跨度发射者共享.如果你的带写这些属性,每个与OTel兼容的后端都可以读取它们.

概念

flowchart TD
  Call[tool call / model call / gate decision] --> Span["SpanBuilder.span()<br/>context manager"]
  Span --> GenAI[GenAISpan<br/>trace_id / span_id / name<br/>attributes:<br/>gen_ai.system<br/>gen_ai.request.*<br/>gen_ai.usage.*<br/>start, end, status]
  GenAI --> Writer[JSONLWriter]
  GenAI --> Metrics[MetricsRegistry]
  Writer --> Traces[traces.jsonl]
  Metrics --> Prom[/metrics text/]

跨度具有一个追踪ID (整个代理调用),一个跨度ID (这个操作),一个名称 (例如 gen_ai.chat现在gen_ai.tool.execution), 基因AI公约的属性,开始和结束时间以及状态.

基因AI公约标准化了这些属性密钥: gen_ai.system(哪个提供商,例如anthropic现在openai), gen_ai.request.model(模型标识),gen_ai.request.max_tokens现在gen_ai.usage.input_tokens现在gen_ai.usage.output_tokens现在gen_ai.response.model现在gen_ai.response.id现在gen_ai.operation.name另外,工具特定的钥匙gen_ai.tool.name其他gen_ai.tool.call.id现在,我们要去.

导出者写JSONL.每行一个JSON对象.这是下游工具可以流媒体,抓取和进口的最简单的格式.一个真正的OTel导出者会说OTLP gRPC;课程的JSONL导出者是离线相当的,并且在每个工作站上出发为零.

工具的每次调用中,一个反增量:tools_called_total{tool="read_file"}历史图记录观察到的延迟:tool_latency_ms{tool="read_file"}它们都将串行成Prometheus文本曝光格式,这是基于拉力的指标的实际标准.

建筑

flowchart LR
  Harness[AgentHarness<br/>lessons 25-27] --> Span[SpanBuilder<br/>context mgr / attrs / status]
  Span --> Exporter[JSONLExporter<br/>traces.jsonl]
  Span --> Metrics[MetricsRegistry<br/>counters / histograms]
  Metrics --> Prom[Prometheus text<br/>exposition]

跨度构造商是一个小类的span(name, attrs)文本管理器记录入口开始时间,记录出口结束时间,将一个例外添加,如果一个被提升,并将最终的跨度推向出口商.

计数是两个指数.{(name, frozen_labels): int}异谱记录原料样本,并将其在暴露时串行到Prometheus异谱桶中.

你会建造什么

main.py船舶:

  1. GenAISpan数据类: trace_id, span_id, parent_span_id,名称,属性, start_unix_nano, end_unix_nano,状态, status_message,事件.
  2. SpanBuilder课程span(name, attrs, parent=None)环境管理者.
  3. JSONLExporter课程export(span)它们的位置是
  4. Counter其他Histogram课程加上MetricsRegistry现在,我们要去.
  5. prometheus_exposition(registry)输出文本格式.
  6. wrap_tool_call(name)装饰器发射跨度,并更新数据.
  7. 演示:合成一个完整的代理调用 (gen_ai.chat跨度在工具跨度周围),写 traces.jsonl,打印Prometheus曝光,退出零.

跨度ID和跟踪ID是16字节的六字符串,由 os.urandom导出者从来没有扔掉, IO 错误被发现,但带仍然运行.

历史图表有一个固定的桶集合 (OTel默认的延迟在毫秒: 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, +Inf).样本存储为列表; 曝光按要求计算每桶计数.

为什么用手动滚动而不是开放式仪表sdk

托特尔 Python SDK 是一个真正的依赖性.它还包括几千行代码,多个过程为OTLP出口商,并有一个运行时间成本,这淹没了课程预算.手动滚动版本教导了线程格式.在生产中,你将相同的属性线程到真正的SDK中,并获得OTLP出口商,批量和资源检测免费.

课程发射的电线格式将在2030年继续分析,因为OTel从来没有打破GenAI属性名称;他们只添加新的属性.

如何与A轨道的其他部分相结合

第25课产生了门链. 第26课产生了沙箱. 第27课产生了评估带. 第28课使所有三个都可观看. 第29课将端到端演示的每个步骤包裹成跨度,并在最后打印了普罗梅蒂乌斯文本.

运行它

bashcd phases/19-capstone-projects/28-observability-otel-traces
python3 code/main.py
python3 -m pytest code/tests/ -v

演示显示一个traces.jsonl在课程工作的 dir (在结束时清洁),然后打印一个三个跨度的样本,然后打印了计数和历史图的普罗梅泰斯曝光.测试验验证,跨度连续回路,可нони性GenAI属性存在,数量正确增加,以及历史图的曝光包含预期的桶数量.

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.