Skip to content

feat: add tool calling support to OCI Generative AI provider#4962

Open
fede-kamel wants to merge 8 commits into
crewAIInc:mainfrom
fede-kamel:feat/oci-tool-calling
Open

feat: add tool calling support to OCI Generative AI provider#4962
fede-kamel wants to merge 8 commits into
crewAIInc:mainfrom
fede-kamel:feat/oci-tool-calling

Conversation

@fede-kamel

Copy link
Copy Markdown

Summary

  • Native function calling for generic (Meta, Google, OpenAI, xAI) and Cohere model families
  • Tool formatting: CrewAI specs → OCI SDK FunctionDefinition / CohereTool
  • Tool call extraction: normalize OCI responses back to CrewAI shape (sync + streaming)
  • Recursive tool execution loop with max_sequential_tool_calls safety limit
  • Cohere-specific: trailing tool results handling, CohereToolCall/CohereToolResult mapping
  • Tool choice control (auto/none/required/function name)
  • Passthrough parameter filtering via SDK introspection (attribute_map/swagger_types)
  • supports_function_calling()True

Depends on #4961 (streaming) and #4959 (basic text). Draft until both merge.
Tracking issue: #4944

Diff breakdown (vs streaming PR)

Change Lines
completion.py (tool methods + message updates) +511/-16
test_oci_tools.py (9 unit tests) +291
test_oci_integration_tools.py (2 integration tests) +100
Total +902

Source is ~527 lines — slightly over 500 but tool calling is one cohesive feature that can't be meaningfully split further.

Test plan

  • 9 unit tests: tool formatting (generic + Cohere), tool call extraction, recursive execution, parallel tool rejection, max depth limit, passthrough filtering, supports_function_calling
  • 2 integration tests against meta.llama-3.3-70b-instruct: raw tool call return + recursive execution with add_numbers
  • All 19 prior unit tests (PR 1 + PR 2) still pass

@fede-kamel fede-kamel force-pushed the feat/oci-tool-calling branch from 9e9f7e4 to a594397 Compare March 29, 2026 16:14
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
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
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 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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Mar 29, 2026
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
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

Copy link
Copy Markdown
Author

Note: we are currently integrating with CrewAI. For now, our official integration is with LangChain: https://github.com/NVIDIA/NeMo-Agent-Toolkit

@fede-kamel fede-kamel force-pushed the feat/oci-tool-calling branch from a594397 to aff1773 Compare April 24, 2026 17:11
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
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
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-tool-calling branch from 02551db to 958c10a Compare May 18, 2026 01:26
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
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
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
@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fede-kamel, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ed62212-56f4-47f6-9e48-07296d3bf490

📥 Commits

Reviewing files that changed from the base of the PR and between afbe463 and c14ef1e.

📒 Files selected for processing (7)
  • lib/crewai/src/crewai/llms/providers/oci/completion.py
  • lib/crewai/tests/llms/oci/conftest.py
  • lib/crewai/tests/llms/oci/test_oci_integration_basic.py
  • lib/crewai/tests/llms/oci/test_oci_integration_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_integration_tools.py
  • lib/crewai/tests/llms/oci/test_oci_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_tools.py
📝 Walkthrough

Walkthrough

Adds OCI as a native CrewAI LLM provider with configurable authentication, generic and Cohere request handling, tool calling, streaming, lifecycle integration, optional SDK installation, and unit/live integration tests.

Changes

OCI provider integration

Layer / File(s) Summary
Provider routing and client setup
lib/crewai/pyproject.toml, lib/crewai/src/crewai/llm.py, lib/crewai/src/crewai/llms/providers/oci/..., lib/crewai/src/crewai/utilities/oci.py
Registers OCI model routing, exports OCICompletion, adds the optional OCI SDK dependency, and supports multiple OCI authentication modes and endpoints.
Message and request construction
lib/crewai/src/crewai/llms/providers/oci/completion.py
Converts CrewAI messages for generic and Cohere models, formats tools, filters request parameters, and applies model-specific token and sampling fields.
Response, streaming, and tool execution
lib/crewai/src/crewai/llms/providers/oci/completion.py
Parses standard and SSE responses, tracks metadata, supports synchronous and asynchronous streaming, executes tools recursively, and integrates lifecycle hooks and events.
Provider validation and integration coverage
lib/crewai/tests/llms/oci/...
Adds mocked SDK fixtures and tests for routing, initialization, request construction, responses, streaming, locking, tool calls, and live OCI operations.

