Skip to content

feat(llamaindex): Instrumentation adjustment for Otel GenAI semconv support #3979

Merged
max-deygin-traceloop merged 10 commits intomainfrom
max/tlp-2032-llamaindex-python-instrumentation
Apr 16, 2026
Merged

feat(llamaindex): Instrumentation adjustment for Otel GenAI semconv support #3979
max-deygin-traceloop merged 10 commits intomainfrom
max/tlp-2032-llamaindex-python-instrumentation

Conversation

@max-deygin-traceloop
Copy link
Copy Markdown
Contributor

@max-deygin-traceloop max-deygin-traceloop commented Apr 12, 2026

Summary

Migrate opentelemetry-instrumentation-llamaindex from legacy indexed attributes (gen_ai.prompt.{i}.* /
gen_ai.completion.{i}.*) to OTel GenAI semconv-compliant JSON format (gen_ai.input.messages / gen_ai.output.messages).

  • Add _message_utils.py — pure functions for building parts-based message JSON and mapping finish reasons across OpenAI,
    Cohere, and Anthropic
  • Add _response_utils.py — response extraction utilities (tokens, model, provider detection, finish reasons) with
    multi-provider support
  • Rewrite span_utils.py to emit JSON messages, set gen_ai.operation.name, gen_ai.provider.name, gen_ai.response.id, and
    gen_ai.response.finish_reasons (as top-level string[])
  • Migrate custom_llm_instrumentor.py — replace GEN_AI_SYSTEM with GEN_AI_PROVIDER_NAME, add chat input/output message
    support, fix wrong max_tokens/top_p attribute mappings
  • Migrate event_emitter.py — replace OpenAI-only finish reason extraction with shared multi-provider extract_finish_reasons()
  • Update event_models.py — default ChoiceEvent.finish_reason from "unknown" to ""
  • Align tool_calls finish reason with upstream Python instrumentation convention (pass-through, not remapped to singular)
  • gen_ai.response.finish_reasons is never gated by should_send_prompts() (metadata, not content)

Bugs fixed

  • custom_llm_instrumentor._handle_request: GEN_AI_REQUEST_MAX_TOKENS was set to context_window (model context size) instead
    of num_output; GEN_AI_REQUEST_TOP_P was set to num_output instead of an actual top_p value
  • custom_llm_instrumentor._handle_response: extract_finish_reasons() called twice per invocation; chat responses lost
    role/tool_call info by using completion format
  • event_emitter.emit_chat_response_events: finish reason extraction only worked for OpenAI dict responses, silently returned
    "" for Cohere/Anthropic
  • _extract_tool_calls: additional_kwargs.get("tool_calls", []) returns None when key exists with None value — fixed to
    get("tool_calls") or []
  • extract_finish_reasons: Cohere raw response objects have non-iterable attributes resembling choices — added isinstance
    guard

Test plan

  • 178 unit + VCR integration tests pass (uv run --group test python -m pytest tests/ -q)
  • ruff clean
  • No semconv-ai version bump needed — all dependency ranges compatible across anthropic, openai, and traceloop-sdk
  • Verify VCR cassettes still replay correctly in CI

Summary by CodeRabbit

  • New Features

    • Richer message serialization: multimodal parts, tool-call capture, and structured input/output messages for GenAI semconv.
    • Provider detection and normalized provider/model/response ID/token extraction across diverse provider formats.
    • Unified finish-reason mapping with consistent string defaults and robust extraction across provider shapes.
    • Provider-aware event emission with propagated provider name.
  • Refactor

    • Replace legacy per-item prompt/completion attributes with consolidated JSON input/output message attributes; simplify span attribute surface.
  • Tests

    • Extensive new/updated tests covering message utils, response extraction, events, finish reasons, and semconv migration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds pure utilities to normalize LlamaIndex messages/responses into OpenTelemetry GenAI semconv JSON, integrates provider detection, message/token/finish-reason extraction and propagation into instrumentation and event emission, and extends tests to validate the new semconv attributes and behaviors.

Changes

