Skip to content

feat: add multimodal support to OCI Generative AI provider#4964

Open
fede-kamel wants to merge 12 commits into
crewAIInc:mainfrom
fede-kamel:feat/oci-multimodal
Open

feat: add multimodal support to OCI Generative AI provider#4964
fede-kamel wants to merge 12 commits into
crewAIInc:mainfrom
fede-kamel:feat/oci-multimodal

Conversation

@fede-kamel

@fede-kamel fede-kamel commented Mar 19, 2026

Copy link
Copy Markdown

Summary

  • vision.py: vision model lists, to_data_uri, load_image, encode_image, is_vision_model helpers
  • _build_generic_content handles image_url, document_url, video_url, audio_url content types → OCI SDK ImageContent, DocumentContent, VideoContent, AudioContent
  • _message_has_multimodal_content detects non-text payloads for Cohere rejection
  • Cohere models reject multimodal with clear error (text-only)
  • supports_multimodal()True

Depends on #4963, #4962, #4961, #4959. Draft until all merge.
Tracking issue: #4944

Diff breakdown (vs structured output PR)

Change Lines
vision.py (new file) +62
completion.py (multimodal content + checks) +58/-4
__init__.py (vision exports) +15/-1
test_oci_multimodal.py (10 unit tests) +195
test_oci_integration_multimodal.py (1 live test) +106
Total +440

Test plan

  • 10 unit tests: image/document/video/audio content building, unsupported type rejection, Cohere multimodal rejection, multimodal detection, supports_multimodal, vision helpers
  • Live integration: sent 2x2 red PNG to google.gemini-2.5-flash via data URI — correctly identified color
  • All 33 prior unit tests still pass (no regressions)

@fede-kamel fede-kamel force-pushed the feat/oci-multimodal branch from 3fab3ad to 45617b1 Compare March 29, 2026 16:16
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
Add OCI embedding support integrated with CrewAI's RAG pipeline:
- OCIEmbeddingFunction: ChromaDB-compatible embedding callable with
  batching, config serialization, image embedding support
- OCIProvider: Pydantic-based provider with alias validation for
  env vars and config keys
- Factory registration in embeddings/factory.py + types.py
- Supports text and image embeddings, output dimensions,
  custom endpoints, all 4 OCI auth modes

Tested live against cohere.embed-english-v3.0 with API_KEY auth.

Depends on: crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
Add OCI embedding support integrated with CrewAI's RAG pipeline:
- OCIEmbeddingFunction: ChromaDB-compatible embedding callable with
  batching, config serialization, image embedding support
- OCIProvider: Pydantic-based provider with alias validation for
  env vars and config keys
- Factory registration in embeddings/factory.py + types.py
- Supports text and image embeddings, output dimensions,
  custom endpoints, all 4 OCI auth modes

Tested live against cohere.embed-english-v3.0 with API_KEY auth.

Depends on: crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

- oci_async.py: OCIAsyncClient with aiohttp, OCI request signing,
  native SSE parsing, connection pooling
- acall(): true async chat completion (no thread pool)
- astream(): true async SSE streaming (no thread+queue bridge)
- Graceful fallback to asyncio.to_thread when aiohttp unavailable
  or client is mocked (unit tests)
- aiohttp + certifi added to crewai[oci] optional deps

Temporary measure until OCI SDK ships native async support.

Tested live: acall, astream, and concurrent acall against
meta.llama-3.3-70b-instruct with API_KEY auth.

Depends on: crewAIInc#4966, crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
@fede-kamel fede-kamel force-pushed the feat/oci-multimodal branch from 45617b1 to 52d905c Compare April 24, 2026 17:11
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
Add OCI embedding support integrated with CrewAI's RAG pipeline:
- OCIEmbeddingFunction: ChromaDB-compatible embedding callable with
  batching, config serialization, image embedding support
- OCIProvider: Pydantic-based provider with alias validation for
  env vars and config keys
- Factory registration in embeddings/factory.py + types.py
- Supports text and image embeddings, output dimensions,
  custom endpoints, all 4 OCI auth modes

Tested live against cohere.embed-english-v3.0 with API_KEY auth.

Depends on: crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
Add OCI embedding support integrated with CrewAI's RAG pipeline:
- OCIEmbeddingFunction: ChromaDB-compatible embedding callable with
  batching, config serialization, image embedding support
- OCIProvider: Pydantic-based provider with alias validation for
  env vars and config keys
- Factory registration in embeddings/factory.py + types.py
- Supports text and image embeddings, output dimensions,
  custom endpoints, all 4 OCI auth modes

Tested live against cohere.embed-english-v3.0 with API_KEY auth.

Depends on: crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

- oci_async.py: OCIAsyncClient with aiohttp, OCI request signing,
  native SSE parsing, connection pooling
- acall(): true async chat completion (no thread pool)
- astream(): true async SSE streaming (no thread+queue bridge)
- Graceful fallback to asyncio.to_thread when aiohttp unavailable
  or client is mocked (unit tests)
- aiohttp + certifi added to crewai[oci] optional deps

Temporary measure until OCI SDK ships native async support.

Tested live: acall, astream, and concurrent acall against
meta.llama-3.3-70b-instruct with API_KEY auth.

Depends on: crewAIInc#4966, crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 6, 2026
- OCICompletion(BaseLLM): sync call() and async acall() for generic
  (Meta, Google, OpenAI, xAI) and Cohere model families
- Shared OCI auth utilities (utilities/oci.py): API key, security token,
  instance principal, and resource principal auth
