Skip to content

Commit df648e9

Browse files
committed
feat(v2): add GenAI, Cohere, and Mistral providers
- Add instructor/v2/providers/genai/ with handlers for TOOLS, JSON modes - Add instructor/v2/providers/cohere/ with handlers for TOOLS, JSON_SCHEMA, MD_JSON modes - Add instructor/v2/providers/mistral/ with handlers for TOOLS, JSON_SCHEMA, MD_JSON modes - Update instructor/v2/__init__.py with from_genai, from_cohere, from_mistral exports - Add tests/v2/test_genai_integration.py - Add tests/v2/test_cohere_handlers.py - Add tests/v2/test_mistral_client.py and test_mistral_handlers.py This PR was written by [Cursor](https://cursor.com)
1 parent 26bcb47 commit df648e9

File tree

14 files changed

+3343
-1
lines changed

14 files changed

+3343
-1
lines changed

instructor/v2/__init__.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
55
Usage:
66
from instructor import Mode
7-
from instructor.v2 import from_anthropic, from_openai
7+
from instructor.v2 import from_anthropic, from_openai, from_genai
88
99
client = from_anthropic(anthropic_client, mode=Mode.TOOLS)
1010
client = from_openai(openai_client, mode=Mode.TOOLS)
11+
client = from_genai(genai_client, mode=Mode.TOOLS)
1112
"""
1213

1314
from instructor import Mode, Provider
@@ -33,6 +34,21 @@
3334
except ImportError:
3435
from_openai = None # type: ignore
3536

37+
try:
38+
from instructor.v2.providers.genai import from_genai
39+
except ImportError:
40+
from_genai = None # type: ignore
41+
42+
try:
43+
from instructor.v2.providers.cohere import from_cohere
44+
except ImportError:
45+
from_cohere = None # type: ignore
46+
47+
try:
48+
from instructor.v2.providers.mistral import from_mistral
49+
except ImportError:
50+
from_mistral = None # type: ignore
51+
3652
__all__ = [
3753
# Re-exports from instructor
3854
"Mode",
@@ -51,5 +67,8 @@
5167
"ResponseParser",
5268
# Providers
5369
"from_anthropic",
70+
"from_cohere",
71+
"from_genai",
72+
"from_mistral",
5473
"from_openai",
5574
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""v2 Cohere provider."""
2+
3+
try:
4+
from instructor.v2.providers.cohere.client import from_cohere
5+
except ImportError:
6+
from_cohere = None # type: ignore
7+
8+
__all__ = ["from_cohere"]
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
"""v2 Cohere client factory.
2+
3+
Creates Instructor instances using v2 hierarchical registry system.
4+
Supports both Cohere V1 and V2 client APIs.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import inspect
10+
import json
11+
import time
12+
from collections.abc import Awaitable
13+
from typing import Any, cast, overload
14+
15+
import cohere
16+
17+
from instructor import AsyncInstructor, Instructor, Mode, Provider
18+
from instructor.v2.core.patch import patch_v2
19+
20+
# Ensure handlers are registered (decorators auto-register on import)
21+
from instructor.v2.providers.cohere import handlers # noqa: F401
22+
23+
24+
@overload
25+
def from_cohere(
26+
client: cohere.Client,
27+
mode: Mode = Mode.TOOLS,
28+
**kwargs: Any,
29+
) -> Instructor: ...
30+
31+
32+
@overload
33+
def from_cohere(
34+
client: cohere.ClientV2,
35+
mode: Mode = Mode.TOOLS,
36+
**kwargs: Any,
37+
) -> Instructor: ...
38+
39+
40+
@overload
41+
def from_cohere(
42+
client: cohere.AsyncClient,
43+
mode: Mode = Mode.TOOLS,
44+
**kwargs: Any,
45+
) -> AsyncInstructor: ...
46+
47+
48+
@overload
49+
def from_cohere(
50+
client: cohere.AsyncClientV2,
51+
mode: Mode = Mode.TOOLS,
52+
**kwargs: Any,
53+
) -> AsyncInstructor: ...
54+
55+
56+
def from_cohere(
57+
client: cohere.Client | cohere.AsyncClient | cohere.ClientV2 | cohere.AsyncClientV2,
58+
mode: Mode = Mode.TOOLS,
59+
**kwargs: Any,
60+
) -> Instructor | AsyncInstructor:
61+
"""Create an Instructor instance from a Cohere client using v2 registry.
62+
63+
Args:
64+
client: A Cohere client instance (V1 or V2, sync or async)
65+
mode: The mode to use (defaults to Mode.TOOLS)
66+
**kwargs: Additional keyword arguments to pass to the Instructor constructor
67+
68+
Returns:
69+
An Instructor instance (sync or async depending on the client type)
70+
71+
Raises:
72+
ModeError: If mode is not registered for Cohere
73+
ClientError: If client is not a valid Cohere client instance
74+
75+
Examples:
76+
>>> import cohere
77+
>>> from instructor import Mode
78+
>>> from instructor.v2.providers.cohere import from_cohere
79+
>>>
80+
>>> # V2 client (recommended)
81+
>>> client = cohere.ClientV2()
82+
>>> instructor_client = from_cohere(client, mode=Mode.TOOLS)
83+
>>>
84+
>>> # V1 client
85+
>>> client = cohere.Client()
86+
>>> instructor_client = from_cohere(client, mode=Mode.JSON_SCHEMA)
87+
"""
88+
from instructor.v2.core.registry import mode_registry, normalize_mode
89+
90+
# Normalize provider-specific modes to generic modes
91+
# COHERE_TOOLS -> TOOLS, COHERE_JSON_SCHEMA -> JSON_SCHEMA
92+
normalized_mode = normalize_mode(Provider.COHERE, mode)
93+
94+
# Validate mode is registered
95+
if not mode_registry.is_registered(Provider.COHERE, normalized_mode):
96+
from instructor.core.exceptions import ModeError
97+
98+
available_modes = mode_registry.get_modes_for_provider(Provider.COHERE)
99+
raise ModeError(
100+
mode=mode.value,
101+
provider=Provider.COHERE.value,
102+
valid_modes=[m.value for m in available_modes],
103+
)
104+
105+
# Use normalized mode for patching
106+
mode = normalized_mode
107+
108+
# Validate client type
109+
valid_client_types = (
110+
cohere.Client,
111+
cohere.AsyncClient,
112+
cohere.ClientV2,
113+
cohere.AsyncClientV2,
114+
)
115+
116+
if not isinstance(client, valid_client_types):
117+
from instructor.core.exceptions import ClientError
118+
119+
raise ClientError(
120+
f"Client must be an instance of one of: {', '.join(t.__name__ for t in valid_client_types)}. "
121+
f"Got: {type(client).__name__}"
122+
)
123+
124+
# Detect client version for request formatting
125+
if isinstance(client, (cohere.ClientV2, cohere.AsyncClientV2)):
126+
client_version = "v2"
127+
else:
128+
client_version = "v1"
129+
130+
# region agent log
131+
with open("/Users/jasonliu/dev/instructor/.cursor/debug.log", "a") as _log:
132+
_log.write(
133+
json.dumps(
134+
{
135+
"sessionId": "debug-session",
136+
"runId": "streaming-pre",
137+
"hypothesisId": "H6",
138+
"location": "instructor/v2/providers/cohere/client.py:from_cohere",
139+
"message": "cohere_from_cohere_client_version",
140+
"data": {
141+
"client_type": type(client).__name__,
142+
"client_version": client_version,
143+
"mode": str(mode),
144+
},
145+
"timestamp": int(time.time() * 1000),
146+
}
147+
)
148+
+ "\n"
149+
)
150+
# endregion agent log
151+
kwargs["_cohere_client_version"] = client_version
152+
153+
# Determine if async client
154+
is_async = isinstance(client, (cohere.AsyncClient, cohere.AsyncClientV2))
155+
156+
if is_async:
157+
158+
async def async_wrapper(*args: Any, **call_kwargs: Any) -> Any:
159+
if call_kwargs.pop("stream", False):
160+
return client.chat_stream(*args, **call_kwargs)
161+
result = client.chat(*args, **call_kwargs)
162+
if inspect.isawaitable(result):
163+
return await cast(Awaitable[Any], result)
164+
return result
165+
166+
patched_create = patch_v2(
167+
func=async_wrapper,
168+
provider=Provider.COHERE,
169+
mode=mode,
170+
)
171+
172+
return AsyncInstructor(
173+
client=client,
174+
create=patched_create,
175+
provider=Provider.COHERE,
176+
mode=mode,
177+
**kwargs,
178+
)
179+
else:
180+
181+
def sync_wrapper(*args: Any, **call_kwargs: Any) -> Any:
182+
if call_kwargs.pop("stream", False):
183+
return client.chat_stream(*args, **call_kwargs)
184+
return client.chat(*args, **call_kwargs)
185+
186+
patched_create = patch_v2(
187+
func=sync_wrapper,
188+
provider=Provider.COHERE,
189+
mode=mode,
190+
)
191+
192+
return Instructor(
193+
client=client,
194+
create=patched_create,
195+
provider=Provider.COHERE,
196+
mode=mode,
197+
**kwargs,
198+
)

0 commit comments

Comments
 (0)