Add opentelemetry-instrumentation-litellm#1
Open
yucheng-berri wants to merge 1 commit into
Open
Conversation
Adds a new GenAI instrumentation for the litellm package. Wraps the four module-level entry points litellm.completion, litellm.acompletion, litellm.embedding, and litellm.aembedding on top of the shared opentelemetry-util-genai TelemetryHandler, emitting gen_ai.* spans, prompt and output messages, and duration + token-usage metrics through the common util-genai lifecycle. Streaming reuses the shared SyncStreamWrapper / AsyncStreamWrapper base classes from util-genai — this instrumentation only implements the litellm-specific chunk parsing and telemetry finalization hooks. Because litellm is a unified interface over many LLM providers, gen_ai.provider.name is resolved per call from litellm's custom_llm_provider (kwarg, then the response's _hidden_params, then litellm.get_llm_provider(model) as fallback) and mapped to the semconv GenAiProviderNameValues enum where a match exists, otherwise passed through verbatim. Includes manual and zero-code examples plus a hermetic test suite that stubs litellm in sys.modules so tests do not need the real package or network credentials. Assisted-by: Claude Opus 4.7
d960133 to
74491ab
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new GenAI instrumentation for litellm under
instrumentation-genai/opentelemetry-instrumentation-litellm/.Wraps the four module-level entry points
litellm.completion,litellm.acompletion,litellm.embedding, andlitellm.aembeddingon top of the sharedopentelemetry-util-genaiTelemetryHandler— following the boundary rules ininstrumentation-genai/CLAUDE.md.What it emits
gen_ai.*spans (chat <model>/embeddings <model>) with request/response model, temperature, top_p, max_tokens, seed, stop_sequences, response id, finish_reasons, and input/output token counts.InvocationMetricsRecorder.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_ONLY(orSPAN_AND_EVENT) andOTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimentalare set.SyncStreamWrapper/AsyncStreamWrapperbases from util-genai; only litellm-specific chunk parsing and finalization live here (stream_wrappers.py).Why litellm needs custom handling
LiteLLM is a unified interface for 100+ providers, so
gen_ai.provider.namecannot be hard-coded. It's resolved per-call from litellm'scustom_llm_provider:custom_llm_providerkwarg the caller passed._hidden_params.custom_llm_provideron the response (populated by litellm after the call resolves).litellm.get_llm_provider(model)fallback (for models with a provider prefix, e.g.anthropic/claude-3-5-sonnet).Known providers map to the semconv
GenAiProviderNameValuesenum; unknown ones pass through verbatim so the instrumentation stays useful for the long tail of providers litellm supports.Base pattern
Modelled after
opentelemetry-instrumentation-openai-v2's post-util-genai refactor (litellm's response shape is OpenAI-compatible, so the extractors transfer cleanly). Divergences:server.address/server.portcome fromapi_base/base_urlkwargs when the caller uses a proxy._v_oldvariant.Verification
Ruff: clean under repo
pyproject.tomlconfig (ruff check+ruff format --check).Pylint 10.00/10 with repo
.pylintrc.12 hermetic unit tests pass on Python 3.12 with the workspace
opentelemetry-util-genaiinstalled. Tests stublitellminsys.modulesso they don't need the real package or network credentials.40/40 live end-to-end assertions passed against real OpenAI + Anthropic HTTP endpoints with
litellm==1.52.0. Matrix:text-embedding-3-small, dim 1536)error.typesetgen_ai.client.operation.duration+gen_ai.client.token.usagepopulatedMulti-provider
gen_ai.provider.nameresolution verified end-to-end: OpenAI calls surfaceopenai, Anthropic calls surfaceanthropic, both derived from the real_hidden_params["custom_llm_provider"]populated by litellm at response time.Shape audit against real litellm 1.52.0:
ModelResponse.choices[0].message.{role,content}— pydantic attributes ✅.usage.prompt_tokens / .completion_tokens— ✅._hidden_params["custom_llm_provider"]— dict, populated at 5+ call sites in litellm source ✅get_llm_provider(model)— returns(model, provider, api_key, api_base)✅EmbeddingResponse.data[0]is adict(not object) — handled viaget_property_valueStreamingChoices.delta.{role,content,tool_calls}— ✅Repo-wide changes
tox.ini: addedlitellm-{oldest,latest}factors,lint-instrumentation-litellm, andtest-instrumentation-litellm/lint-instrumentation-litellmcommands mirroring openai-v2's shape. Added to thetypecheckenv's dep list..github/workflows/{test,lint,core_contrib_test}.yml: regenerated viatox -e generate-workflows— adds 30 test-matrix jobs (5 python × oldest/latest × 3 refs each) and a lint job..github/workflows/package-{release,prepare-release,prepare-patch-release}.yml: addedopentelemetry-instrumentation-litellmto the release choice list..github/component_owners.yml: added ownership entry (currently pointing atyucheng-berriwhile this lives on the fork; upstream would want a maintainer here).pyproject.toml(repo root): added to dev-dependencies, uv workspace sources, pyright include list, and pyright exclude for tests/examples.uv.lock: regenerated viauv lock; addslitellm 1.91.0and transitive deps.scripts/generate_instrumentation_bootstrap.py: added topackages_to_exclude(early-dev, same pattern as anthropic + claude-agent-sdk) andindependent_packages.docs/index.rst: added the editable-install pip line.docs/instrumentation-genai/litellm.rst: new auto-module doc file.Coverage of higher-level litellm surface
The wrappers sit on
litellm.{completion,acompletion,embedding,aembedding}at the module root. Higher-level litellm APIs delegate through those functions and are therefore instrumented incidentally, without needing their own wrappers:litellm.completion/acompletion/embedding/aembeddinglitellm.batch_completion(non-vLLM path)litellm.completionviaThreadPoolExecutor.submit(litellm.completion, ...), so a batch of N messages produces N spansRouter.completion/Router.acompletionRouter._completioncallslitellm.completionat router.py:722Router.embedding/Router.aembeddingRouter._embeddingcallslitellm.embedding/aembeddingat router.py:2154/2224litellm.batch_completion(vLLM path)litellm.completionand callsvllm.batch_completionsdirectly — no telemetry todaybatch_completionKnown caveats
pyrightunder repo strict mode surfaces ~128reportUnknownXxxwarnings, which is in line with (and below) other genai packages included in the pyrightincludelist. Not treated as blocking.Assisted-by: Claude Opus 4.7
🤖 Generated with Claude Code