- Provider routing in llm.py: oci/ prefix and OCI model-id patterns
- oci registered as optional dependency (crewai[oci])
- Configurable timeout via DEFAULT_OCI_TIMEOUT constant
- Cohere and generic request/response paths fully separated

Tracking issue: crewAIInc#4944
Part 1 of series: crewAIInc#4959crewAIInc#4961crewAIInc#4962crewAIInc#4963crewAIInc#4964crewAIInc#4966crewAIInc#4982
@fede-kamel fede-kamel force-pushed the feat/oci-multimodal branch from 52d905c to 50b91f0 Compare May 6, 2026 16:34
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 6, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

- oci_async.py: OCIAsyncClient with aiohttp, OCI request signing,
  native SSE parsing, connection pooling
- acall(): true async chat completion (no thread pool)
- astream(): true async SSE streaming (no thread+queue bridge)
- Graceful fallback to asyncio.to_thread when aiohttp unavailable
  or client is mocked (unit tests)
- aiohttp + certifi added to crewai[oci] optional deps

Temporary measure until OCI SDK ships native async support.

Tested live: acall, astream, and concurrent acall against
meta.llama-3.3-70b-instruct with API_KEY auth.

Depends on: crewAIInc#4966, crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 6, 2026
Add OCI embedding support integrated with CrewAI's RAG pipeline:
- OCIEmbeddingFunction: ChromaDB-compatible embedding callable with
  batching, config serialization, image embedding support
- OCIProvider: Pydantic-based provider with alias validation for
  env vars and config keys
- Factory registration in embeddings/factory.py + types.py
- Supports text and image embeddings, output dimensions,
  custom endpoints, all 4 OCI auth modes

Tested live against cohere.embed-english-v3.0 with API_KEY auth.

Depends on: crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
@fede-kamel

Copy link
Copy Markdown
Author

@greysonlalonde @lorenzejay — bumping for review.

Status (after rebase):

This PR adds: Maps crewAI multimodal parts (inline_data Blobs, file_data URLs) to OCI's ImageContent / AudioContent / VideoContent / DocumentContent blocks. Inline bytes are wrapped as data:<mime>;base64,…. Works against Gemini 2.5 vision and Llama 3.2 Vision.

v1 spec compliance: like #4959, this targets OCI's /20231130/Chat v1 API directly via oci.generative_ai_inference. Live HTTP-body capture confirms all spec fields (compartmentId, servingMode, chatRequest{apiFormat,…}) match Oracle's /20231130/Chat reference.

This is part of the 7-PR series (#4959, #4961, #4962, #4963, #4964, #4966, #4982). Happy to fold any subset into a single PR if that helps review velocity.

fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 6, 2026
- OCICompletion(BaseLLM): sync call() and async acall() for generic
  (Meta, Google, OpenAI, xAI) and Cohere model families
- Shared OCI auth utilities (utilities/oci.py): API key, security token,
  instance principal, and resource principal auth
- Provider routing in llm.py: oci/ prefix and OCI model-id patterns
- oci registered as optional dependency (crewai[oci])
- Configurable timeout via DEFAULT_OCI_TIMEOUT constant
- Cohere and generic request/response paths fully separated

Tracking issue: crewAIInc#4944
Part 1 of series: crewAIInc#4959crewAIInc#4961crewAIInc#4962crewAIInc#4963crewAIInc#4964crewAIInc#4966crewAIInc#4982
@fede-kamel fede-kamel force-pushed the feat/oci-multimodal branch from 50b91f0 to 1c3ee48 Compare May 6, 2026 18:45
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 6, 2026
Add OCI embedding support integrated with CrewAI's RAG pipeline:
- OCIEmbeddingFunction: ChromaDB-compatible embedding callable with
  batching, config serialization, image embedding support
- OCIProvider: Pydantic-based provider with alias validation for
  env vars and config keys
- Factory registration in embeddings/factory.py + types.py
- Supports text and image embeddings, output dimensions,
  custom endpoints, all 4 OCI auth modes

Tested live against cohere.embed-english-v3.0 with API_KEY auth.

Depends on: crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 6, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

- oci_async.py: OCIAsyncClient with aiohttp, OCI request signing,
  native SSE parsing, connection pooling
- acall(): true async chat completion (no thread pool)
- astream(): true async SSE streaming (no thread+queue bridge)
- Graceful fallback to asyncio.to_thread when aiohttp unavailable
  or client is mocked (unit tests)
- aiohttp + certifi added to crewai[oci] optional deps

Temporary measure until OCI SDK ships native async support.

Tested live: acall, astream, and concurrent acall against
meta.llama-3.3-70b-instruct with API_KEY auth.

Depends on: crewAIInc#4966, crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
- OCICompletion(BaseLLM): sync call() and async acall() for generic
  (Meta, Google, OpenAI, xAI) and Cohere model families
- Shared OCI auth utilities (utilities/oci.py): API key, security token,
  instance principal, and resource principal auth
- Provider routing in llm.py: oci/ prefix and OCI model-id patterns
- oci registered as optional dependency (crewai[oci])
- Configurable timeout via DEFAULT_OCI_TIMEOUT constant
- Cohere and generic request/response paths fully separated

Tracking issue: crewAIInc#4944
Part 1 of series: crewAIInc#4959crewAIInc#4961crewAIInc#4962crewAIInc#4963crewAIInc#4964crewAIInc#4966crewAIInc#4982
@fede-kamel fede-kamel force-pushed the feat/oci-multimodal branch from 1c3ee48 to 2bb658f Compare May 18, 2026 01:26
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
Add OCI embedding support integrated with CrewAI's RAG pipeline:
- OCIEmbeddingFunction: ChromaDB-compatible embedding callable with
  batching, config serialization, image embedding support
- OCIProvider: Pydantic-based provider with alias validation for
  env vars and config keys
- Factory registration in embeddings/factory.py + types.py
- Supports text and image embeddings, output dimensions,
  custom endpoints, all 4 OCI auth modes

Tested live against cohere.embed-english-v3.0 with API_KEY auth.

Depends on: crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
Add OCI embedding support integrated with CrewAI's RAG pipeline:
- OCIEmbeddingFunction: ChromaDB-compatible embedding callable with
  batching, config serialization, image embedding support
- OCIProvider: Pydantic-based provider with alias validation for
  env vars and config keys
- Factory registration in embeddings/factory.py + types.py
- Supports text and image embeddings, output dimensions,
  custom endpoints, all 4 OCI auth modes

Tested live against cohere.embed-english-v3.0 with API_KEY auth.

Depends on: crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

- oci_async.py: OCIAsyncClient with aiohttp, OCI request signing,
  native SSE parsing, connection pooling
- acall(): true async chat completion (no thread pool)
- astream(): true async SSE streaming (no thread+queue bridge)
- Graceful fallback to asyncio.to_thread when aiohttp unavailable
  or client is mocked (unit tests)
- aiohttp + certifi added to crewai[oci] optional deps

Temporary measure until OCI SDK ships native async support.

Tested live: acall, astream, and concurrent acall against
meta.llama-3.3-70b-instruct with API_KEY auth.

Depends on: crewAIInc#4966, crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an OCI Generative AI provider with optional SDK installation, native model routing, configurable authentication, provider-specific requests, multimodal inputs, tool calls, structured outputs, synchronous/asynchronous streaming, and unit plus environment-gated integration tests.

Changes

OCI provider integration

Layer / File(s) Summary
Routing and authentication
lib/crewai/pyproject.toml, lib/crewai/src/crewai/llm.py, lib/crewai/src/crewai/utilities/oci.py
Registers the OCI optional dependency, routes OCI model identifiers to OCICompletion, and configures supported OCI authentication modes.
Completion requests and content conversion
lib/crewai/src/crewai/llms/providers/oci/*, lib/crewai/tests/llms/oci/test_oci.py, test_oci_cohere_v2.py, test_oci_multimodal.py
Builds generic, Cohere, and Cohere v2 requests, normalizes messages, handles vision content, and applies model-specific request parameters.
Tools and structured responses
lib/crewai/src/crewai/llms/providers/oci/completion.py, test_oci_tools.py, test_oci_structured.py
Formats tool calls, executes bounded tool recursion, and parses Pydantic structured responses.
Streaming and public APIs
lib/crewai/src/crewai/llms/providers/oci/completion.py, test_oci_streaming.py
Adds synchronous and asynchronous calls, SSE parsing, streamed tool calls, usage metadata, and client locking.
Live integration coverage
lib/crewai/tests/llms/oci/conftest.py, test_oci_integration_*.py
Adds environment-gated tests for basic, multimodal, streaming, structured-output, and tool-calling interactions.

Sequence Diagram(s)

sequenceDiagram
  participant CrewAI
  participant OCICompletion
  participant OCI Generative AI
  participant ToolExecutor
  CrewAI->>OCICompletion: call or stream messages
  OCICompletion->>OCI Generative AI: submit provider-specific request
  OCI Generative AI-->>OCICompletion: text, structured output, or tool calls
  OCICompletion->>ToolExecutor: execute available functions
  ToolExecutor-->>OCICompletion: return tool results
  OCICompletion->>OCI Generative AI: submit follow-up request
  OCI Generative AI-->>CrewAI: return final response or streamed chunks
Loading

Suggested reviewers: alex-clawd, lorenzejay, greysonlalonde

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding multimodal support to the OCI Generative AI provider.
Description check ✅ Passed The description matches the changeset and describes the OCI multimodal helpers, content mapping, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for 45 days with no activity.

- OCICompletion(BaseLLM): sync call() and async acall() for generic
  (Meta, Google, OpenAI, xAI) and Cohere model families
- Shared OCI auth utilities (utilities/oci.py): API key, security token,
  instance principal, and resource principal auth
- Provider routing in llm.py: oci/ prefix and OCI model-id patterns
- oci registered as optional dependency (crewai[oci])
- Configurable timeout via DEFAULT_OCI_TIMEOUT constant
- Cohere and generic request/response paths fully separated

Tracking issue: crewAIInc#4944
Part 1 of series: crewAIInc#4959crewAIInc#4961crewAIInc#4962crewAIInc#4963crewAIInc#4964crewAIInc#4966crewAIInc#4982
- 15 unit tests (mocked OCI SDK, no credentials needed)
- 2 parametrized integration tests across 4 model families:
  meta.llama-3.3-70b-instruct, cohere.command-r-plus-08-2024,
  google.gemini-2.5-flash, openai.gpt-5.2-chat-latest
- conftest.py with shared fixtures for both test suites
Previously: any `openai.*` model on the GENERIC api_format route was
sent `max_completion_tokens` instead of `max_tokens`. That works for
the GPT-5 family (which OCI requires it on with HTTP 400 if you send
`max_tokens`), but is inconsistent with `_is_openai_gpt5_family()`
already used elsewhere in the same method for temperature and stop.

Switch the param-name routing to the same predicate so GPT-4o /
GPT-4.1 keep the standard `max_tokens` field, while only `openai.gpt-5*`
gets `max_completion_tokens`. Also reword the test pair to reflect the
two distinct behaviours.

Verified live against OCI us-chicago-1 — all 7 model families pass:
  openai.gpt-5.5          ok    (max_completion_tokens)
  openai.gpt-5            ok    (max_completion_tokens)
  openai.gpt-4o           Ok.   (max_tokens)
  openai.gpt-4.1          ok    (max_tokens)
  meta.llama-3.3-70b-instruct           ok
  meta.llama-4-scout-17b-16e-instruct   ok
  cohere.command-latest   ok    (CohereChatRequest)
Add reasoning_effort: str | None = None as a constructor kwarg. When
set, it's passed as `reasoning_effort` on the OCI GenericChatRequest —
honoured by GPT-5 family, Gemini 2.5, Grok reasoning variants, and
Cohere Command-A-Reasoning; ignored by non-reasoning models.

Single biggest cost knob for reasoning-capable models on OCI: setting
"LOW" typically cuts reasoning-token spend 5-10×. Other
GenericChatRequest fields (verbosity, parallel_tool_calls, logit_bias,
n, metadata, etc.) aren't exposed — they either duplicate existing
controls or aren't missing primitives.
Coderabbit's pre-merge check flagged 45.10% docstring coverage on this
PR; required threshold is 80%. Add concise docstrings for the
remaining 14 methods on OCICompletion (init, provider helpers,
message helpers, response extractors, call/acall, _chat). Coverage now
100% per ast walk; no behavioural change.
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Jul 10, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

- oci_async.py: OCIAsyncClient with aiohttp, OCI request signing,
  native SSE parsing, connection pooling
- acall(): true async chat completion (no thread pool)
- astream(): true async SSE streaming (no thread+queue bridge)
- Graceful fallback to asyncio.to_thread when aiohttp unavailable
  or client is mocked (unit tests)
- aiohttp + certifi added to crewai[oci] optional deps

Temporary measure until OCI SDK ships native async support.

Tested live: acall, astream, and concurrent acall against
meta.llama-3.3-70b-instruct with API_KEY auth.

Depends on: crewAIInc#4966, crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
@fede-kamel fede-kamel force-pushed the feat/oci-multimodal branch from 2bb658f to 8abc166 Compare July 10, 2026 18:13
@fede-kamel fede-kamel marked this pull request as ready for review July 10, 2026 18:20
fede-kamel and others added 7 commits July 10, 2026 14:22
Drop unreachable os.getenv defaults in the live-config fixture and fix
the pytest path in the run instructions to include the lib/crewai/
prefix.
Add streaming text completion via OCI SSE events:
- stream=True in call() routes to _stream_call_impl with chunk events
- iter_stream() yields raw text chunks (sync generator)
- astream() wraps iter_stream via thread+queue for async callers
- _stream_chat_events holds client lock for full stream duration
- SSE event parsing handles both string and mapping payloads

Tested live against meta.llama-3.3-70b-instruct,
cohere.command-r-plus-08-2024, google.gemini-2.5-flash,
and openai.gpt-5.2-chat-latest.

Depends on: crewAIInc#4959
Tracking issue: crewAIInc#4944
Add native function calling for generic and Cohere model families:
- _format_tools converts CrewAI tool specs to OCI SDK format
- _extract_tool_calls normalizes responses back to CrewAI shape
- _handle_tool_calls executes tools and recurses until model finishes
- Cohere tool message handling with trailing tool results
- Tool choice control (auto/none/required/function)
- Passthrough parameter filtering via SDK introspection
- Streaming tool call accumulation from SSE fragments
- supports_function_calling() returns True

Tested live against meta.llama-3.3-70b-instruct with
raw tool call return and recursive tool execution.

Depends on: crewAIInc#4961 (streaming), crewAIInc#4959 (basic text)
Tracking issue: crewAIInc#4944
Add response_model (Pydantic) support for structured output:
- _build_response_format converts Pydantic schema to OCI
  JsonSchemaResponseFormat (generic) or CohereResponseJsonFormat
- _parse_structured_response validates and returns typed models
- response_model threaded through call, _call_impl, _stream_call_impl,
  and _handle_tool_calls for full coverage
- Handles JSON in markdown fences via base class _validate_structured_output

Tested live against meta.llama-3.3-70b-instruct and
google.gemini-2.5-flash.

Depends on: crewAIInc#4962 (tool calling), crewAIInc#4961 (streaming), crewAIInc#4959 (basic text)
Tracking issue: crewAIInc#4944
Add multimodal content handling for generic model families:
- vision.py: model lists, data URI helpers, image encoding utilities
- _build_generic_content handles image_url, document_url, video_url,
  audio_url content types mapped to OCI SDK content objects
- _message_has_multimodal_content detects non-text payloads
- Cohere models reject multimodal with clear error message
- supports_multimodal() returns True

Depends on: crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
Send a 2x2 red PNG to google.gemini-2.5-flash via data URI and
verify it identifies the color. Tests the full image_url content
pipeline end-to-end against a live OCI vision model.
cohere.command-a-vision rejects the v1 COHERE api format, so route
command-a-vision / command-a-reasoning model ids to a new cohere_v2
request family built on CohereChatRequestV2: message-based history,
text + image_url content, CohereToolV2 function tools, JSON response
format, and SSE streaming (v2 events carry message.content[].text).

Verified live against OCI on-demand in us-chicago-1: plain chat,
streaming, and vision all pass; tool use is wired but the service
currently rejects tools for command-a-vision.
@fede-kamel fede-kamel force-pushed the feat/oci-multimodal branch from 8abc166 to f68fc6e Compare July 10, 2026 18:25
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Jul 10, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

- oci_async.py: OCIAsyncClient with aiohttp, OCI request signing,
  native SSE parsing, connection pooling
- acall(): true async chat completion (no thread pool)
- astream(): true async SSE streaming (no thread+queue bridge)
- Graceful fallback to asyncio.to_thread when aiohttp unavailable
  or client is mocked (unit tests)
- aiohttp + certifi added to crewai[oci] optional deps

Temporary measure until OCI SDK ships native async support.

Tested live: acall, astream, and concurrent acall against
meta.llama-3.3-70b-instruct with API_KEY auth.

Depends on: crewAIInc#4966, crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
@fede-kamel

Copy link
Copy Markdown
Author

Rebased onto current main and un-drafted — mergeable again.

This PR now also adds COHEREV2 api-format support: cohere.command-a-vision rejects the v1 CohereChatRequest, so vision-capable Cohere models route to a new cohere_v2 request family built on CohereChatRequestV2 (message-based history, text + image content, v2 tools, JSON response format, SSE streaming).

Verified live against OCI on-demand (us-chicago-1):

  • generic-format vision (meta.llama-4-scout) ✅
  • cohere.command-a-vision chat, streaming, and image understanding ✅
  • v2 tool calling is wired and unit-tested; the service currently rejects tools for command-a-vision, so it activates when OCI ships a v2 model with tool support.

Unit suite: 53 passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
lib/crewai/tests/llms/oci/test_oci_streaming.py (1)

10-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move import json to module top level.

import json is inside the function body of _make_fake_stream_event rather than at the module level, which deviates from PEP 8 import placement conventions. As per coding guidelines, Python source files should follow Python best practices.

♻️ Proposed fix
 from __future__ import annotations

+import json
 from unittest.mock import MagicMock

 import pytest


 def _make_fake_stream_event(text: str = "", finish_reason: str | None = None, usage: dict | None = None) -> MagicMock:
     """Build a single SSE event with optional text, finish, and usage."""
     payload: dict = {}
     if text:
         payload["message"] = {"content": [{"text": text}]}
     if finish_reason:
         payload["finishReason"] = finish_reason
     if usage:
         payload["usage"] = usage

-    import json
     event = MagicMock()
     event.data = json.dumps(payload)
     return event
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/llms/oci/test_oci_streaming.py` around lines 10 - 23, Move
the json import from inside _make_fake_stream_event to the module-level imports,
and continue using json.dumps(payload) within the helper.

Source: Coding guidelines

lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py (1)

22-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the shared OCI live-test helpers
_env_models and _skip_unless_live are duplicated in test_oci_integration_tools.py, and conftest.py already has the same live-config flow. This copy has also started to drift (OCI_AUTH_FILE_LOCATION support and skip text differ), so pull the shared env parsing / skip logic into one helper or fixture to keep the OCI integration tests in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py` around lines 22
- 42, Replace the duplicated _env_models and _skip_unless_live implementations
with the shared OCI live-test helper or fixture already used by
test_oci_integration_tools.py and conftest.py. Update this test to consume that
shared configuration, preserving support for OCI_AUTH_FILE_LOCATION and
consistent skip messages, and remove the local helper definitions.
lib/crewai/src/crewai/llms/providers/oci/vision.py (1)

46-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add docstrings for public API functions.

load_image, encode_image, and is_vision_model are exported in __all__ but lack docstrings, while to_data_uri has one. As per coding guidelines, public APIs should be documented.

♻️ Proposed fix
 def load_image(file_path: str | Path) -> dict[str, dict[str, str] | str]:
+    """Load an image from a file path and return an image_url content dict."""
     return {"type": "image_url", "image_url": {"url": to_data_uri(file_path)}}


 def encode_image(
     image_bytes: bytes, mime_type: str = "image/png"
 ) -> dict[str, dict[str, str] | str]:
+    """Encode raw image bytes into an image_url content dict with a data URI."""
     return {"type": "image_url", "image_url": {"url": to_data_uri(image_bytes, mime_type)}}


 def is_vision_model(model_id: str) -> bool:
+    """Return True if the model supports vision/multimodal inputs."""
     return model_id in VISION_MODELS
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/llms/providers/oci/vision.py` around lines 46 - 57, Add
concise docstrings to the public functions load_image, encode_image, and
is_vision_model, documenting their parameters, return values, and behavior
consistently with the existing to_data_uri documentation.

Source: Coding guidelines

lib/crewai/tests/llms/oci/test_oci_multimodal.py (1)

10-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Strengthen content-building tests with argument verification.

The image, document, video, and audio content tests verify that the correct OCI constructors are called (assert_called_once()) but don't verify the arguments passed (e.g., URL values). Adding assert_called_once_with(...) would catch bugs where the wrong URL is passed to the constructor.

♻️ Example improvement for test_oci_builds_image_content
     assert len(result) == 2
     patch_oci_module.generative_ai_inference.models.ImageContent.assert_called_once()
-    patch_oci_module.generative_ai_inference.models.ImageUrl.assert_called_once()
+    patch_oci_module.generative_ai_inference.models.ImageUrl.assert_called_once_with(
+        url="data:image/png;base64,abc123"
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/llms/oci/test_oci_multimodal.py` around lines 10 - 104,
Strengthen the multimodal content tests by replacing constructor-only call-count
assertions with argument verification. In test_oci_builds_image_content,
test_oci_builds_document_content, test_oci_builds_video_content, and
test_oci_builds_audio_content, assert ImageUrl, DocumentUrl, VideoUrl, and
AudioUrl are each called once with the corresponding input URL and assert the
content constructors receive the expected URL object.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/pyproject.toml`:
- Around line 107-109: Raise the minimum OCI dependency in the `oci` optional
extra from `2.168.0` to `2.168.2` so installations receive the security-patched
urllib3 release.

In `@lib/crewai/src/crewai/llms/providers/oci/completion.py`:
- Around line 1371-1375: Update the streaming path around `_chat()` and
`response.data.events()` so `_client_lock` remains held for the entire
event-consumption lifetime, not just while creating the response; mirror the
locking pattern used by `_stream_chat_events()` and ensure the lock is released
via an idiomatic context manager even if iteration raises.

In `@lib/crewai/src/crewai/utilities/oci.py`:
- Around line 46-47: Update the OCI security-token configuration handling in the
relevant function to validate that `key_file` and `security_token_file` exist
before accessing them. Raise a descriptive configuration error identifying the
missing SECURITY_TOKEN entries instead of allowing a raw `KeyError` to escape.

---

Nitpick comments:
In `@lib/crewai/src/crewai/llms/providers/oci/vision.py`:
- Around line 46-57: Add concise docstrings to the public functions load_image,
encode_image, and is_vision_model, documenting their parameters, return values,
and behavior consistently with the existing to_data_uri documentation.

In `@lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py`:
- Around line 22-42: Replace the duplicated _env_models and _skip_unless_live
implementations with the shared OCI live-test helper or fixture already used by
test_oci_integration_tools.py and conftest.py. Update this test to consume that
shared configuration, preserving support for OCI_AUTH_FILE_LOCATION and
consistent skip messages, and remove the local helper definitions.

In `@lib/crewai/tests/llms/oci/test_oci_multimodal.py`:
- Around line 10-104: Strengthen the multimodal content tests by replacing
constructor-only call-count assertions with argument verification. In
test_oci_builds_image_content, test_oci_builds_document_content,
test_oci_builds_video_content, and test_oci_builds_audio_content, assert
ImageUrl, DocumentUrl, VideoUrl, and AudioUrl are each called once with the
corresponding input URL and assert the content constructors receive the expected
URL object.

In `@lib/crewai/tests/llms/oci/test_oci_streaming.py`:
- Around line 10-23: Move the json import from inside _make_fake_stream_event to
the module-level imports, and continue using json.dumps(payload) within the
helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b0ef515d-fc37-4df1-b0f7-82d596e458b5

📥 Commits

Reviewing files that changed from the base of the PR and between 85c467d and 8abc166.

📒 Files selected for processing (19)
  • lib/crewai/pyproject.toml
  • lib/crewai/src/crewai/llm.py
  • lib/crewai/src/crewai/llms/providers/oci/__init__.py
  • lib/crewai/src/crewai/llms/providers/oci/completion.py
  • lib/crewai/src/crewai/llms/providers/oci/vision.py
  • lib/crewai/src/crewai/utilities/oci.py
  • lib/crewai/tests/llms/oci/__init__.py
  • lib/crewai/tests/llms/oci/conftest.py
  • lib/crewai/tests/llms/oci/test_oci.py
  • lib/crewai/tests/llms/oci/test_oci_cohere_v2.py
  • lib/crewai/tests/llms/oci/test_oci_integration_basic.py
  • lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_integration_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_integration_structured.py
  • lib/crewai/tests/llms/oci/test_oci_integration_tools.py
  • lib/crewai/tests/llms/oci/test_oci_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_structured.py
  • lib/crewai/tests/llms/oci/test_oci_tools.py

Comment thread lib/crewai/pyproject.toml
Comment on lines +107 to +109
oci = [
"oci>=2.168.0",
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the latest version of the oci Python SDK on PyPI, and are there any known security advisories for versions >=2.168.0?

💡 Result:

The latest version of the Oracle Cloud Infrastructure (OCI) Python SDK available on PyPI as of July 10, 2026, is 2.181.0 [1][2]. Regarding security advisories for versions >= 2.168.0, the SDK itself is typically not the source of direct vulnerabilities but occasionally requires dependency updates (such as for the 'cryptography' or 'urllib3' libraries) to address vulnerabilities found in those upstream packages [3][4][5][6]. For instance, version 2.168.2 included a specific security update to address CVE-2025-66418 by adjusting the upper bound of the 'urllib3' dependency for Python >= 3.10 [3]. Additionally, versions in the 2.168.x range and subsequent releases have addressed previous concerns regarding dependency version constraints (specifically for the 'cryptography' library) that had previously prevented users from applying security patches to those underlying libraries [4]. Oracle manages security for the SDK through regular release cadences [7]. If you are using version 2.168.0 or later, it is recommended to keep the SDK updated to the latest version (2.181.0) to ensure you have the most recent dependency constraints and patches [8][4]. You can track specific vulnerability reports and security updates through the project's official GitHub repository [9][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the pyproject section and OCI usage to judge whether the minimum pin is justified.
printf '\n## pyproject excerpt\n'
sed -n '95,120p' lib/crewai/pyproject.toml

printf '\n## oci imports/usages\n'
rg -n '\bimport oci\b|\bfrom oci\b|oci\.' lib/crewai -g '!**/__pycache__/**'

printf '\n## files mentioning optional dependency or auth helpers\n'
rg -n 'optional-dependencies|extras|oci' lib/crewai -g 'pyproject.toml' -g '*.py'

Repository: crewAIInc/crewAI

Length of output: 50372


Raise the OCI minimum to 2.168.2 oci>=2.168.0 misses the urllib3 security-update release; bump the optional extra so users get the patched dependency floor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/pyproject.toml` around lines 107 - 109, Raise the minimum OCI
dependency in the `oci` optional extra from `2.168.0` to `2.168.2` so
installations receive the security-patched urllib3 release.

Comment on lines +1371 to +1375
response = self._chat(chat_details)
usage_data: dict[str, int] = {}
response_metadata: dict[str, Any] = {}

for event in response.data.events():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep the client lock for the full iter_stream lifetime.

_chat() releases _client_lock before response.data.events() is consumed. A concurrent call can therefore enter client.chat while this stream is active, unlike _stream_chat_events() which correctly holds the lock throughout event consumption.

Proposed fix
-        response = self._chat(chat_details)
         usage_data: dict[str, int] = {}
         response_metadata: dict[str, Any] = {}

-        for event in response.data.events():
+        for event in self._stream_chat_events(chat_details):

As per coding guidelines, “Follow Python best practices and idiomatic patterns in Python source files.”

📝 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
response = self._chat(chat_details)
usage_data: dict[str, int] = {}
response_metadata: dict[str, Any] = {}
for event in response.data.events():
usage_data: dict[str, int] = {}
response_metadata: dict[str, Any] = {}
for event in self._stream_chat_events(chat_details):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/llms/providers/oci/completion.py` around lines 1371 -
1375, Update the streaming path around `_chat()` and `response.data.events()` so
`_client_lock` remains held for the entire event-consumption lifetime, not just
while creating the response; mirror the locking pattern used by
`_stream_chat_events()` and ensure the lock is released via an idiomatic context
manager even if iteration raises.

Source: Coding guidelines

Comment on lines +46 to +47
key_file = config["key_file"]
security_token_file = config["security_token_file"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard against KeyError on SECURITY_TOKEN config access.

Direct dict access config["key_file"] and config["security_token_file"] will raise a cryptic KeyError if the OCI config profile is missing these entries. A descriptive error improves UX for misconfigured security token auth.

🛡️ Proposed fix
         key_file = config.get("key_file")
         if not key_file:
             raise ValueError(
                 "OCI SECURITY_TOKEN auth requires 'key_file' in the config profile"
             )
         security_token_file = config.get("security_token_file")
         if not security_token_file:
             raise ValueError(
                 "OCI SECURITY_TOKEN auth requires 'security_token_file' in the config profile"
             )
📝 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
key_file = config["key_file"]
security_token_file = config["security_token_file"]
key_file = config.get("key_file")
if not key_file:
raise ValueError(
"OCI SECURITY_TOKEN auth requires 'key_file' in the config profile"
)
security_token_file = config.get("security_token_file")
if not security_token_file:
raise ValueError(
"OCI SECURITY_TOKEN auth requires 'security_token_file' in the config profile"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/utilities/oci.py` around lines 46 - 47, Update the OCI
security-token configuration handling in the relevant function to validate that
`key_file` and `security_token_file` exist before accessing them. Raise a
descriptive configuration error identifying the missing SECURITY_TOKEN entries
instead of allowing a raw `KeyError` to escape.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
lib/crewai/src/crewai/llms/providers/oci/completion.py (1)

1371-1375: 🩺 Stability & Availability | 🟠 Major

Keep the client lock for the full iter_stream lifetime.

_chat() releases _client_lock before response.data.events() is consumed. A concurrent call can enter client.chat while this stream is active, unlike _stream_chat_events() which correctly holds the lock throughout event consumption.

Proposed fix
-        response = self._chat(chat_details)
         usage_data: dict[str, int] = {}
         response_metadata: dict[str, Any] = {}

-        for event in response.data.events():
+        for event in self._stream_chat_events(chat_details):

As per coding guidelines, "Follow Python best practices and idiomatic patterns in Python source files."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/llms/providers/oci/completion.py` around lines 1371 -
1375, Update the streaming path around `_chat()` and `response.data.events()` so
`_client_lock` remains acquired for the entire event-iteration lifetime,
matching `_stream_chat_events()`; move or restructure lock acquisition to
encompass the client call and all consumption of `response.data.events()`,
releasing it only after iteration completes, including exception paths.

Source: Coding guidelines

🧹 Nitpick comments (1)
lib/crewai/tests/llms/oci/test_oci_tools.py (1)

148-205: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for Cohere v2 tool formatting and extraction.

_format_tools has a cohere_v2 branch (producing CohereToolV2) and _extract_tool_calls has a cohere_v2 branch (using _cohere_v2_function_field), but neither path is tested. These are important new code paths for the COHEREV2 support described in the PR objectives. As per coding guidelines, "Write unit tests for new functionality that focus on behavior rather than implementation details."

Suggested test additions
def test_oci_completion_formats_cohere_v2_tools(
    patch_oci_module, oci_unit_values
):
    """_format_tools should produce CohereToolV2 for Cohere v2 models."""
    from crewai.llms.providers.oci.completion import OCICompletion

    patch_oci_module.generative_ai_inference.GenerativeAiInferenceClient.return_value = MagicMock()
    llm = OCICompletion(
        model="cohere.command-a-vision",
        compartment_id=oci_unit_values["compartment_id"],
    )
    formatted = llm._format_tools(SAMPLE_TOOLS)

    assert len(formatted) == 1
    models = patch_oci_module.generative_ai_inference.models
    models.CohereToolV2.assert_called_once()


def _make_cohere_v2_tool_call_response(tool_name: str = "get_weather") -> MagicMock:
    """Build a fake OCI Cohere v2 response with a tool call."""
    tc = MagicMock()
    tc.id = "tc_v2_001"
    function = MagicMock()
    function.name = tool_name
    function.arguments = '{"city": "NYC"}'
    tc.function = function

    message = MagicMock()
    message.tool_calls = [tc]

    chat_response = MagicMock()
    chat_response.message = message
    chat_response.text = ""
    chat_response.finish_reason = "COMPLETE"
    chat_response.usage = MagicMock(prompt_tokens=8, completion_tokens=4, total_tokens=12)

    response = MagicMock()
    response.data.chat_response = chat_response
    return response


def test_oci_completion_cohere_v2_extracts_tool_calls(
    patch_oci_module, oci_unit_values
):
    """Cohere v2 tool calls should be normalized with function field extraction."""
    from crewai.llms.providers.oci.completion import OCICompletion

    fake_client = MagicMock()
    fake_client.chat.return_value = _make_cohere_v2_tool_call_response()
    patch_oci_module.generative_ai_inference.GenerativeAiInferenceClient.return_value = fake_client

    llm = OCICompletion(
        model="cohere.command-a-vision",
        compartment_id=oci_unit_values["compartment_id"],
    )
    result = llm.call(
        messages=[{"role": "user", "content": "Weather?"}],
        tools=SAMPLE_TOOLS,
    )

    assert isinstance(result, list)
    assert result[0]["function"]["name"] == "get_weather"
    assert result[0]["id"] == "tc_v2_001"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/llms/oci/test_oci_tools.py` around lines 148 - 205, Add unit
coverage in the OCI completion tests for both Cohere v2 tool paths: create a
test for _format_tools using a Cohere v2 model and assert it returns a tool via
models.CohereToolV2, and add a helper response fixture plus a test for
_extract_tool_calls through OCICompletion.call that verifies the normalized
function name and preserves the tool-call ID.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/src/crewai/llms/providers/oci/completion.py`:
- Around line 1222-1227: In _call_impl, pass normalized_messages rather than
messages to _finalize_text_response, matching _stream_call_impl and the
preceding _parse_structured_response call so downstream hooks receive
list[LLMMessage] consistently.

---

Duplicate comments:
In `@lib/crewai/src/crewai/llms/providers/oci/completion.py`:
- Around line 1371-1375: Update the streaming path around `_chat()` and
`response.data.events()` so `_client_lock` remains acquired for the entire
event-iteration lifetime, matching `_stream_chat_events()`; move or restructure
lock acquisition to encompass the client call and all consumption of
`response.data.events()`, releasing it only after iteration completes, including
exception paths.

---

Nitpick comments:
In `@lib/crewai/tests/llms/oci/test_oci_tools.py`:
- Around line 148-205: Add unit coverage in the OCI completion tests for both
Cohere v2 tool paths: create a test for _format_tools using a Cohere v2 model
and assert it returns a tool via models.CohereToolV2, and add a helper response
fixture plus a test for _extract_tool_calls through OCICompletion.call that
verifies the normalized function name and preserves the tool-call ID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a33013ea-cb76-49eb-a68f-8e3272986042

📥 Commits

Reviewing files that changed from the base of the PR and between 8abc166 and f68fc6e.

📒 Files selected for processing (14)
  • lib/crewai/src/crewai/llms/providers/oci/__init__.py
  • lib/crewai/src/crewai/llms/providers/oci/completion.py
  • lib/crewai/src/crewai/llms/providers/oci/vision.py
  • lib/crewai/tests/llms/oci/conftest.py
  • lib/crewai/tests/llms/oci/test_oci_cohere_v2.py
  • lib/crewai/tests/llms/oci/test_oci_integration_basic.py
  • lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_integration_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_integration_structured.py
  • lib/crewai/tests/llms/oci/test_oci_integration_tools.py
  • lib/crewai/tests/llms/oci/test_oci_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_structured.py
  • lib/crewai/tests/llms/oci/test_oci_tools.py
✅ Files skipped from review due to trivial changes (1)
  • lib/crewai/src/crewai/llms/providers/oci/init.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • lib/crewai/tests/llms/oci/test_oci_integration_basic.py
  • lib/crewai/tests/llms/oci/test_oci_integration_tools.py
  • lib/crewai/src/crewai/llms/providers/oci/vision.py
  • lib/crewai/tests/llms/oci/test_oci_integration_structured.py
  • lib/crewai/tests/llms/oci/test_oci_integration_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_cohere_v2.py
  • lib/crewai/tests/llms/oci/conftest.py
  • lib/crewai/tests/llms/oci/test_oci_multimodal.py

Comment on lines +1222 to +1227
return self._finalize_text_response(
content=content,
messages=messages,
from_task=from_task,
from_agent=from_agent,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pass normalized_messages instead of messages to _finalize_text_response.

_call_impl passes the original messages (which may be a string) to _finalize_text_response, while _stream_call_impl (line 1349) correctly passes normalized_messages. The _parse_structured_response call at line 1217 also correctly uses normalized_messages, confirming this is an oversight. If _call_impl is ever called directly with a string, _finalize_text_response and its downstream hooks/events would receive a string instead of the expected list[LLMMessage].

Proposed fix
         return self._finalize_text_response(
             content=content,
-            messages=messages,
+            messages=normalized_messages,
             from_task=from_task,
             from_agent=from_agent,
         )
📝 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
return self._finalize_text_response(
content=content,
messages=messages,
from_task=from_task,
from_agent=from_agent,
)
return self._finalize_text_response(
content=content,
messages=normalized_messages,
from_task=from_task,
from_agent=from_agent,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/llms/providers/oci/completion.py` around lines 1222 -
1227, In _call_impl, pass normalized_messages rather than messages to
_finalize_text_response, matching _stream_call_impl and the preceding
_parse_structured_response call so downstream hooks receive list[LLMMessage]
consistently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant