instructor 1.2.2__tar.gz → 1.2.4__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 (35) hide show
  1. {instructor-1.2.2 → instructor-1.2.4}/PKG-INFO +6 -6
  2. {instructor-1.2.2 → instructor-1.2.4}/README.md +5 -5
  3. {instructor-1.2.2 → instructor-1.2.4}/instructor/client.py +28 -6
  4. {instructor-1.2.2 → instructor-1.2.4}/instructor/function_calls.py +13 -9
  5. {instructor-1.2.2 → instructor-1.2.4}/instructor/patch.py +4 -0
  6. {instructor-1.2.2 → instructor-1.2.4}/instructor/retry.py +28 -14
  7. {instructor-1.2.2 → instructor-1.2.4}/instructor/utils.py +23 -5
  8. {instructor-1.2.2 → instructor-1.2.4}/pyproject.toml +1 -1
  9. {instructor-1.2.2 → instructor-1.2.4}/LICENSE +0 -0
  10. {instructor-1.2.2 → instructor-1.2.4}/instructor/__init__.py +0 -0
  11. {instructor-1.2.2 → instructor-1.2.4}/instructor/_types/__init__.py +0 -0
  12. {instructor-1.2.2 → instructor-1.2.4}/instructor/_types/_alias.py +0 -0
  13. {instructor-1.2.2 → instructor-1.2.4}/instructor/cli/__init__.py +0 -0
  14. {instructor-1.2.2 → instructor-1.2.4}/instructor/cli/cli.py +0 -0
  15. {instructor-1.2.2 → instructor-1.2.4}/instructor/cli/files.py +0 -0
  16. {instructor-1.2.2 → instructor-1.2.4}/instructor/cli/hub.py +0 -0
  17. {instructor-1.2.2 → instructor-1.2.4}/instructor/cli/jobs.py +0 -0
  18. {instructor-1.2.2 → instructor-1.2.4}/instructor/cli/usage.py +0 -0
  19. {instructor-1.2.2 → instructor-1.2.4}/instructor/client_anthropic.py +0 -0
  20. {instructor-1.2.2 → instructor-1.2.4}/instructor/client_cohere.py +0 -0
  21. {instructor-1.2.2 → instructor-1.2.4}/instructor/client_groq.py +0 -0
  22. {instructor-1.2.2 → instructor-1.2.4}/instructor/client_mistral.py +0 -0
  23. {instructor-1.2.2 → instructor-1.2.4}/instructor/distil.py +0 -0
  24. {instructor-1.2.2 → instructor-1.2.4}/instructor/dsl/__init__.py +0 -0
  25. {instructor-1.2.2 → instructor-1.2.4}/instructor/dsl/citation.py +0 -0
  26. {instructor-1.2.2 → instructor-1.2.4}/instructor/dsl/iterable.py +0 -0
  27. {instructor-1.2.2 → instructor-1.2.4}/instructor/dsl/maybe.py +0 -0
  28. {instructor-1.2.2 → instructor-1.2.4}/instructor/dsl/parallel.py +0 -0
  29. {instructor-1.2.2 → instructor-1.2.4}/instructor/dsl/partial.py +0 -0
  30. {instructor-1.2.2 → instructor-1.2.4}/instructor/dsl/simple_type.py +0 -0
  31. {instructor-1.2.2 → instructor-1.2.4}/instructor/dsl/validators.py +0 -0
  32. {instructor-1.2.2 → instructor-1.2.4}/instructor/exceptions.py +0 -0
  33. {instructor-1.2.2 → instructor-1.2.4}/instructor/mode.py +0 -0
  34. {instructor-1.2.2 → instructor-1.2.4}/instructor/process_response.py +0 -0
  35. {instructor-1.2.2 → instructor-1.2.4}/instructor/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: instructor
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: structured outputs for llm
5
5
  Home-page: https://github.com/jxnl/instructor
6
6
  License: MIT
@@ -54,9 +54,9 @@ Instructor is a Python library that makes it a breeze to work with structured ou
54
54
 
55
55
  - **Response Models**: Specify Pydantic models to define the structure of your LLM outputs
56
56
  - **Retry Management**: Easily configure the number of retry attempts for your requests
57
- **Validation**: Ensure LLM responses conform to your expectations with Pydantic validation
57
+ - **Validation**: Ensure LLM responses conform to your expectations with Pydantic validation
58
58
  - **Streaming Support**: Work with Lists and Partial responses effortlessly
59
- **Flexible Backends**: Seamlessly integrate with various LLM providers beyond OpenAI
59
+ - **Flexible Backends**: Seamlessly integrate with various LLM providers beyond OpenAI
60
60
 
61
61
  ## Get Started in Minutes
62
62
 
@@ -201,7 +201,7 @@ assert resp.name == "Jason"
201
201
  assert resp.age == 25
202
202
  ```
203
203
 
204
- ## Type are infered correctly
204
+ ## Type are inferred correctly
205
205
 
206
206
  This was the dream of instructor but due to the patching of openai, it wasnt possible for me to get typing to work well. Now, with the new client, we can get typing to work well! We've also added a few `create_*` methods to make it easier to create iterables and partials, and to access the original completion.
207
207
 
@@ -229,7 +229,7 @@ user = client.chat.completions.create(
229
229
  )
230
230
  ```
231
231
 
232
- Now if you use a IDE, you can see the type is correctly infered.
232
+ Now if you use a IDE, you can see the type is correctly inferred.
233
233
 
234
234
  ![type](./docs/blog/posts/img/type.png)
235
235
 
@@ -341,7 +341,7 @@ for user in user_stream:
341
341
  # name='John Doe' age=30
342
342
  ```
343
343
 
344
- Notice now that the type infered is `Generator[User, None]`
344
+ Notice now that the type inferred is `Generator[User, None]`
345
345
 
346
346
  ![generator](./docs/blog/posts/img/generator.png)
347
347
 
@@ -11,9 +11,9 @@ Instructor is a Python library that makes it a breeze to work with structured ou
11
11
 
12
12
  - **Response Models**: Specify Pydantic models to define the structure of your LLM outputs
13
13
  - **Retry Management**: Easily configure the number of retry attempts for your requests
14
- **Validation**: Ensure LLM responses conform to your expectations with Pydantic validation
14
+ - **Validation**: Ensure LLM responses conform to your expectations with Pydantic validation
15
15
  - **Streaming Support**: Work with Lists and Partial responses effortlessly
16
- **Flexible Backends**: Seamlessly integrate with various LLM providers beyond OpenAI
16
+ - **Flexible Backends**: Seamlessly integrate with various LLM providers beyond OpenAI
17
17
 
18
18
  ## Get Started in Minutes
19
19
 
@@ -158,7 +158,7 @@ assert resp.name == "Jason"
158
158
  assert resp.age == 25
159
159
  ```
160
160
 
161
- ## Type are infered correctly
161
+ ## Type are inferred correctly
162
162
 
163
163
  This was the dream of instructor but due to the patching of openai, it wasnt possible for me to get typing to work well. Now, with the new client, we can get typing to work well! We've also added a few `create_*` methods to make it easier to create iterables and partials, and to access the original completion.
164
164
 
@@ -186,7 +186,7 @@ user = client.chat.completions.create(
186
186
  )
187
187
  ```
188
188
 
189
- Now if you use a IDE, you can see the type is correctly infered.
189
+ Now if you use a IDE, you can see the type is correctly inferred.
190
190
 
191
191
  ![type](./docs/blog/posts/img/type.png)
192
192
 
@@ -298,7 +298,7 @@ for user in user_stream:
298
298
  # name='John Doe' age=30
299
299
  ```
300
300
 
301
- Notice now that the type infered is `Generator[User, None]`
301
+ Notice now that the type inferred is `Generator[User, None]`
302
302
 
303
303
  ![generator](./docs/blog/posts/img/generator.png)
304
304
 