Sequence Diagram(s)

sequenceDiagram
  participant CrewAI
  participant OCICompletion
  participant OCI SDK
  participant ToolFunction
  CrewAI->>OCICompletion: call messages and tools
  OCICompletion->>OCI SDK: submit OCI chat request
  OCI SDK-->>OCICompletion: response or stream events
  OCICompletion->>ToolFunction: execute returned tool call
  ToolFunction-->>OCICompletion: tool result
  OCICompletion->>OCI SDK: submit follow-up chat request
  OCI SDK-->>OCICompletion: final response
  OCICompletion-->>CrewAI: text, chunks, or normalized tool calls
Loading

Suggested reviewers: lorenzejay, greysonlalonde, alex-clawd

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.93% 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 tool calling support to the OCI Generative AI provider.
Description check ✅ Passed The description directly matches the changeset, covering OCI tool calling, recursive execution, tool formatting, 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.

fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
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
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
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
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
@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-tool-calling branch from 958c10a to afbe463 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 3 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
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-tool-calling branch from afbe463 to c14ef1e Compare July 10, 2026 18:25
@fede-kamel

Copy link
Copy Markdown
Author

Rebased onto current main and un-drafted — mergeable again. Tool calling verified live against OCI on-demand (us-chicago-1): full call → tool execution → final-answer loop on meta.llama-3.3-70b-instruct. Unit suite: 29 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: 2

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

50-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add StreamOptions to conftest's fake OCI module to eliminate repeated setup.

The StreamOptions mock is manually created in three streaming tests. Adding it to _make_fake_oci_module in conftest.py would remove this duplication and prevent tests from silently breaking if the mock shape changes.

♻️ Proposed refactor

In conftest.py, add StreamOptions to _make_fake_oci_module:

     # ChatDetails
     models.ChatDetails = MagicMock(side_effect=lambda **kw: MagicMock(**kw))

+    # Stream options
+    models.StreamOptions = MagicMock(side_effect=lambda **kw: MagicMock(**kw))
+
     # Auth helpers

Then remove the manual setup from all three streaming tests:

-    # StreamOptions mock
-    patch_oci_module.generative_ai_inference.models.StreamOptions = MagicMock(
-        side_effect=lambda **kw: MagicMock(**kw)
-    )

Also applies to: 84-86, 114-116

🤖 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 50 - 53, Move
the shared StreamOptions mock setup into the _make_fake_oci_module fixture
factory in conftest.py, defining it with the same keyword-preserving MagicMock
behavior. Remove the duplicated
patch_oci_module.generative_ai_inference.models.StreamOptions assignments from
all three streaming tests, including the referenced sections.
lib/crewai/tests/llms/oci/test_oci_integration_tools.py (1)

19-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate duplicated live-config utilities into conftest fixtures.

_env_models and _skip_unless_live duplicate logic from conftest.py (_env_models at line 152, _skip_unless_live_config at line 158). More importantly, _skip_unless_live is missing OCI_AUTH_FILE_LOCATION handling that exists in the conftest version, so live tool tests cannot run with custom auth file locations while basic and streaming tests can.

Consider adding oci_tool_model and oci_tool_config fixtures to conftest.py that reuse the existing _env_models and _skip_unless_live_config functions, then remove the duplicates here.

♻️ Proposed refactor

In conftest.py, add:

`@pytest.fixture`(
    params=_env_models("OCI_TEST_TOOL_MODELS", "OCI_TEST_TOOL_MODEL", "meta.llama-3.3-70b-instruct"),
    ids=lambda m: m,
)
def oci_tool_model(request: pytest.FixtureRequest) -> str:
    return request.param


`@pytest.fixture`()
def oci_tool_config() -> dict[str, str]:
    return _skip_unless_live_config()

Then in test_oci_integration_tools.py, remove _env_models, _skip_unless_live, oci_tool_model, and oci_tool_config, and use the conftest fixtures directly:

-from __future__ import annotations
-
-import os
-
-import pytest
-
-from crewai.llms.providers.oci.completion import OCICompletion
-
-
-def _env_models(env_var: str, fallback: str, default: str) -> list[str]:
-    raw = os.getenv(env_var) or os.getenv(fallback) or default
-    return [m.strip() for m in raw.split(",") if m.strip()]
-
-
-def _skip_unless_live():
-    compartment = os.getenv("OCI_COMPARTMENT_ID")
-    if not compartment:
-        pytest.skip("OCI_COMPARTMENT_ID not set")
-    region = os.getenv("OCI_REGION")
-    endpoint = os.getenv("OCI_SERVICE_ENDPOINT")
-    if not region and not endpoint:
-        pytest.skip("Set OCI_REGION or OCI_SERVICE_ENDPOINT")
-    config: dict[str, str] = {"compartment_id": compartment}
-    if endpoint:
-        config["service_endpoint"] = endpoint
-    if os.getenv("OCI_AUTH_TYPE"):
-        config["auth_type"] = os.getenv("OCI_AUTH_TYPE", "API_KEY")
-    if os.getenv("OCI_AUTH_PROFILE"):
-        config["auth_profile"] = os.getenv("OCI_AUTH_PROFILE", "DEFAULT")
-    return config
-
-
 TOOL_SPEC = [
-@pytest.fixture(
-    params=_env_models("OCI_TEST_TOOL_MODELS", "OCI_TEST_TOOL_MODEL", "meta.llama-3.3-70b-instruct"),
-    ids=lambda m: m,
-)
-def oci_tool_model(request):
-    return request.param
-
-
-@pytest.fixture()
-def oci_tool_config():
-    return _skip_unless_live()
-
-
 def test_oci_live_tool_call_returns_raw(oci_tool_model: str, oci_tool_config: dict):
🤖 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_tools.py` around lines 19 -
39, Consolidate the duplicated OCI live-test configuration helpers into shared
fixtures. In conftest.py, add the proposed oci_tool_model and oci_tool_config
fixtures using _env_models and _skip_unless_live_config, preserving support for
OCI_AUTH_FILE_LOCATION; then remove the local _env_models, _skip_unless_live,
oci_tool_model, and oci_tool_config definitions from
test_oci_integration_tools.py and update tests to consume the shared fixtures.
lib/crewai/src/crewai/llms/providers/oci/completion.py (1)

475-488: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Passthrough filtering depends on OCI SDK internal/undocumented attributes.

_allowed_passthrough_request_keys introspects attribute_map/swagger_types (generated Swagger client internals) or falls back to inspect.signature to decide which additional_params are safe to forward to GenericChatRequest/CohereChatRequest. These aren't part of the OCI SDK's documented public contract and could change across SDK releases (e.g., oci>=2.168.0 pin has no upper bound), silently changing which passthrough params are accepted or dropped.

Worth verifying this behavior holds across the supported oci version range, since a future SDK release altering these generated model internals would fail silently (params dropped) rather than raising a clear error.

🤖 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 475 -
488, Replace the undocumented introspection in _allowed_passthrough_request_keys
with an explicit, version-stable allowlist of supported GenericChatRequest and
CohereChatRequest fields, or validate against a documented OCI SDK API. Ensure
unsupported additional_params are rejected or reported clearly rather than
silently dropped, and add coverage for the supported OCI version range and both
request classes.
🤖 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 279-345: Update _build_cohere_chat_history so mid-history tool
messages reconstruct CohereToolResult.call using the corresponding
previous_tool_calls entry keyed by tool_call_id, preserving the original name
and parameters instead of hardcoding an empty parameter map. Reuse or centralize
the existing lookup logic used by the trailing tool-result branch, and remove
any resulting duplication.
- Around line 1042-1078: The iter_stream method currently consumes
response.data.events() outside the OCI client lock. Replace the direct _chat
call and event iteration with _stream_chat_events(chat_details), ensuring the
entire SSE stream is consumed through the helper under _client_lock while
preserving text, usage, metadata handling, and final tracking behavior.

---

Nitpick comments:
In `@lib/crewai/src/crewai/llms/providers/oci/completion.py`:
- Around line 475-488: Replace the undocumented introspection in
_allowed_passthrough_request_keys with an explicit, version-stable allowlist of
supported GenericChatRequest and CohereChatRequest fields, or validate against a
documented OCI SDK API. Ensure unsupported additional_params are rejected or
reported clearly rather than silently dropped, and add coverage for the
supported OCI version range and both request classes.

In `@lib/crewai/tests/llms/oci/test_oci_integration_tools.py`:
- Around line 19-39: Consolidate the duplicated OCI live-test configuration
helpers into shared fixtures. In conftest.py, add the proposed oci_tool_model
and oci_tool_config fixtures using _env_models and _skip_unless_live_config,
preserving support for OCI_AUTH_FILE_LOCATION; then remove the local
_env_models, _skip_unless_live, oci_tool_model, and oci_tool_config definitions
from test_oci_integration_tools.py and update tests to consume the shared
fixtures.

In `@lib/crewai/tests/llms/oci/test_oci_streaming.py`:
- Around line 50-53: Move the shared StreamOptions mock setup into the
_make_fake_oci_module fixture factory in conftest.py, defining it with the same
keyword-preserving MagicMock behavior. Remove the duplicated
patch_oci_module.generative_ai_inference.models.StreamOptions assignments from
all three streaming tests, including the referenced sections.
🪄 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: e26f3745-3bab-4d23-bcf4-2b7cbcd6769c

📥 Commits

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

📒 Files selected for processing (13)
  • 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/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_integration_basic.py
  • lib/crewai/tests/llms/oci/test_oci_integration_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_integration_tools.py
  • lib/crewai/tests/llms/oci/test_oci_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_tools.py

Comment thread lib/crewai/src/crewai/llms/providers/oci/completion.py
Comment on lines +1042 to +1078
def iter_stream(
self,
messages: str | list[LLMMessage],
tools: list[dict[str, BaseTool]] | None = None,
callbacks: list[Any] | None = None,
available_functions: dict[str, Any] | None = None,
from_task: Task | None = None,
from_agent: Agent | None = None,
) -> Any:
"""Yield raw text chunks from OCI without triggering tool recursion."""
normalized_messages = self._normalize_messages(messages)
chat_request = self._build_chat_request(normalized_messages, is_stream=True)
chat_details = self._oci.generative_ai_inference.models.ChatDetails(
compartment_id=self.compartment_id,
serving_mode=self._build_serving_mode(),
chat_request=chat_request,
)
response = self._chat(chat_details)
usage_data: dict[str, int] = {}
response_metadata: dict[str, Any] = {}

for event in response.data.events():
event_data = self._parse_stream_event(event)
if not event_data:
continue
text_chunk = self._extract_text_from_stream_event(event_data)
if text_chunk:
yield text_chunk
usage_chunk = self._extract_usage_from_stream_event(event_data)
if usage_chunk:
usage_data = usage_chunk
response_metadata.update(self._extract_metadata_from_stream_event(event_data))

if usage_data:
self._track_token_usage_internal(usage_data)
response_metadata["usage"] = usage_data
self.last_response_metadata = response_metadata or None

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the relevant file and inspect the surrounding methods.
FILE="lib/crewai/src/crewai/llms/providers/oci/completion.py"

echo "== outline =="
ast-grep outline "$FILE" --view expanded | sed -n '1,220p'

echo
echo "== iter_stream and _stream_chat_events locations =="
rg -n "def iter_stream|def _stream_chat_events|_client_lock|response\.data\.events\(" "$FILE"

echo
echo "== targeted excerpt =="
sed -n '1000,1265p' "$FILE"

Repository: crewAIInc/crewAI

Length of output: 12317


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/crewai/src/crewai/llms/providers/oci/completion.py"

echo "== iter_stream and _stream_chat_events locations =="
rg -n "def iter_stream|def _stream_chat_events|_client_lock|response\.data\.events\(" "$FILE"

echo
echo "== surrounding methods =="
sed -n '1030,1245p' "$FILE"

Repository: crewAIInc/crewAI

Length of output: 8795


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/crewai/src/crewai/llms/providers/oci/completion.py"

echo "== call sites of iter_stream / _stream_chat_events =="
rg -n "\biter_stream\(" "$FILE"
rg -n "\b_stream_chat_events\(" "$FILE"

echo
echo "== stream implementation excerpt =="
sed -n '920,1045p' "$FILE"

Repository: crewAIInc/crewAI

Length of output: 5308


Hold the OCI client lock for the full SSE stream iter_stream should use _stream_chat_events(chat_details) so response.data.events() is consumed under _client_lock, matching _stream_call_impl. astream delegates to iter_stream, so concurrent calls can otherwise hit the SDK’s non-reentrant client.

🤖 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 1042 -
1078, The iter_stream method currently consumes response.data.events() outside
the OCI client lock. Replace the direct _chat call and event iteration with
_stream_chat_events(chat_details), ensuring the entire SSE stream is consumed
through the helper under _client_lock while preserving text, usage, metadata
handling, and final tracking behavior.

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