Skip to content

Commit 152dbb1

Browse files
authored
feat: Add Oracle Cloud Infrastructure (OCI) Generative AI client support (#718)
* 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.
1 parent 160dd05 commit 152dbb1

8 files changed

Lines changed: 2534 additions & 0 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!

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ 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"]
5155

5256
[tool.poetry.group.dev.dependencies]
5357
mypy = "==1.13.0"

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
28+
return oci
29+
except ImportError:
30+
raise ImportError(OCI_INSTALLATION_MESSAGE)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import typing
2+
3+
from httpx import SyncByteStream
4+
5+
6+
class Streamer(SyncByteStream):
7+
"""Wrap an iterator of bytes for httpx streaming responses."""
8+
9+
lines: typing.Iterator[bytes]
10+
11+
def __init__(self, lines: typing.Iterator[bytes]):
12+
self.lines = lines
13+
14+
def __iter__(self) -> typing.Iterator[bytes]:
15+
return self.lines

0 commit comments

Comments
 (0)