Skip to content

fix(cohere): add v2 api instrumentation#3378

Merged
nirga merged 5 commits intotraceloop:mainfrom
lmnr-ai:cohere-v2
Sep 14, 2025
Merged

fix(cohere): add v2 api instrumentation#3378
nirga merged 5 commits intotraceloop:mainfrom
lmnr-ai:cohere-v2

Conversation

@dinmukhamedm
Copy link
Copy Markdown
Collaborator

@dinmukhamedm dinmukhamedm commented Sep 14, 2025

  • Add streaming support
  • Add async support
  • Add basic support for API v2 including tools and history messages

I did not instrument generate_stream (completions) because they literally discontinue it tomorrow

  • I have added tests that cover my changes.
  • If adding a new instrumentation or changing an existing one, I've added screenshots from some observability platform showing the change.
  • PR name follows conventional commits format: feat(instrumentation): ... or fix(instrumentation): ....
  • (If applicable) I have updated the documentation accordingly.

Important

Adds support for Cohere API v2 with async and streaming capabilities, updates tests, and introduces new functions for handling input and response content.

  • Behavior:
    • Adds support for Cohere API v2 in __init__.py, including async and streaming capabilities.
    • Introduces handling for tool calls and history messages in v2.
    • Does not instrument generate_stream due to deprecation.
  • Functions:
    • Adds process_chat_v1_streaming_response and process_chat_v2_streaming_response in streaming.py for handling streaming responses.
    • Introduces set_input_content_attributes and set_response_content_attributes in span_utils.py for handling input and response content.
  • Tests:
    • Updates tests in test_chat.py, test_embed.py, and test_rerank.py to cover v2 API, async, and streaming functionalities.
    • Adds new test cases for tool calls and history messages in v2.
  • Misc:
    • Updates cohere dependency to ^5.18.0 in pyproject.toml.
    • Adds pytest-asyncio to test dependencies in pyproject.toml.

This description was created by Ellipsis for 96db843. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • New Features

    • Streaming telemetry for Cohere chat (v1 & v2) in sync/async with incremental chunks and final assembly.
    • Content-specific request/response attribute handling, explicit span creation/management, multi-version normalization, and helpers for object dumping/dict conversion.
    • Better capture of tool plans/calls, history, embeddings, rerank, and stable response IDs in spans.
  • Bug Fixes

    • Improved error handling and logging to prevent instrumentation crashes.
  • Tests

    • Large expanded suite with many new cassettes and fixtures covering v2, streaming, async, tools, embeddings, rerank.
  • Chores

    • Updated test dependencies (Cohere SDK, pytest-asyncio).

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 14, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds content-specific span attribute handlers, explicit span lifecycle control (sync/async), streaming processors for Cohere v1/v2 (sync+async), response normalization/utilities, module-aware dynamic wrapping, many test cassettes/tests, and test dependency bumps.

Changes

