主题
1. 架构设计
1. 三层架构
tRPC-Agent 的 Langfuse 集成分为三层,职责清晰分离:
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: 埋点层(Instrumentation) │
│ trpc_agent_sdk/telemetry/_trace.py │
│ trpc_agent_sdk/runners.py, agents/, ... │
│ → 在 Agent 执行时创建 OTel Span,写入 gen_ai.* 属性 │
└────────────────────────────┬────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: 映射层(Transformation) │
│ trpc_agent_sdk/server/langfuse/tracing/opentelemetry.py │
│ _LangfuseMixin / _LangfuseBatchSpanProcessor │
│ → 按 gen_ai.operation.name 映射为 langfuse.* 属性 │
└────────────────────────────┬────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: 导出层(Export) │
│ _LangfuseOTLPExporter → Langfuse /api/public/otel/v1/traces│
│ → Basic Auth,BatchSpanProcessor 批量异步发送 │
└─────────────────────────────────────────────────────────────┘生活类比:
- 埋点层 = 工人在各工位填原始生产记录
- 映射层 = 行政把内部字段翻译成 Langfuse 能读懂的面单格式
- 导出层 = 物流把包裹批量发往 Langfuse 仓库
2. 包与模块关系
trpc-agent(业务仓库)
│
├── trpc-agent-py(pip 依赖,模块名 trpc_agent_sdk)
│ ├── telemetry/ ← 埋点实现
│ └── server/langfuse/ ← Langfuse 映射 + 导出
│
└── trpc_agent_ecosystem/
├── langfuse/ ← re-export SDK(方便 from trpc_agent_ecosystem.langfuse import ...)
└── trpc_langfuse/ ← tRPC-Python 插件包装trpc_agent/telemetry/trace.py 仅做 re-export,实际逻辑全在 trpc_agent_sdk 中:
python
from trpc_agent_sdk.telemetry import tracer
from trpc_agent_sdk.telemetry import trace_runner
from trpc_agent_sdk.telemetry import trace_call_llm
# ...3. OpenTelemetry 与 Langfuse 的角色
| 组件 | 角色 |
|---|---|
tracer = trace.get_tracer("trpc.python.agent") | 全局 Tracer,创建 Span |
TracerProvider | 由 langfuse_setup() 注册,挂载 SpanProcessor |
SpanProcessor | Span 结束时触发映射与导出 |
OTLPSpanExporter | HTTP 发送到 Langfuse |
| Langfuse Server | 解析 OTel Span,渲染为 Trace / Generation / Span UI |
Langfuse 通过 特定 Span Attribute 前缀(langfuse.trace.*、langfuse.observation.*)识别语义,详见 4_attribute_mapping.md。
4. 核心类一览
| 类 / 函数 | 文件 | 职责 |
|---|---|---|
LangfuseConfig | opentelemetry.py | 配置 public_key / secret_key / host 等 |
setup() | opentelemetry.py | 初始化 TracerProvider + Processor + Exporter |
_LangfuseMixin | opentelemetry.py | 属性映射、Span 过滤、重命名 |
_LangfuseBatchSpanProcessor | opentelemetry.py | 批量导出(默认) |
_LangfuseSpanProcessor | opentelemetry.py | 同步导出(batch_export=False) |
_LangfuseOTLPExporter | opentelemetry.py | 带 debug 日志的 OTLP HTTP Exporter |
trace_runner() 等 | _trace.py | 向当前 Span 写入业务属性 |
TrpcLangfusePlugin | trpc_langfuse/_langfuse.py | tRPC 插件,在 MASTER 进程启动时调用 setup |
5. setup() 做了什么
langfuse_setup() 的核心步骤(简化):
python
def setup(config: LangfuseConfig) -> TracerProvider:
# 1. 解析配置(支持环境变量 LANGFUSE_*)
# 2. 构造 Basic Auth: base64(public_key:secret_key)
# 3. endpoint = f"{host}/api/public/otel/v1/traces"
# 4. exporter = _LangfuseOTLPExporter(endpoint, headers)
# 5. processor = _LangfuseBatchSpanProcessor(exporter) # 或 Simple
# 6. trace_provider = TracerProvider()
# 7. trace_provider.add_span_processor(processor)
# 8. trace.set_tracer_provider(trace_provider)
return trace_provider必须在 Runner 执行前调用,否则 Span 没有 Exporter,数据不会到达 Langfuse。
6. 设计选择:为何用 OTel 而非 Langfuse SDK
| OTel 通路 | Langfuse SDK 直写 |
|---|---|
| 与 Galileo、ZhiyanLLM 等共用同一套埋点 | 每种后端各写一套上报逻辑 |
| 框架只关心 Span 属性,后端可插拔 | 框架与 Langfuse API 强耦合 |
| Langfuse 官方支持 OTel 接入 | 对小脚本更直观 |
tRPC-Agent 同时集成了多种可观测后端(Langfuse、Galileo、ZhiyanLLM),统一走 OTel 埋点 + 不同 SpanProcessor/Exporter 是更可持续的架构。
7. 异步 Generator 与 Span 上下文
框架在 Runner.run_async() 和 BaseAgent.run_async() 中使用 tracer.start_span() 而非 start_as_current_span(),原因是:
在 async generator 中使用
start_as_current_span,取消时可能从另一上下文关闭 generator,触发 detach token 错误。
因此外层 Span(invocation、agent_run)创建但不激活为 current context;内层 LLM / Tool 使用 start_as_current_span()。这会影响 OTel 严格的 parent-child 链接行为,但 Langfuse 仍能通过 langfuse.session.id、langfuse.user.id 及 Span 时间序做会话级关联。
详见 3_instrumentation.md 第 2 节。