Cohort / File(s) Summary
Message utilities
packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py
New pure helpers: map_finish_reason, argument parsing, content→OTel parts normalization (text/image/blob/uri/reasoning), tool-call extraction/wrapping, and builders build_input_messages, build_output_message, build_completion_output_message.
Response utilities
packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_response_utils.py
New TokenUsage dataclass and helpers: detect_provider_name, extract_model_from_raw, extract_response_id, extract_token_usage, and extract_finish_reasons handling multiple provider response shapes and mapping finish reasons.
Instrumentation integration
packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/custom_llm_instrumentor.py
Instrumentor now sets gen_ai.operation.name, derives provider via detect_provider_name, records request model/max tokens, conditionally serializes gen_ai.input.messages and gen_ai.tool.definitions, and extracts/sets response model/id, token usage, finish reasons, and gen_ai.output.messages.
Span/event propagation & models
packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/dispatcher_wrapper.py, .../event_emitter.py, .../event_models.py, .../span_utils.py
Adds provider_name tracking/propagation, replaces static provider attribute with per-call/provider-aware attributes, threads provider through event emitters, changes ChoiceEvent.finish_reason default to "", and migrates from indexed prompt/completion attributes to JSON gen_ai.input.messages/gen_ai.output.messages.
Tests — message/response utilities
packages/opentelemetry-instrumentation-llamaindex/tests/test_message_utils.py, .../test_response_utils.py
New unit tests for argument parsing, content→parts mapping (text/image/blob/uri/reasoning), tool-call extraction, provider detection, token usage parsing, model/id extraction, and finish-reason mapping/extraction.
Tests — instrumentation & semconv
packages/opentelemetry-instrumentation-llamaindex/tests/test_custom_llm_semconv.py, .../test_event_emitter_semconv.py, .../test_semconv_migration.py, .../test_finish_reasons.py
New suites validating semconv attribute emission, provider threading into events, finish-reason propagation/mapping, gating by prompt sending, and migration away from legacy attributes.
Tests — updates to existing suites
packages/opentelemetry-instrumentation-llamaindex/tests/test_agents.py, .../test_none_content_fix.py, .../test_structured_llm.py
Updated assertions to require gen_ai.input.messages/gen_ai.output.messages, removed legacy semconv imports/attributes, and adjusted checks for None/empty content handling.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Instrumentor as Custom LLM Instrumentor
    participant MsgUtils as Message Utilities
    participant RespUtils as Response Utilities
    participant Span as Span/Attributes
    participant Event as EventEmitter

    Client->>Instrumentor: _handle_request(instance, args, kwargs)
    Instrumentor->>RespUtils: detect_provider_name(instance)
    RespUtils-->>Instrumentor: provider_name
    Instrumentor->>MsgUtils: build_input_messages(messages)
    MsgUtils-->>Instrumentor: messages JSON
    Instrumentor->>Span: set_attribute(gen_ai.input.messages, JSON)
    Instrumentor->>Span: set_attribute(gen_ai.request.*)

    Client->>Instrumentor: _handle_response(response)
    Instrumentor->>RespUtils: extract_response_id(response.raw)
    RespUtils-->>Instrumentor: response_id
    Instrumentor->>RespUtils: extract_token_usage(response.raw)
    RespUtils-->>Instrumentor: TokenUsage
    Instrumentor->>RespUtils: extract_finish_reasons(response.raw)
    RespUtils-->>Instrumentor: finish_reasons[]
    Instrumentor->>MsgUtils: build_output_message(response.message, finish_reason)
    MsgUtils-->>Instrumentor: output message JSON
    Instrumentor->>Span: set_attribute(gen_ai.output.messages, JSON)
    Instrumentor->>Event: emit_chat_response_events(event, provider_name)
    Event->>RespUtils: extract_finish_reasons(event.response.raw)
    RespUtils-->>Event: finish_reasons[]
    Event->>Span: emit log with gen_ai.provider.name
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 With whiskers twitched and hoppy glee,
I parse the parts and map the plea,
Finish reasons sing, providers hop through,
Tokens counted, messages made true,
A semconv feast — hop, hop — anew!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.34% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: migration to OpenTelemetry GenAI semantic conventions compliance with specific focus on instrumentation adjustments.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch max/tlp-2032-llamaindex-python-instrumentation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py (1)