Cohort / File(s) Summary
Instrumentation core
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py
Replaced generic input/response handlers with content-specific variants; explicit span creation and context management for sync/async wrappers; streaming delegation via per-method stream_process_func; WRAPPED_METHODS/WRAPPED_AMETHODS entries include module; dynamic wrap/unwrap guarded against import errors; error recording and span status handling added.
Span attribute utils
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py
Renamed and added setters (e.g., set_input_content_attributes, set_span_request_attributes, set_span_response_attributes); normalized multi-version responses via to_dict; switched to GEN_AI_* attribute naming; expanded handling for CHAT/EMBEDDING/RERANK, tools, stop_sequences, and unified token/usage extraction.
Streaming support
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py
New module providing sync/async streaming processors for chat v1/v2, a stream-item accumulator, DEFAULT_MESSAGE scaffold, chunk/event emission, final_response assembly, span attribute setting, event vs prompt handling, and error/status handling.
Utilities
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py
Added dump_object and to_dict helpers (dataclasses/json imports) for resilient serialization and model normalization.
Project config
packages/opentelemetry-instrumentation-cohere/pyproject.toml
Bumped test dependency cohere to ^5.18.0 and added pytest-asyncio = "^1.2.0".
Test fixtures — clients
packages/opentelemetry-instrumentation-cohere/tests/conftest.py
Added fixtures: async_cohere_client, cohere_client_v2, async_cohere_client_v2.
Tests — chat logic
packages/opentelemetry-instrumentation-cohere/tests/test_chat.py
Large additions: TOOLS constant and many tests covering legacy/v2, sync/async, streaming, tool-calls, event emission, and detailed span attribute assertions.
Tests — embed
packages/opentelemetry-instrumentation-cohere/tests/test_embed.py
New v2 embedding tests (sync + async) asserting prompts, span attributes, and gen_ai response IDs.
Tests — rerank
packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py
New v2 rerank tests (sync + async) with detailed assertions and enhanced log assertions.
VCR cassettes — chat
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/*
Added numerous cassettes for legacy/v2 chat: streaming and non-streaming, tool-calls, history, sync/async, with deprecation/usage metadata.
VCR cassettes — embed
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/*
Added v2 embed cassettes (sync/async) with embeddings and billing/tokens metadata.
VCR cassettes — rerank
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/*
Added v2 rerank cassettes (sync/async) with results and usage metrics.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Wrapper as Instrumentation Wrapper
  participant Cohere as Cohere SDK
  participant Tracer as OTel Tracer

  App->>Wrapper: call Client.method(...)/AsyncClient.method(...)
  Wrapper->>Tracer: start_span(span_name, attrs: LLM_SYSTEM, LLM_REQUEST_TYPE)
  Wrapper->>Wrapper: set_span_request_attributes(kwargs)
  Wrapper->>Wrapper: set_input_content_attributes(kwargs)
  alt streaming method
    Wrapper->>Cohere: invoke chat_stream(...)
    loop chunks/events
      Cohere-->>Wrapper: chunk/event
      Wrapper-->>App: yield chunk
      Wrapper->>Wrapper: accumulate into final_response
    end
    Wrapper->>Wrapper: set_span_response_attributes(final_response)
    Wrapper->>Tracer: end span
  else non-streaming
    Wrapper->>Cohere: invoke method(...)
    Cohere-->>Wrapper: response
    Wrapper->>Wrapper: set_span_response_attributes(response)
    Wrapper->>Wrapper: set_response_content_attributes(response)
    Wrapper->>Tracer: end span
  end
  Note over Wrapper,Tracer: on exception → record_exception, set_status(ERROR), end span, re-raise
Loading
sequenceDiagram
  autonumber
  actor App
  participant Wrapper as Stream Wrapper (v2)
  participant Cohere as Cohere v2 SSE
  participant Acc as Accumulator

  App->>Wrapper: chat(stream=True, messages, tools)
  Wrapper->>Cohere: request SSE stream
  Cohere-->>Wrapper: message-start / content-start
  loop deltas
    Cohere-->>Wrapper: content-delta / tool-plan-delta / tool-call-*
    Wrapper->>Acc: _accumulate_stream_item(delta)
    Wrapper-->>App: yield delta
  end
  Cohere-->>Wrapper: content-end / message-end
  Wrapper->>Acc: finalize final_response
  Wrapper->>Wrapper: set_span_response_attributes(final_response)
  Wrapper->>Tracer: end span (or set ERROR if exception)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • doronkopit5

Poem

A nibble of streams, a hop through the trace,
I stitch tiny deltas in telemetry space.
Tools in my satchel, prompts tidy and neat,
Spans start, then finish, as chunks skip a beat.
I thump: Cohere’s traced — swift, small, and sweet 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "fix(cohere): add v2 api instrumentation" is concise, follows conventional commit style, and accurately summarizes the primary change—adding Cohere v2 API instrumentation (including streaming and async support) found in the changeset; it is specific and relevant for a teammate scanning the history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Contributor

@ellipsis-dev ellipsis-dev 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

Changes requested ❌

Reviewed everything up to 96db843 in 2 minutes and 36 seconds. Click for details.
  • Reviewed 5671 lines of code in 25 files
  • Skipped 1 files when reviewing.
  • Skipped posting 14 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py:119
  • Draft comment:
    Swallowing exceptions silently in _accumulate_stream_item (using 'except Exception: pass') may hide errors. Consider logging the exception details.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% The code is deliberately designed to not throw errors - the @dont_throw decorator and try/except blocks are intentional. This is streaming response processing code where we want to continue processing even if one chunk fails. The final response has an error field that can capture errors. Logging every parsing error could be noisy and unhelpful since errors are expected in streaming processing. Silent exception handling could hide real bugs or issues that should be investigated. Without any logging, debugging problems could be more difficult. The code is explicitly designed to be resilient to errors in stream processing. The error handling strategy appears intentional rather than accidental. The final response object has a dedicated error field for capturing important errors. The comment should be deleted. The exception handling appears to be an intentional design choice for streaming resilience rather than a bug or oversight.
2. packages/opentelemetry-instrumentation-cohere/tests/test_chat.py:71
  • Draft comment:
    Inconsistent assertion found for LLM_USAGE_PROMPT_TOKENS; one test expects 58 tokens while other tests expect 7. Verify that this discrepancy is intentional and correctly reflects API behavior.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
3. packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py:113
  • Draft comment:
    Typo alert: The async stream process function is named "aprocess_chat_v1_streaming_response". Consider whether adding an underscore (e.g. "a_process_chat_v1_streaming_response") would improve clarity and consistency with the synchronous version.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% 1. The function is imported from another module, so renaming it here would require changes in other files. 2. The 'a' prefix without underscore is a common convention in Python for async variants. 3. The current name is clear and consistent with Python async naming patterns. 4. The comment is about style rather than a substantive issue. The comment raises a valid point about naming consistency, and underscores in function names are common in Python. However, the 'a' prefix without underscore is an established pattern for async variants, and changing an imported function name would require cross-file changes for minimal benefit. Delete the comment as it suggests a style change to an imported function that follows acceptable Python conventions and would require cross-file changes.
4. packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py:230
  • Draft comment:
    Typo: There's an extra closing quote at the end of the comment in this line. Consider removing the extra " after '(possibly pre-prompted)'.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% While this is technically correct - there is an extra quote - it's an extremely minor issue in a comment. Comments are not executed code and this doesn't affect functionality at all. The meaning of the comment is still perfectly clear. This feels like unnecessary nitpicking that doesn't improve code quality in any meaningful way. The comment is factually correct - there is an extra quote. And some might argue that even small issues should be fixed for perfect code quality. But we need to focus on meaningful improvements. Nitpicking about comment formatting is more likely to be annoying than helpful. Delete this comment. While technically correct, it's an extremely minor formatting issue in a comment that doesn't impact functionality or readability.
5. packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming_async.yaml:117
  • Draft comment:
    Typographical suggestion: The text "''s" appears to be an incorrect contraction. Did you mean to use "here's" instead?
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% This is a test cassette file that records actual API responses. The "''s" is part of the actual response from the API - it's not code we wrote, but rather data we received. Making style suggestions about API response content doesn't make sense since we can't change what the API returns. Additionally, this is a test file meant to capture real responses. Could this unusual formatting actually be a bug in how we're recording the API responses? Maybe it deserves investigation? No - looking at the full context, this is clearly part of a streaming response where text is being returned piece by piece. The formatting is exactly as received from the API and is working as intended. Delete this comment. We should not suggest changes to recorded API responses in test cassettes, as they need to exactly match what the API returns.
6. packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming_async.yaml:437
  • Draft comment:
    Typographical suggestion: The fragment "''t" seems off. Consider using the proper contraction (e.g. "shan't") if that was intended.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% This is a test cassette file that records actual API responses. The unusual formatting "''t" is part of the API's response data and is intentionally capturing how the API returns the text. Suggesting changes to the API's response format would be incorrect since we want to preserve the exact response for testing purposes. Maybe the unusual formatting indicates a bug in the API that should be reported? Maybe this formatting could cause issues in the tests? Even if the formatting is unusual, this is a test cassette that should accurately record the API's behavior. If there are issues with the API's response format, that should be handled separately from these tests. Delete the comment since it suggests modifying recorded API response data in a test cassette, which would defeat the purpose of having accurate test recordings.
7. packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/test_cohere_v2_embed_legacy.yaml:4
  • Draft comment:
    Typographical error: There appears to be an extra space between "the" and "2010" in the text. Please remove the extra space.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is a test fixture file that records an API interaction for testing purposes. The extra space is in the request data that gets sent to the API. Since this is test data, the exact formatting of the text, including spaces, could potentially matter for matching the recorded response. We should be very careful about modifying test fixtures unless we're sure it won't break tests. The comment points out a real issue, but is it actually a problem? The API seems to handle the extra space fine since the response data shows the text normalized with a single space. Looking at the response data (line 43), the API normalizes the spaces anyway, so the extra space in the request doesn't affect functionality. This is a purely cosmetic issue in test data. The comment should be deleted. While technically correct about the extra space, this is a minor formatting issue in test data that doesn't affect functionality since the API normalizes spaces anyway.
8. packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/test_cohere_v2_embed_legacy_async.yaml:4
  • Draft comment:
    Typographical error: There is an extra space between 'At the' and '2010' in the request body (line 4). Please remove the additional space.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is a test fixture file that contains recorded API interactions for testing purposes. The extra space is technically present, but does it actually matter? The response data shows the API normalized the text anyway by removing the extra space. Since this is test data and the API handles the space gracefully, pointing out this minor formatting issue seems overly pedantic and not valuable. The space could potentially cause issues if exact string matching is being done in tests. The consistency of test data can be important. While consistency is good, this is clearly not causing any actual problems since the API normalizes the text. The comment is focusing on an insignificant formatting detail that has no impact on functionality. Delete this comment as it points out a trivial formatting issue in test fixture data that has no functional impact.
9. packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/test_cohere_v2_rerank_legacy.yaml:5
  • Draft comment:
    Typographical error: There appears to be an extra space in the text "At the 2010 United States Census" on line 5. Consider replacing "At the 2010" with "At the 2010".
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is test data that's likely captured from real API interactions. The extra space is in a JSON string that's part of the test fixture. Since this is test data meant to validate API behavior, the exact formatting of the text content (including extra spaces) is not relevant to the functionality being tested. The comment is focusing on a purely cosmetic issue in test data. Maybe the extra space could cause issues with text matching in tests? Maybe consistency in test data is important for maintainability? The tests are likely checking API behavior and response handling, not exact string matching. The extra space doesn't affect the semantic meaning or the test's ability to verify the correct functionality. Delete this comment as it's pointing out a trivial formatting issue in test data that doesn't impact functionality or test effectiveness.
10. packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/test_cohere_v2_rerank_legacy_async.yaml:5
  • Draft comment:
    Typographical error: There is a double space between "At the" and "2010" in the document text on this line. Consider reducing it to a single space.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is test data in a cassette file used for recording API interactions. The double space is in example text that's being sent to test an API. The spacing in test data is irrelevant as long as the tests pass. This seems like an extremely minor formatting issue that doesn't impact functionality. Maybe consistent formatting is important for test data readability? Maybe the double space could cause subtle test failures? The double space is part of a JSON string being sent to an API - it won't affect test outcomes since it's just example text. This level of formatting nitpicking on test data isn't valuable. Delete this comment as it's an extremely minor formatting issue in test data that doesn't impact functionality or readability in any meaningful way.
11. packages/opentelemetry-instrumentation-cohere/tests/test_embed.py:12
  • Draft comment:
    Typographical error: There is an extra space in "At the 2010 United States Census". Please change it to "At the 2010 United States Census".
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is test data being sent to an embedding API. The extra space is likely irrelevant to the functionality being tested. The tests are checking the correct handling of API calls, responses, and telemetry, not the specific content of the text. The presence or absence of an extra space won't affect the test's ability to verify the core functionality. The extra space could be a genuine mistake that makes the test data less clean and professional. Consistent formatting in test data might make the tests more readable. While clean test data is nice to have, this is an extremely minor formatting issue that doesn't impact the test's effectiveness or readability in any meaningful way. The comment should be deleted as it addresses a trivial formatting issue in test data that doesn't impact functionality or test effectiveness.
12. packages/opentelemetry-instrumentation-cohere/tests/test_embed.py:71
  • Draft comment:
    Typographical error: There is an extra space in "At the 2010 United States Census". Please change it to "At the 2010 United States Census".
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is a test file and the extra space is in test data. The space doesn't affect the functionality being tested (embedding API instrumentation). The tests are passing with the current data. The extra space might even be intentional to test how the API handles various text formats. We should focus on actual code issues rather than cosmetic issues in test data. The extra space could be considered a data quality issue, and consistent, clean test data might be valuable for future test maintenance. While clean data is good, this is an extremely minor formatting issue in test data that doesn't impact the test's effectiveness or readability. The comment violates the rule about not making purely informative or unimportant comments. Delete the comment as it addresses a trivial formatting issue in test data that doesn't affect functionality or test effectiveness.
13. packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py:214
  • Draft comment:
    Typographical issue: There's a double space in the string "At the 2010 United States Census, Carson City had a population of 55,274." Please remove the extra space.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is test data that's being used consistently across multiple test cases. The double space, while technically a typo, appears to be intentionally copied to maintain consistency. Changing it would not affect functionality. In test data, exact string matching is often important for assertions, so we should be cautious about changing strings unless there's a clear benefit. The double space could be considered a real typo that should be fixed. Consistent doesn't necessarily mean correct. While true, this is test data where the exact string content doesn't matter as long as it's consistent. The tests are about reranking functionality, not string correctness. The comment should be deleted as it suggests a trivial change to test data that would not improve code quality or fix any real issues.
14. packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py:295
  • Draft comment:
    Typographical issue: Detected a double space in the string "At the 2010 United States Census, Carson City had a population of 55,274." Please remove the extra space.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is test data that's being used consistently across multiple test cases. The double space, while technically a typo, is part of the test input data. Changing it would not affect functionality. In fact, changing it might make the tests less realistic since real-world text often contains such imperfections. The consistency of the double space across test cases suggests it might even be intentional. The double space is indeed a typographical error, and generally we want clean, well-formatted code. Could fixing it improve readability? While clean code is important, this is test data that simulates real-world input. Having slightly messy test data can actually be beneficial for testing robustness. The change would be purely cosmetic and doesn't improve test quality. The comment should be deleted. The double space is in test data, appears consistently across test cases, and fixing it provides no meaningful benefit while potentially making the tests less realistic.

Workflow ID: wflow_2BbpbeluZuYYdyNM

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

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: 16

Caution

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

⚠️ Outside diff range comments (3)
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_history.yaml (1)

1-165: Add VCR scrubbing for volatile headers and dynamic IDs

packages/opentelemetry-instrumentation-cohere/tests/conftest.py: vcr_config() currently only filters "authorization" (line 122) — add additional filter_headers and a before_record hook to redact volatile headers and dynamic values (suggested: user-agent, x-fern-sdk-version, x-fern-language, date, x-debug-trace-id, x-envoy-upstream-service-time, num_tokens, num_chars, x-api-warning) and redact tool_call IDs / UUIDs in request/response bodies.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming.yaml (1)

1-659: Replace deprecated model "command" with Cohere's recommended v2 model "command-a-03-2025".

Update the cassette(s) (e.g. packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming.yaml) to use model: "command-a-03-2025" and re-record tests — "command" is deprecated and will be removed on September 15, 2025.

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (1)

167-177: Harden tool-call accumulation and reset between calls.

Avoid KeyError when function missing and prevent state bleed across tool calls.

     elif item_dict.get("type") == "tool-call-delta":
         message = (item_dict.get("delta") or {}).get("message") or {}
         new_arguments = ((message.get("tool_calls") or {}).get("function") or {}).get("arguments")
         if new_arguments:
-            existing_arguments = (current_tool_call_item.get("function") or {}).get("arguments")
-            current_tool_call_item["function"]["arguments"] = (existing_arguments or "") + new_arguments
+            current_tool_call_item.setdefault("function", {})
+            existing_arguments = current_tool_call_item["function"].get("arguments")
+            current_tool_call_item["function"]["arguments"] = (existing_arguments or "") + new_arguments
     elif item_dict.get("type") == "tool-call-end":
-        final_response["message"]["tool_calls"].append({**current_tool_call_item})
+        import copy as _copy
+        final_response["message"]["tool_calls"].append(_copy.deepcopy(current_tool_call_item))
+        # reset for next tool call
+        current_tool_call_item.clear()
+        current_tool_call_item.update({"id": "", "type": "function", "function": {"name": "", "arguments": "", "description": ""}})
🧹 Nitpick comments (32)
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy.yaml (4)

30-34: Scrub IDs and volatile headers to avoid leakage and cassette churn.

Redact response "id", x-debug-trace-id, Date, Content-Length, Via, Server, x‑envoy‑upstream‑service‑time (and similar) in cassettes. These aren’t needed for replay, can be considered sensitive ops metadata, and cause noisy diffs.

Apply minimal redactions:

-      string: "{\"id\":\"83e3e297-264b-478e-9b22-5058386292ed\",\"message\":{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"Arrrr,
+      string: "{\"id\":\"<redacted-id>\",\"message\":{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"Arrrr,
@@
-      Content-Length:
-      - '552'
+      Content-Length:
+      - '<redacted>'
@@
-      Via:
-      - 1.1 google
+      Via:
+      - '<redacted>'
@@
-      date:
-      - Sun, 14 Sep 2025 01:50:19 GMT
+      date:
+      - '<redacted>'
@@
-      server:
-      - envoy
+      server:
+      - '<redacted>'
@@
-      x-debug-trace-id:
-      - 3ea490a581ea0c90e48fbdccabff104b
+      x-debug-trace-id:
+      - '<redacted>'
@@
-      x-envoy-upstream-service-time:
-      - '2637'
+      x-envoy-upstream-service-time:
+      - '<redacted>'

Prefer doing this centrally via VCR hooks (before_record[_response]) to auto-scrub across all cassettes. I can draft the hook if helpful.

Also applies to: 38-41, 49-49, 59-59, 67-70


52-55: Optional: redact num_chars/num_tokens to reduce brittle diffs.

Token/char counts are non-essential for replay and often change. Consider replacing with placeholders.

-      num_chars:
-      - '324'
+      num_chars:
+      - '<redacted>'
-      num_tokens:
-      - '95'
+      num_tokens:
+      - '<redacted>'

18-25: Optional: scrub request identifying headers (UA, SDK version, host).

To avoid churn when SDK updates, filter user-agent, x-fern-* and host in recorded requests.

-      user-agent:
-      - cohere/5.18.0
+      user-agent:
+      - '<redacted>'
@@
-      x-fern-sdk-version:
-      - 5.18.0
+      x-fern-sdk-version:
+      - '<redacted>'
@@
-      host:
-      - api.cohere.com
+      host:
+      - '<redacted>'

Also applies to: 16-17


64-66: Legacy model is being removed on September 15, 2025 — keep this cassette “frozen”.

x-api-warning shows model 'command' deprecation effective 2025‑09‑15. Treat this as a legacy test; avoid re‑recording, and add a separate cassette using a supported model to ensure forward‑compat coverage.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming_async.yaml (1)

544-546: Scrub x-debug-trace-id in cassettes.

Trace/diagnostic IDs shouldn’t be committed. Recommend redacting in fixtures and adding a global VCR header filter.

Apply immediate redaction here (still prefer a global filter):

-      x-debug-trace-id:
-      - bc877ec6699cd83aa9c7d1952c3fbb20
+      x-debug-trace-id:
+      - "<REDACTED>"
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml (3)

30-41: Redact provider-generated IDs in cassette body (response_id, generation_id).

Unique identifiers aren’t secrets per se, but we avoid committing provider traceables to reduce risk and churn. Replace with stable placeholders.

-      string: '{"response_id":"ea2d074c-4f25-47cb-bef8-b00dc2ae991b","text":"Arrrr,
+      string: '{"response_id":"REDACTED","text":"Arrrr,
 ...
-        find a new treasure trove before then!","generation_id":"41bd7ae5-511d-449f-a64d-dd54e5d697e4","chat_history":[{"role":"USER","message":"Tell
+        find a new treasure trove before then!","generation_id":"REDACTED","chat_history":[{"role":"USER","message":"Tell

56-57: Scrub volatile headers (Date, x-debug-trace-id, x-envoy-upstream-service-time).

These are non-deterministic and unnecessary for playback; scrubbing reduces cassette churn and accidental leakage. Aligns with our “no sensitive data in cassettes” guidance.

-      date:
-      - Sun, 14 Sep 2025 01:23:07 GMT
+      date:
+      - REDACTED
 ...
-      x-debug-trace-id:
-      - 37d8ceb858f86160192181d6ece63b36
+      x-debug-trace-id:
+      - REDACTED
 ...
-      x-envoy-upstream-service-time:
-      - '2401'
+      x-envoy-upstream-service-time:
+      - REDACTED

Also applies to: 74-77


6-25: Consider sanitizing additional low-value, high-churn headers.

Alt-Svc, Via, cache-control variants, pragma, vary, and Accept-Encoding add noise without test value. Safe to scrub or drop at record time.

Also applies to: 42-69

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py (2)

52-61: Tighten exception handling and cap payload size in dump_object to prevent log bloat.
Avoid broad Exception, add size cap, and use safer JSON fallback.

-def dump_object(obj):
-    try:
-        if hasattr(obj, "model_dump_json") and callable(obj.model_dump_json):
-            return obj.model_dump_json()
-    except Exception:
-        pass
-    try:
-        return json.dumps(obj)
-    except Exception:
-        return ""
+def dump_object(obj):
+    # Prefer model_dump_json when available
+    try:
+        if hasattr(obj, "model_dump_json") and callable(obj.model_dump_json):
+            s = obj.model_dump_json()
+            return s if len(s) <= 8192 else s[:8192] + "…"
+    except (AttributeError, TypeError, ValueError):
+        pass
+    # Fallback to JSON with a safe default
+    try:
+        s = json.dumps(obj, default=str, ensure_ascii=False)
+        return s if len(s) <= 8192 else s[:8192] + "…"
+    except (TypeError, ValueError):
+        # Final fallback: repr
+        s = repr(obj)
+        return s if len(s) <= 8192 else s[:8192] + "…"

64-77: Narrow excepts in to_dict and handle common containers more defensively.
Reduce blind catches; prefer predictable fallbacks.

-def to_dict(obj):
-    try:
-        if hasattr(obj, "model_dump") and callable(obj.model_dump):
-            return obj.model_dump()
-    except Exception:
-        pass
-    if isinstance(obj, dict):
-        return obj
-    if dataclasses.is_dataclass(obj):
-        return dataclasses.asdict(obj)
-    try:
-        return dict(obj)
-    except Exception:
-        return obj
+def to_dict(obj):
+    try:
+        if hasattr(obj, "model_dump") and callable(obj.model_dump):
+            return obj.model_dump()
+    except (AttributeError, TypeError, ValueError):
+        pass
+    if isinstance(obj, dict):
+        return obj
+    if dataclasses.is_dataclass(obj):
+        return dataclasses.asdict(obj)
+    # Last attempts: mapping-like or object __dict__
+    try:
+        return dict(obj)
+    except (TypeError, ValueError):
+        return getattr(obj, "__dict__", obj)
packages/opentelemetry-instrumentation-cohere/tests/conftest.py (4)

55-58: Close async client to avoid connector leaks.

Make this an async-yield fixture and close the client.

 @pytest.fixture
-def async_cohere_client():
-    return cohere.AsyncClient(os.environ.get("COHERE_API_KEY"))
+async def async_cohere_client():
+    client = cohere.AsyncClient(os.environ.get("COHERE_API_KEY"))
+    try:
+        yield client
+    finally:
+        if hasattr(client, "aclose"):
+            await client.aclose()
+        elif hasattr(client, "close"):
+            client.close()

60-63: Close v2 sync client to free resources.

Yield and close after use.

 @pytest.fixture
 def cohere_client_v2():
-    return cohere.ClientV2(os.environ.get("COHERE_API_KEY"))
+    client = cohere.ClientV2(os.environ.get("COHERE_API_KEY"))
+    try:
+        yield client
+    finally:
+        if hasattr(client, "close"):
+            client.close()

65-68: Close async v2 client as well.

Same pattern as async v1.

 @pytest.fixture
-def async_cohere_client_v2():
-    return cohere.AsyncClientV2(os.environ.get("COHERE_API_KEY"))
+async def async_cohere_client_v2():
+    client = cohere.AsyncClientV2(os.environ.get("COHERE_API_KEY"))
+    try:
+        yield client
+    finally:
+        if hasattr(client, "aclose"):
+            await client.aclose()
+        elif hasattr(client, "close"):
+            client.close()

120-123: Reduce cassette churn: filter dynamic headers in VCR.

Mask/remove headers like date, x-debug-trace-id, x-envoy-upstream-service-time to keep diffs stable.

 @pytest.fixture(scope="module")
 def vcr_config():
-    return {"filter_headers": ["authorization"]}
+    return {
+        "filter_headers": [
+            "authorization",
+            "x-debug-trace-id",
+            "x-envoy-upstream-service-time",
+            "date",
+            "alt-svc",
+            "via",
+            "server",
+            "user-agent",
+        ],
+        "before_record_request": before_record_request,
+        "before_record_response": before_record_response,
+    }

Add these helpers anywhere in this file:

def before_record_request(request):
    headers = request.get("headers", {})
    for h in ["x-debug-trace-id", "x-envoy-upstream-service-time", "date", "alt-svc", "via", "user-agent"]:
        headers.pop(h, None)
    return request

def before_record_response(response):
    headers = response.get("headers", {})
    for h in ["x-debug-trace-id", "x-envoy-upstream-service-time", "date", "alt-svc", "via", "server"]:
        headers.pop(h, None)
    return response
packages/opentelemetry-instrumentation-cohere/tests/test_embed.py (2)

7-9: Silence Ruff ARG001 for fixture-only parameter.

instrument_legacy is intentionally unused but required to activate instrumentation via fixture. Add a no-op reference to placate Ruff.

 def test_cohere_v2_embed_legacy(
     span_exporter, log_exporter, instrument_legacy, cohere_client_v2
 ):
+    _ = instrument_legacy  # noqa: ARG001

64-68: Same Ruff ARG001 in async test.

Add a no-op reference.

 async def test_cohere_v2_embed_legacy_async(
     span_exporter, log_exporter, instrument_legacy, async_cohere_client_v2
 ):
+    _ = instrument_legacy  # noqa: ARG001
packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py (2)

207-210: Silence Ruff ARG001 for fixture-only parameter.

 def test_cohere_v2_rerank_legacy(
     span_exporter, log_exporter, instrument_legacy, cohere_client_v2
 ):
+    _ = instrument_legacy  # noqa: ARG001

289-291: Silence Ruff ARG001 for async fixture-only parameter.

 async def test_cohere_v2_rerank_legacy_async(
     span_exporter, log_exporter, instrument_legacy, async_cohere_client_v2
 ):
+    _ = instrument_legacy  # noqa: ARG001
packages/opentelemetry-instrumentation-cohere/tests/test_chat.py (2)

1-1: Silence repeated Ruff ARG001 warnings in this test module.

Given many fixture-only parameters, prefer a single file-level directive.

+# ruff: noqa: ARG001
 import json

271-374: Reduce assertion duplication with small helpers.

The repeated blocks validating request functions and tool_calls can be centralized to cut noise and ease maintenance.

+# Helpers to reduce assertion duplication
+def assert_request_functions(span, tools, prefix=SpanAttributes.LLM_REQUEST_FUNCTIONS):
+    for i, tool in enumerate(tools):
+        fn = tool["function"]
+        assert span.attributes.get(f"{prefix}.{i}.name") == fn["name"]
+        assert span.attributes.get(f"{prefix}.{i}.description") == fn["description"]
+        import json as _json
+        assert _json.loads(span.attributes.get(f"{prefix}.{i}.parameters")) == fn["parameters"]
+
+def assert_tool_call(span, idx, tc):
+    assert span.attributes.get(f"{SpanAttributes.LLM_COMPLETIONS}.0.tool_calls.{idx}.id") == tc.id
+    assert span.attributes.get(f"{SpanAttributes.LLM_COMPLETIONS}.0.tool_calls.{idx}.name") == tc.function.name
+    assert span.attributes.get(f"{SpanAttributes.LLM_COMPLETIONS}.0.tool_calls.{idx}.arguments") == tc.function.arguments

Then replace the repeated assertions with calls to these helpers.

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (3)

80-89: Duplicate write of LLM_REQUEST_FUNCTIONS.parameters.

parameters is set twice; drop the duplicate.

                 _set_span_attribute(
                     span,
                     f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{index}.parameters",
                     dump_object(function.get("parameters")),
                 )
-                _set_span_attribute(
-                    span,
-                    f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{index}.parameters",
-                    dump_object(function.get("parameters")),
-                )

148-156: Support both max_tokens_to_sample and max_tokens.

Cohere v2 uses max_tokens; v1 often used max_tokens_to_sample. Record whichever is provided.

-    _set_span_attribute(
-        span, GEN_AI_REQUEST_MAX_TOKENS, kwargs.get("max_tokens_to_sample")
-    )
+    _set_span_attribute(
+        span, GEN_AI_REQUEST_MAX_TOKENS, kwargs.get("max_tokens", kwargs.get("max_tokens_to_sample"))
+    )

171-179: Guard against None response.

Avoid to_dict(None) and no-op early.

 def set_span_response_attributes(span, response):
     if not span.is_recording():
         return
-
-    response_dict = to_dict(response)
+    if response is None:
+        return
+    response_dict = to_dict(response)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (5)

77-84: Don’t swallow exceptions silently; log at debug.

Replace bare except/pass with debug logging to aid diagnostics.

-        try:
-            _accumulate_stream_item(item, current_content_item, current_tool_call_item, final_response)
-        except Exception:
-            pass
+        try:
+            _accumulate_stream_item(item, current_content_item, current_tool_call_item, final_response)
+        except Exception as e:
+            import logging as _logging
+            _logging.getLogger(__name__).debug("Failed to accumulate v2 stream item: %s", e, exc_info=True)

118-123: Same: add debug logging instead of swallowing.

-        try:
-            _accumulate_stream_item(item, current_content_item, current_tool_call_item, final_response)
-        except Exception:
-            pass
+        try:
+            _accumulate_stream_item(item, current_content_item, current_tool_call_item, final_response)
+        except Exception as e:
+            import logging as _logging
+            _logging.getLogger(__name__).debug("Failed to accumulate v2 async stream item: %s", e, exc_info=True)

85-86: Remove stray print.

Avoid console noise in libraries.

-    print(f"final_response: {final_response}")

86-91: Guard event emission on event_logger presence (v2 sync).

Prevent potential None deref if instrumented without event logger.

-    if should_emit_events():
+    if should_emit_events() and event_logger is not None:
         emit_response_events(event_logger, llm_request_type, final_response)

124-129: Guard event emission on event_logger presence (v2 async).

-    if should_emit_events():
+    if should_emit_events() and event_logger is not None:
         emit_response_events(event_logger, llm_request_type, final_response)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (4)

202-205: Event emission may miss v2 chat inputs.
emit_input_event expects message (v1), but v2 passes messages. Consider emitting events for v2 messages as well.

Would you like a small PR to extend emit_input_event to handle messages (list) for v2 while retaining v1?


179-181: Silence Ruff ARG001 on unused instance params.
Prefix unused parameters with underscores to satisfy lints without changing behavior.

Apply this diff:

@@
-        def wrapper(wrapped, instance, args, kwargs):
-            return func(tracer, event_logger, to_wrap, wrapped, instance, args, kwargs)
+        def wrapper(wrapped, _instance, args, kwargs):
+            return func(tracer, event_logger, to_wrap, wrapped, _instance, args, kwargs)
@@
-def _wrap(
+def _wrap(
     tracer: Tracer,
     event_logger: Union[EventLogger, None],
     to_wrap,
     wrapped,
-    instance,
+    _instance,
     args,
     kwargs,
 ):
@@
-async def _awrap(
+async def _awrap(
     tracer: Tracer,
     event_logger: Union[EventLogger, None],
     to_wrap,
     wrapped,
-    instance,
+    _instance,
     args,
     kwargs,
 ):

Also applies to: 216-224, 264-273


329-340: Capture exception context when instrumentation binding fails.
Adds exc_info to debug logs; helps diagnose version drift or API changes.

Apply this diff:

@@
-            except (ImportError, ModuleNotFoundError, AttributeError):
-                logger.debug(f"Failed to instrument {wrap_module}.{wrap_object}.{wrap_method}")
+            except (ImportError, ModuleNotFoundError, AttributeError):
+                logger.debug(
+                    f"Failed to instrument {wrap_module}.{wrap_object}.{wrap_method}",
+                    exc_info=True,
+                )
@@
-            except (ImportError, ModuleNotFoundError, AttributeError):
-                logger.debug(f"Failed to instrument {wrap_module}.{wrap_object}.{wrap_method}")
+            except (ImportError, ModuleNotFoundError, AttributeError):
+                logger.debug(
+                    f"Failed to instrument {wrap_module}.{wrap_object}.{wrap_method}",
+                    exc_info=True,
+                )

Also applies to: 341-353


354-376: Minor: silence Ruff ARG002 and add exc_info during uninstrument failures.
Keeps logs useful and lints quiet.

Apply this diff:

-def _uninstrument(self, **kwargs):
+def _uninstrument(self, **_kwargs):
@@
-            except (ImportError, ModuleNotFoundError, AttributeError):
-                logger.debug(f"Failed to uninstrument {wrap_module}.{wrap_object}.{wrap_method}")
+            except (ImportError, ModuleNotFoundError, AttributeError):
+                logger.debug(
+                    f"Failed to uninstrument {wrap_module}.{wrap_object}.{wrap_method}",
+                    exc_info=True,
+                )
@@
-            except (ImportError, ModuleNotFoundError, AttributeError):
-                logger.debug(f"Failed to uninstrument {wrap_module}.{wrap_object}.{wrap_method}")
+            except (ImportError, ModuleNotFoundError, AttributeError):
+                logger.debug(
+                    f"Failed to uninstrument {wrap_module}.{wrap_object}.{wrap_method}",
+                    exc_info=True,
+                )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a189d2d and 96db843.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-cohere/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (7 hunks)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (5 hunks)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py (2 hunks)
  • packages/opentelemetry-instrumentation-cohere/pyproject.toml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_with_streaming.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_with_streaming_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_content_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_no_content_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_history.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_history_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_streaming.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_streaming_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/test_cohere_v2_embed_legacy.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/test_cohere_v2_embed_legacy_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/test_cohere_v2_rerank_legacy.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/test_cohere_v2_rerank_legacy_async.yaml (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/conftest.py (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/test_chat.py (4 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/test_embed.py (1 hunks)
  • packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/cassettes/**/*.{yaml,yml,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Never commit secrets or PII in VCR cassettes; scrub sensitive data

Files:

  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_content_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/test_cohere_v2_rerank_legacy_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_history.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/test_cohere_v2_embed_legacy.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_streaming.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_history_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_with_streaming.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_with_streaming_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/test_cohere_v2_embed_legacy_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/test_cohere_v2_rerank_legacy.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_no_content_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_streaming_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming_async.yaml
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/opentelemetry-instrumentation-cohere/tests/test_embed.py
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py
  • packages/opentelemetry-instrumentation-cohere/tests/conftest.py
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py
  • packages/opentelemetry-instrumentation-cohere/tests/test_chat.py
  • packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py
🧬 Code graph analysis (5)
packages/opentelemetry-instrumentation-cohere/tests/test_embed.py (3)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (1)
  • SpanAttributes (64-261)
packages/opentelemetry-instrumentation-cohere/tests/conftest.py (3)
  • instrument_legacy (71-79)
  • cohere_client_v2 (61-62)
  • async_cohere_client_v2 (66-67)
packages/traceloop-sdk/traceloop/sdk/utils/in_memory_span_exporter.py (1)
  • get_finished_spans (40-43)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (3)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py (4)
  • dont_throw (13-35)
  • should_send_prompts (38-41)
  • to_dict (64-77)
  • should_emit_events (44-49)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (2)
  • set_span_response_attributes (171-249)
  • _set_span_chat_response (252-293)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (1)
  • SpanAttributes (64-261)
packages/opentelemetry-instrumentation-cohere/tests/test_chat.py (1)
packages/opentelemetry-instrumentation-cohere/tests/conftest.py (7)
  • instrument_legacy (71-79)
  • cohere_client_v2 (61-62)
  • cohere_client (51-52)
  • async_cohere_client (56-57)
  • instrument_with_content (83-95)
  • instrument_with_no_content (99-111)
  • async_cohere_client_v2 (66-67)
packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py (2)
packages/opentelemetry-instrumentation-cohere/tests/conftest.py (3)
  • instrument_legacy (71-79)
  • cohere_client_v2 (61-62)
  • async_cohere_client_v2 (66-67)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (1)
  • SpanAttributes (64-261)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (2)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py (5)
  • dump_object (52-61)
  • should_send_prompts (38-41)
  • to_dict (64-77)
  • should_emit_events (44-49)
  • dont_throw (13-35)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (2)
  • LLMRequestTypeValues (293-298)
  • SpanAttributes (64-261)
🪛 Ruff (0.12.2)
packages/opentelemetry-instrumentation-cohere/tests/test_embed.py

8-8: Unused function argument: instrument_legacy

(ARG001)


67-67: Unused function argument: instrument_legacy

(ARG001)

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py

56-57: try-except-pass detected, consider logging the exception

(S110)


56-56: Do not catch blind exception: Exception

(BLE001)


60-60: Do not catch blind exception: Exception

(BLE001)


68-69: try-except-pass detected, consider logging the exception

(S110)


68-68: Do not catch blind exception: Exception

(BLE001)


76-76: Do not catch blind exception: Exception

(BLE001)

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py

81-82: try-except-pass detected, consider logging the exception

(S110)


81-81: Do not catch blind exception: Exception

(BLE001)


120-121: try-except-pass detected, consider logging the exception

(S110)


120-120: Do not catch blind exception: Exception

(BLE001)

packages/opentelemetry-instrumentation-cohere/tests/test_chat.py

90-90: Unused function argument: instrument_legacy

(ARG001)


134-134: Unused function argument: instrument_legacy

(ARG001)


176-176: Unused function argument: instrument_legacy

(ARG001)


226-226: Unused function argument: instrument_legacy

(ARG001)


418-418: Unused function argument: instrument_legacy

(ARG001)


578-578: Unused function argument: instrument_legacy

(ARG001)


616-616: Unused function argument: instrument_with_content

(ARG001)


646-646: Unused function argument: instrument_with_no_content

(ARG001)


676-676: Unused function argument: instrument_legacy

(ARG001)


719-719: Unused function argument: instrument_legacy

(ARG001)


770-770: Unused function argument: instrument_legacy

(ARG001)


922-922: Unused function argument: instrument_legacy

(ARG001)

packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py

209-209: Unused function argument: instrument_legacy

(ARG001)


290-290: Unused function argument: instrument_legacy

(ARG001)

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py

270-270: Unused function argument: instance

(ARG001)


354-354: Unused method argument: kwargs

(ARG002)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Packages (3.11)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Lint
🔇 Additional comments (28)
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming_async.yaml (4)

3-4: AI summary mismatch: cassette uses model "command", not "command-r".

The AI summary mentions "command-r", but the request body here sets "model": "command".


1-2: LGTM: cassette structure and SSE headers look correct.

Good: POST /v2/chat, text/event-stream, 200 OK, and streaming content captured.

Also applies to: 27-28, 527-550


60-64: SSE JSON validated — no hard-wrapped data lines detected.
Provided validator printed "OK"; no action required.


1-551: Repo‑wide cassette secrets/PII sweep required.

I ran the scan against packages/opentelemetry-instrumentation-cohere/tests/cassettes (rg produced no output). Absence of output isn't proof of safety — run a repo‑wide sweep from the repo root:

# repo-wide search for common secret patterns (exclude node_modules/vendor)
rg -n --hidden -S -C1 -g '!.git/**' -g '!**/node_modules/**' -g '!**/vendor/**' \
  -e 'Authorization:' -e 'Bearer\s+[A-Za-z0-9._-]+' -e 'X-Api-Key' -e 'x-api-key' \
  -e 'api_key' -e 'access_token' -e 'client_secret' -e 'AWS_SECRET_ACCESS_KEY' .

# search only VCR cassettes (yaml/yml/json)
rg -n --hidden -S -C1 -g '**/cassettes/**/*.{yaml,yml,json}' \
  -e 'Authorization:' -e 'Bearer\s+[A-Za-z0-9._-]+' -e 'X-Api-Key' -e 'api_key' \
  -e 'BEGIN RSA PRIVATE KEY' -e '-----BEGIN PRIVATE KEY-----'

If matches are found: remove/scrub the cassette (use VCR scrubbers or delete the file), and rotate/revoke any exposed credentials. Follow repo policy: never commit secrets or PII in /cassettes/**/*.{yaml,yml,json}.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml (1)

24-25: Verify cassette header matchers or re-record with Cohere ^5.18.x

Sandbox couldn't access repository files; unable to confirm. Check packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml (lines 24–25): if the recorded header x-fern-sdk-version: 5.6.1 is included in the cassette's match_on, remove/ignore that header from matchers or re-record the cassette with the bumped Cohere SDK (^5.18.x) to avoid playback breakage.

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py (2)

1-2: Imports LGTM.
Required for new helpers; no conflicts.


52-77: Privacy guardrail — confirm dump_object/to_dict call sites are only executed when content tracing is enabled.

Search failed (opentelemetry/instrumentation/cohere not found), so I couldn't verify. Run from the repo root and confirm each match is gated by content-tracing flags:

rg -n -C3 "dump_object(|to_dict("
rg -n -C3 "TRACELOOP_TRACE_CONTENT|override_enable_content_tracing"

If any call site is unguarded, gate it or avoid logging content to prevent PII exposure.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/test_cohere_v2_rerank_legacy.yaml (1)

1-77: Rerank cassette LGTM.
No sensitive headers recorded; payload size reasonable.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_history_async.yaml (1)

1-164: Cassette otherwise looks consistent and scrubbed.
Deprecation warnings and usage fields are fine.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/test_cohere_v2_embed_legacy_async.yaml (1)

1-93: Embed cassette LGTM.
No secrets; payload sizes acceptable for repo.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_content_async.yaml (1)

1-84: Cassette appears scrubbed — verify repo‑wide header filters.
rg returned no matches; absence of matches is inconclusive. Confirm repository-level VCR/cassette header-filtering (Authorization / Bearer / api[-_]key / x-api-key / Set-Cookie / Cookie) is enforced and provide the config or CI check that implements it.

packages/opentelemetry-instrumentation-cohere/pyproject.toml (2)

39-39: Cohere bumped to ^5.18.0 — PyPI 5.18.0 present and python_requires = >=3.9,<4.0.
Matches our 3.9–<4 CI range.


45-45: OK to keep pytest-asyncio = ^1.2.0. PyPI shows pytest-asyncio 1.2.0 is published; pytest‑8 compatibility was declared in v0.23.5 and recent releases constrain pytest to >=8.2,<9. (newreleases.io)

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_no_content_async.yaml (1)

1-84: Cassette OK — header drift is informational; no playback break detected.

vcr_config only filters "authorization" (packages/opentelemetry-instrumentation-cohere/tests/conftest.py:121) and there are no custom VCR matchers ('match_on' / 'register_matcher') in the cohere tests, so recorded headers (including x-fern-sdk-version) are not used for matching — re-record or relax header matching only if you intentionally enable header-based matching.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_streaming_async.yaml (1)

1-283: LGTM: good streaming + tool-calls coverage, no secrets observed.

Cassette looks clean (no Authorization header, no PII). Exercises v2 streaming/tool-calls well.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_streaming.yaml (1)

1-249: LGTM: deterministic v2 tool-call stream.

Looks properly scrubbed and representative; fine to merge.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_rerank/test_cohere_v2_rerank_legacy_async.yaml (1)

1-77: LGTM: v2 rerank async cassette is clean and minimal.

Good token accounting; no secrets present.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_with_streaming_async.yaml (1)

1-376: LGTM: v1 streaming path covered; no sensitive headers recorded.

All good for playback.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_embed/test_cohere_v2_embed_legacy.yaml (1)

1-93: LGTM: v2 embed cassette is tidy.

No secrets/PII; deterministic payload.

packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_with_streaming.yaml (1)

1-300: LGTM: legacy v1 streaming cassette looks fine.

Safe to merge.

packages/opentelemetry-instrumentation-cohere/tests/conftest.py (2)

55-68: Async fixtures confirmed — no action required.
All references to async_cohere_client / async_cohere_client_v2 are in tests annotated with @pytest.mark.asyncio; no synchronous tests reference these fixtures.


55-68: Confirmed — AsyncClient/AsyncClientV2 use aclose() and support async context manager.
Cohere Python SDK v5.18.0 async clients wrap httpx.AsyncClient, expose aclose(), and support async with; the fixtures are correct.

packages/opentelemetry-instrumentation-cohere/tests/test_embed.py (1)

32-41: LGTM: Core span assertions look correct for v2 embed (legacy).

Attributes validated here match the instrumentation contract (system, request type/model, prompt shape). Keep as-is.

packages/opentelemetry-instrumentation-cohere/tests/test_rerank.py (1)

229-241: LGTM: v2 rerank (legacy) span assertions are consistent.

Prompt indexing (docs as system, query as user) and model/type checks align with span_utils behavior.

packages/opentelemetry-instrumentation-cohere/tests/test_chat.py (1)

13-48: LGTM: Tools schema mirrors Cohere v2 expectations.

Tool definitions (names/descriptions/parameters) look correct for tool_calls coverage.

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (3)

14-24: LGTM: import reshuffle aligns with the new content/streaming responsibilities.
No issues spotted with the new imports (content attribute handlers, span-level helpers, streaming processors, and use_span).

Also applies to: 37-37


189-196: LGTM: request-type mapping covers v1/v2 and streaming.
The mapping of chat/chat_stream→CHAT, generate/generate_stream→COMPLETION, rerank, embed looks correct.


42-42: Verify supported cohere versions for v2 coverage.
Range is "cohere >=4.2.7, <6". If v2 APIs require >=5.x, consider bumping lower bound or adding a note to docs/tests.

Would you like me to scan the tests/lockfiles to confirm the minimum supported cohere version and propose an update?

Comment on lines +255 to +257
if to_wrap.get("stream_process_func"):
return to_wrap.get("stream_process_func")(span, event_logger, llm_request_type, response)

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

Streaming span lifecycle can leak on early-cancel; verify v2 error branch ends the span.
If the caller stops consuming the stream early, process_chat_* may never run its trailer and span may not end. Also, the v2 streaming processor sets ERROR but doesn’t end the span in the error path.

  • In streaming.py, wrap the yield loop in try/finally and end the span in finally when final_response is present or on cancel.
  • Also end the span in the error branch for v2 processors.

Example (streaming.py) pattern:

def process_chat_v2_streaming_response(span, event_logger, llm_request_type, response):
    final_response = {...}
    try:
        for item in response:
            span.add_event(name=f"{SpanAttributes.LLM_CONTENT_COMPLETION_CHUNK}")
            _accumulate_stream_item(..., final_response)
            yield item
    except Exception as e:
        if span.is_recording():
            span.set_status(Status(StatusCode.ERROR, str(e)))
            span.record_exception(e)
        raise
    finally:
        # set attributes only if we have something to set; still end the span
        if final_response:
            set_span_response_attributes(span, final_response)
            if should_emit_events():
                emit_response_events(event_logger, llm_request_type, final_response)
            elif should_send_prompts():
                _set_span_chat_response(span, final_response)
        if span.status is None or span.status.status_code == StatusCode.UNSET:
            span.set_status(Status(StatusCode.OK))
        span.end()

Comment on lines +40 to +41
''command'' is deprecated and will be removed September 15 2025. Please consider
upgrading to a newer model to avoid future service disruptions"],"billed_units":{"input_tokens":7,"output_tokens":80},"tokens":{"input_tokens":69,"output_tokens":81}}}'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Impending deprecation: model "command" removal dated September 15, 2025. Ensure tests never attempt live re-record.

If VCR ever re-records, this path will 404/410 after Sep 15, 2025. Confirm CI forces record=none and that we don’t depend on live calls for this legacy path.

Also applies to: 72-73


🏁 Script executed:

#!/bin/bash
# Verify VCR is not re-recording legacy cassettes and locate any remaining uses of the deprecated model.

set -euo pipefail

echo "Searching for pytest-vcr record modes and VCR configs..."
rg -nC2 -P '\brecord\s*=\s*["'\'']?(none|all|once|new_episodes)["'\'']?' -g '!**/node_modules/**'

echo
echo "Looking for global vcr_config fixtures or pytest.ini settings..."
rg -nC2 -P 'def\s+vcr_config|vcr=\{|\[pytest\]|\bvcr_cassette_library_dir\b' -g '!**/node_modules/**'

echo
echo "Checking for usages of deprecated Cohere model 'command' across tests and cassettes..."
rg -n -C2 -P "\bcommand\b" tests packages | sed 's/^/HIT: /'

Length of output: 205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "--- root listing ---"
ls -la | sed -n '1,120p' || true

cassette="packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml"
echo
echo "--- check cassette file: $cassette ---"
if [ -f "$cassette" ]; then
  echo "FOUND: $cassette"
  sed -n '1,160p' "$cassette" || true
else
  echo "MISSING: $cassette"
fi

echo
echo "--- list cassettes under cohere package (fallback-safe) ---"
if command -v fd >/dev/null 2>&1; then
  fd -H -e yaml -c never --hidden packages/opentelemetry-instrumentation-cohere/tests/cassettes || true
else
  find packages -type f -path "*/tests/cassettes/*.yaml" -print 2>/dev/null || true
fi

echo
echo "--- search repo for pytest-vcr / pytest-recording record flags and pytest addopts ---"
rg -n -S -uu --hidden -F -- '--vcr-record' || echo "no --vcr-record found"
rg -n -S -uu --hidden -F -- '--record-mode' || echo "no --record-mode found"
rg -n -S -uu --hidden -P '\baddopts\b' || echo "no addopts occurrences"

echo
echo "--- search CI/workflow files for pytest invocations and vcr flags ---"
rg -n -S -uu --hidden -g '.github/**' 'pytest' || echo "no pytest in .github"
rg -n -S -uu --hidden -g '.github/**' '--vcr-record' || echo "no --vcr-record in .github"

echo
echo "--- search for vcr fixtures / cassette dir config in tests/conftest.py or similar ---"
rg -n -S -uu --hidden -C2 'def\s+vcr_config|vcr_cassette_library_dir|vcr_cassette_dir|vcr_config' || echo "no vcr_config/vcr_cassette_dir found"

echo
echo "--- search tests and cassettes for the word 'command' (model) and 'cohere' ---"
rg -n -S -uu --hidden -C2 -- '\"command\"|\bcommand\b|cohere' tests packages || echo "no 'command' or 'cohere' matches in tests/packages"

echo
echo "--- full-repo 'command' scan (case-sensitive) ---"
rg -n -S -uu --hidden -- '\"command\"|\bcommand\b' || echo "no 'command' matches in repo"

Length of output: 16786


Enforce VCR record=none in CI — legacy Cohere "command" cassette expires September 15, 2025

  • Evidence: packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml contains "model":"command" and an API warning about removal on September 15, 2025; packages/opentelemetry-instrumentation-cohere/tests/conftest.py defines vcr_config but does not set a record mode; no global pytest addopts or --record-mode found in CI workflows.
  • Action: Update CI (.github/workflows/ci.yml test job) to set VCRPY_RECORD_MODE=none or run pytest with --record-mode=none, or remove/replace the legacy cassette before September 15, 2025.
🤖 Prompt for AI Agents
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml
lines 40-41: this cassette uses the deprecated "command" model and will expire
on Sept 15, 2025, so update CI to prevent VCR from attempting to re-record it;
modify .github/workflows/ci.yml test job to set VCRPY_RECORD_MODE=none (or run
pytest with --record-mode=none) so cassettes are only replayed in CI, or
alternatively remove/replace the legacy cassette with one using a supported
model before that date.

Comment on lines +3 to +4
body: '{"model": "command", "messages": [{"role": "user", "content": "Tell me
a joke, pirate style"}], "stream": true}'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Deprecated model in cassette will time-bomb tests; re-record with supported model.

Response warns model "command" is deprecated and removed on September 15, 2025. Re-record this cassette with a supported v2 model (e.g., command‑r) to avoid brittle replays and future re-record failures.

Run to find other affected cassettes:

Also applies to: 541-543


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --no-heading -C1 '"model":\s*"command"' packages/opentelemetry-instrumentation-cohere/tests/cassettes
rg -n --no-heading -C1 'x-api-warning:.*deprecated' packages/opentelemetry-instrumentation-cohere/tests/cassettes

Length of output: 6791


Replace deprecated "command" model in test cassettes — re-record with a supported v2 model.

Multiple cassettes record requests using "model": "command" (deprecated); migrate to a supported v2 model to avoid future test failures. (docs.cohere.com)

Affected cassettes (found by ripgrep):

  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_completion/test_cohere_completion_with_events_with_no_content.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_content.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_no_content_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_content_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_with_streaming_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_async.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy_with_streaming.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_legacy.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_completion/test_cohere_completion_with_events_with_content.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_completion/test_cohere_completion_legacy.yaml
  • packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_chat_with_events_with_no_content.yaml

Action (one-line): Re-record these cassettes replacing "model": "command" with a current Cohere model (e.g., "command-r" or "command-a") and commit the new cassettes; then run the test suite. (docs.cohere.com)

🤖 Prompt for AI Agents
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_streaming_async.yaml
(lines 3-4): the cassette records a deprecated model ("command"); re-record this
cassette (and the other affected cassettes listed in the review) by updating the
request payload to use a supported v2 model such as "command-r" or "command-a",
regenerate the HTTP cassette(s) using the test recorder so responses align with
the new model, commit the updated cassette files, and run the full test suite to
ensure no failures.

Comment on lines +84 to +92
"get_weather_mznfyh090g90", "content": "4:20 PM"}, {"role": "tool", "tool_call_id":
"get_time_eksdqe8f9g4y", "content": "Sunny 20 degrees Celsius"}], "tools": [{"type":
"function", "function": {"name": "get_weather", "description": "Get the current
weather in a given location", "parameters": {"type": "object", "properties":
{"location": {"type": "string", "description": "The city and state, e.g. San
Francisco, CA"}}, "required": ["location"]}}}, {"type": "function", "function":
{"name": "get_time", "description": "Get the current time in a given location",
"parameters": {"type": "object", "properties": {"location": {"type": "string",
"description": "The city and state, e.g. San Francisco, CA"}}, "required": ["location"]}}}],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Potentially swapped tool outputs in async history step.
Tool results seem inverted: weather id carries time and time id carries weather. Consider fixing test data or re-recording.


🏁 Script executed:

#!/bin/bash
# Show the mismatched lines for quick inspection
sed -n '70,105p' packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_history_async.yaml

Length of output: 1719


Fix swapped tool outputs in async cassette

get_weather (id get_weather_mznfyh090g90) contains "4:20 PM" and get_time (id get_time_eksdqe8f9g4y) contains "Sunny 20 degrees Celsius" — swap these tool output entries in packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_history_async.yaml (lines ~84–92) or re-record the cassette.

Comment on lines +3 to +11
body: '{"model": "command-r", "messages": [{"role": "user", "content": "What is
the weather and current time in Tokyo?"}], "tools": [{"type": "function", "function":
{"name": "get_weather", "description": "Get the current weather in a given location",
"parameters": {"type": "object", "properties": {"location": {"type": "string",
"description": "The city and state, e.g. San Francisco, CA"}}, "required": ["location"]}}},
{"type": "function", "function": {"name": "get_time", "description": "Get the
current time in a given location", "parameters": {"type": "object", "properties":
{"location": {"type": "string", "description": "The city and state, e.g. San
Francisco, CA"}}, "required": ["location"]}}}], "stream": true}'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Heads-up: model 'command-r' deprecation (removal set for September 15, 2025).

Re-records after that date will fail. Consider switching to a supported model before next re-record.


🌐 Web query:

Cohere model deprecations as of September 2025 — what replaces "command-r" for chat + tools?

💡 Result:

Short answer: Cohere's "command-r" chat models have been deprecated and replaced by the 08-2024 family — use "command-r-08-2024" (and "command-r-plus-08-2024" for the + tier) for chat and tools. [1][2][3]

Sources:

  • Cohere deprecations page (deprecation history). [1]
  • OCI / Cohere Command R 08-2024 release notes (chat/tool replacement). [2]
  • GitHub Models changelog noting migration to Command R 08-2024 (and R+ 08-2024). [3]

Replace deprecated model 'command-r' with 'command-r-08-2024' (use 'command-r-plus-08-2024' for + tier)
Cohere's 08-2024 family replaces "command-r"; re-records will fail after September 15, 2025 — update this cassette before the next re-record.
File: packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_streaming_async.yaml (lines 3-11)

🤖 Prompt for AI Agents
In
packages/opentelemetry-instrumentation-cohere/tests/cassettes/test_chat/test_cohere_v2_chat_legacy_with_tool_calls_and_streaming_async.yaml
around lines 3 to 11, the cassette uses the deprecated model name "command-r";
update the body JSON to use "command-r-08-2024" (or "command-r-plus-08-2024" if
this test should target the + tier) by replacing the "model": "command-r" entry
accordingly and ensure the cassette is re-recorded after updating the model
string.

Comment on lines +1 to +27
interactions:
- request:
body: '{"model": "command", "messages": [{"role": "user", "content": "Tell me
a joke, pirate style"}], "stream": false}'
headers:
accept:
- '*/*'
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- '112'
content-type:
- application/json
host:
- api.cohere.com
user-agent:
- cohere/5.18.0
x-fern-language:
- Python
x-fern-sdk-name:
- cohere
x-fern-sdk-version:
- 5.18.0
method: POST
uri: https://api.cohere.com/v2/chat
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify global header filtering for secrets.

No Authorization header here, good. Please confirm VCR config filters Authorization and API keys across all cassettes.


🏁 Script executed:

#!/bin/bash
# Verify no auth secrets are recorded and that filtering is configured.
rg -nI -C2 -g 'packages/**/cassettes/**' -e 'Authorization' -e 'api_key' -e 'Bearer ' -e 'COHERE_API_KEY'
rg -nI -C3 -e 'filter_headers' -e 'before_record' -e 'before_record_response' -e 'placeholders' -g 'packages/**/tests/**'

Length of output: 60355


Critical: recorded API keys found in cassettes — scrub secrets and enforce global VCR filtering

Ripgrep found API keys in cassette request bodies (examples: apikey=test_api_key; "api_key": "phc_C44vUK9R1J6HYVdfJarTEPqVAoRPJzMXzFcj8PIrJgP"). Tests have per-module vcr_config fixtures but filtering is inconsistent (many only filter "authorization").

  • Remove/rotate any real credentials in committed cassettes under packages//cassettes/ (search for api_key / apikey / secret_access_key / phc_*).
  • Add a central, autouse vcr_config (top-level tests/conftest.py) that sets filter_headers for authorization plus common API-key headers (api-key, x-api-key, x-openai-api-key, x-cohere-api-key, etc.) and implement before_record_request/response to scrub body fields (apikey, api_key, secret_access_key) and insert placeholders.
  • Sanitize existing cassettes (replace tokens with FILTERED), commit the sanitized files, and re-run rg to confirm no remaining real tokens.

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

♻️ Duplicate comments (8)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (6)

155-157: Append a copy; then reset accumulator.

Avoid mutating previously appended content items.

-    elif item_dict.get("type") == "content-end":
-        final_response["message"]["content"].append(current_content_item)
+    elif item_dict.get("type") == "content-end":
+        import copy as _copy
+        final_response["message"]["content"].append(_copy.deepcopy(current_content_item))
+        current_content_item.clear()

21-41: Guard missing stream-end; end span; decorate with dont_throw.

Handle final_response None, avoid emitting with None, and guard event_logger in event mode. Also decorate for parity.

- def process_chat_v1_streaming_response(span, event_logger, llm_request_type, response):
+@dont_throw
+def process_chat_v1_streaming_response(span, event_logger, llm_request_type, response):
@@
-    set_span_response_attributes(span, final_response)
-    if should_emit_events():
-        emit_response_events(event_logger, llm_request_type, final_response)
-    elif should_send_prompts():
-        _set_span_chat_response(span, final_response)
-    span.set_status(Status(StatusCode.OK))
+    if final_response is not None:
+        set_span_response_attributes(span, final_response)
+        if should_emit_events() and event_logger is not None:
+            emit_response_events(event_logger, llm_request_type, final_response)
+        elif should_send_prompts():
+            _set_span_chat_response(span, final_response)
+        span.set_status(Status(StatusCode.OK))
+    else:
+        span.set_status(Status(StatusCode.ERROR, "stream terminated without final response"))
     span.end()

43-63: Same guards/decorator for async v1 streamer.

-async def aprocess_chat_v1_streaming_response(span, event_logger, llm_request_type, response):
+@dont_throw
+async def aprocess_chat_v1_streaming_response(span, event_logger, llm_request_type, response):
@@
-    set_span_response_attributes(span, final_response)
-    if should_emit_events():
-        emit_response_events(event_logger, llm_request_type, final_response)
-    elif should_send_prompts():
-        _set_span_chat_response(span, final_response)
-    span.set_status(Status(StatusCode.OK))
+    if final_response is not None:
+        set_span_response_attributes(span, final_response)
+        if should_emit_events() and event_logger is not None:
+            emit_response_events(event_logger, llm_request_type, final_response)
+        elif should_send_prompts():
+            _set_span_chat_response(span, final_response)
+        span.set_status(Status(StatusCode.OK))
+    else:
+        span.set_status(Status(StatusCode.ERROR, "stream terminated without final response"))
     span.end()

67-76: Avoid mutating DEFAULT_MESSAGE; initialize accumulators safely.

final_response["message"] references a module‑level dict; later mutations leak across requests. Also initialize accumulators with expected nested keys to avoid KeyError.

-    final_response = {
+    import copy as _copy
+    final_response = {
         "finish_reason": None,
-        "message": DEFAULT_MESSAGE,
+        "message": _copy.deepcopy(DEFAULT_MESSAGE),
         "usage": {},
         "id": "",
         "error": None,
     }
-    current_content_item = {}
-    current_tool_call_item = {}
+    current_content_item = {"type": "text", "thinking": None, "text": ""}
+    current_tool_call_item = {
+        "id": "",
+        "type": "function",
+        "function": {"name": "", "arguments": "", "description": ""},
+    }

85-90: Always end span; guard event_logger; record proper exception.

-    set_span_response_attributes(span, final_response)
-    if should_emit_events():
-        emit_response_events(event_logger, llm_request_type, final_response)
+    set_span_response_attributes(span, final_response)
+    if should_emit_events() and event_logger is not None:
+        emit_response_events(event_logger, llm_request_type, final_response)
@@
-    if final_response.get("error"):
-        span.set_status(Status(StatusCode.ERROR, final_response.get("error")))
-        span.record_exception(final_response.get("error"))
-    else:
-        span.set_status(Status(StatusCode.OK))
-        span.end()
+    if final_response.get("error"):
+        err = final_response.get("error")
+        span.set_status(Status(StatusCode.ERROR, str(err)))
+        # record as Exception if it's a string
+        span.record_exception(err if isinstance(err, BaseException) else Exception(str(err)))
+    else:
+        span.set_status(Status(StatusCode.OK))
+    span.end()

Also applies to: 91-97


99-107: Same fixes for async v2.

-    final_response = {
+    import copy as _copy
+    final_response = {
         "finish_reason": None,
-        "message": DEFAULT_MESSAGE,
+        "message": _copy.deepcopy(DEFAULT_MESSAGE),
         "usage": {},
         "id": "",
         "error": None,
     }
@@
-    if should_emit_events():
-        emit_response_events(event_logger, llm_request_type, final_response)
+    if should_emit_events() and event_logger is not None:
+        emit_response_events(event_logger, llm_request_type, final_response)
@@
-    if final_response.get("error"):
-        span.set_status(Status(StatusCode.ERROR, final_response.get("error")))
-        span.record_exception(final_response.get("error"))
-    else:
-        span.set_status(Status(StatusCode.OK))
-        span.end()
+    if final_response.get("error"):
+        err = final_response.get("error")
+        span.set_status(Status(StatusCode.ERROR, str(err)))
+        span.record_exception(err if isinstance(err, BaseException) else Exception(str(err)))
+    else:
+        span.set_status(Status(StatusCode.OK))
+    span.end()

Also applies to: 123-134

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (2)

234-241: Exception can be swallowed when span isn’t recording (sync).

Always re‑raise; end span even when not recording.

-        except Exception as e:
-            if span.is_recording():
-                span.set_status(Status(StatusCode.ERROR, str(e)))
-                span.record_exception(e)
-                span.end()
-                raise
+        except Exception as e:
+            if span.is_recording():
+                span.set_status(Status(StatusCode.ERROR, str(e)))
+                span.record_exception(e)
+            # We started the span; end it even if not recording.
+            span.end()
+            raise

281-287: Same exception swallowing bug (async).

-        except Exception as e:
-            if span.is_recording():
-                span.set_status(Status(StatusCode.ERROR, str(e)))
-                span.record_exception(e)
-                raise
+        except Exception as e:
+            if span.is_recording():
+                span.set_status(Status(StatusCode.ERROR, str(e)))
+                span.record_exception(e)
+            raise
🧹 Nitpick comments (7)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (4)

94-103: Avoid None in len(kwargs.get('documents')).

len(None) will raise. Cache with a default.

-            _set_span_attribute(
-                span,
-                f"{GEN_AI_PROMPT}.{len(kwargs.get('documents'))}.role",
-                "user",
-            )
+            docs = kwargs.get("documents") or []
+            _set_span_attribute(
+                span,
+                f"{GEN_AI_PROMPT}.{len(docs)}.role",
+                "user",
+            )
             _set_span_attribute(
                 span,
-                f"{GEN_AI_PROMPT}.{len(kwargs.get('documents'))}.content",
+                f"{GEN_AI_PROMPT}.{len(docs)}.content",
                 kwargs.get("query"),
             )

110-116: Guard EMBEDDING texts None case.

If inputs/texts are both missing, iterating texts fails.

-            if not inputs:
-                texts = kwargs.get("texts")
-                inputs = [
-                    {"type": "text", "text": text} for text in texts
-                ]
+            if not inputs:
+                texts = kwargs.get("texts") or []
+                inputs = [{"type": "text", "text": text} for text in texts]

197-245: Token usage set multiple times; choose one precedence.

You set usage from meta.billed_units and again from usage.billed_units. This overwrites and can confuse backends. Prefer one precedence and skip the other if already set.


299-301: Unify completion prefix for RERANK response.

Chat paths use GEN_AI_COMPLETION.; rerank uses legacy LLM_COMPLETIONS.. For consistency with the semantic conventions learning, prefer GEN_AI_COMPLETION.*.

-        prefix = f"{SpanAttributes.LLM_COMPLETIONS}.{idx}"
+        prefix = f"{GEN_AI_COMPLETION}.{idx}"
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (3)

81-83: Don’t swallow exceptions during accumulation.

Blind except/ pass hides data bugs. At least record the exception.

-        try:
+        try:
             _accumulate_stream_item(item, current_content_item, current_tool_call_item, final_response)
-        except Exception:
-            pass
+        except Exception as e:
+            span.record_exception(e)

Also applies to: 119-121


166-171: Defensive structure for tool-call deltas.

Ensure nested dicts exist before concatenation.

-    elif item_dict.get("type") == "tool-call-delta":
-        message = (item_dict.get("delta") or {}).get("message") or {}
-        new_arguments = ((message.get("tool_calls") or {}).get("function") or {}).get("arguments")
-        if new_arguments:
-            existing_arguments = (current_tool_call_item.get("function") or {}).get("arguments")
-            current_tool_call_item["function"]["arguments"] = (existing_arguments or "") + new_arguments
+    elif item_dict.get("type") == "tool-call-delta":
+        message = (item_dict.get("delta") or {}).get("message") or {}
+        tool_calls = message.get("tool_calls") or {}
+        func = (tool_calls.get("function") or {})
+        new_arguments = func.get("arguments")
+        if new_arguments:
+            current_tool_call_item.setdefault("function", {})
+            existing_arguments = current_tool_call_item["function"].get("arguments")
+            current_tool_call_item["function"]["arguments"] = (existing_arguments or "") + new_arguments

136-176: Consider try/finally to end spans on early cancel.

If the caller stops consuming early, spans may never end. Wrap the loop in try/finally and finalize in finally (only emit/attribute when you have a final_response). Same for async variant.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 96db843 and 0c103c9.

📒 Files selected for processing (3)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (7 hunks)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (5 hunks)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: traceloop/openllmetry#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T15:06:48.109Z
Learning: Instrumentation packages must leverage the semantic conventions package and emit OTel-compliant spans
🧬 Code graph analysis (2)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (2)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py (5)
  • dump_object (52-61)
  • should_send_prompts (38-41)
  • to_dict (64-77)
  • should_emit_events (44-49)
  • dont_throw (13-35)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (2)
  • LLMRequestTypeValues (293-298)
  • SpanAttributes (64-261)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (3)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py (4)
  • dont_throw (13-35)
  • should_send_prompts (38-41)
  • to_dict (64-77)
  • should_emit_events (44-49)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (2)
  • set_span_response_attributes (166-244)
  • _set_span_chat_response (247-288)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (1)
  • SpanAttributes (64-261)
🪛 Ruff (0.12.2)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py

258-258: Unused function argument: instance

(ARG001)


342-342: Unused method argument: kwargs

(ARG002)

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py

81-82: try-except-pass detected, consider logging the exception

(S110)


81-81: Do not catch blind exception: Exception

(BLE001)


119-120: try-except-pass detected, consider logging the exception

(S110)


119-119: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Lint
  • GitHub Check: Build Packages (3.11)
🔇 Additional comments (2)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (2)

219-228: LGTM on span construction and semconv tags.

SpanKind, system, and request type attributes look correct and align with semconv.


316-341: Resilient wrapping/unwrapping.

The import guards and debug logs around wrap/unwrap are good for optional deps.

Comment on lines +39 to +45
if should_send_prompts() and not should_emit_events():
if llm_request_type == LLMRequestTypeValues.COMPLETION:
_set_span_attribute(span, f"{SpanAttributes.LLM_PROMPTS}.0.role", "user")
_set_span_attribute(span, f"{GEN_AI_PROMPT}.0.role", "user")
_set_span_attribute(
span, f"{SpanAttributes.LLM_PROMPTS}.0.content", kwargs.get("prompt")
span, f"{GEN_AI_PROMPT}.0.content", kwargs.get("prompt")
)
elif llm_request_type == LLMRequestTypeValues.CHAT:
_set_span_attribute(span, f"{SpanAttributes.LLM_PROMPTS}.0.role", "user")
# client V1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Don’t drop tool definitions when events are enabled.

Tools are only recorded inside the “should_send_prompts() and not should_emit_events()” gate. In event mode (non‑legacy), tool definitions are silently lost. Either emit them as events or set the attributes when content tracing is enabled regardless of event mode.

Minimal fix: move the tools block outside the “not should_emit_events()” gate but keep the “should_send_prompts()” guard.

-    if should_send_prompts() and not should_emit_events():
+    if should_send_prompts() and not should_emit_events():
       ...
-        if kwargs.get("tools"):
-            for index, tool in enumerate(kwargs.get("tools")):
-                ...
-                _set_span_attribute(
-                    span,
-                    f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{index}.parameters",
-                    dump_object(function.get("parameters")),
-                )
+        # prompt attributes only (legacy) above
+
+    # record tools when content tracing is enabled (legacy or events)
+    if should_send_prompts() and kwargs.get("tools"):
+        for index, tool in enumerate(kwargs.get("tools")):
+            function = tool.get("function")
+            if not function:
+                continue
+            _set_span_attribute(span, f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{index}.name", function.get("name"))
+            _set_span_attribute(span, f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{index}.description", function.get("description"))
+            _set_span_attribute(span, f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{index}.parameters", dump_object(function.get("parameters")))

Also applies to: 65-85


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for span_utils.py file path..."
FILE_PATH=$(rg --hidden --no-ignore --files | rg 'packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py' || true)

if [ -z "$FILE_PATH" ]; then
  FILE_PATH=$(find . -type f -name 'span_utils.py' 2>/dev/null | rg 'opentelemetry.*cohere' || true)
fi

if [ -z "$FILE_PATH" ]; then
  echo "FILE_NOT_FOUND"
  rg -n --hidden --no-ignore 'should_send_prompts|should_emit_events|SpanAttributes.LLM_REQUEST_FUNCTIONS|GEN_AI_PROMPT|\"tools\"' || true
  exit 0
fi

echo "FOUND: $FILE_PATH"
echo "---- print file with line numbers (1..240) ----"
nl -ba "$FILE_PATH" | sed -n '1,240p'
echo "---- grep for relevant symbols in file ----"
rg -n -C3 'should_send_prompts|should_emit_events|tools|LLM_REQUEST_FUNCTIONS|GEN_AI_PROMPT|SpanAttributes' "$FILE_PATH" || true

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for span_utils.py..."
FILE=$(rg --hidden --no-ignore --files | rg 'packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py' || true)

if [ -z "$FILE" ]; then
  FILE=$(find . -type f -name 'span_utils.py' 2>/dev/null | rg 'opentelemetry.*cohere' || true)
fi

if [ -z "$FILE" ]; then
  echo "FILE_NOT_FOUND"
  rg -n --hidden --no-ignore 'should_send_prompts|should_emit_events|SpanAttributes.LLM_REQUEST_FUNCTIONS|GEN_AI_PROMPT|\"tools\"' || true
  exit 0
fi

echo "FOUND: $FILE"
echo "---- file line count ----"
wc -l "$FILE" || true
echo "---- printing file with line numbers (first 400 lines) ----"
awk '{printf "%4d: %s\n", NR, $0}' "$FILE" | sed -n '1,400p'
echo "---- grep for relevant symbols with context ----"
rg -n -C3 'should_send_prompts|should_emit_events|tools|SpanAttributes|GEN_AI_PROMPT|LLMRequestTypeValues' "$FILE" || true

Length of output: 21946


Don't drop tool definitions when events are enabled.

Tool definitions are only set inside the if should_send_prompts() and not should_emit_events(): branch, so when events are enabled the tools are not recorded — move the tools loop (currently lines 65–84) outside the not should_emit_events() guard while keeping the should_send_prompts() check so tools are recorded for both legacy and event modes. File: packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py.

🤖 Prompt for AI Agents
In
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py
around lines 39–45 and the tools loop at ~65–84, the current code only sets tool
definitions inside the branch guarded by "not should_emit_events()", which
causes tools to be omitted when events are enabled; move the tools loop out of
the "and not should_emit_events()" guard but keep it inside the
"should_send_prompts()" check so tools are always recorded regardless of events
mode, i.e., ensure should_send_prompts() is evaluated before setting prompts and
tools, extract the tools loop so it runs when should_send_prompts() is true
(independent of should_emit_events()), and adjust any indentation/branching
accordingly.

Comment on lines +140 to +146
final_response["message"] = (item_dict.get("delta") or {}).get("message") or DEFAULT_MESSAGE
final_response["id"] = item_dict.get("id")
elif item_dict.get("type") == "content-start":
new_content_item = ((item_dict.get("delta") or {}).get("message") or {}).get("content")
current_content_item.clear()
current_content_item.update(new_content_item or {})
elif item_dict.get("type") == "content-delta":
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

Copy DEFAULT_MESSAGE on fallback to avoid shared mutation.

-    if item_dict.get("type") == "message-start":
-        final_response["message"] = (item_dict.get("delta") or {}).get("message") or DEFAULT_MESSAGE
+    if item_dict.get("type") == "message-start":
+        import copy as _copy
+        final_response["message"] = (item_dict.get("delta") or {}).get("message") or _copy.deepcopy(DEFAULT_MESSAGE)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final_response["message"] = (item_dict.get("delta") or {}).get("message") or DEFAULT_MESSAGE
final_response["id"] = item_dict.get("id")
elif item_dict.get("type") == "content-start":
new_content_item = ((item_dict.get("delta") or {}).get("message") or {}).get("content")
current_content_item.clear()
current_content_item.update(new_content_item or {})
elif item_dict.get("type") == "content-delta":
if item_dict.get("type") == "message-start":
import copy as _copy
final_response["message"] = (item_dict.get("delta") or {}).get("message") or _copy.deepcopy(DEFAULT_MESSAGE)
final_response["id"] = item_dict.get("id")
elif item_dict.get("type") == "content-start":
new_content_item = ((item_dict.get("delta") or {}).get("message") or {}).get("content")
current_content_item.clear()
current_content_item.update(new_content_item or {})
elif item_dict.get("type") == "content-delta":
🤖 Prompt for AI Agents
In
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py
around lines 140 to 146, final_response["message"] falls back to DEFAULT_MESSAGE
directly which can lead to shared-mutation bugs; change the fallback to return a
new copy of DEFAULT_MESSAGE (e.g., use copy.deepcopy(DEFAULT_MESSAGE) or
DEFAULT_MESSAGE.copy()) so each response gets its own independent dict instance.

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

♻️ Duplicate comments (3)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (3)

125-144: Async v2 handler doesn't call span.end() on the error path

At line 144, span.end() is only called in the else branch (success case). If an error occurs, the span won't be ended, leading to leaked spans.

Apply this fix to ensure the span is always ended:

     if final_response.get("error"):
         span.set_status(Status(StatusCode.ERROR, final_response.get("error")))
         span.record_exception(final_response.get("error"))
     else:
         span.set_status(Status(StatusCode.OK))
-        span.end()
+    span.end()

12-18: ⚠️ Avoid mutation of DEFAULT_MESSAGE during streaming operations

The current approach of assigning DEFAULT_MESSAGE directly to final_response["message"] at line 74 and 114 creates a shared reference that could be mutated, leading to cross-contamination between different streaming operations.

Apply this fix to create a deep copy of DEFAULT_MESSAGE:

+import copy
+
DEFAULT_MESSAGE = {
    "content": [],
    "role": "assistant",
    "tool_calls": [],
    "tool_plan": "",
    # TODO: Add citations
}

Then update lines 74 and 114:

-        "message": DEFAULT_MESSAGE,
+        "message": copy.deepcopy(DEFAULT_MESSAGE),

111-124: Missing try/finally in async v2 handler

The async v2 handler (lines 111-144) doesn't have a try/finally block like the sync version (lines 71-107), meaning spans could leak if an exception occurs during iteration.

Wrap the async for loop in a try/finally block:

 async def aprocess_chat_v2_streaming_response(span, event_logger, llm_request_type, response):
     final_response = {
         "finish_reason": None,
-        "message": DEFAULT_MESSAGE,
+        "message": copy.deepcopy(DEFAULT_MESSAGE),
         "usage": {},
         "id": "",
         "error": None,
     }
     current_content_item = {"type": "text", "thinking": None, "text": ""}
     current_tool_call_item = {
         "id": "",
         "type": "function",
         "function": {"name": "", "arguments": "", "description": ""},
     }
+    try:
         async for item in response:
             span.add_event(name=f"{SpanAttributes.LLM_CONTENT_COMPLETION_CHUNK}")
             item_to_yield = item
             try:
                 _accumulate_stream_item(item, current_content_item, current_tool_call_item, final_response)
             except Exception:
                 pass
             yield item_to_yield
     
         set_span_response_attributes(span, final_response)
         if should_emit_events():
             emit_response_events(event_logger, llm_request_type, final_response)
         elif should_send_prompts():
             _set_span_chat_response(span, final_response)
         if final_response.get("error"):
             span.set_status(Status(StatusCode.ERROR, final_response.get("error")))
             span.record_exception(final_response.get("error"))
         else:
             span.set_status(Status(StatusCode.OK))
-            span.end()
+    finally:
+        span.end()
🧹 Nitpick comments (4)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (3)

101-103: Record proper exception objects instead of strings

span.record_exception() expects an Exception object, but final_response.get("error") might be a string or other non-exception type. This could cause issues with telemetry backends.

Wrap the error in an exception if it's not already one:

     if final_response.get("error"):
         span.set_status(Status(StatusCode.ERROR, final_response.get("error")))
-        span.record_exception(final_response.get("error"))
+        error = final_response.get("error")
+        if isinstance(error, Exception):
+            span.record_exception(error)
+        else:
+            span.record_exception(Exception(str(error)))

Apply the same fix to line 141 in the async version.


151-151: Fallback to copy of DEFAULT_MESSAGE instead of empty dict

At line 151, when there's no message in the delta, the code falls back to {**DEFAULT_MESSAGE}, which creates a shallow copy. The nested lists (content and tool_calls) are still shared references.

Use deep copy for proper isolation:

-        final_response["message"] = (item_dict.get("delta") or {}).get("message") or {**DEFAULT_MESSAGE}
+        final_response["message"] = (item_dict.get("delta") or {}).get("message") or copy.deepcopy(DEFAULT_MESSAGE)

28-29: Remove unnecessary f-string formatting

Line 28 uses an f-string but doesn't include any variables to format.

-            span.add_event(name=f"{SpanAttributes.LLM_CONTENT_COMPLETION_CHUNK}")
+            span.add_event(name=SpanAttributes.LLM_CONTENT_COMPLETION_CHUNK)

Apply the same fix to lines 53, 87, and 126.

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (1)

287-288: Missing span.end() before re-raising exception in async handler

In the async exception handler, span.end() is called at line 287 but the span is managed by the context manager and should not be manually ended here.

Remove the manual span.end() call as the context manager handles it:

         except Exception as e:
             if span.is_recording():
                 span.set_status(Status(StatusCode.ERROR, str(e)))
                 span.record_exception(e)
-                span.end()
             raise
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c103c9 and 0ad5023.

📒 Files selected for processing (2)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (7 hunks)
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Store API keys only in environment variables/secure vaults; never hardcode secrets in code
Use Flake8 for code linting and adhere to its rules

Files:

  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py
  • packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py
🧬 Code graph analysis (1)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (3)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/utils.py (4)
  • dont_throw (13-35)
  • should_send_prompts (38-41)
  • to_dict (64-77)
  • should_emit_events (44-49)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/span_utils.py (2)
  • set_span_response_attributes (166-244)
  • _set_span_chat_response (247-288)
packages/opentelemetry-semantic-conventions-ai/opentelemetry/semconv_ai/__init__.py (1)
  • SpanAttributes (64-261)
🪛 Ruff (0.12.2)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py

258-258: Unused function argument: instance

(ARG001)


343-343: Unused method argument: kwargs

(ARG002)

packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py

91-92: try-except-pass detected, consider logging the exception

(S110)


91-91: Do not catch blind exception: Exception

(BLE001)


130-131: try-except-pass detected, consider logging the exception

(S110)


130-130: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Packages (3.12)
  • GitHub Check: Test Packages (3.10)
  • GitHub Check: Test Packages (3.11)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Lint
🔇 Additional comments (2)
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py (2)

321-328: LGTM! Good defensive error handling for instrumentation

The try/except blocks properly handle missing modules or attributes during instrumentation without crashing the entire process, and log failures at debug level for troubleshooting.


234-241: ```shell
#!/bin/bash
set -euo pipefail

ROOT="packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere"
echo "PWD: $(pwd)"
echo

if [ -f "$ROOT/init.py" ]; then
echo "=== Showing $ROOT/init.py (up to 400 lines) ==="
if command -v sed >/dev/null 2>&1; then
sed -n '1,400p' "$ROOT/init.py"
else
head -n 400 "$ROOT/init.py" || true
fi
else
echo "File not found: $ROOT/init.py"
fi

echo
echo "=== Searching for @dont_throw decorator ==="
if command -v rg >/dev/null 2>&1; then
rg -n "@dont_throw" --hidden || true
else
grep -RIn "@dont_throw" packages || true
fi

echo
echo "=== Searching for span.end occurrences ==="
if command -v rg >/dev/null 2>&1; then
rg -n "span.end(" --hidden -S -C3 || true
else
grep -RIn "span.end(" packages || true
fi

echo
echo "=== Searching for record_exception occurrences ==="
if command -v rg >/dev/null 2>&1; then
rg -n "record_exception" --hidden -S -C3 || true
else
grep -RIn "record_exception" packages || true
fi

echo
echo "=== Searching for except Exception blocks that reference span ==="
if command -v rg >/dev/null 2>&1; then
rg -nP "except\s+Exception[\s\S]{0,200}span." --hidden -S || true
rg -n "except Exception" --hidden -S -C3 || true
else
grep -RIn "except Exception" packages || true
fi

echo
echo "=== Searching for functions (def / async def) that take 'response' and reference span ==="
if command -v rg >/dev/null 2>&1; then
rg -nP "def\s+\w+([^)]response[^)]):[\s\S]{0,200}span." --hidden -S || true
rg -nP "async def\s+\w+([^)]response[^)]):[\s\S]{0,200}span." --hidden -S || true
else
grep -RIn "def " packages | rg "response" || true
fi

echo
echo "=== Listing .py files under packages/opentelemetry-instrumentation-cohere ==="
if [ -d "packages/opentelemetry-instrumentation-cohere" ]; then
find packages/opentelemetry-instrumentation-cohere -type f -name "*.py" -print || true
else
echo "directory packages/opentelemetry-instrumentation-cohere not found"
fi


</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +243 to +244
if to_wrap.get("stream_process_func"):
return to_wrap.get("stream_process_func")(span, event_logger, llm_request_type, response)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify stream processor functions handle all edge cases

The streaming delegation correctly passes control to specialized stream processors. Ensure these processors handle early cancellation, connection drops, and other streaming edge cases.


🏁 Script executed:

#!/bin/bash
# Check if stream processors handle early cancellation and exceptions
ast-grep --pattern 'def $_(span, $_, $_, response):
  $$$
  try:
    $$$
  finally:
    span.end()
  $$$'

# Check test coverage for streaming edge cases
rg -A5 -B5 "test.*stream.*cancel|test.*stream.*error|test.*stream.*interrupt" --type=py

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Show file around lines 200-320 ==="
nl -ba packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py | sed -n '1,400p' || true

echo "=== Search for 'stream_process_func' occurrences ==="
rg -n "stream_process_func" -S || true

echo "=== Search for 'stream' references in the package ==="
rg -n --hidden --no-ignore -S "stream" packages/opentelemetry-instrumentation-cohere || true

echo "=== Search for function defs containing 'stream' ==="
rg -n --type py "def\s+.*stream" -S || true

echo "=== Search tests for streaming/cancel/error keywords ==="
rg -n --type py "stream.*cancel|stream.*error|stream.*interrupt|cancel.*stream|interrupt.*stream|stream.*exception" -S || true

Length of output: 58215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== streaming.py (cohere) ==="
awk '{printf("%5d: %s\n", NR, $0)}' packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py || true

echo "=== __init__.py (cohere) ==="
awk '{printf("%5d: %s\n", NR, $0)}' packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/__init__.py || true

echo "=== Relevant test snippets (tests/test_chat.py head) ==="
awk '{printf("%5d: %s\n", NR, $0)}' packages/opentelemetry-instrumentation-cohere/tests/test_chat.py | sed -n '1,260p' || true

echo "=== Search for try/finally/exception handling in streaming.py ==="
rg -n --hidden --no-ignore -S "try:|except |finally|span.end\(|stream-end|is_finished" packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py || true

echo "=== Search for tests covering streaming exceptions/cancellation in cohere tests ==="
rg -n --hidden --no-ignore -S "exception|cancel|not_consumed|partial_consumption|interrupt|disconnect|connection|broken|stream.*exception" packages/opentelemetry-instrumentation-cohere/tests || true

Length of output: 39167


Guarantee span.end() and robust cancellation/error handling for async v2 streaming

  • aprocess_chat_v2_streaming_response is missing a try/finally and only calls span.end() on the success path — file packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py (async def aprocess_chat_v2_streaming_response, ~lines 111–144). Wrap the async iteration in try/finally to always end the span and record exceptions consistently (match the other processors that use finally).
  • Add unit tests that simulate early cancellation/connection drops and exceptions during iteration in packages/opentelemetry-instrumentation-cohere/tests/test_chat.py (no cohere tests currently cover these edge cases).

Comment on lines +91 to +92
except Exception:
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Log exceptions during stream accumulation instead of silently swallowing them

The except Exception: pass pattern at lines 91-92 and 130-131 silently swallows exceptions that could indicate data parsing issues. This makes debugging difficult and could lead to incomplete telemetry data.

Consider logging these exceptions at debug level:

+import logging
+logger = logging.getLogger(__name__)

Then update the exception handlers:

-            except Exception:
-                pass
+            except Exception as e:
+                logger.debug(f"Failed to accumulate stream item: {e}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception:
pass
import logging
logger = logging.getLogger(__name__)
except Exception as e:
logger.debug(f"Failed to accumulate stream item: {e}")
🧰 Tools
🪛 Ruff (0.12.2)

91-92: try-except-pass detected, consider logging the exception

(S110)


91-91: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In
packages/opentelemetry-instrumentation-cohere/opentelemetry/instrumentation/cohere/streaming.py
around lines 91-92 and 130-131, replace the silent "except Exception: pass"
handlers with logged exceptions: catch the exception as a variable (e.g., except
Exception as e) and emit a debug-level log that includes a clear context message
and the exception details/stack (using the module logger and logger.debug(...,
exc_info=True) or equivalent), so parsing/accumulation errors are recorded
without changing control flow.

@nirga nirga merged commit 7b994c0 into traceloop:main Sep 14, 2025
9 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