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.
Files changed (39) hide show
  1. {instructor-1.3.2 → instructor-1.3.3}/PKG-INFO +18 -27
  2. {instructor-1.3.2 → instructor-1.3.3}/README.md +12 -25
  3. {instructor-1.3.2 → instructor-1.3.3}/instructor/__init__.py +5 -0
  4. {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/usage.py +5 -5
  5. {instructor-1.3.2 → instructor-1.3.3}/instructor/client.py +9 -18
  6. {instructor-1.3.2 → instructor-1.3.3}/instructor/client_anthropic.py +6 -11
  7. {instructor-1.3.2 → instructor-1.3.3}/instructor/client_cohere.py +2 -4
  8. instructor-1.3.3/instructor/client_gemini.py +61 -0
  9. {instructor-1.3.2 → instructor-1.3.3}/instructor/client_groq.py +2 -4
  10. {instructor-1.3.2 → instructor-1.3.3}/instructor/client_mistral.py +2 -4
  11. instructor-1.3.3/instructor/client_vertexai.py +90 -0
  12. {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/parallel.py +14 -7
  13. {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/partial.py +7 -4
  14. {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/simple_type.py +24 -4
  15. instructor-1.3.3/instructor/exceptions.py +34 -0
  16. {instructor-1.3.2 → instructor-1.3.3}/instructor/function_calls.py +25 -4
  17. {instructor-1.3.2 → instructor-1.3.3}/instructor/mode.py +2 -1
  18. {instructor-1.3.2 → instructor-1.3.3}/instructor/patch.py +6 -12
  19. {instructor-1.3.2 → instructor-1.3.3}/instructor/process_response.py +9 -0
  20. {instructor-1.3.2 → instructor-1.3.3}/instructor/retry.py +12 -32
  21. {instructor-1.3.2 → instructor-1.3.3}/instructor/utils.py +9 -2
  22. {instructor-1.3.2 → instructor-1.3.3}/pyproject.toml +30 -7
  23. instructor-1.3.2/instructor/client_gemini.py +0 -31
  24. instructor-1.3.2/instructor/exceptions.py +0 -13
  25. {instructor-1.3.2 → instructor-1.3.3}/LICENSE +0 -0
  26. {instructor-1.3.2 → instructor-1.3.3}/instructor/_types/__init__.py +0 -0
  27. {instructor-1.3.2 → instructor-1.3.3}/instructor/_types/_alias.py +0 -0
  28. {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/__init__.py +0 -0
  29. {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/cli.py +0 -0
  30. {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/files.py +0 -0
  31. {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/hub.py +0 -0
  32. {instructor-1.3.2 → instructor-1.3.3}/instructor/cli/jobs.py +0 -0
  33. {instructor-1.3.2 → instructor-1.3.3}/instructor/distil.py +0 -0
  34. {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/__init__.py +0 -0
  35. {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/citation.py +0 -0
  36. {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/iterable.py +0 -0
  37. {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/maybe.py +0 -0
  38. {instructor-1.3.2 → instructor-1.3.3}/instructor/dsl/validators.py +0 -0
  39. {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.2
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.26.0,<0.27.0) ; extra == "anthropic" or extra == "test-docs"
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", # model defaults to "gemini-pro"
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=25
373
- #> name=None age=25
374
- #> name=None age=25
375
- #> name=None age=25
376
- #> name=None age=25
377
- #> name=None age=25
378
- #> name='John Doe' age=25
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=25
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", # model defaults to "gemini-pro"
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=25
327
- #> name=None age=25
328
- #> name=None age=25
329
- #> name=None age=25
330
- #> name=None age=25
331
- #> name=None age=25
332
- #> name='John Doe' age=25
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=25
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
- str, defaultdict[str, dict[str, Union[int, float]]]
123
- ] = defaultdict(
124
- lambda: defaultdict(
125
- lambda: {"total_requests": 0, "total_tokens": 0, "total_cost": 0.0}
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.ANTHROPIC_JSON,
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.ANTHROPIC_JSON,
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.ANTHROPIC_JSON,
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
- if mode == instructor.Mode.ANTHROPIC_TOOLS:
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 get_origin(get_args(typehint)[0]) is Union:
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 pydantic_core
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
- obj = pydantic_core.from_json(potential_object or "{}", allow_partial=True)
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 = pydantic_core.from_json(potential_object or "{}", allow_partial=True)
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
- tmp = create_model(
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
- return tmp
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
- if isclass(response_model) and issubclass(response_model, BaseModel):
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 ( # type: ignore - remove once Pydantic is updated
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: ChatCompletion,
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._result.candidates[0].finish_reason}\n\n{completion_result.candidates[0].safety_ratings}"
180
+ f"Error response: {completion.result.candidates[0].finish_reason}\n\n{completion.result.candidates[0].safety_ratings}"
177
181
  )
178
182
 
179
- extra_text = extract_json_from_codeblock(text)
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 InstructorRetryException(
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 InstructorRetryException(
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({"role": "user", "parts": [message["content"]]})
241
+ messages_gemini.append(
242
+ {"role": "user", "parts": [message.get("content", "")]}
243
+ )
239
244
  elif message["role"] == "assistant":
240
- messages_gemini.append({"role": "model", "parts": [message["content"]]})
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.2"
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.26.0", optional = true }
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 = ["fastapi", "redis", "diskcache", "pandas", "tabulate", "pydantic_extra_types", "litellm", "anthropic", "groq", "cohere", "mistralai"]
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.26.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.26.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