68-82: ⚠️ Potential issue | 🟡 Minor

Potential AttributeError if response.message is None.

Per the context snippet from _message_utils.py, build_output_message() accesses response_message.role and response_message.content without null checking. If response.message is None (which can happen with some LLM responses), this will raise an AttributeError.

Consider adding a guard or falling back to build_completion_output_message():

🛡️ Proposed fix
 `@dont_throw`
 def set_llm_chat_response(event, span) -> None:
     if not span.is_recording():
         return

     response = event.response
     if should_send_prompts():
         fr = None
         try:
             finish_reasons = extract_finish_reasons(response.raw) if response.raw else []
             fr = finish_reasons[0] if finish_reasons else None
         except Exception:
             pass

-        output_msg = build_output_message(response.message, finish_reason=fr)
+        if response.message is not None:
+            output_msg = build_output_message(response.message, finish_reason=fr)
+        else:
+            output_msg = build_completion_output_message(getattr(response, "text", "") or "", finish_reason=fr)
         span.set_attribute(GenAIAttributes.GEN_AI_OUTPUT_MESSAGES, json.dumps([output_msg]))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py`
around lines 68 - 82, In set_llm_chat_response, avoid AttributeError when
response.message can be None: check response.message before calling
build_output_message and if it's None call build_completion_output_message (or
otherwise construct a safe output message from response/response.raw),
preserving the finish_reason logic that uses
extract_finish_reasons(response.raw); then set the attribute
GenAIAttributes.GEN_AI_OUTPUT_MESSAGES with the JSON of that safe output message
instead of directly passing response.message to build_output_message.
🧹 Nitpick comments (2)
packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/custom_llm_instrumentor.py (1)

170-174: Minor inconsistency in completion input message construction.

For completions, the message is manually constructed with {"type": "text", "content": text} even when text could be None. This differs from build_input_messages() used for chat, which calls _content_to_parts() that returns [] for None content.

Consider using a consistent pattern:

♻️ Suggested consistency fix
         elif llm_request_type == LLMRequestTypeValues.COMPLETION and args:
             prompt = args[0]
             text = prompt[0] if isinstance(prompt, list) else prompt
-            msg = [{"role": "user", "parts": [{"type": "text", "content": text}]}]
+            parts = [{"type": "text", "content": text}] if text else []
+            msg = [{"role": "user", "parts": parts}]
             span.set_attribute(GenAIAttributes.GEN_AI_INPUT_MESSAGES, json.dumps(msg))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/custom_llm_instrumentor.py`
around lines 170 - 174, In the COMPLETION branch
(LLMRequestTypeValues.COMPLETION) avoid creating a {"type":"text","content":
text} entry when text is None; instead reuse the same pattern as
build_input_messages()/_content_to_parts() so parts become [] for None. Update
the block that builds msg (currently using prompt, text, msg,
span.set_attribute) to compute parts = _content_to_parts(text) or explicitly set
parts = [] when text is None, then set msg = [{"role":"user","parts": parts}]
before json.dumps and span.set_attribute to keep behavior consistent with chat
message construction.
packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py (1)

144-150: Minor redundancy in _maybe_wrap_tool_response.

The conditional if parts else "" on line 149 is redundant since line 147-148 already returns early when parts is falsy.

♻️ Suggested simplification
 def _maybe_wrap_tool_response(msg: Any, parts: List[Dict]) -> List[Dict]:
     """Wrap content as tool_call_response for tool-role messages if tool_call_id present."""
     tool_call_id = getattr(msg, "additional_kwargs", {}).get("tool_call_id")
     if not tool_call_id or not parts:
         return parts