@@ -67,6 +67,7 @@ class Instructor:
67
67
  messages: List[ChatCompletionMessageParam],
68
68
  max_retries: int = 3,
69
69
  validation_context: dict | None = None,
70
+ strict: bool = True,
70
71
  **kwargs,
71
72
  ) -> T:
72
73
  kwargs = self.handle_kwargs(kwargs)
@@ -76,6 +77,7 @@ class Instructor:
76
77
  messages=messages,
77
78
  max_retries=max_retries,
78
79
  validation_context=validation_context,
80
+ strict=strict,
79
81
  **kwargs,
80
82
  )
81
83
 
@@ -85,6 +87,7 @@ class Instructor:
85
87
  messages: List[ChatCompletionMessageParam],
86
88
  max_retries: int = 3,
87
89
  validation_context: dict | None = None,
90
+ strict: bool = True,
88
91
  **kwargs,
89
92
  ) -> Generator[T, None, None]:
90
93
  assert self.provider != Provider.ANTHROPIC, "Anthropic doesn't support partial"
@@ -99,6 +102,7 @@ class Instructor:
99
102
  response_model=response_model,
100
103
  max_retries=max_retries,
101
104
  validation_context=validation_context,
105
+ strict=strict,
102
106
  **kwargs,
103
107
  )
104
108
 
@@ -108,6 +112,7 @@ class Instructor:
108
112
  response_model: Type[T],
109
113
  max_retries: int = 3,
110
114
  validation_context: dict | None = None,
115
+ strict: bool = True,
111
116
  **kwargs,
112
117
  ) -> Iterable[T]:
113
118
  assert self.provider != Provider.ANTHROPIC, "Anthropic doesn't support iterable"
@@ -121,6 +126,7 @@ class Instructor:
121
126
  response_model=response_model,
122
127
  max_retries=max_retries,
123
128
  validation_context=validation_context,
129
+ strict=strict,
124
130
  **kwargs,
125
131
  )
126
132
 
@@ -130,6 +136,7 @@ class Instructor:
130
136
  response_model: Type[T],
131
137
  max_retries: int = 3,
132
138
  validation_context: dict | None = None,
139
+ strict: bool = True,
133
140
  **kwargs,
134
141
  ) -> Tuple[T, ChatCompletion | Any]:
135
142
  kwargs = self.handle_kwargs(kwargs)
@@ -138,6 +145,7 @@ class Instructor:
138
145
  response_model=response_model,
139
146
  max_retries=max_retries,
140
147
  validation_context=validation_context,
148
+ strict=strict,
141
149
  **kwargs,
142
150
  )
143
151
  return model, model._raw_response
@@ -176,6 +184,7 @@ class AsyncInstructor(Instructor):
176
184
  response_model: Type[T],
177
185
  validation_context: dict | None = None,
178
186
  max_retries: int = 3,
187
+ strict: bool = True,
179
188
  **kwargs,
180
189
  ) -> T:
181
190
  kwargs = self.handle_kwargs(kwargs)
@@ -184,6 +193,7 @@ class AsyncInstructor(Instructor):
184
193
  validation_context=validation_context,
185
194
  max_retries=max_retries,
186
195
  messages=messages,
196
+ strict=strict,
187
197
  **kwargs,
188
198
  )
189
199
 
@@ -193,6 +203,7 @@ class AsyncInstructor(Instructor):
193
203
  messages: List[ChatCompletionMessageParam],
194
204
  validation_context: dict | None = None,
195
205
  max_retries: int = 3,
206
+ strict: bool = True,
196
207
  **kwargs,
197
208
  ) -> AsyncGenerator[T, None]:
198
209
  assert self.provider != Provider.ANTHROPIC, "Anthropic doesn't support partial"
@@ -204,6 +215,7 @@ class AsyncInstructor(Instructor):
204
215
  validation_context=validation_context,
205
216
  max_retries=max_retries,
206
217
  messages=messages,
218
+ strict=strict,
207
219
  **kwargs,
208
220
  ):
209
221
  yield item
