快速结论:当 LiteLLM 开启 OTel v2(LITELLM_OTEL_V2=true)并启用事件导出时,gen_ai.client.operation.exception 事件因 Event.body 默认为 None,导致 OTLP 编码器在序列化时抛异常,整批日志被静默丢弃。排查重点是 v2 事件路径与 OTLP 编码器,而不是 v1 的 opentelemetry.py。
适用环境:LiteLLM 代理部署;已确认的依赖为 LiteLLM 硬锁定的 opentelemetry-api/-sdk/-exporter-otlp==1.28.0;导出协议为 OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf。Issue 未提供操作系统、Python、CUDA、显卡信息。
最快修复方案:Issue 中作者验证有效的修复是:在 litellm/integrations/otel/plumbing/events.py 的 record_operation_exception 中,为 Event 传入 body=message(异常消息本身已在 exception.message 中)。该修复已由 #42431 合并落地。
注意事项:依赖升级到 opentelemetry-exporter-otlp-proto-common>=1.43.0 虽然从版本层面能避开 None 编码异常,但 LiteLLM 当前硬锁定 1.28.0,单纯升版本与仓库约束冲突;且原 Issue 明确指出这不是推荐的修复路径。修复前所有该 pipeline 上的日志批次都会丢失,不只是异常事件。
问题场景
用户运行 LiteLLM 代理,并通过 litellm_settings.callbacks: ["otel"] 启用 OTel 回调,同时开启 v2 集成与事件导出:
LITELLM_OTEL_V2=true
LITELLM_OTEL_INTEGRATION_ENABLE_EVENTS=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://<collector>
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
在这种配置下,所有 gen_ai.client.operation.exception 事件都不会出现在 OTLP collector 侧,且与被一同批处理的日志记录一起被丢弃。
报错原文
Exception while exporting logs.
.../opentelemetry/sdk/_logs/_internal/export/__init__.py line 307, in _export_batch
.../opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py line 159, in export
serialized_data = encode_logs(batch).SerializeToString()
.../opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py line 57, in _encode_log
body=_encode_value(log_data.log_record.body),
.../opentelemetry/exporter/otlp/proto/common/_internal/__init__.py line 90, in _encode_value
raise Exception(f"Invalid type {type(value)} of value {value}")
Exception: Invalid type <class 'NoneType'> of value None
原因分析
最可能的原因是:GenAIEventRecorder.record_operation_exception(位于 litellm/integrations/otel/plumbing/events.py)在构造 Event 时没有传 body。而 opentelemetry._events.Event.body 默认是 None,OTLP 的 AnyValue 没有 None 的表示方式,于是 _encode_value 在序列化整批日志时抛异常。
更关键的是数据丢失机制:BatchLogRecordProcessor._export_batch 捕获异常后清空本批记录并返回 idx,将这批数据当作已成功导出处理,因此不会重试、不崩溃,只在日志里留下 traceback。由于 record_operation_exception 是 v2 logs signal 上唯一的 emit 点,该 pipeline 上的每一批都包含不可编码记录,最终 logs signal 什么都发不出去。
Issue 还确认:上游 OTel 在 opentelemetry-exporter-otlp-proto-common 1.43.0 中为 _encode_value 增加了 None 分支(if value is None: return PB2AnyValue()),但从 1.28.0 到 1.42.1 都会抛异常;LiteLLM 硬锁 1.28.0,所以按发布版本该事件永远无法导出。现有 CI 未发现该问题,是因为测试都用 InMemoryLogExporter,从不经过真正的 OTLP 编码器。
环境排查
- 确认是否设置了
LITELLM_OTEL_V2=true,即问题是否出现在 v2 集成路径而非 v1opentelemetry.py。 - 确认是否设置了
LITELLM_OTEL_INTEGRATION_ENABLE_EVENTS=true,事件导出是否开启。 - 确认
OTEL_EXPORTER_OTLP_PROTOCOL是否为http/protobuf(即是否会触发 OTLP 编码器)。 - 确认
litellm_settings.callbacks中是否包含otel。 - 确认安装的
opentelemetry-api/opentelemetry-sdk/opentelemetry-exporter-otlp版本;Issue 中 LiteLLM 锁定为 1.28.0,1.42.1 及以下同样会触发该编码异常。 - 检查日志中是否存在上述
Exception while exporting logs.和Invalid type <class 'NoneType'> of value None。
解决步骤
- 升级或应用包含 #42431 修复的 LiteLLM 版本(该修复已合并,提交
238f434153)。 - 若需自行打补丁,修改
litellm/integrations/otel/plumbing/events.py中record_operation_exception构造的Event,补上body=message,使异常消息成为事件 body。 - 该改动依赖已有变量
message(异常消息已存在于exception.message),不引入新的敏感信息,并且不依赖把opentelemetry-*从 1.28.0 升到 1.43.0。 - 修复后重启 LiteLLM 服务,再触发一次会抛出异常的请求,让
gen_ai.client.operation.exception事件重新产生。
验证方法
在 OTLP collector 侧确认 gen_ai.client.operation.exception 事件已能收到,且日志中不再出现 Exception while exporting logs. 与 Invalid type <class 'NoneType'> of value None。
若在本地验证,Issue 作者补充的测试方式更可靠:运行真实的 encode_logs(...).SerializeToString() 编码路径,而非仅用 InMemoryLogExporter。按 Issue 数据,未修复时为 2 failed, 6 passed,修复后为 8 passed,完整 v2 otel 套件在锁定 opentelemetry-*==1.28.0 下为 342 passed, 6 skipped。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