-    response_content = parts[0].get("content", "") if parts else ""
+    response_content = parts[0].get("content", "")
     return [{"type": "tool_call_response", "id": tool_call_id, "response": response_content}]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py`
around lines 144 - 150, The function _maybe_wrap_tool_response has a redundant
conditional when computing response_content; remove the unnecessary "if parts
else ''" fallback since the earlier check "if not tool_call_id or not parts:
return parts" guarantees parts is truthy, so compute response_content =
parts[0].get("content", "") and return the wrapped dict using tool_call_id and
response_content.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py`:
- Around line 68-82: In set_llm_chat_response, avoid AttributeError when
response.message can be None: check response.message before calling
build_output_message and if it's None call build_completion_output_message (or
otherwise construct a safe output message from response/response.raw),
preserving the finish_reason logic that uses
extract_finish_reasons(response.raw); then set the attribute
GenAIAttributes.GEN_AI_OUTPUT_MESSAGES with the JSON of that safe output message
instead of directly passing response.message to build_output_message.

---

Nitpick comments:
In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py`:
- Around line 144-150: The function _maybe_wrap_tool_response has a redundant
conditional when computing response_content; remove the unnecessary "if parts
else ''" fallback since the earlier check "if not tool_call_id or not parts:
return parts" guarantees parts is truthy, so compute response_content =
parts[0].get("content", "") and return the wrapped dict using tool_call_id and
response_content.

In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/custom_llm_instrumentor.py`:
- Around line 170-174: In the COMPLETION branch
(LLMRequestTypeValues.COMPLETION) avoid creating a {"type":"text","content":
text} entry when text is None; instead reuse the same pattern as
build_input_messages()/_content_to_parts() so parts become [] for None. Update
the block that builds msg (currently using prompt, text, msg,
span.set_attribute) to compute parts = _content_to_parts(text) or explicitly set
parts = [] when text is None, then set msg = [{"role":"user","parts": parts}]
before json.dumps and span.set_attribute to keep behavior consistent with chat
message construction.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 186aefe5-3a93-4546-84f6-692d53e2bd6c

📥 Commits

Reviewing files that changed from the base of the PR and between 21d89e4 and 7cf200e.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-llamaindex/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_response_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/custom_llm_instrumentor.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/dispatcher_wrapper.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/event_emitter.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/event_models.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_agents.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_custom_llm_semconv.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_event_emitter_semconv.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_message_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_none_content_fix.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_response_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_semconv_migration.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_structured_llm.py

@max-deygin-traceloop max-deygin-traceloop force-pushed the max/tlp-2032-llamaindex-python-instrumentation branch from 7cf200e to 1c36c34 Compare April 12, 2026 12:47
@max-deygin-traceloop max-deygin-traceloop force-pushed the max/tlp-2032-llamaindex-python-instrumentation branch from d34ebaf to bc5e5e2 Compare April 12, 2026 14:51
@max-deygin-traceloop max-deygin-traceloop marked this pull request as ready for review April 12, 2026 14:57
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py (1)

49-59: ⚠️ Potential issue | 🟠 Major

Unwrap StructuredLLM before resolving gen_ai.provider.name.

Provider detection happens on the outer model_dict, but model/temperature are intentionally read from model_dict["llm"]. For wrapped models this can emit the wrapper class as the provider instead of the underlying vendor, so the span ends up internally inconsistent.

Suggested fix
     model_dict = event.model_dict
     span.set_attribute(GenAIAttributes.GEN_AI_OPERATION_NAME, "chat")
 
-    class_name = model_dict.get("class_name")
-    provider = detect_provider_name(class_name)
+    underlying_model_dict = model_dict.get("llm", model_dict)
+    provider = detect_provider_name(
+        underlying_model_dict.get("class_name") or model_dict.get("class_name")
+    )
     if provider:
         span.set_attribute(GenAIAttributes.GEN_AI_PROVIDER_NAME, provider)
 
     # For StructuredLLM, the model and temperature are nested under model_dict.llm
-    if "llm" in model_dict:
-        model_dict = model_dict.get("llm", {})
+    model_dict = underlying_model_dict