@@ -214,6 +226,7 @@ class AsyncInstructor(Instructor):
214
226
  messages: List[ChatCompletionMessageParam],
215
227
  validation_context: dict | None = None,
216
228
  max_retries: int = 3,
229
+ strict: bool = True,
217
230
  **kwargs,
218
231
  ) -> AsyncGenerator[T, None]:
219
232
  assert self.provider != Provider.ANTHROPIC, "Anthropic doesn't support iterable"
@@ -225,6 +238,7 @@ class AsyncInstructor(Instructor):
225
238
  validation_context=validation_context,
226
239
  max_retries=max_retries,
227
240
  messages=messages,
241
+ strict=strict,
228
242
  **kwargs,
229
243
  ):
230
244
  yield item
@@ -235,6 +249,7 @@ class AsyncInstructor(Instructor):
235
249
  messages: List[ChatCompletionMessageParam],
236
250
  validation_context: dict | None = None,
237
251
  max_retries: int = 3,
252
+ strict: bool = True,
238
253
  **kwargs,
239
254
  ) -> Tuple[T, dict]:
240
255
  kwargs = self.handle_kwargs(kwargs)
@@ -243,6 +258,7 @@ class AsyncInstructor(Instructor):
243
258
  validation_context=validation_context,
244
259
  max_retries=max_retries,
245
260
  messages=messages,
261
+ strict=strict,
246
262
  **kwargs,
247
263
  )
248
264
  return response, response._raw_response
@@ -269,11 +285,18 @@ def from_openai(
269
285
  mode: instructor.Mode = instructor.Mode.TOOLS,
270
286
  **kwargs,
271
287
  ) -> Instructor | AsyncInstructor:
272
- provider = get_provider(str(client.base_url))
288
+ if hasattr(client, "base_url"):
289
+ provider = get_provider(str(client.base_url))
290
+ else:
291
+ provider = Provider.OPENAI
292
+
293
+ if not isinstance(client, (openai.OpenAI, openai.AsyncOpenAI)):
294
+ import warnings
273
295
 
274
- assert isinstance(
275
- client, (openai.OpenAI, openai.AsyncOpenAI)
276
- ), "Client must be an instance of openai.OpenAI or openai.AsyncOpenAI"
296
+ warnings.warn(
297
+ "Client should be an instance of openai.OpenAI or openai.AsyncOpenAI. "
298
+ "Unexpected behavior may occur with other client types."
299
+ )
277
300
 
278
301
  if provider in {Provider.ANYSCALE, Provider.TOGETHER}:
279
302
  assert mode in {
@@ -316,8 +339,7 @@ def from_litellm(
316
339
  completion: Callable,
317
340
  mode: instructor.Mode = instructor.Mode.TOOLS,
318
341
  **kwargs,
319
- ) -> Instructor:
320
- ...
342
+ ) -> Instructor: ...
321
343
 
322
344
 
323
345
  @overload
@@ -1,15 +1,14 @@
1
- from typing import Any, Dict, Optional, Type, TypeVar
2
- from docstring_parser import parse
1
+ import logging
3
2
  from functools import wraps
4
- from pydantic import BaseModel, create_model
3
+ from typing import Annotated, Any, Dict, Optional, Type, TypeVar
4
+
5
+ from docstring_parser import parse
5
6
  from openai.types.chat import ChatCompletion
6
- from typing import Any, Dict, Optional, Type
7
- from instructor.mode import Mode
8
- from instructor.utils import extract_json_from_codeblock
7
+ from pydantic import BaseModel, Field, TypeAdapter, create_model
8
+
9
9
  from instructor.exceptions import IncompleteOutputException
10
10
  from instructor.mode import Mode
11
- import logging
12
-
11
+ from instructor.utils import extract_json_from_codeblock
13
12
 
14
13
  T = TypeVar("T")
15
14
 
@@ -119,7 +118,12 @@ class OpenAISchema(BaseModel): # type: ignore[misc]
119
118
  validation_context: Optional[Dict[str, Any]] = None,
120
119
  strict: Optional[bool] = None,
121
120
  ) -> BaseModel:
122
- tool_call = [c.input for c in completion.content if c.type == "tool_use"][0]
121
+ tool_calls = [c.input for c in completion.content if c.type == "tool_use"]
122
+
123
+ tool_calls_validator = TypeAdapter(
124
+ Annotated[list, Field(min_length=1, max_length=1)]
125
+ )
126
+ tool_call = tool_calls_validator.validate_python(tool_calls)[0]
123
127
 
124
128
  return cls.model_validate(tool_call, context=validation_context, strict=strict) # type:ignore
125
129
 
@@ -116,6 +116,7 @@ def patch(
116
116
  response_model: Type[T_Model] = None,
117
117
  validation_context: dict = None,
118
118
  max_retries: int = 1,
119
+ strict: bool = True,
119
120
  *args: T_ParamSpec.args,
120
121
  **kwargs: T_ParamSpec.kwargs,
121
122
  ) -> T_Model:
@@ -129,6 +130,7 @@ def patch(
129
130
  max_retries=max_retries,
130
131
  args=args,
131
132
  kwargs=new_kwargs,
133
+ strict=strict,
132
134
  mode=mode,
133
135
  ) # type: ignore
134
136
  return response
@@ -138,6 +140,7 @@ def patch(
138
140
  response_model: Type[T_Model] = None,
139
141
  validation_context: dict = None,
140
142
  max_retries: int = 1,
143
+ strict: bool = True,
141
144
  *args: T_ParamSpec.args,
142
145
  **kwargs: T_ParamSpec.kwargs,
143
146
  ) -> T_Model:
@@ -150,6 +153,7 @@ def patch(
150
153
  validation_context=validation_context,
151
154
  max_retries=max_retries,
152
155
  args=args,
156
+ strict=strict,
153
157
  kwargs=new_kwargs,
154
158
  mode=mode,
155
159
  )
@@ -62,22 +62,27 @@ def reask_messages(response: ChatCompletion, mode: Mode, exception: Exception):
62
62
  ):
63
63
  tool_use_id = content.id
64
64
 
65
- assert tool_use_id is not None, "Tool use ID not found in the response"
66
65
  yield {
67
66
  "role": "assistant",
68
67
  "content": assistant_content,
69
68
  }
70
- yield {
71
- "role": "user",
72
- "content": [
73
- {
74
- "type": "tool_result",
75
- "tool_use_id": tool_use_id,
76
- "content": f"Validation Error found:\n{exception}\nRecall the function correctly, fix the errors",
77
- "is_error": True,
78
- }
79
- ],
80
- }
69
+ if tool_use_id is not None:
70
+ yield {
71
+ "role": "user",
72
+ "content": [
73
+ {
74
+ "type": "tool_result",
75
+ "tool_use_id": tool_use_id,
76
+ "content": f"Validation Error found:\n{exception}\nRecall the function correctly, fix the errors",
77
+ "is_error": True,
78
+ }
79
+ ],
80
+ }
81
+ else:
82
+ yield {
83
+ "role": "user",
84
+ "content": f"Validation Error due to no tool invocation:\n{exception}\nRecall the function correctly, fix the errors",
85
+ }
81
86
  return
82
87
  if mode == Mode.ANTHROPIC_JSON:
83
88
  from anthropic.types import Message
@@ -128,6 +133,10 @@ def retry_sync(
128
133
  mode: Mode = Mode.TOOLS,
129
134
  ) -> T_Model:
130
135
  total_usage = CompletionUsage(completion_tokens=0, prompt_tokens=0, total_tokens=0)
136
+ if mode in {Mode.ANTHROPIC_TOOLS, Mode.ANTHROPIC_JSON}:
137
+ from anthropic.types import Usage as AnthropicUsage
138
+
139
+ total_usage = AnthropicUsage(input_tokens=0, output_tokens=0)
131
140
 
132
141
  # If max_retries is int, then create a Retrying object
133
142
  if isinstance(max_retries, int):
@@ -189,6 +198,11 @@ async def retry_async(
189
198
  mode: Mode = Mode.TOOLS,
190
199
  ) -> T:
191
200
  total_usage = CompletionUsage(completion_tokens=0, prompt_tokens=0, total_tokens=0)
201
+ if mode in {Mode.ANTHROPIC_TOOLS, Mode.ANTHROPIC_JSON}:
202
+ from anthropic.types import Usage as AnthropicUsage
203
+
204
+ total_usage = AnthropicUsage(input_tokens=0, output_tokens=0)
205
+
192
206
  # If max_retries is int, then create a AsyncRetrying object
193
207
  if isinstance(max_retries, int):
194
208
  logger.debug(f"max_retries: {max_retries}")
@@ -227,7 +241,7 @@ async def retry_async(
227
241
  raise InstructorRetryException(
228
242
  e,
229
243
  last_completion=response,
230
- n_attempts=e.attempt_number,
244
+ n_attempts=attempt.retry_state.attempt_number,
231
245
  messages=kwargs["messages"],
232
246
  total_usage=total_usage,
233
247
  ) from e
@@ -236,7 +250,7 @@ async def retry_async(
236
250
  raise InstructorRetryException(
237
251
  e,
238
252
  last_completion=response,
239
- n_attempts=e.attempt_number,
253
+ n_attempts=attempt.retry_state.attempt_number,
240
254
  messages=kwargs["messages"],
241
255
  total_usage=total_usage,
242
256
  ) from e
@@ -2,16 +2,18 @@ from __future__ import annotations
2
2
 
3
3
  import inspect
4
4
  import json
5
+ import logging
5
6
  from typing import Callable, Generator, Iterable, AsyncGenerator, TypeVar
6
7
 
7
8
  from pydantic import BaseModel
8
-
9
+ from openai.types import CompletionUsage as OpenAIUsage
9
10
  from openai.types.chat import (
10
11
  ChatCompletion,
11
12
  ChatCompletionMessage,
12
13
  ChatCompletionMessageParam,
13
14
  )
14
15
 
16
+ logger = logging.getLogger("instructor")
15
17
  T_Model = TypeVar("T_Model", bound=BaseModel)
16
18
 
17
19
  from enum import Enum
@@ -93,11 +95,27 @@ async def extract_json_from_stream_async(
93
95
 
94
96
 
95
97
  def update_total_usage(response: T_Model, total_usage) -> T_Model | ChatCompletion:
96
- if isinstance(response, ChatCompletion) and response.usage is not None:
97
- total_usage.completion_tokens += response.usage.completion_tokens or 0
98
- total_usage.prompt_tokens += response.usage.prompt_tokens or 0
99
- total_usage.total_tokens += response.usage.total_tokens or 0
98
+ response_usage = getattr(response, "usage", None)
99
+ if isinstance(response_usage, OpenAIUsage):
100
+ total_usage.completion_tokens += response_usage.completion_tokens or 0
101
+ total_usage.prompt_tokens += response_usage.prompt_tokens or 0
102
+ total_usage.total_tokens += response_usage.total_tokens or 0
100
103
  response.usage = total_usage # Replace each response usage with the total usage
104
+ return response
105
+
106
+ # Anthropic usage
107
+ try:
108
+ from anthropic.types import Usage as AnthropicUsage
109
+
110
+ if isinstance(response_usage, AnthropicUsage):
111
+ total_usage.input_tokens += response_usage.input_tokens or 0
112
+ total_usage.output_tokens += response_usage.output_tokens or 0
113
+ response.usage = total_usage
114
+ return response
115
+ except ImportError:
116
+ pass
117
+
118
+ logger.debug("No compatible response.usage found, token usage not updated.")
101
119
  return response
102
120
 
103
121
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "instructor"
3
- version = "1.2.2"
3
+ version = "1.2.4"
4
4
  description = "structured outputs for llm"
5
5
  authors = ["Jason Liu <jason@jxnl.co>"]
6
6
  license = "MIT"
File without changes