Skip to content

feat: add OCI Generative AI embeddings provider#4966

Open
fede-kamel wants to merge 13 commits into
crewAIInc:mainfrom
fede-kamel:feat/oci-embeddings
Open

feat: add OCI Generative AI embeddings provider#4966
fede-kamel wants to merge 13 commits into
crewAIInc:mainfrom
fede-kamel:feat/oci-embeddings

Conversation

@fede-kamel

Copy link
Copy Markdown

Summary

  • OCIEmbeddingFunction: ChromaDB-compatible embedding callable with batching, config serialization, and image embedding support
  • OCIProvider: Pydantic-based provider with AliasChoices for env var and config key validation
  • Factory registration in embeddings/factory.py + types.py (provider name: "oci")
  • Supports text and image embeddings, configurable output dimensions, custom endpoints, all 4 OCI auth modes
  • Shared auth via utilities/oci.py (from PR 1)

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

Diff breakdown (vs multimodal PR)

Change Lines
embedding_callable.py (new) +181
oci_provider.py (new) +75
types.py (new) +30
__init__.py (new) +17
factory.py (registration + overload) +9
types.py (ProviderSpec + Literal) +3
test_factory_oci.py (5 unit tests) +207
test_oci_embedding_integration.py (2 live tests) +66
Total +588

Test plan

  • 5 unit tests: factory wiring, batching, output dimensions, config serialization, image embeddings
  • 2 live integration tests against cohere.embed-english-v3.0 (API_KEY_AUTH): single text + batch
  • All 42 prior LLM unit tests still pass

@fede-kamel fede-kamel force-pushed the feat/oci-embeddings branch from 3f53bcc to d3e2666 Compare March 29, 2026 16:20
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

@fede-kamel fede-kamel force-pushed the feat/oci-embeddings branch from d3e2666 to 9527ffb Compare April 24, 2026 17:11
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request Apr 24, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

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

Temporary measure until OCI SDK ships native async support.

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

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

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

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

Temporary measure until OCI SDK ships native async support.

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

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

Copy link
Copy Markdown
Author

@greysonlalonde @lorenzejay — bumping for review.

Status (after rebase):

This PR adds: Adds an OCIEmbedding provider hitting /20231130/actions/embedText. Routes cohere.embed-english-v3.0, embed-multilingual-v3.0, embed-v4.0 (multimodal text+image), plus the *-image-v3.0 variants. Returns standard crewAI EmbeddingResponse shape.

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

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

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

Tracking issue: crewAIInc#4944
Part 1 of series: crewAIInc#4959crewAIInc#4961crewAIInc#4962crewAIInc#4963crewAIInc#4964crewAIInc#4966crewAIInc#4982
@fede-kamel fede-kamel force-pushed the feat/oci-embeddings branch from f02efa1 to f5b9555 Compare May 6, 2026 18:45
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-embeddings branch from f5b9555 to cfa8d8a Compare May 18, 2026 01:26
fede-kamel added a commit to fede-kamel/crewAI that referenced this pull request May 18, 2026
Replace asyncio.to_thread wrappers with true async I/O using aiohttp
for acall() and astream(). The OCI SDK is sync-only, so we bypass it
for HTTP and use its signer for request authentication directly.

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

Temporary measure until OCI SDK ships native async support.

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

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

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fe21824-e6c4-4e62-8f3a-4ff3adf2af34

📥 Commits

Reviewing files that changed from the base of the PR and between 499bcd0 and 8267577.

📒 Files selected for processing (22)
  • lib/crewai/src/crewai/llms/providers/oci/__init__.py
  • lib/crewai/src/crewai/llms/providers/oci/completion.py
  • lib/crewai/src/crewai/llms/providers/oci/vision.py
  • lib/crewai/src/crewai/rag/embeddings/factory.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/__init__.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/embedding_callable.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/oci_provider.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/types.py
  • lib/crewai/src/crewai/rag/embeddings/types.py
  • lib/crewai/tests/llms/oci/conftest.py
  • lib/crewai/tests/llms/oci/test_oci_cohere_v2.py
  • lib/crewai/tests/llms/oci/test_oci_integration_basic.py
  • lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_integration_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_integration_structured.py
  • lib/crewai/tests/llms/oci/test_oci_integration_tools.py
  • lib/crewai/tests/llms/oci/test_oci_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_structured.py
  • lib/crewai/tests/llms/oci/test_oci_tools.py
  • lib/crewai/tests/rag/embeddings/test_factory_oci.py
  • lib/crewai/tests/rag/embeddings/test_oci_embedding_integration.py
✅ Files skipped from review due to trivial changes (4)
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/init.py
  • lib/crewai/src/crewai/llms/providers/oci/init.py
  • lib/crewai/src/crewai/rag/embeddings/factory.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/types.py
🚧 Files skipped from review as they are similar to previous changes (14)
  • lib/crewai/tests/rag/embeddings/test_oci_embedding_integration.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_basic.py
  • lib/crewai/src/crewai/rag/embeddings/types.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/oci_provider.py
  • lib/crewai/src/crewai/llms/providers/oci/vision.py
  • lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_integration_tools.py
  • lib/crewai/tests/rag/embeddings/test_factory_oci.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/embedding_callable.py
  • lib/crewai/tests/llms/oci/test_oci_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_cohere_v2.py
  • lib/crewai/tests/llms/oci/conftest.py

📝 Walkthrough

Walkthrough

Changes

OCI support adds optional SDK installation, native LLM routing, chat completion, Cohere variants, multimodal requests, streaming, structured output, tool calling, and OCI text/image embeddings. Mocked and live tests cover the new LLM and embedding flows.

OCI Provider

Layer / File(s) Summary
Routing, dependency, and authentication wiring
lib/crewai/pyproject.toml, lib/crewai/src/crewai/llm.py, lib/crewai/src/crewai/utilities/oci.py
Registers OCI as a native provider, adds the optional SDK dependency, and supports OCI authentication configuration.
OCI chat completion implementation
lib/crewai/src/crewai/llms/providers/oci/*
Adds OCICompletion with provider-specific requests, Cohere v1/v2 handling, multimodal content, tool calls, structured responses, streaming, async execution, and capability reporting.
OCI embedding provider and contracts
lib/crewai/src/crewai/rag/embeddings/*
Adds OCI provider types, factory registration, configurable client construction, batched text embeddings, image embeddings, and serializable configuration.
OCI completion validation
lib/crewai/tests/llms/oci/*
Adds mocked and live tests for routing, request construction, Cohere formats, multimodal input, streaming, structured output, tool calls, and async calls.
OCI embedding validation
lib/crewai/tests/rag/embeddings/*oci*
Adds mocked and live tests for provider selection, batching, request options, configuration round trips, image embeddings, and returned vectors.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant LLM
  participant OCICompletion
  participant OCIInferenceClient
  Client->>LLM: create model with oci prefix
  LLM->>OCICompletion: route model to native provider
  OCICompletion->>OCIInferenceClient: send chat request
  OCIInferenceClient-->>OCICompletion: return response or SSE events
  OCICompletion-->>Client: return text, structured output, or tool calls
Loading

Suggested reviewers: lucasgomide, alex-clawd, greysonlalonde, lorenzejay

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.02% 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 an OCI Generative AI embeddings provider.
Description check ✅ Passed The description is directly aligned with the changeset and accurately summarizes the OCI embeddings provider work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

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

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

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

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

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

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

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

Temporary measure until OCI SDK ships native async support.

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

Depends on: crewAIInc#4966, crewAIInc#4964, crewAIInc#4963, crewAIInc#4962, crewAIInc#4961, crewAIInc#4959
Tracking issue: crewAIInc#4944
@fede-kamel fede-kamel force-pushed the feat/oci-embeddings branch from cfa8d8a to 499bcd0 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 8 commits July 10, 2026 14:22
Drop unreachable os.getenv defaults in the live-config fixture and fix
the pytest path in the run instructions to include the lib/crewai/
prefix.
Add streaming text completion via OCI SSE events:
- stream=True in call() routes to _stream_call_impl with chunk events
- iter_stream() yields raw text chunks (sync generator)
- astream() wraps iter_stream via thread+queue for async callers
- _stream_chat_events holds client lock for full stream duration
- SSE event parsing handles both string and mapping payloads

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

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

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

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

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

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

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

Verified live against OCI on-demand in us-chicago-1: plain chat,
streaming, and vision all pass; tool use is wired but the service
currently rejects tools for command-a-vision.
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 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-embeddings branch from 499bcd0 to 8267577 Compare July 10, 2026 18:25
@fede-kamel

Copy link
Copy Markdown
Author

Rebased onto current main and un-drafted — mergeable again. Embeddings verified live against OCI on-demand (us-chicago-1): cohere.embed-english-v3.0 (dim 1024) and the current-gen cohere.embed-v4.0 (dim 1536) both pass through the RAG factory path. Unit suite: 58 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: 6

🧹 Nitpick comments (3)
lib/crewai/src/crewai/rag/embeddings/providers/oci/embedding_callable.py (1)

171-181: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider batching image embedding requests.

embed_image_batch sends one image per API call. If the OCI embed_text endpoint accepts multiple inputs with input_type="IMAGE", batching images into a single request (respecting batch_size) would significantly reduce latency for large image sets.

🤖 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/rag/embeddings/providers/oci/embedding_callable.py`
around lines 171 - 181, Update embed_image_batch to group images according to
the configured batch_size, convert each group to data URIs, and call
_require_client().embed_text once per group with all data URIs and
input_type="IMAGE"; accumulate each response’s embeddings in order and preserve
the existing return behavior.
lib/crewai/src/crewai/rag/embeddings/providers/oci/types.py (1)

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

Annotated[str, "cohere.embed-english-v3.0"] metadata may be misleading.

The second argument to Annotated is metadata, not a default value. Readers may mistake it for a default. Consider adding a comment or moving the default documentation to the docstring, since the actual default lives in OCIProvider and OCIEmbeddingFunction.

📝 Suggested clarification
-    model_name: Annotated[str, "cohere.embed-english-v3.0"]
+    model_name: str  # default: "cohere.embed-english-v3.0" (see OCIProvider)
🤖 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/rag/embeddings/providers/oci/types.py` at line 11,
Clarify the `model_name` declaration in the OCI types definition so the
`Annotated` metadata is not mistaken for a default value. Move the default-model
documentation into the relevant docstring or add an explicit comment, and
reference `OCIProvider` and `OCIEmbeddingFunction` as the locations where the
actual default is defined.
lib/crewai/tests/llms/oci/conftest.py (1)

182-187: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: Avoid redundant os.getenv calls in _skip_unless_live_config.

Each auth env var is fetched twice — once in the if guard and once in the assignment with an unreachable default. Extracting the value once is cleaner:

♻️ Proposed refactor
-    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")
-    if os.getenv("OCI_AUTH_FILE_LOCATION"):
-        config["auth_file_location"] = os.getenv("OCI_AUTH_FILE_LOCATION", "~/.oci/config")
+    auth_type = os.getenv("OCI_AUTH_TYPE")
+    if auth_type:
+        config["auth_type"] = auth_type
+    auth_profile = os.getenv("OCI_AUTH_PROFILE")
+    if auth_profile:
+        config["auth_profile"] = auth_profile
+    auth_file_location = os.getenv("OCI_AUTH_FILE_LOCATION")
+    if auth_file_location:
+        config["auth_file_location"] = auth_file_location
🤖 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/conftest.py` around lines 182 - 187, In
_skip_unless_live_config, avoid fetching each OCI authentication environment
variable twice. Store the result of each os.getenv call once, then use that
value for the conditional check and corresponding config assignment, removing
the unreachable fallback defaults.
🤖 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 1401-1433: Update the async stream facade containing `_producer`
to support cancellation-aware cleanup: use a bounded queue and have the producer
perform backpressured, cancellation-safe queue writes while checking a stop
signal. Wrap consumption in `try/finally` so early iterator closure or task
cancellation signals the producer to stop, unblocks any pending queue operation,
and joins the thread without leaking resources; preserve propagation of producer
exceptions after normal completion.
- Around line 1371-1375: Keep _client_lock held through the entire
response.data.events() consumption in the streaming path, rather than releasing
it immediately after _chat() returns. Refactor the relevant method and inherited
astream()/iter_stream() flow so event iteration occurs within the same lock
context, matching _stream_chat_events() and preventing concurrent use of the
non-reentrant client.
- Around line 1276-1300: The per-event enumerate index in the tool-call
aggregation loop can misassociate continuation chunks. Update
_extract_tool_calls_from_stream_event() to provide each call’s stable OCI call
ID or index, then use that key for tool_calls_by_index.setdefault and all
related state updates instead of the local enumerate index, while preserving
existing ID, function, argument accumulation, and event emission behavior.

In `@lib/crewai/src/crewai/utilities/oci.py`:
- Around line 46-47: Update the SECURITY_TOKEN configuration handling in the OCI
configuration function to validate that both “key_file” and
“security_token_file” are present before accessing them. Raise a descriptive
configuration error identifying the missing required key and explaining that
both entries are needed for security-token authentication, instead of allowing a
bare KeyError.

In `@lib/crewai/tests/llms/oci/test_oci_integration_tools.py`:
- Around line 87-100: Update test_oci_live_tool_call_with_execution to track
whether add_numbers is invoked, such as by recording calls or setting an
invocation flag inside the helper, and assert that the recorded invocation
occurred in addition to validating the result. Keep the existing
available_functions wiring and result assertions.

In `@lib/crewai/tests/rag/embeddings/test_factory_oci.py`:
- Around line 111-115: Remove the `.tolist()` conversion from the result
processing in the test, since the mocked embeddings are already plain Python
lists. Compare `result` directly with `expected_rows` using the existing length
and `pytest.approx` assertions, preserving the behavioral verification without
assuming a NumPy type.

---

Nitpick comments:
In `@lib/crewai/src/crewai/rag/embeddings/providers/oci/embedding_callable.py`:
- Around line 171-181: Update embed_image_batch to group images according to the
configured batch_size, convert each group to data URIs, and call
_require_client().embed_text once per group with all data URIs and
input_type="IMAGE"; accumulate each response’s embeddings in order and preserve
the existing return behavior.

In `@lib/crewai/src/crewai/rag/embeddings/providers/oci/types.py`:
- Line 11: Clarify the `model_name` declaration in the OCI types definition so
the `Annotated` metadata is not mistaken for a default value. Move the
default-model documentation into the relevant docstring or add an explicit
comment, and reference `OCIProvider` and `OCIEmbeddingFunction` as the locations
where the actual default is defined.

In `@lib/crewai/tests/llms/oci/conftest.py`:
- Around line 182-187: In _skip_unless_live_config, avoid fetching each OCI
authentication environment variable twice. Store the result of each os.getenv
call once, then use that value for the conditional check and corresponding
config assignment, removing the unreachable fallback defaults.
🪄 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: b682955a-f6a2-4424-867b-6a2d80d77a6c

📥 Commits

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

📒 Files selected for processing (27)
  • lib/crewai/pyproject.toml
  • lib/crewai/src/crewai/llm.py
  • lib/crewai/src/crewai/llms/providers/oci/__init__.py
  • lib/crewai/src/crewai/llms/providers/oci/completion.py
  • lib/crewai/src/crewai/llms/providers/oci/vision.py
  • lib/crewai/src/crewai/rag/embeddings/factory.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/__init__.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/embedding_callable.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/oci_provider.py
  • lib/crewai/src/crewai/rag/embeddings/providers/oci/types.py
  • lib/crewai/src/crewai/rag/embeddings/types.py
  • lib/crewai/src/crewai/utilities/oci.py
  • lib/crewai/tests/llms/oci/__init__.py
  • lib/crewai/tests/llms/oci/conftest.py
  • lib/crewai/tests/llms/oci/test_oci.py
  • lib/crewai/tests/llms/oci/test_oci_cohere_v2.py
  • lib/crewai/tests/llms/oci/test_oci_integration_basic.py
  • lib/crewai/tests/llms/oci/test_oci_integration_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_integration_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_integration_structured.py
  • lib/crewai/tests/llms/oci/test_oci_integration_tools.py
  • lib/crewai/tests/llms/oci/test_oci_multimodal.py
  • lib/crewai/tests/llms/oci/test_oci_streaming.py
  • lib/crewai/tests/llms/oci/test_oci_structured.py
  • lib/crewai/tests/llms/oci/test_oci_tools.py
  • lib/crewai/tests/rag/embeddings/test_factory_oci.py
  • lib/crewai/tests/rag/embeddings/test_oci_embedding_integration.py

Comment on lines +1276 to +1300
stream_tool_calls = self._extract_tool_calls_from_stream_event(event_data)
for index, tc in enumerate(stream_tool_calls):
state = tool_calls_by_index.setdefault(
index,
{"id": None, "type": "function", "function": {"name": None, "arguments": ""}},
)
if tc.get("id"):
state["id"] = tc["id"]
fn = tc.get("function", {})
if fn.get("name"):
state["function"]["name"] = fn["name"]
chunk_args = fn.get("arguments")
if chunk_args:
state["function"]["arguments"] += str(chunk_args)
self._emit_stream_chunk_event(
chunk=str(chunk_args or ""),
tool_call={"id": state["id"], "type": "function", "function": {
"name": state["function"]["name"],
"arguments": str(chunk_args or ""),
}},
from_task=from_task,
from_agent=from_agent,
call_type=LLMCallType.TOOL_CALL,
response_id=response_id,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1230,1335p' lib/crewai/src/crewai/llms/providers/oci/completion.py

Repository: crewAIInc/crewAI

Length of output: 1955


🏁 Script executed:

sed -n '1180,1365p' lib/crewai/src/crewai/llms/providers/oci/completion.py && printf '\n---\n' && rg -n "_extract_tool_calls_from_stream_event|tool_calls_by_index|_emit_stream_chunk_event" lib/crewai/src/crewai/llms/providers/oci/completion.py

Repository: crewAIInc/crewAI

Length of output: 1955


🌐 Web query:

crewAIInc/crewAI lib/crewai/src/crewai/llms/providers/oci/completion.py _extract_tool_calls_from_stream_event tool_calls_by_index

💡 Result:

The method _extract_tool_calls_from_stream_event in the OCI LLM provider for CrewAI (located at lib/crewai/src/crewai/llms/providers/oci/completion.py) is part of the implementation for handling tool calling in streaming responses [1][2]. As part of the OCI Generative AI integration, the provider needs to aggregate fragmented tool call data received across multiple streaming chunks [2]. The variable tool_calls_by_index is used within this process to group and accumulate these partial tool call fragments (such as IDs, function names, and arguments) by their index [3][4]. This allows the provider to reconstruct complete tool calls from the stream before they are passed to the agent's tool-execution loop [3][2]. This functionality was introduced as part of a series of pull requests adding native Oracle Cloud Infrastructure support to CrewAI, specifically focusing on native function calling support for various model families [2][5]. The implementation follows standard patterns for streaming tool call reconstruction found in agentic frameworks, ensuring that incremental deltas are correctly merged before downstream processing [3][6][4].

Citations:


🌐 Web query:

OCI Generative AI streaming tool call delta index repeated per event function calling schema

💡 Result:

In Oracle Cloud Infrastructure (OCI) Generative AI streaming, tool call deltas are provided as progressive event fragments [1]. Unlike a static schema, the streaming output structure for function calling involves the model emitting initial chunks containing identification metadata (such as the id and name of the tool) followed by subsequent chunks containing only argument fragments [1]. Key aspects of handling this streaming index include: 1. Tracking Tool Call Consistency: Because OCI may not send the tool call id in every continuation chunk, clients must track the stable tool call index internally [1]. The index is typically anchored by the id when present. If a chunk arrives without an id, the system inherits the current index to ensure argument fragments are appended to the correct tool call object rather than incorrectly incrementing the index [1]. 2. Handling Delta Fragments: To correctly reconstruct the tool call, you should accumulate fragments [1][2]. A common implementation pattern involves using a dictionary or map that links the tool call index or ID to its partially constructed data [3]. 3. Streaming Schema Behavior: - Initial Chunk: Contains the tool call id, function name, and potentially the start of the arguments string. - Continuation Chunks: Contain only the incremental arguments delta [1]. - Finalization: Once the model completes, it emits a finish_reason (e.g., "tool_calls") [1]. If your implementation is seeing fragmented tool call indices (e.g., [1] instead of), it is likely due to the logic failing to recognize that a chunk without an ID belongs to the same tool call as the preceding chunk [1]. You must verify that your streaming processor preserves the index association when the unique ID is absent [1].

Citations:


Preserve a stable tool-call key here. enumerate() resets for each stream event, so continuation chunks without an id can be attached to the wrong accumulator when OCI emits multiple tool calls. Use the OCI call ID/index from _extract_tool_calls_from_stream_event() instead of the per-event loop index.

🤖 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 1276 -
1300, The per-event enumerate index in the tool-call aggregation loop can
misassociate continuation chunks. Update _extract_tool_calls_from_stream_event()
to provide each call’s stable OCI call ID or index, then use that key for
tool_calls_by_index.setdefault and all related state updates instead of the
local enumerate index, while preserving existing ID, function, argument
accumulation, and event emission behavior.

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

for event in response.data.events():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep the client lock while consuming iter_stream events.

Line 1371 releases _client_lock when _chat() returns, then Line 1375 iterates the stream unlocked. This bypasses _stream_chat_events() and races concurrent requests despite the client being documented as non-reentrant. astream() inherits the same issue through iter_stream().

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):
📝 Committable suggestion

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

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

In `@lib/crewai/src/crewai/llms/providers/oci/completion.py` around lines 1371 -
1375, Keep _client_lock held through the entire response.data.events()
consumption in the streaming path, rather than releasing it immediately after
_chat() returns. Refactor the relevant method and inherited
astream()/iter_stream() flow so event iteration occurs within the same lock
context, matching _stream_chat_events() and preventing concurrent use of the
non-reentrant client.

Comment on lines +1401 to +1433
"""Expose the sync OCI SSE stream through an async generator facade."""
loop = asyncio.get_running_loop()
queue: asyncio.Queue[str | None] = asyncio.Queue()
error_holder: list[BaseException] = []

def _producer() -> None:
try:
for chunk in self.iter_stream(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
):
loop.call_soon_threadsafe(queue.put_nowait, chunk)
except BaseException as error:
error_holder.append(error)
finally:
loop.call_soon_threadsafe(queue.put_nowait, None)

thread = threading.Thread(target=_producer, daemon=True)
thread.start()

while True:
chunk = await queue.get()
if chunk is None:
break
yield chunk

thread.join()
if error_holder:
raise error_holder[0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Cancel the producer when the async iterator is closed.

If an async for consumer exits early or is cancelled, the daemon thread continues reading OCI events and appending to the unbounded queue. Long-lived streams can leak a thread and unbounded memory. Add cancellation-aware cleanup plus bounded, backpressured queue writes.

🤖 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 1401 -
1433, Update the async stream facade containing `_producer` to support
cancellation-aware cleanup: use a bounded queue and have the producer perform
backpressured, cancellation-safe queue writes while checking a stop signal. Wrap
consumption in `try/finally` so early iterator closure or task cancellation
signals the producer to stop, unblocks any pending queue operation, and joins
the thread without leaking resources; preserve propagation of producer
exceptions after normal completion.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add validation for required SECURITY_TOKEN config keys.

Direct config["key_file"] / config["security_token_file"] access raises a bare KeyError if the OCI profile is missing these entries, giving the user no actionable guidance. A descriptive error would significantly improve the debugging experience for misconfigured security-token auth.

🛡️ Proposed fix
         config = oci.config.from_file(
             file_location=auth_file_location,
             profile_name=auth_profile,
         )
-        key_file = config["key_file"]
-        security_token_file = config["security_token_file"]
+        key_file = config.get("key_file")
+        security_token_file = config.get("security_token_file")
+        if not key_file or not security_token_file:
+            raise ValueError(
+                "SECURITY_TOKEN auth requires 'key_file' and 'security_token_file' "
+                f"in OCI config profile '{auth_profile}'"
+            )
         private_key = oci.signer.load_private_key_from_file(key_file, None)
📝 Committable suggestion

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

Suggested change
key_file = config["key_file"]
security_token_file = config["security_token_file"]
key_file = config.get("key_file")
security_token_file = config.get("security_token_file")
if not key_file or not security_token_file:
raise ValueError(
"SECURITY_TOKEN auth requires 'key_file' and 'security_token_file' "
f"in OCI config profile '{auth_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
SECURITY_TOKEN configuration handling in the OCI configuration function to
validate that both “key_file” and “security_token_file” are present before
accessing them. Raise a descriptive configuration error identifying the missing
required key and explaining that both entries are needed for security-token
authentication, instead of allowing a bare KeyError.

Comment on lines +87 to +100
def test_oci_live_tool_call_with_execution(oci_tool_model: str, oci_tool_config: dict):
"""With available_functions, tools should execute and model should respond."""
def add_numbers(a: float, b: float) -> str:
return str(float(a) + float(b))

llm = OCICompletion(model=oci_tool_model, **oci_tool_config)
result = llm.call(
messages=[{"role": "user", "content": "What is 3 + 7? Use the add_numbers tool."}],
tools=TOOL_SPEC,
available_functions={"add_numbers": add_numbers},
)

assert isinstance(result, str)
assert "10" in result

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that the supplied tool actually ran.

The model can return "10" without calling add_numbers, so this test can pass if tool execution regresses. Record the invocation and assert it occurred.

Proposed fix
 def test_oci_live_tool_call_with_execution(oci_tool_model: str, oci_tool_config: dict):
     """With available_functions, tools should execute and model should respond."""
+    calls = []
+
     def add_numbers(a: float, b: float) -> str:
+        calls.append((a, b))
         return str(float(a) + float(b))
...
     assert isinstance(result, str)
+    assert calls == [(3, 7)]
     assert "10" in result

As per coding guidelines, “Write unit tests for new functionality that focus on behavior rather than implementation details.”

📝 Committable suggestion

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

Suggested change
def test_oci_live_tool_call_with_execution(oci_tool_model: str, oci_tool_config: dict):
"""With available_functions, tools should execute and model should respond."""
def add_numbers(a: float, b: float) -> str:
return str(float(a) + float(b))
llm = OCICompletion(model=oci_tool_model, **oci_tool_config)
result = llm.call(
messages=[{"role": "user", "content": "What is 3 + 7? Use the add_numbers tool."}],
tools=TOOL_SPEC,
available_functions={"add_numbers": add_numbers},
)
assert isinstance(result, str)
assert "10" in result
def test_oci_live_tool_call_with_execution(oci_tool_model: str, oci_tool_config: dict):
"""With available_functions, tools should execute and model should respond."""
calls = []
def add_numbers(a: float, b: float) -> str:
calls.append((a, b))
return str(float(a) + float(b))
llm = OCICompletion(model=oci_tool_model, **oci_tool_config)
result = llm.call(
messages=[{"role": "user", "content": "What is 3 + 7? Use the add_numbers tool."}],
tools=TOOL_SPEC,
available_functions={"add_numbers": add_numbers},
)
assert isinstance(result, str)
assert calls == [(3, 7)]
assert "10" in result
🤖 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 87 -
100, Update test_oci_live_tool_call_with_execution to track whether add_numbers
is invoked, such as by recording calls or setting an invocation flag inside the
helper, and assert that the recorded invocation occurred in addition to
validating the result. Keep the existing available_functions wiring and result
assertions.

Source: Coding guidelines

Comment on lines +111 to +115
result_rows = [embedding.tolist() for embedding in result]
expected_rows = [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]
assert len(result_rows) == len(expected_rows)
for actual, expected in zip(result_rows, expected_rows, strict=True):
assert actual == pytest.approx(expected)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

.tolist() will raise AttributeError on plain Python lists.

The mock returns embeddings as plain lists ([[0.1, 0.2], [0.3, 0.4], ...]), not numpy arrays. After __call__ extends the results, result is a list of lists. Calling .tolist() on a plain list raises AttributeError: 'list' object has no attribute 'tolist'.

As per coding guidelines, tests should focus on behavior rather than implementation details — the .tolist() call assumes a numpy type that isn't present.

🐛 Proposed fix
-    result_rows = [embedding.tolist() for embedding in result]
+    result_rows = [list(embedding) for embedding in result]
📝 Committable suggestion

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

Suggested change
result_rows = [embedding.tolist() for embedding in result]
expected_rows = [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]
assert len(result_rows) == len(expected_rows)
for actual, expected in zip(result_rows, expected_rows, strict=True):
assert actual == pytest.approx(expected)
result_rows = [list(embedding) for embedding in result]
expected_rows = [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]
assert len(result_rows) == len(expected_rows)
for actual, expected in zip(result_rows, expected_rows, strict=True):
assert actual == pytest.approx(expected)
🤖 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/rag/embeddings/test_factory_oci.py` around lines 111 - 115,
Remove the `.tolist()` conversion from the result processing in the test, since
the mocked embeddings are already plain Python lists. Compare `result` directly
with `expected_rows` using the existing length and `pytest.approx` assertions,
preserving the behavioral verification without assuming a NumPy type.

Source: Coding guidelines

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