Based on learnings: Instrumentation packages should leverage the semantic conventions package to generate spans and tracing data compliant with OpenTelemetry semantic conventions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py`
around lines 49 - 59, The provider detection is happening on the outer
model_dict which can be a StructuredLLM wrapper; move the unwrap logic up so the
code uses the inner model info for provider resolution: first, if "llm" in
model_dict then set model_dict = model_dict.get("llm", {}), then obtain
class_name and call detect_provider_name(class_name) and set
GenAIAttributes.GEN_AI_PROVIDER_NAME on the span (span.set_attribute). Update
references to model_dict and class_name accordingly so provider reflects the
underlying vendor rather than the wrapper class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/dispatcher_wrapper.py`:
- Line 91: The provider_name field is only set in the LLMChatStartEvent branch
so rerank (and the other branches around the same pattern) emits None and falls
back to "llamaindex"; update the code to set self.provider_name to the resolved
provider in every branch that emits events (e.g., in the rerank handling code
path and the other event branches referenced) before calling
event_emitter._event_attributes(), so gen_ai.provider.name receives the resolved
provider. Locate uses of provider_name, the LLMChatStartEvent branch, and the
rerank handling code in dispatcher_wrapper.py and assign the same resolved
provider value (the one you derive for LLMChatStartEvent) to self.provider_name
in those branches as well. Ensure the assignment happens prior to calling
event_emitter._event_attributes() so all emitted rerank and other events carry
the correct provider_name.

---

Outside diff comments:
In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py`:
- Around line 49-59: The provider detection is happening on the outer model_dict
which can be a StructuredLLM wrapper; move the unwrap logic up so the code uses
the inner model info for provider resolution: first, if "llm" in model_dict then
set model_dict = model_dict.get("llm", {}), then obtain class_name and call
detect_provider_name(class_name) and set GenAIAttributes.GEN_AI_PROVIDER_NAME on
the span (span.set_attribute). Update references to model_dict and class_name
accordingly so provider reflects the underlying vendor rather than the wrapper
class.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2f99b53-6390-414a-9a22-0c38ad4a2b63

📥 Commits

Reviewing files that changed from the base of the PR and between 1c36c34 and bc5e5e2.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-llamaindex/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_response_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/custom_llm_instrumentor.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/dispatcher_wrapper.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/event_emitter.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/event_models.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_agents.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_custom_llm_semconv.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_event_emitter_semconv.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_finish_reasons.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_message_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_none_content_fix.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_response_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_semconv_migration.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_structured_llm.py
✅ Files skipped from review due to trivial changes (2)
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_structured_llm.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/event_models.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_agents.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_none_content_fix.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_event_emitter_semconv.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_response_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_custom_llm_semconv.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/custom_llm_instrumentor.py

@max-deygin-traceloop max-deygin-traceloop changed the title Max/tlp 2032 llamaindex python instrumentations OTEL semconve compliance Max/tlp 2032 llamaindex python instrumentations OTEL semconv compliance Apr 12, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py`:
- Around line 132-134: The current branch assumes event.query has .query_str and
drops plain string rerank queries; update the block in span_utils.py (around
should_send_prompts(), GenAIAttributes.GEN_AI_INPUT_MESSAGES, and
emit_rerank_message_event()) to check the type of event.query: if it's a str use
that string as the message content, otherwise read event.query.query_str from
the query object, then build the msg list and json.dumps it before calling
span.set_attribute so string rerank queries are included as
gen_ai.input.messages.
- Around line 49-52: The provider detection is happening before unwrapping
nested StructuredLLM metadata so model_dict.get("class_name") misses cases like
{"llm": {"class_name": "OpenAI", ...}}; modify the logic in span_utils.py to
perform the StructuredLLM unwrapping first (ensure you detect and replace
model_dict with the nested llm dict when class/type indicates StructuredLLM),
then call class_name = model_dict.get("class_name") and provider =
detect_provider_name(class_name) and finally set
span.set_attribute(GenAIAttributes.GEN_AI_PROVIDER_NAME, provider) only after
provider is computed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d10d0f4-37c2-43cc-af11-8e3b98411a8b

📥 Commits

Reviewing files that changed from the base of the PR and between 8c90a22 and 1417229.

