feat: add multimodal support to OCI Generative AI provider#4964
feat: add multimodal support to OCI Generative AI provider#4964fede-kamel wants to merge 12 commits into
Conversation
3fab3ad to
45617b1
Compare
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
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
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
|
OCI GenAI PR series (tracking issue: #4944)
Depends on #4963. |
45617b1 to
52d905c
Compare
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
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
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
- 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#4959 → crewAIInc#4961 → crewAIInc#4962 → crewAIInc#4963 → crewAIInc#4964 → crewAIInc#4966 → crewAIInc#4982
52d905c to
50b91f0
Compare
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
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
|
@greysonlalonde @lorenzejay — bumping for review. Status (after rebase):
This PR adds: Maps crewAI multimodal parts ( v1 spec compliance: like #4959, this targets OCI's 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. |
- 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#4959 → crewAIInc#4961 → crewAIInc#4962 → crewAIInc#4963 → crewAIInc#4964 → crewAIInc#4966 → crewAIInc#4982
50b91f0 to
1c3ee48
Compare
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
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
- 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#4959 → crewAIInc#4961 → crewAIInc#4962 → crewAIInc#4963 → crewAIInc#4964 → crewAIInc#4966 → crewAIInc#4982
1c3ee48 to
2bb658f
Compare
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
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
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
📝 WalkthroughWalkthroughAdds 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. ChangesOCI provider integration
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
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
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#4959 → crewAIInc#4961 → crewAIInc#4962 → crewAIInc#4963 → crewAIInc#4964 → crewAIInc#4966 → crewAIInc#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.
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
2bb658f to
8abc166
Compare
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.
8abc166 to
f68fc6e
Compare
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
|
Rebased onto current main and un-drafted — mergeable again. This PR now also adds COHEREV2 api-format support: Verified live against OCI on-demand (us-chicago-1):
Unit suite: 53 passed. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
lib/crewai/tests/llms/oci/test_oci_streaming.py (1)
10-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove
import jsonto module top level.
import jsonis inside the function body of_make_fake_stream_eventrather 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 winReuse the shared OCI live-test helpers
_env_modelsand_skip_unless_liveare duplicated intest_oci_integration_tools.py, andconftest.pyalready has the same live-config flow. This copy has also started to drift (OCI_AUTH_FILE_LOCATIONsupport 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 valueAdd docstrings for public API functions.
load_image,encode_image, andis_vision_modelare exported in__all__but lack docstrings, whileto_data_urihas 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 valueStrengthen 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). Addingassert_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
📒 Files selected for processing (19)
lib/crewai/pyproject.tomllib/crewai/src/crewai/llm.pylib/crewai/src/crewai/llms/providers/oci/__init__.pylib/crewai/src/crewai/llms/providers/oci/completion.pylib/crewai/src/crewai/llms/providers/oci/vision.pylib/crewai/src/crewai/utilities/oci.pylib/crewai/tests/llms/oci/__init__.pylib/crewai/tests/llms/oci/conftest.pylib/crewai/tests/llms/oci/test_oci.pylib/crewai/tests/llms/oci/test_oci_cohere_v2.pylib/crewai/tests/llms/oci/test_oci_integration_basic.pylib/crewai/tests/llms/oci/test_oci_integration_multimodal.pylib/crewai/tests/llms/oci/test_oci_integration_streaming.pylib/crewai/tests/llms/oci/test_oci_integration_structured.pylib/crewai/tests/llms/oci/test_oci_integration_tools.pylib/crewai/tests/llms/oci/test_oci_multimodal.pylib/crewai/tests/llms/oci/test_oci_streaming.pylib/crewai/tests/llms/oci/test_oci_structured.pylib/crewai/tests/llms/oci/test_oci_tools.py
| oci = [ | ||
| "oci>=2.168.0", | ||
| ] |
There was a problem hiding this comment.
🔒 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:
- 1: https://pypi.org/project/oci/
- 2: https://pypistats.org/packages/oci
- 3: https://github.com/oracle/oci-python-sdk/releases/tag/v2.168.2
- 4: Cannot take security patch for Cryptography library due to upper bound on cryptography version oracle/oci-python-sdk#829
- 5: Cryptography security vulnerabilities CVE-2023-50782, CVE-2023-5678, CVE-2023-6129, CVE-2023-6237 oracle/oci-python-sdk#618
- 6: Upgrade cryptography Version to >=43.0.1 Due to High Severity Security Vulnerability oracle/oci-python-sdk#700
- 7: https://github.com/oracle/oci-python-sdk/security
- 8: https://docs.oracle.com/en-us/iaas/tools/python/latest/installation.html
- 9: https://github.com/oracle/oci-python-sdk/releases
🏁 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.
| response = self._chat(chat_details) | ||
| usage_data: dict[str, int] = {} | ||
| response_metadata: dict[str, Any] = {} | ||
|
|
||
| for event in response.data.events(): |
There was a problem hiding this comment.
🩺 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.
| 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
| key_file = config["key_file"] | ||
| security_token_file = config["security_token_file"] |
There was a problem hiding this comment.
🎯 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.
| 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.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
lib/crewai/src/crewai/llms/providers/oci/completion.py (1)
1371-1375: 🩺 Stability & Availability | 🟠 MajorKeep the client lock for the full
iter_streamlifetime.
_chat()releases_client_lockbeforeresponse.data.events()is consumed. A concurrent call can enterclient.chatwhile 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 winAdd tests for Cohere v2 tool formatting and extraction.
_format_toolshas acohere_v2branch (producingCohereToolV2) and_extract_tool_callshas acohere_v2branch (using_cohere_v2_function_field), but neither path is tested. These are important new code paths for theCOHEREV2support 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
📒 Files selected for processing (14)
lib/crewai/src/crewai/llms/providers/oci/__init__.pylib/crewai/src/crewai/llms/providers/oci/completion.pylib/crewai/src/crewai/llms/providers/oci/vision.pylib/crewai/tests/llms/oci/conftest.pylib/crewai/tests/llms/oci/test_oci_cohere_v2.pylib/crewai/tests/llms/oci/test_oci_integration_basic.pylib/crewai/tests/llms/oci/test_oci_integration_multimodal.pylib/crewai/tests/llms/oci/test_oci_integration_streaming.pylib/crewai/tests/llms/oci/test_oci_integration_structured.pylib/crewai/tests/llms/oci/test_oci_integration_tools.pylib/crewai/tests/llms/oci/test_oci_multimodal.pylib/crewai/tests/llms/oci/test_oci_streaming.pylib/crewai/tests/llms/oci/test_oci_structured.pylib/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
| return self._finalize_text_response( | ||
| content=content, | ||
| messages=messages, | ||
| from_task=from_task, | ||
| from_agent=from_agent, | ||
| ) |
There was a problem hiding this comment.
🎯 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.
| 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.
Summary
vision.py: vision model lists,to_data_uri,load_image,encode_image,is_vision_modelhelpers_build_generic_contenthandlesimage_url,document_url,video_url,audio_urlcontent types → OCI SDKImageContent,DocumentContent,VideoContent,AudioContent_message_has_multimodal_contentdetects non-text payloads for Cohere rejectiontext-only)supports_multimodal()→TrueDepends on #4963, #4962, #4961, #4959. Draft until all merge.
Tracking issue: #4944
Diff breakdown (vs structured output PR)
vision.py(new file)completion.py(multimodal content + checks)__init__.py(vision exports)test_oci_multimodal.py(10 unit tests)test_oci_integration_multimodal.py(1 live test)Test plan
google.gemini-2.5-flashvia data URI — correctly identified color