instructor 1.3.2__tar.gz → 1.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {instructor-1.3.2 → instructor-1.3.3}/PKG-INFO +18 -27
- {instructor-1.3.2 → instructor-1.3.3}/README.md +12 -25
- {instructor-1.3.2 → instructor-1.3.3}/instructor/__init__.py +5 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/usage.py +5 -5
- {instructor-1.3.2 → instructor-1.3.3}/instructor/client.py +9 -18
- {instructor-1.3.2 → instructor-1.3.3}/instructor/client_anthropic.py +6 -11
- {instructor-1.3.2 → instructor-1.3.3}/instructor/client_cohere.py +2 -4
- instructor-1.3.3/instructor/client_gemini.py +61 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/client_groq.py +2 -4
- {instructor-1.3.2 → instructor-1.3.3}/instructor/client_mistral.py +2 -4
- instructor-1.3.3/instructor/client_vertexai.py +90 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/parallel.py +14 -7
- {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/partial.py +7 -4
- {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/simple_type.py +24 -4
- instructor-1.3.3/instructor/exceptions.py +34 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/function_calls.py +25 -4
- {instructor-1.3.2 → instructor-1.3.3}/instructor/mode.py +2 -1
- {instructor-1.3.2 → instructor-1.3.3}/instructor/patch.py +6 -12
- {instructor-1.3.2 → instructor-1.3.3}/instructor/process_response.py +9 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/retry.py +12 -32
- {instructor-1.3.2 → instructor-1.3.3}/instructor/utils.py +9 -2
- {instructor-1.3.2 → instructor-1.3.3}/pyproject.toml +30 -7
- instructor-1.3.2/instructor/client_gemini.py +0 -31
- instructor-1.3.2/instructor/exceptions.py +0 -13
- {instructor-1.3.2 → instructor-1.3.3}/LICENSE +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/_types/__init__.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/_types/_alias.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/__init__.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/cli.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/files.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/hub.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/jobs.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/distil.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/__init__.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/citation.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/iterable.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/maybe.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/validators.py +0 -0
- {instructor-1.3.2 → instructor-1.3.3}/instructor/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: instructor
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.3
|
|
4
4
|
Summary: structured outputs for llm
|
|
5
5
|
Home-page: https://github.com/jxnl/instructor
|
|
6
6
|
License: MIT
|
|
@@ -20,14 +20,18 @@ Provides-Extra: groq
|
|
|
20
20
|
Provides-Extra: litellm
|
|
21
21
|
Provides-Extra: mistralai
|
|
22
22
|
Provides-Extra: test-docs
|
|
23
|
+
Provides-Extra: vertexai
|
|
23
24
|
Requires-Dist: aiohttp (>=3.9.1,<4.0.0)
|
|
24
|
-
Requires-Dist: anthropic (>=0.
|
|
25
|
+
Requires-Dist: anthropic (>=0.27.0,<0.28.0) ; extra == "anthropic" or extra == "test-docs"
|
|
25
26
|
Requires-Dist: cohere (>=5.1.8,<6.0.0) ; extra == "cohere" or extra == "test-docs"
|
|
26
27
|
Requires-Dist: diskcache (>=5.6.3,<6.0.0) ; extra == "test-docs"
|
|
27
28
|
Requires-Dist: docstring-parser (>=0.16,<0.17)
|
|
28
29
|
Requires-Dist: fastapi (>=0.109.2,<0.110.0) ; extra == "test-docs"
|
|
30
|
+
Requires-Dist: google-cloud-aiplatform (>=1.52.0,<2.0.0) ; extra == "vertexai"
|
|
29
31
|
Requires-Dist: google-generativeai (>=0.5.4,<0.6.0) ; extra == "google-generativeai"
|
|
30
32
|
Requires-Dist: groq (>=0.4.2,<0.5.0) ; extra == "groq" or extra == "test-docs"
|
|
33
|
+
Requires-Dist: jiter (>=0.4.1,<0.5.0)
|
|
34
|
+
Requires-Dist: jsonref (>=1.1.0,<2.0.0) ; extra == "vertexai"
|
|
31
35
|
Requires-Dist: litellm (>=1.35.31,<2.0.0) ; extra == "test-docs" or extra == "litellm"
|
|
32
36
|
Requires-Dist: mistralai (>=0.1.8,<0.2.0) ; extra == "test-docs" or extra == "mistralai"
|
|
33
37
|
Requires-Dist: openai (>=1.1.0,<2.0.0)
|
|
@@ -184,32 +188,19 @@ import instructor
|
|
|
184
188
|
import google.generativeai as genai
|
|
185
189
|
from pydantic import BaseModel
|
|
186
190
|
|
|
191
|
+
|
|
187
192
|
class User(BaseModel):
|
|
188
193
|
name: str
|
|
189
194
|
age: int
|
|
190
195
|
|
|
196
|
+
|
|
191
197
|
# genai.configure(api_key=os.environ["API_KEY"]) # alternative API key configuration
|
|
192
198
|
client = instructor.from_gemini(
|
|
193
199
|
client=genai.GenerativeModel(
|
|
194
|
-
model_name="models/gemini-1.5-flash-latest",
|
|
200
|
+
model_name="models/gemini-1.5-flash-latest", # model defaults to "gemini-pro"
|
|
195
201
|
),
|
|
196
202
|
mode=instructor.Mode.GEMINI_JSON,
|
|
197
203
|
)
|
|
198
|
-
|
|
199
|
-
# note that client.chat.completions.create will also work
|
|
200
|
-
resp = client.chat.completions.create(
|
|
201
|
-
messages=[
|
|
202
|
-
{
|
|
203
|
-
"role": "user",
|
|
204
|
-
"content": "Extract Jason is 25 years old.",
|
|
205
|
-
}
|
|
206
|
-
],
|
|
207
|
-
response_model=User,
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
assert isinstance(resp, User)
|
|
211
|
-
assert resp.name == "Jason"
|
|
212
|
-
assert resp.age == 25
|
|
213
204
|
```
|
|
214
205
|
|
|
215
206
|
### Using Litellm
|
|
@@ -369,13 +360,13 @@ for user in user_stream:
|
|
|
369
360
|
#> name=None age=None
|
|
370
361
|
#> name=None age=None
|
|
371
362
|
#> name=None age=None
|
|
372
|
-
#> name=None age=
|
|
373
|
-
#> name=None age=
|
|
374
|
-
#> name=
|
|
375
|
-
#> name=
|
|
376
|
-
#> name=
|
|
377
|
-
#> name=
|
|
378
|
-
#> name='John Doe' age=
|
|
363
|
+
#> name=None age=None
|
|
364
|
+
#> name=None age=None
|
|
365
|
+
#> name='John Doe' age=None
|
|
366
|
+
#> name='John Doe' age=None
|
|
367
|
+
#> name='John Doe' age=None
|
|
368
|
+
#> name='John Doe' age=30
|
|
369
|
+
#> name='John Doe' age=30
|
|
379
370
|
# name=None age=None
|
|
380
371
|
# name='' age=None
|
|
381
372
|
# name='John' age=None
|
|
@@ -415,8 +406,8 @@ users = client.chat.completions.create_iterable(
|
|
|
415
406
|
|
|
416
407
|
for user in users:
|
|
417
408
|
print(user)
|
|
418
|
-
#> name='John' age=30
|
|
419
|
-
#> name='Jane' age=
|
|
409
|
+
#> name='John Doe' age=30
|
|
410
|
+
#> name='Jane Doe' age=28
|
|
420
411
|
# User(name='John Doe', age=30)
|
|
421
412
|
# User(name='Jane Smith', age=25)
|
|
422
413
|
```
|
|
@@ -138,32 +138,19 @@ import instructor
|
|
|
138
138
|
import google.generativeai as genai
|
|
139
139
|
from pydantic import BaseModel
|
|
140
140
|
|
|
141
|
+
|
|
141
142
|
class User(BaseModel):
|
|
142
143
|
name: str
|
|
143
144
|
age: int
|
|
144
145
|
|
|
146
|
+
|
|
145
147
|
# genai.configure(api_key=os.environ["API_KEY"]) # alternative API key configuration
|
|
146
148
|
client = instructor.from_gemini(
|
|
147
149
|
client=genai.GenerativeModel(
|
|
148
|
-
model_name="models/gemini-1.5-flash-latest",
|
|
150
|
+
model_name="models/gemini-1.5-flash-latest", # model defaults to "gemini-pro"
|
|
149
151
|
),
|
|
150
152
|
mode=instructor.Mode.GEMINI_JSON,
|
|
151
153
|
)
|
|
152
|
-
|
|
153
|
-
# note that client.chat.completions.create will also work
|
|
154
|
-
resp = client.chat.completions.create(
|
|
155
|
-
messages=[
|
|
156
|
-
{
|
|
157
|
-
"role": "user",
|
|
158
|
-
"content": "Extract Jason is 25 years old.",
|
|
159
|
-
}
|
|
160
|
-
],
|
|
161
|
-
response_model=User,
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
assert isinstance(resp, User)
|
|
165
|
-
assert resp.name == "Jason"
|
|
166
|
-
assert resp.age == 25
|
|
167
154
|
```
|
|
168
155
|
|
|
169
156
|
### Using Litellm
|
|
@@ -323,13 +310,13 @@ for user in user_stream:
|
|
|
323
310
|
#> name=None age=None
|
|
324
311
|
#> name=None age=None
|
|
325
312
|
#> name=None age=None
|
|
326
|
-
#> name=None age=
|
|
327
|
-
#> name=None age=
|
|
328
|
-
#> name=
|
|
329
|
-
#> name=
|
|
330
|
-
#> name=
|
|
331
|
-
#> name=
|
|
332
|
-
#> name='John Doe' age=
|
|
313
|
+
#> name=None age=None
|
|
314
|
+
#> name=None age=None
|
|
315
|
+
#> name='John Doe' age=None
|
|
316
|
+
#> name='John Doe' age=None
|
|
317
|
+
#> name='John Doe' age=None
|
|
318
|
+
#> name='John Doe' age=30
|
|
319
|
+
#> name='John Doe' age=30
|
|
333
320
|
# name=None age=None
|
|
334
321
|
# name='' age=None
|
|
335
322
|
# name='John' age=None
|
|
@@ -369,8 +356,8 @@ users = client.chat.completions.create_iterable(
|
|
|
369
356
|
|
|
370
357
|
for user in users:
|
|
371
358
|
print(user)
|
|
372
|
-
#> name='John' age=30
|
|
373
|
-
#> name='Jane' age=
|
|
359
|
+
#> name='John Doe' age=30
|
|
360
|
+
#> name='Jane Doe' age=28
|
|
374
361
|
# User(name='John Doe', age=30)
|
|
375
362
|
# User(name='Jane Smith', age=25)
|
|
376
363
|
```
|
|
@@ -74,3 +74,8 @@ if importlib.util.find_spec("cohere") is not None:
|
|
|
74
74
|
from .client_cohere import from_cohere
|
|
75
75
|
|
|
76
76
|
__all__ += ["from_cohere"]
|
|
77
|
+
|
|
78
|
+
if importlib.util.find_spec("vertexai") is not None:
|
|
79
|
+
from .client_vertexai import from_vertexai
|
|
80
|
+
|
|
81
|
+
__all__ += ["from_vertexai"]
|
|
@@ -118,11 +118,11 @@ def calculate_cost(
|
|
|
118
118
|
|
|
119
119
|
def group_and_sum_by_date_and_snapshot(usage_data: list[dict[str, Any]]) -> Table:
|
|
120
120
|
"""Group and sum the usage data by date and snapshot, including costs."""
|
|
121
|
-
summary: defaultdict[
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
summary: defaultdict[str, defaultdict[str, dict[str, Union[int, float]]]] = (
|
|
122
|
+
defaultdict(
|
|
123
|
+
lambda: defaultdict(
|
|
124
|
+
lambda: {"total_requests": 0, "total_tokens": 0, "total_cost": 0.0}
|
|
125
|
+
)
|
|
126
126
|
)
|
|
127
127
|
)
|
|
128
128
|
|
|
@@ -63,8 +63,7 @@ class Instructor:
|
|
|
63
63
|
validation_context: dict[str, Any] | None = None,
|
|
64
64
|
strict: bool = True,
|
|
65
65
|
**kwargs: Any,
|
|
66
|
-
) -> Awaitable[T]:
|
|
67
|
-
...
|
|
66
|
+
) -> Awaitable[T]: ...
|
|
68
67
|
|
|
69
68
|
@overload
|
|
70
69
|
def create(
|
|
@@ -75,8 +74,7 @@ class Instructor:
|
|
|
75
74
|
validation_context: dict[str, Any] | None = None,
|
|
76
75
|
strict: bool = True,
|
|
77
76
|
**kwargs: Any,
|
|
78
|
-
) -> T:
|
|
79
|
-
...
|
|
77
|
+
) -> T: ...
|
|
80
78
|
|
|
81
79
|
# TODO: we should overload a case where response_model is None
|
|
82
80
|
def create(
|
|
@@ -108,8 +106,7 @@ class Instructor:
|
|
|
108
106
|
validation_context: dict[str, Any] | None = None,
|
|
109
107
|
strict: bool = True,
|
|
110
108
|
**kwargs: Any,
|
|
111
|
-
) -> AsyncGenerator[T, None]:
|
|
112
|
-
...
|
|
109
|
+
) -> AsyncGenerator[T, None]: ...
|
|
113
110
|
|
|
114
111
|
@overload
|
|
115
112
|
def create_partial(
|
|
@@ -120,8 +117,7 @@ class Instructor:
|
|
|
120
117
|
validation_context: dict[str, Any] | None = None,
|
|
121
118
|
strict: bool = True,
|
|
122
119
|
**kwargs: Any,
|
|
123
|
-
) -> Generator[T, None, None]:
|
|
124
|
-
...
|
|
120
|
+
) -> Generator[T, None, None]: ...
|
|
125
121
|
|
|
126
122
|
def create_partial(
|
|
127
123
|
self,
|
|
@@ -155,8 +151,7 @@ class Instructor:
|
|
|
155
151
|
validation_context: dict[str, Any] | None = None,
|
|
156
152
|
strict: bool = True,
|
|
157
153
|
**kwargs: Any,
|
|
158
|
-
) -> AsyncGenerator[T, None]:
|
|
159
|
-
...
|
|
154
|
+
) -> AsyncGenerator[T, None]: ...
|
|
160
155
|
|
|
161
156
|
@overload
|
|
162
157
|
def create_iterable(
|
|
@@ -167,8 +162,7 @@ class Instructor:
|
|
|
167
162
|
validation_context: dict[str, Any] | None = None,
|
|
168
163
|
strict: bool = True,
|
|
169
164
|
**kwargs: Any,
|
|
170
|
-
) -> Generator[T, None, None]:
|
|
171
|
-
...
|
|
165
|
+
) -> Generator[T, None, None]: ...
|
|
172
166
|
|
|
173
167
|
def create_iterable(
|
|
174
168
|
self,
|
|
@@ -203,8 +197,7 @@ class Instructor:
|
|
|
203
197
|
validation_context: dict[str, Any] | None = None,
|
|
204
198
|
strict: bool = True,
|
|
205
199
|
**kwargs: Any,
|
|
206
|
-
) -> Awaitable[tuple[T, Any]]:
|
|
207
|
-
...
|
|
200
|
+
) -> Awaitable[tuple[T, Any]]: ...
|
|
208
201
|
|
|
209
202
|
@overload
|
|
210
203
|
def create_with_completion(
|
|
@@ -215,8 +208,7 @@ class Instructor:
|
|
|
215
208
|
validation_context: dict[str, Any] | None = None,
|
|
216
209
|
strict: bool = True,
|
|
217
210
|
**kwargs: Any,
|
|
218
|
-
) -> tuple[T, Any]:
|
|
219
|
-
...
|
|
211
|
+
) -> tuple[T, Any]: ...
|
|
220
212
|
|
|
221
213
|
def create_with_completion(
|
|
222
214
|
self,
|
|
@@ -432,8 +424,7 @@ def from_litellm(
|
|
|
432
424
|
completion: Callable[..., Any],
|
|
433
425
|
mode: instructor.Mode = instructor.Mode.TOOLS,
|
|
434
426
|
**kwargs: Any,
|
|
435
|
-
) -> Instructor:
|
|
436
|
-
...
|
|
427
|
+
) -> Instructor: ...
|
|
437
428
|
|
|
438
429
|
|
|
439
430
|
@overload
|
|
@@ -11,10 +11,9 @@ def from_anthropic(
|
|
|
11
11
|
client: (
|
|
12
12
|
anthropic.Anthropic | anthropic.AnthropicBedrock | anthropic.AnthropicVertex
|
|
13
13
|
),
|
|
14
|
-
mode: instructor.Mode = instructor.Mode.
|
|
14
|
+
mode: instructor.Mode = instructor.Mode.ANTHROPIC_TOOLS,
|
|
15
15
|
**kwargs: Any,
|
|
16
|
-
) -> instructor.Instructor:
|
|
17
|
-
...
|
|
16
|
+
) -> instructor.Instructor: ...
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
@overload
|
|
@@ -24,10 +23,9 @@ def from_anthropic(
|
|
|
24
23
|
| anthropic.AsyncAnthropicBedrock
|
|
25
24
|
| anthropic.AsyncAnthropicVertex
|
|
26
25
|
),
|
|
27
|
-
mode: instructor.Mode = instructor.Mode.
|
|
26
|
+
mode: instructor.Mode = instructor.Mode.ANTHROPIC_TOOLS,
|
|
28
27
|
**kwargs: Any,
|
|
29
|
-
) -> instructor.AsyncInstructor:
|
|
30
|
-
...
|
|
28
|
+
) -> instructor.AsyncInstructor: ...
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
def from_anthropic(
|
|
@@ -39,7 +37,7 @@ def from_anthropic(
|
|
|
39
37
|
| anthropic.AsyncAnthropicVertex
|
|
40
38
|
| anthropic.AnthropicVertex
|
|
41
39
|
),
|
|
42
|
-
mode: instructor.Mode = instructor.Mode.
|
|
40
|
+
mode: instructor.Mode = instructor.Mode.ANTHROPIC_TOOLS,
|
|
43
41
|
**kwargs: Any,
|
|
44
42
|
) -> instructor.Instructor | instructor.AsyncInstructor:
|
|
45
43
|
assert (
|
|
@@ -62,10 +60,7 @@ def from_anthropic(
|
|
|
62
60
|
),
|
|
63
61
|
), "Client must be an instance of {anthropic.Anthropic, anthropic.AsyncAnthropic, anthropic.AnthropicBedrock, anthropic.AsyncAnthropicBedrock, anthropic.AnthropicVertex, anthropic.AsyncAnthropicVertex}"
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
create = client.beta.tools.messages.create # type: ignore - unknown in stubs
|
|
67
|
-
else:
|
|
68
|
-
create = client.messages.create
|
|
63
|
+
create = client.messages.create
|
|
69
64
|
|
|
70
65
|
if isinstance(
|
|
71
66
|
client,
|
|
@@ -23,8 +23,7 @@ def from_cohere(
|
|
|
23
23
|
client: cohere.Client,
|
|
24
24
|
mode: instructor.Mode = instructor.Mode.COHERE_TOOLS,
|
|
25
25
|
**kwargs: Any,
|
|
26
|
-
) -> instructor.Instructor:
|
|
27
|
-
...
|
|
26
|
+
) -> instructor.Instructor: ...
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
@overload
|
|
@@ -32,8 +31,7 @@ def from_cohere(
|
|
|
32
31
|
client: cohere.AsyncClient,
|
|
33
32
|
mode: instructor.Mode = instructor.Mode.COHERE_TOOLS,
|
|
34
33
|
**kwargs: Any,
|
|
35
|
-
) -> instructor.AsyncInstructor:
|
|
36
|
-
...
|
|
34
|
+
) -> instructor.AsyncInstructor: ...
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
def from_cohere(
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Literal, overload
|
|
5
|
+
|
|
6
|
+
import google.generativeai as genai
|
|
7
|
+
|
|
8
|
+
import instructor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@overload
|
|
12
|
+
def from_gemini(
|
|
13
|
+
client: genai.GenerativeModel,
|
|
14
|
+
mode: instructor.Mode = instructor.Mode.GEMINI_JSON,
|
|
15
|
+
use_async: Literal[True] = True,
|
|
16
|
+
**kwargs: Any,
|
|
17
|
+
) -> instructor.AsyncInstructor: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@overload
|
|
21
|
+
def from_gemini(
|
|
22
|
+
client: genai.GenerativeModel,
|
|
23
|
+
mode: instructor.Mode = instructor.Mode.GEMINI_JSON,
|
|
24
|
+
use_async: Literal[False] = False,
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
) -> instructor.Instructor: ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def from_gemini(
|
|
30
|
+
client: genai.GenerativeModel,
|
|
31
|
+
mode: instructor.Mode = instructor.Mode.GEMINI_JSON,
|
|
32
|
+
use_async: bool = False,
|
|
33
|
+
**kwargs: Any,
|
|
34
|
+
) -> instructor.Instructor | instructor.AsyncInstructor:
|
|
35
|
+
assert (
|
|
36
|
+
mode == instructor.Mode.GEMINI_JSON
|
|
37
|
+
), "Mode must be instructor.Mode.GEMINI_JSON"
|
|
38
|
+
|
|
39
|
+
assert isinstance(
|
|
40
|
+
client,
|
|
41
|
+
(genai.GenerativeModel),
|
|
42
|
+
), "Client must be an instance of genai.generativemodel"
|
|
43
|
+
|
|
44
|
+
if use_async:
|
|
45
|
+
create = client.generate_content_async
|
|
46
|
+
return instructor.AsyncInstructor(
|
|
47
|
+
client=client,
|
|
48
|
+
create=instructor.patch(create=create, mode=mode),
|
|
49
|
+
provider=instructor.Provider.GEMINI,
|
|
50
|
+
mode=mode,
|
|
51
|
+
**kwargs,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
create = client.generate_content
|
|
55
|
+
return instructor.Instructor(
|
|
56
|
+
client=client,
|
|
57
|
+
create=instructor.patch(create=create, mode=mode),
|
|
58
|
+
provider=instructor.Provider.GEMINI,
|
|
59
|
+
mode=mode,
|
|
60
|
+
**kwargs,
|
|
61
|
+
)
|
|
@@ -11,8 +11,7 @@ def from_groq(
|
|
|
11
11
|
client: groq.Groq,
|
|
12
12
|
mode: instructor.Mode = instructor.Mode.TOOLS,
|
|
13
13
|
**kwargs: Any,
|
|
14
|
-
) -> instructor.Instructor:
|
|
15
|
-
...
|
|
14
|
+
) -> instructor.Instructor: ...
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
@overload
|
|
@@ -20,8 +19,7 @@ def from_groq(
|
|
|
20
19
|
client: groq.AsyncGroq,
|
|
21
20
|
mode: instructor.Mode = instructor.Mode.TOOLS,
|
|
22
21
|
**kwargs: Any,
|
|
23
|
-
) -> instructor.AsyncInstructor:
|
|
24
|
-
...
|
|
22
|
+
) -> instructor.AsyncInstructor: ...
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
def from_groq(
|
|
@@ -12,8 +12,7 @@ def from_mistral(
|
|
|
12
12
|
client: mistralai.client.MistralClient,
|
|
13
13
|
mode: instructor.Mode = instructor.Mode.MISTRAL_TOOLS,
|
|
14
14
|
**kwargs: Any,
|
|
15
|
-
) -> instructor.Instructor:
|
|
16
|
-
...
|
|
15
|
+
) -> instructor.Instructor: ...
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
@overload
|
|
@@ -21,8 +20,7 @@ def from_mistral(
|
|
|
21
20
|
client: mistralaiasynccli.MistralAsyncClient,
|
|
22
21
|
mode: instructor.Mode = instructor.Mode.MISTRAL_TOOLS,
|
|
23
22
|
**kwargs: Any,
|
|
24
|
-
) -> instructor.AsyncInstructor:
|
|
25
|
-
...
|
|
23
|
+
) -> instructor.AsyncInstructor: ...
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
def from_mistral(
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from vertexai.preview.generative_models import ToolConfig # type: ignore[reportMissingTypeStubs]
|
|
6
|
+
import vertexai.generative_models as gm # type: ignore[reportMissingTypeStubs]
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
import instructor
|
|
9
|
+
import jsonref # type: ignore[reportMissingTypeStubs]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _create_vertexai_tool(model: BaseModel) -> gm.Tool:
|
|
13
|
+
schema: dict[Any, Any] = jsonref.replace_refs(model.model_json_schema()) # type: ignore[reportMissingTypeStubs]
|
|
14
|
+
|
|
15
|
+
parameters: dict[Any, Any] = {
|
|
16
|
+
"type": schema["type"],
|
|
17
|
+
"properties": schema["properties"],
|
|
18
|
+
"required": schema["required"],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declaration = gm.FunctionDeclaration(
|
|
22
|
+
name=model.__name__, description=model.__doc__, parameters=parameters
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
tool = gm.Tool(function_declarations=[declaration])
|
|
26
|
+
|
|
27
|
+
return tool
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _vertexai_message_parser(message: dict[str, str]) -> gm.Content:
|
|
31
|
+
return gm.Content(
|
|
32
|
+
role=message["role"], parts=[gm.Part.from_text(message["content"])]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def vertexai_function_response_parser(
|
|
37
|
+
response: gm.GenerationResponse, exception: Exception
|
|
38
|
+
) -> gm.Content:
|
|
39
|
+
return gm.Content(
|
|
40
|
+
parts=[
|
|
41
|
+
gm.Part.from_function_response(
|
|
42
|
+
name=response.candidates[0].content.parts[0].function_call.name,
|
|
43
|
+
response={
|
|
44
|
+
"content": f"Validation Error found:\n{exception}\nRecall the function correctly, fix the errors"
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def vertexai_process_response(_kwargs: dict[str, Any], model: BaseModel):
|
|
52
|
+
messages = _kwargs.pop("messages")
|
|
53
|
+
contents = [
|
|
54
|
+
_vertexai_message_parser(message) # type: ignore[reportUnkownArgumentType]
|
|
55
|
+
if isinstance(message, dict)
|
|
56
|
+
else message
|
|
57
|
+
for message in messages
|
|
58
|
+
]
|
|
59
|
+
tool = _create_vertexai_tool(model=model)
|
|
60
|
+
tool_config = ToolConfig(
|
|
61
|
+
function_calling_config=ToolConfig.FunctionCallingConfig(
|
|
62
|
+
mode=ToolConfig.FunctionCallingConfig.Mode.ANY,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
return contents, [tool], tool_config
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def from_vertexai(
|
|
69
|
+
client: gm.GenerativeModel,
|
|
70
|
+
mode: instructor.Mode = instructor.Mode.VERTEXAI_TOOLS,
|
|
71
|
+
_async: bool = False,
|
|
72
|
+
**kwargs: Any,
|
|
73
|
+
) -> instructor.Instructor:
|
|
74
|
+
assert (
|
|
75
|
+
mode == instructor.Mode.VERTEXAI_TOOLS
|
|
76
|
+
), "Mode must be instructor.Mode.VERTEXAI_TOOLS"
|
|
77
|
+
|
|
78
|
+
assert isinstance(
|
|
79
|
+
client, gm.GenerativeModel
|
|
80
|
+
), "Client must be an instance of vertexai.generative_models.GenerativeModel"
|
|
81
|
+
|
|
82
|
+
create = client.generate_content_async if _async else client.generate_content
|
|
83
|
+
|
|
84
|
+
return instructor.Instructor(
|
|
85
|
+
client=client,
|
|
86
|
+
create=instructor.patch(create=create, mode=mode),
|
|
87
|
+
provider=instructor.Provider.VERTEXAI,
|
|
88
|
+
mode=mode,
|
|
89
|
+
**kwargs,
|
|
90
|
+
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
from typing import (
|
|
2
3
|
Any,
|
|
3
4
|
Optional,
|
|
@@ -44,18 +45,24 @@ class ParallelBase:
|
|
|
44
45
|
)
|
|
45
46
|
|
|
46
47
|
|
|
48
|
+
if sys.version_info >= (3, 10):
|
|
49
|
+
from types import UnionType
|
|
50
|
+
|
|
51
|
+
def is_union_type(typehint: type[Iterable[T]]) -> bool:
|
|
52
|
+
return get_origin(get_args(typehint)[0]) in (Union, UnionType)
|
|
53
|
+
else:
|
|
54
|
+
|
|
55
|
+
def is_union_type(typehint: type[Iterable[T]]) -> bool:
|
|
56
|
+
return get_origin(get_args(typehint)[0]) is Union
|
|
57
|
+
|
|
58
|
+
|
|
47
59
|
def get_types_array(typehint: type[Iterable[T]]) -> tuple[type[T], ...]:
|
|
48
60
|
should_be_iterable = get_origin(typehint)
|
|
49
61
|
if should_be_iterable is not Iterable:
|
|
50
62
|
raise TypeError(f"Model should be with Iterable instead if {typehint}")
|
|
51
63
|
|
|
52
|
-
if
|
|
53
|
-
# works for Iterable[Union[int, str]]
|
|
54
|
-
the_types = get_args(get_args(typehint)[0])
|
|
55
|
-
return the_types
|
|
56
|
-
|
|
57
|
-
if get_origin(get_args(typehint)[0]) is Union:
|
|
58
|
-
# works for Iterable[Union[int, str]]
|
|
64
|
+
if is_union_type(typehint):
|
|
65
|
+
# works for Iterable[Union[int, str]], Iterable[int | str]
|
|
59
66
|
the_types = get_args(get_args(typehint)[0])
|
|
60
67
|
return the_types
|
|
61
68
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
from jiter import from_json
|
|
12
12
|
from pydantic import BaseModel, create_model # type: ignore - remove once Pydantic is updated
|
|
13
13
|
from pydantic.fields import FieldInfo
|
|
14
14
|
from typing import (
|
|
@@ -127,8 +127,9 @@ class PartialBase(Generic[T_Model]):
|
|
|
127
127
|
partial_model = cls.get_partial_model()
|
|
128
128
|
for chunk in json_chunks:
|
|
129
129
|
potential_object += chunk
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
obj = from_json(
|
|
131
|
+
(potential_object or "{}").encode(), partial_mode="trailing-strings"
|
|
132
|
+
)
|
|
132
133
|
obj = partial_model.model_validate(obj, strict=None, **kwargs)
|
|
133
134
|
yield obj
|
|
134
135
|
|
|
@@ -140,7 +141,9 @@ class PartialBase(Generic[T_Model]):
|
|
|
140
141
|
partial_model = cls.get_partial_model()
|
|
141
142
|
async for chunk in json_chunks:
|
|
142
143
|
potential_object += chunk
|
|
143
|
-
obj =
|
|
144
|
+
obj = from_json(
|
|
145
|
+
(potential_object or "{}").encode(), partial_mode="trailing-strings"
|
|
146
|
+
)
|
|
144
147
|
obj = partial_model.model_validate(obj, strict=None, **kwargs)
|
|
145
148
|
yield obj
|
|
146
149
|
|
|
@@ -23,21 +23,41 @@ class ModelAdapter(typing.Generic[T]):
|
|
|
23
23
|
|
|
24
24
|
def __class_getitem__(cls, response_model: type[BaseModel]) -> type[BaseModel]:
|
|
25
25
|
assert is_simple_type(response_model), "Only simple types are supported"
|
|
26
|
-
|
|
26
|
+
return create_model(
|
|
27
27
|
"Response",
|
|
28
28
|
content=(response_model, ...),
|
|
29
29
|
__doc__="Correctly Formated and Extracted Response.",
|
|
30
30
|
__base__=(AdapterBase, OpenAISchema),
|
|
31
31
|
)
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def validateIsSubClass(response_model: type):
|
|
35
|
+
"""
|
|
36
|
+
Temporary guard against issues with generics in Python 3.9
|
|
37
|
+
"""
|
|
38
|
+
import sys
|
|
39
|
+
|
|
40
|
+
if sys.version_info < (3, 10):
|
|
41
|
+
if len(typing.get_args(response_model)) == 0:
|
|
42
|
+
return False
|
|
43
|
+
return issubclass(typing.get_args(response_model)[0], BaseModel)
|
|
44
|
+
return issubclass(response_model, BaseModel)
|
|
33
45
|
|
|
34
46
|
|
|
35
47
|
def is_simple_type(
|
|
36
|
-
response_model: type[BaseModel] | str | int | float | bool,
|
|
48
|
+
response_model: type[BaseModel] | str | int | float | bool | typing.Any,
|
|
37
49
|
) -> bool:
|
|
38
50
|
# ! we're getting mixes between classes and instances due to how we handle some
|
|
39
51
|
# ! response model types, we should fix this in later PRs
|
|
40
|
-
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
if isclass(response_model) and validateIsSubClass(response_model):
|
|
55
|
+
return False
|
|
56
|
+
except TypeError:
|
|
57
|
+
# ! In versions < 3.11, typing.Iterable is not a class, so we can't use isclass
|
|
58
|
+
# ! for now if `response_model` is an Iterable isclass and issubclass will raise
|
|
59
|
+
# ! TypeError, so we need to check if `response_model` is an Iterable
|
|
60
|
+
# ! This is a workaround for now, we should fix this in later PRs
|
|
41
61
|
return False
|
|
42
62
|
|
|
43
63
|
if typing.get_origin(response_model) in {typing.Iterable, Partial}:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IncompleteOutputException(Exception):
|
|
7
|
+
"""Exception raised when the output from LLM is incomplete due to max tokens limit reached."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
*args: list[Any],
|
|
12
|
+
last_completion: Any | None = None,
|
|
13
|
+
message: str = "The output is incomplete due to a max_tokens length limit.",
|
|
14
|
+
**kwargs: dict[str, Any],
|
|
15
|
+
):
|
|
16
|
+
self.last_completion = last_completion
|
|
17
|
+
super().__init__(message, *args, **kwargs)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class InstructorRetryException(Exception):
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
*args: list[Any],
|
|
24
|
+
last_completion: Any | None = None,
|
|
25
|
+
messages: list[Any] | None = None,
|
|
26
|
+
n_attempts: int,
|
|
27
|
+
total_usage: int,
|
|
28
|
+
**kwargs: dict[str, Any],
|
|
29
|
+
):
|
|
30
|
+
self.last_completion = last_completion
|
|
31
|
+
self.messages = messages
|
|
32
|
+
self.n_attempts = n_attempts
|
|
33
|
+
self.total_usage = total_usage
|
|
34
|
+
super().__init__(*args, **kwargs)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# type: ignore
|
|
1
2
|
import json
|
|
2
3
|
import logging
|
|
3
4
|
from functools import wraps
|
|
@@ -5,7 +6,7 @@ from typing import Annotated, Any, Optional, TypeVar, cast
|
|
|
5
6
|
|
|
6
7
|
from docstring_parser import parse
|
|
7
8
|
from openai.types.chat import ChatCompletion
|
|
8
|
-
from pydantic import (
|
|
9
|
+
from pydantic import (
|
|
9
10
|
BaseModel,
|
|
10
11
|
ConfigDict,
|
|
11
12
|
Field,
|
|
@@ -102,6 +103,9 @@ class OpenAISchema(BaseModel):
|
|
|
102
103
|
if mode == Mode.ANTHROPIC_JSON:
|
|
103
104
|
return cls.parse_anthropic_json(completion, validation_context, strict)
|
|
104
105
|
|
|
106
|
+
if mode == Mode.VERTEXAI_TOOLS:
|
|
107
|
+
return cls.parse_vertexai_tools(completion, validation_context, strict)
|
|
108
|
+
|
|
105
109
|
if mode == Mode.COHERE_TOOLS:
|
|
106
110
|
return cls.parse_cohere_tools(completion, validation_context, strict)
|
|
107
111
|
|
|
@@ -165,7 +169,7 @@ class OpenAISchema(BaseModel):
|
|
|
165
169
|
@classmethod
|
|
166
170
|
def parse_gemini_json(
|
|
167
171
|
cls: type[BaseModel],
|
|
168
|
-
completion:
|
|
172
|
+
completion: Any,
|
|
169
173
|
validation_context: Optional[dict[str, Any]] = None,
|
|
170
174
|
strict: Optional[bool] = None,
|
|
171
175
|
) -> BaseModel:
|
|
@@ -173,10 +177,13 @@ class OpenAISchema(BaseModel):
|
|
|
173
177
|
text = completion.text
|
|
174
178
|
except ValueError:
|
|
175
179
|
logger.debug(
|
|
176
|
-
f"Error response: {completion.
|
|
180
|
+
f"Error response: {completion.result.candidates[0].finish_reason}\n\n{completion.result.candidates[0].safety_ratings}"
|
|
177
181
|
)
|
|
178
182
|
|
|
179
|
-
|
|
183
|
+
try:
|
|
184
|
+
extra_text = extract_json_from_codeblock(text) # type: ignore
|
|
185
|
+
except UnboundLocalError:
|
|
186
|
+
raise ValueError("Unable to extract JSON from completion text") from None
|
|
180
187
|
|
|
181
188
|
if strict:
|
|
182
189
|
return cls.model_validate_json(
|
|
@@ -188,6 +195,20 @@ class OpenAISchema(BaseModel):
|
|
|
188
195
|
# Pydantic non-strict: https://docs.pydantic.dev/latest/concepts/strict_mode/
|
|
189
196
|
return cls.model_validate(parsed, context=validation_context, strict=False)
|
|
190
197
|
|
|
198
|
+
@classmethod
|
|
199
|
+
def parse_vertexai_tools(
|
|
200
|
+
cls: type[BaseModel],
|
|
201
|
+
completion: ChatCompletion,
|
|
202
|
+
validation_context: Optional[dict[str, Any]] = None,
|
|
203
|
+
strict: Optional[bool] = None,
|
|
204
|
+
) -> BaseModel:
|
|
205
|
+
strict = False
|
|
206
|
+
tool_call = completion.candidates[0].content.parts[0].function_call.args # type: ignore
|
|
207
|
+
model = {}
|
|
208
|
+
for field in tool_call: # type: ignore
|
|
209
|
+
model[field] = tool_call[field]
|
|
210
|
+
return cls.model_validate(model, context=validation_context, strict=strict)
|
|
211
|
+
|
|
191
212
|
@classmethod
|
|
192
213
|
def parse_cohere_tools(
|
|
193
214
|
cls: type[BaseModel],
|
|
@@ -3,7 +3,7 @@ import warnings
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class _WarnOnFunctionsAccessEnumMeta(enum.EnumMeta):
|
|
6
|
-
def __getattribute__(cls, name):
|
|
6
|
+
def __getattribute__(cls, name: str):
|
|
7
7
|
if name == "FUNCTIONS":
|
|
8
8
|
warnings.warn(
|
|
9
9
|
"FUNCTIONS is deprecated and will be removed in future versions",
|
|
@@ -26,4 +26,5 @@ class Mode(enum.Enum, metaclass=_WarnOnFunctionsAccessEnumMeta):
|
|
|
26
26
|
ANTHROPIC_TOOLS = "anthropic_tools"
|
|
27
27
|
ANTHROPIC_JSON = "anthropic_json"
|
|
28
28
|
COHERE_TOOLS = "cohere_tools"
|
|
29
|
+
VERTEXAI_TOOLS = "vertexai_tools"
|
|
29
30
|
GEMINI_JSON = "gemini_json"
|
|
@@ -35,8 +35,7 @@ class InstructorChatCompletionCreate(Protocol):
|
|
|
35
35
|
max_retries: int = 1,
|
|
36
36
|
*args: T_ParamSpec.args,
|
|
37
37
|
**kwargs: T_ParamSpec.kwargs,
|
|
38
|
-
) -> T_Model:
|
|
39
|
-
...
|
|
38
|
+
) -> T_Model: ...
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
class AsyncInstructorChatCompletionCreate(Protocol):
|
|
@@ -47,40 +46,35 @@ class AsyncInstructorChatCompletionCreate(Protocol):
|
|
|
47
46
|
max_retries: int = 1,
|
|
48
47
|
*args: T_ParamSpec.args,
|
|
49
48
|
**kwargs: T_ParamSpec.kwargs,
|
|
50
|
-
) -> T_Model:
|
|
51
|
-
...
|
|
49
|
+
) -> T_Model: ...
|
|
52
50
|
|
|
53
51
|
|
|
54
52
|
@overload
|
|
55
53
|
def patch(
|
|
56
54
|
client: OpenAI,
|
|
57
55
|
mode: Mode = Mode.TOOLS,
|
|
58
|
-
) -> OpenAI:
|
|
59
|
-
...
|
|
56
|
+
) -> OpenAI: ...
|
|
60
57
|
|
|
61
58
|
|
|
62
59
|
@overload
|
|
63
60
|
def patch(
|
|
64
61
|
client: AsyncOpenAI,
|
|
65
62
|
mode: Mode = Mode.TOOLS,
|
|
66
|
-
) -> AsyncOpenAI:
|
|
67
|
-
...
|
|
63
|
+
) -> AsyncOpenAI: ...
|
|
68
64
|
|
|
69
65
|
|
|
70
66
|
@overload
|
|
71
67
|
def patch(
|
|
72
68
|
create: Callable[T_ParamSpec, T_Retval],
|
|
73
69
|
mode: Mode = Mode.TOOLS,
|
|
74
|
-
) -> InstructorChatCompletionCreate:
|
|
75
|
-
...
|
|
70
|
+
) -> InstructorChatCompletionCreate: ...
|
|
76
71
|
|
|
77
72
|
|
|
78
73
|
@overload
|
|
79
74
|
def patch(
|
|
80
75
|
create: Awaitable[T_Retval],
|
|
81
76
|
mode: Mode = Mode.TOOLS,
|
|
82
|
-
) -> InstructorChatCompletionCreate:
|
|
83
|
-
...
|
|
77
|
+
) -> InstructorChatCompletionCreate: ...
|
|
84
78
|
|
|
85
79
|
|
|
86
80
|
def patch(
|
|
@@ -417,6 +417,15 @@ The output must be a valid JSON object that `{response_model.__name__}.model_val
|
|
|
417
417
|
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
|
|
418
418
|
HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
|
|
419
419
|
}
|
|
420
|
+
elif mode == Mode.VERTEXAI_TOOLS:
|
|
421
|
+
from instructor.client_vertexai import vertexai_process_response
|
|
422
|
+
|
|
423
|
+
contents, tools, tool_config = vertexai_process_response(
|
|
424
|
+
new_kwargs, response_model
|
|
425
|
+
)
|
|
426
|
+
new_kwargs["contents"] = contents
|
|
427
|
+
new_kwargs["tools"] = tools
|
|
428
|
+
new_kwargs["tool_config"] = tool_config
|
|
420
429
|
else:
|
|
421
430
|
raise ValueError(f"Invalid patch mode: {mode}")
|
|
422
431
|
|
|
@@ -11,6 +11,7 @@ from instructor.utils import (
|
|
|
11
11
|
update_total_usage,
|
|
12
12
|
merge_consecutive_messages,
|
|
13
13
|
)
|
|
14
|
+
from instructor.exceptions import InstructorRetryException
|
|
14
15
|
|
|
15
16
|
from openai.types.completion_usage import CompletionUsage
|
|
16
17
|
from pydantic import ValidationError
|
|
@@ -30,23 +31,6 @@ T_ParamSpec = ParamSpec("T_ParamSpec")
|
|
|
30
31
|
T = TypeVar("T")
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
class InstructorRetryException(Exception):
|
|
34
|
-
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
*args,
|
|
37
|
-
last_completion,
|
|
38
|
-
messages: list,
|
|
39
|
-
n_attempts: int,
|
|
40
|
-
total_usage,
|
|
41
|
-
**kwargs,
|
|
42
|
-
):
|
|
43
|
-
self.last_completion = last_completion
|
|
44
|
-
self.messages = messages
|
|
45
|
-
self.n_attempts = n_attempts
|
|
46
|
-
self.total_usage = total_usage
|
|
47
|
-
super().__init__(*args, **kwargs)
|
|
48
|
-
|
|
49
|
-
|
|
50
34
|
def reask_messages(response: ChatCompletion, mode: Mode, exception: Exception):
|
|
51
35
|
if mode == Mode.ANTHROPIC_TOOLS:
|
|
52
36
|
# The original response
|
|
@@ -107,6 +91,12 @@ def reask_messages(response: ChatCompletion, mode: Mode, exception: Exception):
|
|
|
107
91
|
],
|
|
108
92
|
}
|
|
109
93
|
return
|
|
94
|
+
if mode == Mode.VERTEXAI_TOOLS:
|
|
95
|
+
from .client_vertexai import vertexai_function_response_parser
|
|
96
|
+
|
|
97
|
+
yield response.candidates[0].content
|
|
98
|
+
yield vertexai_function_response_parser(response, exception)
|
|
99
|
+
return
|
|
110
100
|
|
|
111
101
|
yield dump_message(response.choices[0].message)
|
|
112
102
|
# TODO: Give users more control on configuration
|
|
@@ -157,6 +147,7 @@ def retry_sync(
|
|
|
157
147
|
raise ValueError("max_retries must be an int or a `tenacity.Retrying` object")
|
|
158
148
|
|
|
159
149
|
try:
|
|
150
|
+
response = None
|
|
160
151
|
for attempt in max_retries:
|
|
161
152
|
with attempt:
|
|
162
153
|
try:
|
|
@@ -173,7 +164,7 @@ def retry_sync(
|
|
|
173
164
|
)
|
|
174
165
|
except (ValidationError, JSONDecodeError) as e:
|
|
175
166
|
logger.debug(f"Error response: {response}")
|
|
176
|
-
if mode in {Mode.GEMINI_JSON}:
|
|
167
|
+
if mode in {Mode.GEMINI_JSON, Mode.VERTEXAI_TOOLS}:
|
|
177
168
|
kwargs["contents"].extend(reask_messages(response, mode, e))
|
|
178
169
|
else:
|
|
179
170
|
kwargs["messages"].extend(reask_messages(response, mode, e))
|
|
@@ -181,13 +172,7 @@ def retry_sync(
|
|
|
181
172
|
kwargs["messages"] = merge_consecutive_messages(
|
|
182
173
|
kwargs["messages"]
|
|
183
174
|
)
|
|
184
|
-
raise
|
|
185
|
-
e,
|
|
186
|
-
last_completion=response,
|
|
187
|
-
n_attempts=attempt.retry_state.attempt_number,
|
|
188
|
-
messages=kwargs.get("messages", kwargs.get("contents")),
|
|
189
|
-
total_usage=total_usage,
|
|
190
|
-
) from e
|
|
175
|
+
raise e
|
|
191
176
|
except RetryError as e:
|
|
192
177
|
raise InstructorRetryException(
|
|
193
178
|
e,
|
|
@@ -227,6 +212,7 @@ async def retry_async(
|
|
|
227
212
|
)
|
|
228
213
|
|
|
229
214
|
try:
|
|
215
|
+
response = None
|
|
230
216
|
async for attempt in max_retries:
|
|
231
217
|
logger.debug(f"Retrying, attempt: {attempt}")
|
|
232
218
|
with attempt:
|
|
@@ -249,13 +235,7 @@ async def retry_async(
|
|
|
249
235
|
kwargs["messages"] = merge_consecutive_messages(
|
|
250
236
|
kwargs["messages"]
|
|
251
237
|
)
|
|
252
|
-
raise
|
|
253
|
-
e,
|
|
254
|
-
last_completion=response,
|
|
255
|
-
n_attempts=attempt.retry_state.attempt_number,
|
|
256
|
-
messages=kwargs["messages"],
|
|
257
|
-
total_usage=total_usage,
|
|
258
|
-
) from e
|
|
238
|
+
raise e
|
|
259
239
|
except RetryError as e:
|
|
260
240
|
logger.exception(f"Failed after retries: {e.last_attempt.exception}")
|
|
261
241
|
raise InstructorRetryException(
|
|
@@ -37,6 +37,7 @@ class Response(Protocol):
|
|
|
37
37
|
|
|
38
38
|
class Provider(Enum):
|
|
39
39
|
OPENAI = "openai"
|
|
40
|
+
VERTEXAI = "vertexai"
|
|
40
41
|
ANTHROPIC = "anthropic"
|
|
41
42
|
ANYSCALE = "anyscale"
|
|
42
43
|
TOGETHER = "together"
|
|
@@ -67,6 +68,8 @@ def get_provider(base_url: str) -> Provider:
|
|
|
67
68
|
return Provider.GEMINI
|
|
68
69
|
elif "databricks" in str(base_url):
|
|
69
70
|
return Provider.DATABRICKS
|
|
71
|
+
elif "vertexai" in str(base_url):
|
|
72
|
+
return Provider.VERTEXAI
|
|
70
73
|
return Provider.UNKNOWN
|
|
71
74
|
|
|
72
75
|
|
|
@@ -235,9 +238,13 @@ def transform_to_gemini_prompt(
|
|
|
235
238
|
if message["role"] == "system":
|
|
236
239
|
system_prompt = message["content"]
|
|
237
240
|
elif message["role"] == "user":
|
|
238
|
-
messages_gemini.append(
|
|
241
|
+
messages_gemini.append(
|
|
242
|
+
{"role": "user", "parts": [message.get("content", "")]}
|
|
243
|
+
)
|
|
239
244
|
elif message["role"] == "assistant":
|
|
240
|
-
messages_gemini.append(
|
|
245
|
+
messages_gemini.append(
|
|
246
|
+
{"role": "model", "parts": [message.get("content", "")]}
|
|
247
|
+
)
|
|
241
248
|
if system_prompt:
|
|
242
249
|
messages_gemini[0]["parts"].insert(0, f"*{system_prompt}*")
|
|
243
250
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "instructor"
|
|
3
|
-
version = "1.3.
|
|
3
|
+
version = "1.3.3"
|
|
4
4
|
description = "structured outputs for llm"
|
|
5
5
|
authors = ["Jason Liu <jason@jxnl.co>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
8
|
-
packages = [{include = "instructor"}]
|
|
8
|
+
packages = [{ include = "instructor" }]
|
|
9
9
|
repository = "https://github.com/jxnl/instructor"
|
|
10
10
|
|
|
11
11
|
[tool.poetry.dependencies]
|
|
@@ -18,6 +18,7 @@ rich = "^13.7.0"
|
|
|
18
18
|
aiohttp = "^3.9.1"
|
|
19
19
|
tenacity = "^8.2.3"
|
|
20
20
|
pydantic-core = "^2.18.0"
|
|
21
|
+
jiter = "^0.4.1"
|
|
21
22
|
|
|
22
23
|
# dependency versions for extras
|
|
23
24
|
fastapi = { version = "^0.109.2", optional = true }
|
|
@@ -27,21 +28,36 @@ pandas = { version = "^2.2.0", optional = true }
|
|
|
27
28
|
tabulate = { version = "^0.9.0", optional = true }
|
|
28
29
|
pydantic_extra_types = { version = "^2.6.0", optional = true }
|
|
29
30
|
litellm = { version = "^1.35.31", optional = true }
|
|
30
|
-
anthropic = { version = "^0.
|
|
31
|
+
anthropic = { version = "^0.27.0", optional = true }
|
|
31
32
|
xmltodict = { version = "^0.13.0", optional = true }
|
|
32
33
|
groq = { version = "^0.4.2", optional = true }
|
|
33
34
|
cohere = { version = "^5.1.8", optional = true }
|
|
34
35
|
mistralai = { version = "^0.1.8", optional = true }
|
|
35
36
|
google-generativeai = { version = "^0.5.4", optional = true }
|
|
37
|
+
google-cloud-aiplatform = { version = "^1.52.0", optional = true }
|
|
38
|
+
jsonref = { version = "^1.1.0", optional = true }
|
|
36
39
|
|
|
37
40
|
[tool.poetry.extras]
|
|
38
41
|
anthropic = ["anthropic", "xmltodict"]
|
|
39
42
|
groq = ["groq"]
|
|
40
43
|
cohere = ["cohere"]
|
|
41
|
-
test-docs = [
|
|
44
|
+
test-docs = [
|
|
45
|
+
"fastapi",
|
|
46
|
+
"redis",
|
|
47
|
+
"diskcache",
|
|
48
|
+
"pandas",
|
|
49
|
+
"tabulate",
|
|
50
|
+
"pydantic_extra_types",
|
|
51
|
+
"litellm",
|
|
52
|
+
"anthropic",
|
|
53
|
+
"groq",
|
|
54
|
+
"cohere",
|
|
55
|
+
"mistralai",
|
|
56
|
+
]
|
|
42
57
|
mistralai = ["mistralai"]
|
|
43
58
|
litellm = ["litellm"]
|
|
44
59
|
google-generativeai = ["google-generativeai"]
|
|
60
|
+
vertexai = ["google-cloud-aiplatform", "jsonref"]
|
|
45
61
|
|
|
46
62
|
[tool.poetry.scripts]
|
|
47
63
|
instructor = "instructor.cli.cli:app"
|
|
@@ -51,10 +67,11 @@ pytest = "^7.4.0"
|
|
|
51
67
|
pytest-asyncio = "^0.21.1"
|
|
52
68
|
coverage = "^7.3.2"
|
|
53
69
|
pyright = "^1.1.360"
|
|
70
|
+
jsonref = "^1.1.0"
|
|
54
71
|
|
|
55
72
|
[tool.poetry.group.docs.dependencies]
|
|
56
73
|
mkdocs = "^1.4.3"
|
|
57
|
-
mkdocs-material = {extras = ["imaging"], version = "^9.5.9"}
|
|
74
|
+
mkdocs-material = { extras = ["imaging"], version = "^9.5.9" }
|
|
58
75
|
mkdocstrings = "^0.22.0"
|
|
59
76
|
mkdocstrings-python = "^1.1.2"
|
|
60
77
|
pytest-examples = "^0.0.10"
|
|
@@ -64,7 +81,7 @@ mkdocs-minify-plugin = "^0.8.0"
|
|
|
64
81
|
mkdocs-redirects = "^1.2.1"
|
|
65
82
|
|
|
66
83
|
[tool.poetry.group.anthropic.dependencies]
|
|
67
|
-
anthropic = "^0.
|
|
84
|
+
anthropic = "^0.27.0"
|
|
68
85
|
|
|
69
86
|
[tool.poetry.group.test-docs.dependencies]
|
|
70
87
|
fastapi = "^0.109.2"
|
|
@@ -74,7 +91,7 @@ pandas = "^2.2.0"
|
|
|
74
91
|
tabulate = "^0.9.0"
|
|
75
92
|
pydantic_extra_types = "^2.6.0"
|
|
76
93
|
litellm = "^1.35.31"
|
|
77
|
-
anthropic = "^0.
|
|
94
|
+
anthropic = "^0.27.0"
|
|
78
95
|
xmltodict = "^0.13.0"
|
|
79
96
|
groq = "^0.4.2"
|
|
80
97
|
phonenumbers = "^8.13.33"
|
|
@@ -84,6 +101,12 @@ mistralai = "^0.1.8"
|
|
|
84
101
|
[tool.poetry.group.litellm.dependencies]
|
|
85
102
|
litellm = "^1.35.31"
|
|
86
103
|
|
|
104
|
+
[tool.poetry.group.google-generativeai.dependencies]
|
|
105
|
+
google-generativeai = "^0.5.4"
|
|
106
|
+
|
|
107
|
+
[tool.poetry.group.vertexai.dependencies]
|
|
108
|
+
google-cloud-aiplatform = "^1.52.0"
|
|
109
|
+
|
|
87
110
|
[build-system]
|
|
88
111
|
requires = ["poetry-core"]
|
|
89
112
|
build-backend = "poetry.core.masonry.api"
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import google.generativeai as genai
|
|
4
|
-
import instructor
|
|
5
|
-
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def from_gemini(
|
|
10
|
-
client: genai.GenerativeModel,
|
|
11
|
-
mode: instructor.Mode = instructor.Mode.GEMINI_JSON,
|
|
12
|
-
**kwargs: Any,
|
|
13
|
-
) -> instructor.Instructor:
|
|
14
|
-
assert (
|
|
15
|
-
mode == instructor.Mode.GEMINI_JSON
|
|
16
|
-
), "Mode must be instructor.Mode.GEMINI_JSON"
|
|
17
|
-
|
|
18
|
-
assert isinstance(
|
|
19
|
-
client,
|
|
20
|
-
(genai.GenerativeModel),
|
|
21
|
-
), "Client must be an instance of genai.generativemodel"
|
|
22
|
-
|
|
23
|
-
create = client.generate_content
|
|
24
|
-
|
|
25
|
-
return instructor.Instructor(
|
|
26
|
-
client=client,
|
|
27
|
-
create=instructor.patch(create=create, mode=mode),
|
|
28
|
-
provider=instructor.Provider.GEMINI,
|
|
29
|
-
mode=mode,
|
|
30
|
-
**kwargs,
|
|
31
|
-
)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
class IncompleteOutputException(Exception):
|
|
2
|
-
"""Exception raised when the output from LLM is incomplete due to max tokens limit reached."""
|
|
3
|
-
|
|
4
|
-
def __init__(
|
|
5
|
-
self,
|
|
6
|
-
*args,
|
|
7
|
-
last_completion=None,
|
|
8
|
-
message: str = "The output is incomplete due to a max_tokens length limit.",
|
|
9
|
-
**kwargs,
|
|
10
|
-
):
|
|
11
|
-
self.last_completion = last_completion
|
|
12
|
-
super().__init__(message, *args, **kwargs)
|
|
13
|
-
super().__init__(*args, **kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|