📒 Files selected for processing (7)
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/custom_llm_instrumentor.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/event_emitter.py
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_custom_llm_semconv.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_message_utils.py
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_semconv_migration.py
✅ Files skipped from review due to trivial changes (1)
  • packages/opentelemetry-instrumentation-llamaindex/tests/test_message_utils.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/_message_utils.py

Comment on lines +49 to +52
class_name = model_dict.get("class_name")
provider = detect_provider_name(class_name)
if provider:
span.set_attribute(GenAIAttributes.GEN_AI_PROVIDER_NAME, provider)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Move provider detection after unwrapping StructuredLLM.

class_name is read before the nested llm metadata is unwrapped, so {"llm": {"class_name": "OpenAI", ...}} never sets gen_ai.provider.name.

Suggested fix
-    class_name = model_dict.get("class_name")
-    provider = detect_provider_name(class_name)
-    if provider:
-        span.set_attribute(GenAIAttributes.GEN_AI_PROVIDER_NAME, provider)
-
     # For StructuredLLM, the model and temperature are nested under model_dict.llm
     if "llm" in model_dict:
         model_dict = model_dict.get("llm", {})
+
+    class_name = model_dict.get("class_name")
+    provider = detect_provider_name(class_name)
+    if provider:
+        span.set_attribute(GenAIAttributes.GEN_AI_PROVIDER_NAME, provider)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py`
around lines 49 - 52, The provider detection is happening before unwrapping
nested StructuredLLM metadata so model_dict.get("class_name") misses cases like
{"llm": {"class_name": "OpenAI", ...}}; modify the logic in span_utils.py to
perform the StructuredLLM unwrapping first (ensure you detect and replace
model_dict with the nested llm dict when class/type indicates StructuredLLM),
then call class_name = model_dict.get("class_name") and provider =
detect_provider_name(class_name) and finally set
span.set_attribute(GenAIAttributes.GEN_AI_PROVIDER_NAME, provider) only after
provider is computed.

Comment on lines 132 to +134
if should_send_prompts():
span.set_attribute(
f"{LLMRequestTypeValues.RERANK.value}.query",
event.query.query_str,
)
msg = [{"role": "user", "parts": [{"type": "text", "content": event.query.query_str}]}]
span.set_attribute(GenAIAttributes.GEN_AI_INPUT_MESSAGES, json.dumps(msg))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle string rerank queries here too.

emit_rerank_message_event() already accepts both str and query objects. This path assumes .query_str, so a plain string will hit @dont_throw, skip the attribute, and silently lose gen_ai.input.messages.

Suggested fix
     if should_send_prompts():
-        msg = [{"role": "user", "parts": [{"type": "text", "content": event.query.query_str}]}]
+        query = event.query if isinstance(event.query, str) else event.query.query_str
+        msg = [{"role": "user", "parts": [{"type": "text", "content": query}]}]
         span.set_attribute(GenAIAttributes.GEN_AI_INPUT_MESSAGES, json.dumps(msg))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/opentelemetry-instrumentation-llamaindex/opentelemetry/instrumentation/llamaindex/span_utils.py`
around lines 132 - 134, The current branch assumes event.query has .query_str
and drops plain string rerank queries; update the block in span_utils.py (around
should_send_prompts(), GenAIAttributes.GEN_AI_INPUT_MESSAGES, and
emit_rerank_message_event()) to check the type of event.query: if it's a str use
that string as the message content, otherwise read event.query.query_str from
the query object, then build the msg list and json.dumps it before calling
span.set_attribute so string rerank queries are included as
gen_ai.input.messages.

Copy link
Copy Markdown
Contributor

@OzBenSimhonTraceloop OzBenSimhonTraceloop left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@max-deygin-traceloop Just don't forget to update PR title ofc :)

@max-deygin-traceloop max-deygin-traceloop changed the title Max/tlp 2032 llamaindex python instrumentations OTEL semconv compliance feat(llamaindex): Instrumentation adjustment for Otel GenAI semconv support Apr 16, 2026
@max-deygin-traceloop max-deygin-traceloop merged commit dcd5cb0 into main Apr 16, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants