Skip to content

Commit 15ac93d

Browse files
authored
feat(oci): Add Oracle Cloud Infrastructure (OCI) Generative AI client support (#754)
* feat: Add Oracle Cloud Infrastructure (OCI) Generative AI client support Adds OciClient (V1 API) and OciClientV2 (V2 API) for the OCI Generative AI service, following the BedrockClient pattern with httpx event hooks. Authentication: config file, custom profiles, session tokens, direct credentials, instance principal, resource principal. API coverage: embed (all models), chat with streaming (OciClient for Command R family, OciClientV2 for Command A). Lazy-loads oci SDK as an optional dependency; install with `pip install cohere[oci]`. * fix: address review feedback — remove stale model names and fix test profile - README: remove specific model names from Supported APIs and Model Availability sections (per mkozakov review — will go out of date) - tests: default OCI_PROFILE to DEFAULT instead of API_KEY_AUTH * fix: remove dead chat_stream endpoint and body-based stream detection The "stream" in endpoint check was dead code — both V1 and V2 SDK always route through endpoint "chat" (v1/chat and v2/chat paths). Streaming is reliably signalled via body["stream"], which the SDK always sets. - Drop "stream" in endpoint guard on is_stream and isStream detection - Remove "chat_stream" from action_map, transform, and response branches - Update unit tests to use "chat" endpoint (the only real one) * fix: don't trigger content-type transition on finish-only stream events _current_content_type now returns None for events with no message content (e.g. {"finishReason": "COMPLETE"}). The transition branch in _transform_v2_event is skipped when event_content_type is None, so a finish-only event after a thinking block no longer opens a spurious empty text block before emitting content-end. * test: add integration tests for light models, command-r-plus, multi-turn, and system message * fix: use 'or []' to guard against explicit content=None in V2 messages * Address cursor review: raise on unsupported endpoint, refresh session token per-request - transform_request_to_oci now raises ValueError for endpoints other than 'embed' and 'chat' instead of silently returning the untransformed body - Session token auth uses a refreshing wrapper that re-reads the token file before each signing call, so OCI CLI token refreshes are picked up without restarting the client - Add test_unsupported_endpoint_raises to cover the new explicit error - Update test_session_auth_prefers_security_token_signer to expect multi-call behaviour from the refreshing signer * Add test proving session token is re-read on subsequent requests test_session_token_refreshed_on_subsequent_requests writes a real token file, makes two requests with the file updated between them, and asserts that the second signing call uses the new token — verifying the refreshing signer works end-to-end. * fix(oci): resolve mypy type errors and add type-checking test gate The OCI client code introduced several mypy errors that went unnoticed because mypy was configured but never enforced in tests or CI. Type fixes: - lazy_oci_deps.py: suppress import-untyped for oci SDK (no type stubs) - oci_client.py: cast response.stream to Iterator[bytes] (httpx types it as SyncByteStream | AsyncByteStream but it's iterable at runtime) - oci_client.py: use .get("model", "") to satisfy str expectation - test_oci_client.py: suppress attr-defined on dynamic module stubs New test gate (tests/test_oci_mypy.py): - Runs mypy on OCI source and test files as part of pytest - Uses --follow-imports=silent to isolate from pre-existing AWS errors - Skips gracefully if mypy is not on PATH - Ensures future type regressions fail the test suite immediately * fix(oci): add response_type to embed response for SDK discriminated union The SDK's EmbedResponse is a discriminated union on response_type (embeddings_floats vs embeddings_by_type). The OCI embed response transformation was missing this field, causing pydantic to return None instead of an EmbedResponse object. This broke V1 embed when the SDK's merge_embed_responses tried to access .meta on None. V1 (flat float arrays) now returns response_type="embeddings_floats", V2 (typed dict) returns response_type="embeddings_by_type". * test(oci): assert response_type on embed responses (unit + integration) Adds unit tests for V1 (embeddings_floats) and V2 (embeddings_by_type) response_type presence, and adds assertions to the live integration embed tests. Ensures the discriminated union field can't be silently removed without test failure. * fix(oci): address Bugbot review — deduplicate Streamer and add V1 stream-start 1. Remove duplicate Streamer class (manually_maintained/streaming.py) and import from aws_client.py instead. Both were identical SyncByteStream wrappers. 2. Emit stream-start event with generation_id at the beginning of V1 streams, matching the standard Cohere V1 streaming chat format. Consumers relying on stream-start for state initialization will now receive it before text-generation events. Updated test_v1_stream_wrapper_preserves_finish_reason to verify stream-start is emitted first. * fix(oci): cover both import-untyped and import-not-found for oci SDK When oci is installed but lacks stubs, mypy raises import-untyped. When oci is not installed (optional dep), mypy raises import-not-found. Cover both cases since cohere[oci] is optional.
1 parent cdb9f69 commit 15ac93d

9 files changed

Lines changed: 3246 additions & 286 deletions

File tree

.fernignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ src/cohere/manually_maintained/__init__.py
1515
src/cohere/bedrock_client.py
1616
src/cohere/aws_client.py
1717
src/cohere/sagemaker_client.py
18+
src/cohere/oci_client.py
1819
src/cohere/client_v2.py
1920
mypy.ini
2021
src/cohere/aliases.py

README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,111 @@ for event in response:
5858
print(event.delta.message.content.text, end="")
5959
```
6060

61+
## Oracle Cloud Infrastructure (OCI)
62+
63+
The SDK supports Oracle Cloud Infrastructure (OCI) Generative AI service. First, install the OCI SDK:
64+
65+
```
66+
pip install 'cohere[oci]'
67+
```
68+
69+
Then use the `OciClient` or `OciClientV2`:
70+
71+
```Python
72+
import cohere
73+
74+
# Using OCI config file authentication (default: ~/.oci/config)
75+
co = cohere.OciClient(
76+
oci_region="us-chicago-1",
77+
oci_compartment_id="ocid1.compartment.oc1...",
78+
)
79+
80+
response = co.embed(
81+
model="embed-english-v3.0",
82+
texts=["Hello world"],
83+
input_type="search_document",
84+
)
85+
86+
print(response.embeddings)
87+
```
88+
89+
### OCI Authentication Methods
90+
91+
**1. Config File (Default)**
92+
```Python
93+
co = cohere.OciClient(
94+
oci_region="us-chicago-1",
95+
oci_compartment_id="ocid1.compartment.oc1...",
96+
# Uses ~/.oci/config with DEFAULT profile
97+
)
98+
```
99+
100+
**2. Custom Profile**
101+
```Python
102+
co = cohere.OciClient(
103+
oci_profile="MY_PROFILE",
104+
oci_region="us-chicago-1",
105+
oci_compartment_id="ocid1.compartment.oc1...",
106+
)
107+
```
108+
109+
**3. Session-based Authentication (Security Token)**
110+
```Python
111+
# Works with OCI CLI session tokens
112+
co = cohere.OciClient(
113+
oci_profile="MY_SESSION_PROFILE", # Profile with security_token_file
114+
oci_region="us-chicago-1",
115+
oci_compartment_id="ocid1.compartment.oc1...",
116+
)
117+
```
118+
119+
**4. Direct Credentials**
120+
```Python
121+
co = cohere.OciClient(
122+
oci_user_id="ocid1.user.oc1...",
123+
oci_fingerprint="xx:xx:xx:...",
124+
oci_tenancy_id="ocid1.tenancy.oc1...",
125+
oci_private_key_path="~/.oci/key.pem",
126+
oci_region="us-chicago-1",
127+
oci_compartment_id="ocid1.compartment.oc1...",
128+
)
129+
```
130+
131+
**5. Instance Principal (for OCI Compute instances)**
132+
```Python
133+
co = cohere.OciClient(
134+
auth_type="instance_principal",
135+
oci_region="us-chicago-1",
136+
oci_compartment_id="ocid1.compartment.oc1...",
137+
)
138+
```
139+
140+
### Supported OCI APIs
141+
142+
The OCI client supports the following Cohere APIs:
143+
- **Embed**: Full support for all embedding models
144+
- **Chat**: Full support with both V1 (`OciClient`) and V2 (`OciClientV2`) APIs
145+
- Streaming available via `chat_stream()`
146+
- Supports Command-R and Command-A model families
147+
148+
### OCI Model Availability and Limitations
149+
150+
**Available on OCI On-Demand Inference:**
151+
-**Embed models**: available on OCI Generative AI
152+
-**Chat models**: available via `OciClient` (V1) and `OciClientV2` (V2)
153+
154+
**Not Available on OCI On-Demand Inference:**
155+
-**Generate API**: OCI TEXT_GENERATION models are base models that require fine-tuning before deployment
156+
-**Rerank API**: OCI TEXT_RERANK models are base models that require fine-tuning before deployment
157+
-**Multiple Embedding Types**: OCI on-demand models only support single embedding type per request (cannot request both `float` and `int8` simultaneously)
158+
159+
**Note**: To use Generate or Rerank models on OCI, you need to:
160+
1. Fine-tune the base model using OCI's fine-tuning service
161+
2. Deploy the fine-tuned model to a dedicated endpoint
162+
3. Update your code to use the deployed model endpoint
163+
164+
For the latest model availability, see the [OCI Generative AI documentation](https://docs.oracle.com/en-us/iaas/Content/generative-ai/home.htm).
165+
61166
## Contributing
62167

63168
While we value open-source contributions to this SDK, the code is generated programmatically. Additions made directly would have to be moved over to our generation code, otherwise they would be overwritten upon the next generated release. Feel free to open a PR as a proof of concept, but know that we will not be able to merge it as-is. We suggest opening an issue first to discuss with us!

poetry.lock

Lines changed: 613 additions & 283 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ requests = "^2.0.0"
4848
tokenizers = ">=0.15,<1"
4949
types-requests = "^2.0.0"
5050
typing_extensions = ">= 4.0.0"
51+
oci = { version = "^2.165.0", optional = true }
52+
53+
[tool.poetry.extras]
54+
oci = ["oci"]
55+
aiohttp=["aiohttp", "httpx-aiohttp"]
5156

5257
[tool.poetry.group.dev.dependencies]
5358
mypy = "==1.13.0"
@@ -95,6 +100,3 @@ section-order = ["future", "standard-library", "third-party", "first-party"]
95100
[build-system]
96101
requires = ["poetry-core"]
97102
build-backend = "poetry.core.masonry.api"
98-
99-
[tool.poetry.extras]
100-
aiohttp=["aiohttp", "httpx-aiohttp"]

src/cohere/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,8 @@
523523
"NotFoundError": ".errors",
524524
"NotImplementedError": ".errors",
525525
"OAuthAuthorizeResponse": ".types",
526+
"OciClient": ".oci_client",
527+
"OciClientV2": ".oci_client",
526528
"ParseInfo": ".types",
527529
"RerankDocument": ".types",
528530
"RerankRequestDocumentsItem": ".types",
@@ -860,6 +862,8 @@ def __dir__():
860862
"NotFoundError",
861863
"NotImplementedError",
862864
"OAuthAuthorizeResponse",
865+
"OciClient",
866+
"OciClientV2",
863867
"ParseInfo",
864868
"RerankDocument",
865869
"RerankRequestDocumentsItem",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Lazy loading for optional OCI SDK dependency."""
2+
3+
from typing import Any
4+
5+
OCI_INSTALLATION_MESSAGE = """
6+
The OCI SDK is required to use OciClient or OciClientV2.
7+
8+
Install it with:
9+
pip install oci
10+
11+
Or with the optional dependency group:
12+
pip install cohere[oci]
13+
"""
14+
15+
16+
def lazy_oci() -> Any:
17+
"""
18+
Lazily import the OCI SDK.
19+
20+
Returns:
21+
The oci module
22+
23+
Raises:
24+
ImportError: If the OCI SDK is not installed
25+
"""
26+
try:
27+
import oci # type: ignore[import-untyped, import-not-found]
28+
return oci
29+
except ImportError:
30+
raise ImportError(OCI_INSTALLATION_MESSAGE)

0 commit comments

Comments
 (0)