Skip to content

Commit 58e2e87

Browse files
authored
[codex] consolidate latest fixes and docs (#2398)
1 parent 754e4fb commit 58e2e87

90 files changed

Lines changed: 1274 additions & 1100 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
77

88
---
99

10+
## [1.15.4] - 2026-06-27
11+
12+
### Fixed
13+
- **CLI fine-tuning**: Use the uploaded validation file ID when creating a fine-tuning job from local files, instead of passing the local validation file path through to OpenAI. ([#2397](https://github.com/567-labs/instructor/pull/2397))
14+
- **v2 core**: Prepare list and primitive response models before provider handler dispatch, fixing `list[Model]` and scalar response-model crashes such as `AttributeError: type object 'list' has no attribute 'model_json_schema'`. ([#2374](https://github.com/567-labs/instructor/issues/2374))
15+
- **v2 streaming**: Preserve backticks inside JSON string values during streamed JSON extraction.
16+
- **v2 multimodal**: Accept raw bytes in `Image.autodetect()` for JPEG, PNG, GIF, and WebP, while raising clear errors for unsupported image inputs. ([#2344](https://github.com/567-labs/instructor/issues/2344))
17+
- **Docs**: Refresh stale OpenAI and Ollama model strings in documentation examples. ([#2395](https://github.com/567-labs/instructor/issues/2395))
18+
19+
---
20+
1021
## [1.15.3] - 2026-06-15
1122

1223
### Fixed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Getting structured data from LLMs is hard. You need to:
5555

5656
```python
5757
response = openai.chat.completions.create(
58-
model="gpt-4",
58+
model="gpt-5.4-mini",
5959
messages=[{"role": "user", "content": "..."}],
6060
tools=[
6161
{
@@ -88,7 +88,7 @@ if "name" not in user_data:
8888
<td>
8989

9090
```python
91-
client = instructor.from_provider("openai/gpt-4")
91+
client = instructor.from_provider("openai/gpt-5.4-mini")
9292

9393
user = client.chat.completions.create(
9494
response_model=User,

docs/blog/posts/best_framework.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class User(BaseModel):
4040
client = instructor.from_provider("openai/gpt-5-nano")
4141

4242
user = client.create(
43-
model="gpt-3.5-turbo",
43+
model="gpt-5.4-mini",
4444
response_model=User, # (1)!
4545
messages=[
4646
{

docs/blog/posts/caching.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class UserDetail(BaseModel):
123123

124124
def extract(data) -> UserDetail:
125125
return client.create(
126-
model="gpt-3.5-turbo",
126+
model="gpt-5.4-mini",
127127
response_model=UserDetail,
128128
messages=[
129129
{"role": "user", "content": data},
@@ -156,7 +156,7 @@ import functools
156156
@functools.cache
157157
def extract(data):
158158
return client.create(
159-
model="gpt-3.5-turbo",
159+
model="gpt-5.4-mini",
160160
response_model=UserDetail,
161161
messages=[
162162
{"role": "user", "content": data},
@@ -240,7 +240,7 @@ import functools
240240

241241

242242
@functools.lru_cache(maxsize=1000) # Limit cache to 1000 entries
243-
def extract_with_limit(data: str, model: str = "gpt-3.5-turbo") -> UserDetail:
243+
def extract_with_limit(data: str, model: str = "gpt-5.4-mini") -> UserDetail:
244244
return client.create(
245245
model=model,
246246
response_model=UserDetail,
@@ -379,7 +379,7 @@ class UserDetail(BaseModel):
379379
@instructor_cache
380380
def extract(data) -> UserDetail:
381381
return client.create(
382-
model="gpt-3.5-turbo",
382+
model="gpt-5.4-mini",
383383
response_model=UserDetail,
384384
messages=[
385385
{"role": "user", "content": data},
@@ -542,7 +542,7 @@ class UserDetail(BaseModel):
542542
def extract(data) -> UserDetail:
543543
# Assuming client.chat.completions.create returns a UserDetail instance
544544
return client.create(
545-
model="gpt-3.5-turbo",
545+
model="gpt-5.4-mini",
546546
response_model=UserDetail,
547547
messages=[
548548
{"role": "user", "content": data},
@@ -649,7 +649,7 @@ def extract_l2(data: str) -> UserDetail:
649649
@redis_decorator # L3
650650
def extract_l3(data: str) -> UserDetail:
651651
return client.create(
652-
model="gpt-3.5-turbo",
652+
model="gpt-5.4-mini",
653653
response_model=UserDetail,
654654
messages=[{"role": "user", "content": data}],
655655
)
@@ -725,7 +725,7 @@ cache = AsyncInstructorCache()
725725
@cache.cache(ttl=3600)
726726
async def extract_async(data: str) -> UserDetail:
727727
return await client.create(
728-
model="gpt-3.5-turbo",
728+
model="gpt-5.4-mini",
729729
response_model=UserDetail,
730730
messages=[{"role": "user", "content": data}],
731731
)
@@ -742,7 +742,7 @@ Combine caching with [streaming responses](../../concepts/partial.md) for optima
742742
def extract_streamable(data: str) -> UserDetail:
743743
"""Cache the final result while still allowing streaming for new requests."""
744744
return client.create_partial(
745-
model="gpt-3.5-turbo",
745+
model="gpt-5.4-mini",
746746
response_model=UserDetail,
747747
messages=[{"role": "user", "content": data}],
748748
stream=True,

docs/blog/posts/chain-of-density.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ def summarize_article(article: str, summary_steps: int = 3):
294294
summary_chain = []
295295
# We first generate an initial summary
296296
summary: InitialSummary = client.create( # (2)!
297-
model="gpt-4-0613",
297+
model="gpt-5.4-mini",
298298
response_model=InitialSummary,
299299
messages=[
300300
{
@@ -323,7 +323,7 @@ def summarize_article(article: str, summary_steps: int = 3):
323323
]
324324
)
325325
new_summary: RewrittenSummary = client.create( # (3)!
326-
model="gpt-4-0613",
326+
model="gpt-5.4-mini",
327327
messages=[
328328
{
329329
"role": "system",
@@ -478,14 +478,14 @@ instructor jobs create-from-file generated.jsonl
478478
Once the job is complete, all we need to do is to then change the annotation in the function call to `distil_summarization` in our original file above to start using our new model.
479479

480480
```py
481-
@instructions.distil(model='gpt-3.5-turbo:finetuned-123', mode="dispatch") # (1)!
481+
@instructions.distil(model='gpt-5.4-mini:finetuned-123', mode="dispatch") # (1)!
482482
def distil_summarization(text: str) -> GeneratedSummary:
483483
summary_chain: List[str] = summarize_article(text)
484484
return GeneratedSummary(summary=summary_chain[-1])
485485
```
486486

487487
1. Don't forget to replace this with your new model id. OpenAI identifies fine tuned models with an id of
488-
ft:gpt-3.5-turbo-0613:personal::<id> under their Fine-tuning tab on their dashboard
488+
ft:gpt-5.4-mini:personal::<id> under their Fine-tuning tab on their dashboard
489489

490490
With that, you've now got your own fine-tuned model ready to go and serve data in production. We've seen how Instructor can make your life easier, from fine-tuning to distillation.
491491

docs/blog/posts/citations.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class Statements(BaseModel):
122122
"content": f"Does the following citation exist in the following context?\n\nCitation: {self.substring_quote}\n\nContext: {context}",
123123
}
124124
],
125-
model="gpt-3.5-turbo",
125+
model="gpt-5.4-mini",
126126
)
127127

128128
if resp.is_valid:
@@ -228,7 +228,7 @@ class AnswerWithCitaton(BaseModel):
228228
"content": f"Does the following answers match the question and the context?\n\nQuestion: {self.question}\n\nAnswer: {self.answer}\n\nContext: {context}",
229229
}
230230
],
231-
model="gpt-3.5-turbo",
231+
model="gpt-5.4-mini",
232232
)
233233

234234
if resp.is_valid:

docs/blog/posts/distilation-part1.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ instructions = Instructions(
154154
)
155155

156156

157-
@instructions.distil(model='gpt-3.5-turbo:finetuned-123', mode="dispatch") # (2)!
157+
@instructions.distil(model='gpt-5.4-mini:finetuned-123', mode="dispatch") # (2)!
158158
def fn(a: int, b: int) -> Multiply:
159159
resp = a + b
160160
return Multiply(a=a, b=b, result=resp)
@@ -164,7 +164,7 @@ def fn(a: int, b: int) -> Multiply:
164164
automatically serialize the content back into the `Pydantic`` model that we're looking for.
165165

166166
2. Don't forget to replace this with your new model id. OpenAI identifies fine tuned models with an id
167-
of `ft:gpt-3.5-turbo-0613:personal::<id>` under their **Fine-tuning** tab on their dashboard
167+
of `ft:gpt-5.4-mini:personal::<id>` under their **Fine-tuning** tab on their dashboard
168168

169169
With this, you can swap the function implementation, making it backward compatible. You can even imagine using the different models for different tasks or validating and running evals by using the original function and comparing it to the distillation.
170170

docs/blog/posts/fake-data.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ client = instructor.from_provider("openai/gpt-5-nano")
4040

4141
def generate_fake_users(count: int) -> Iterable[UserDetail]:
4242
return client.create(
43-
model="gpt-3.5-turbo",
43+
model="gpt-5.4-mini",
4444
response_model=Iterable[UserDetail],
4545
messages=[
4646
{"role": "user", "content": f"Generate a {count} synthetic users"},
@@ -79,7 +79,7 @@ client = instructor.from_provider("openai/gpt-5-nano")
7979

8080
def generate_fake_users(count: int) -> Iterable[UserDetail]:
8181
return client.create(
82-
model="gpt-3.5-turbo",
82+
model="gpt-5.4-mini",
8383
response_model=Iterable[UserDetail],
8484
messages=[
8585
{"role": "user", "content": f"Generate a {count} synthetic users"},
@@ -100,7 +100,7 @@ By incorporating names of celebrities as examples, we have shifted towards gener
100100

101101
## Leveraging Complex Example
102102

103-
To effectively generate synthetic examples with more nuance, lets upgrade to the "gpt-4-turbo-preview" model, use model level examples rather than attribute level examples:
103+
To effectively generate synthetic examples with more nuance, lets upgrade to the "gpt-5.4-mini" model, use model level examples rather than attribute level examples:
104104

105105
```Python
106106
import instructor
@@ -132,7 +132,7 @@ client = instructor.from_provider("openai/gpt-5-nano")
132132

133133
def generate_fake_users(count: int) -> Iterable[UserDetail]:
134134
return client.create(
135-
model="gpt-4-turbo-preview",
135+
model="gpt-5.4-mini",
136136
response_model=Iterable[UserDetail],
137137
messages=[
138138
{"role": "user", "content": f"Generate `{count}` synthetic examples"},
@@ -175,7 +175,7 @@ client = instructor.from_provider("openai/gpt-5-nano")
175175

176176
def generate_fake_users(count: int) -> Iterable[UserDetail]:
177177
return client.create(
178-
model="gpt-3.5-turbo",
178+
model="gpt-5.4-mini",
179179
response_model=Iterable[UserDetail],
180180
messages=[
181181
{"role": "user", "content": f"Generate `{count}` synthetic users"},

docs/blog/posts/full-fastapi-visibility.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ client = instructor.from_provider("openai/gpt-5-nano", async_client=True)
6464
@app.post("/user", response_model=UserDetail)
6565
async def endpoint_function(data: UserData) -> UserDetail:
6666
user_detail = await client.create(
67-
model="gpt-3.5-turbo",
67+
model="gpt-5.4-mini",
6868
response_model=UserDetail,
6969
messages=[
7070
{"role": "user", "content": f"Extract: `{data.query}`"},
@@ -103,7 +103,7 @@ client = instructor.from_provider("openai/gpt-4o")
103103
@app.post("/user", response_model=UserDetail)
104104
async def endpoint_function(data: UserData) -> UserDetail:
105105
user_detail = await client.create(
106-
model="gpt-3.5-turbo",
106+
model="gpt-5.4-mini",
107107
response_model=UserDetail,
108108
messages=[
109109
{"role": "user", "content": f"Extract: `{data.query}`"},
@@ -158,7 +158,7 @@ Sometimes, we might need to run multiple jobs in parallel. Let's see how we can
158158
async def extract_many_users(data: MultipleUserData):
159159
async def extract_user(query: str):
160160
user_detail = await client.create(
161-
model="gpt-3.5-turbo",
161+
model="gpt-5.4-mini",
162162
response_model=UserDetail,
163163
messages=[
164164
{"role": "user", "content": f"Extract: `{query}`"},
@@ -205,7 +205,7 @@ Sometimes, we might need to run multiple jobs in parallel. Let's see how we can
205205
@app.post("/user", response_model=UserDetail)
206206
async def endpoint_function(data: UserData) -> UserDetail:
207207
user_detail = await client.create(
208-
model="gpt-3.5-turbo",
208+
model="gpt-5.4-mini",
209209
response_model=UserDetail,
210210
messages=[
211211
{"role": "user", "content": f"Extract: `{data.query}`"},
@@ -219,7 +219,7 @@ Sometimes, we might need to run multiple jobs in parallel. Let's see how we can
219219
async def extract_many_users(data: MultipleUserData):
220220
async def extract_user(query: str):
221221
user_detail = await client.create(
222-
model="gpt-3.5-turbo",
222+
model="gpt-5.4-mini",
223223
response_model=UserDetail,
224224
messages=[
225225
{"role": "user", "content": f"Extract: `{query}`"},
@@ -277,7 +277,7 @@ Let's add a new endpoint to our server to see how this might work
277277
) # (1)!
278278
client = instructor.from_provider("openai/gpt-4o")
279279
users = await client.create(
280-
model="gpt-3.5-turbo",
280+
model="gpt-5.4-mini",
281281
response_model=Iterable[UserDetail],
282282
stream=True,
283283
messages=[
@@ -334,7 +334,7 @@ Let's add a new endpoint to our server to see how this might work
334334
@app.post("/user", response_model=UserDetail)
335335
async def endpoint_function(data: UserData) -> UserDetail:
336336
user_detail = await client.create(
337-
model="gpt-3.5-turbo",
337+
model="gpt-5.4-mini",
338338
response_model=UserDetail,
339339
messages=[
340340
{"role": "user", "content": f"Extract: `{data.query}`"},
@@ -348,7 +348,7 @@ Let's add a new endpoint to our server to see how this might work
348348
async def extract_many_users(data: MultipleUserData):
349349
async def extract_user(query: str):
350350
user_detail = await client.create(
351-
model="gpt-3.5-turbo",
351+
model="gpt-5.4-mini",
352352
response_model=UserDetail,
353353
messages=[
354354
{"role": "user", "content": f"Extract: `{query}`"},
@@ -367,7 +367,7 @@ Let's add a new endpoint to our server to see how this might work
367367
logfire.instrument_openai(suppressed_client, suppress_other_instrumentation=False)
368368
client = instructor.from_provider("openai/gpt-4o")
369369
users = await client.create(
370-
model="gpt-3.5-turbo",
370+
model="gpt-5.4-mini",
371371
response_model=Iterable[UserDetail],
372372
stream=True,
373373
messages=[

docs/blog/posts/generator.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ client = OpenAI(
138138
)
139139

140140
response_generator = client.create(
141-
model='gpt-3.5-turbo',
141+
model='gpt-5.4-mini',
142142
messages=[{'role': 'user', 'content': "What are some good reasons to smile?"}],
143143
temperature=0,
144144
stream=True,
@@ -268,7 +268,7 @@ prompt = (
268268

269269
start_perf = time.perf_counter()
270270
recommendations_stream = client.create(
271-
model="gpt-3.5-turbo-1106",
271+
model="gpt-5.4-mini",
272272
temperature=0.1,
273273
response_model=Iterable[ProductRecommendation],
274274
stream=True,
@@ -297,7 +297,7 @@ Time for first result (generator): 4.33 seconds
297297
```python
298298
start_perf = time.perf_counter()
299299
recommendations_list = client.create(
300-
model="gpt-3.5-turbo-1106",
300+
model="gpt-5.4-mini",
301301
temperature=0.1,
302302
response_model=Iterable[ProductRecommendation],
303303
stream=False,

0 commit comments

Comments
 (0)