Skip to content

Commit a3ad1a5

Browse files
jxnlxtzie
andauthored
docs(agents): note uv run --with (#2029)
Co-authored-by: ben lu <benlu.dev@outlook.com> Co-authored-by: Ben Lu <58869945+xtzie@users.noreply.github.com>
1 parent 61c2663 commit a3ad1a5

File tree

7 files changed

+52
-45
lines changed

7 files changed

+52
-45
lines changed

AGENT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Run tests: `uv run pytest tests/`
66
- Run single test: `uv run pytest tests/path_to_test.py::test_name`
77
- Skip LLM tests: `uv run pytest tests/ -k 'not llm and not openai'`
8+
- Temp deps for a run: `uv run --with <pkg>[==version] <command>` (example: `uv run --with pytest-asyncio --with anthropic pytest tests/...`)
89
- Type check: `uv run ty check`
910
- Lint: `uv run ruff check instructor examples tests`
1011
- Format: `uv run ruff format instructor examples tests`

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ Documentation improvements are always welcome! Follow these guidelines:
241241

242242
We encourage contributions to our evaluation tests:
243243

244-
1. Explore existing evals in the [evals directory](https://github.com/instructor-ai/instructor/tree/main/tests/llm/test_openai/evals)
244+
1. Explore existing evals in the [evals directory](https://github.com/instructor-ai/instructor/tree/main/tests/llm)
245245
2. Contribute new evals as pytest tests
246246
3. Evals should test specific capabilities or edge cases of the library or models
247247
4. Follow the existing patterns for structuring eval tests

instructor/dsl/partial.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -445,21 +445,16 @@ def model_from_chunks(
445445
# Always use trailing-strings mode to preserve incomplete data during streaming
446446
# PartialLiteralMixin is deprecated - completeness-based validation handles Literals
447447
partial_mode = "trailing-strings"
448-
chunk_buffer = []
449448
final_obj = None
450449
for chunk in json_chunks:
451-
chunk_buffer += chunk
452-
if len(chunk_buffer) < 2:
450+
if chunk is None:
453451
continue
454-
potential_object += remove_control_chars("".join(chunk_buffer))
455-
chunk_buffer = []
456-
obj = process_potential_object(
457-
potential_object, partial_mode, partial_model, **kwargs
458-
)
459-
final_obj = obj
460-
yield obj
461-
if chunk_buffer:
462-
potential_object += remove_control_chars(chunk_buffer[0])
452+
if not isinstance(chunk, str):
453+
try:
454+
chunk = str(chunk)
455+
except Exception:
456+
continue
457+
potential_object += remove_control_chars(chunk)
463458
obj = process_potential_object(
464459
potential_object, partial_mode, partial_model, **kwargs
465460
)
@@ -487,7 +482,14 @@ async def model_from_chunks_async(
487482
partial_mode = "trailing-strings"
488483
final_obj = None
489484
async for chunk in json_chunks:
490-
potential_object += chunk
485+
if chunk is None:
486+
continue
487+
if not isinstance(chunk, str):
488+
try:
489+
chunk = str(chunk)
490+
except Exception:
491+
continue
492+
potential_object += remove_control_chars(chunk)
491493
obj = process_potential_object(
492494
potential_object, partial_mode, partial_model, **kwargs
493495
)

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ dev = [
5050
"python-dotenv>=1.0.1",
5151
"pytest-xdist>=3.8.0",
5252
"pre-commit>=4.2.0",
53+
"anthropic==0.71.0",
54+
"xmltodict>=0.13,<1.1",
5355
]
5456
docs = [
5557
"mkdocs<2.0.0,>=1.6.1",

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ typer==0.20.0
115115
# via instructor (pyproject.toml)
116116
typing-extensions==4.15.0
117117
# via
118+
# aiosignal
119+
# anyio
118120
# openai
119121
# pydantic
120122
# pydantic-core

tests/dsl/test_partial.py

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,32 @@ def test_partial():
9797
}, "Partial model JSON schema has changed"
9898

9999

100+
partial_chunks = ["\n", "\t", " ", "\x00", '{"a": 42, "b": {"b": 1}}']
101+
expected_sync_models = [
102+
# First model has default values (nested models show their fields as None)
103+
{"a": None, "b": {"b": None}},
104+
{"a": None, "b": {"b": None}},
105+
{"a": None, "b": {"b": None}},
106+
{"a": None, "b": {"b": None}},
107+
# Last model has all fields populated from JSON
108+
{"a": 42, "b": {"b": 1}},
109+
]
110+
expected_async_models = [
111+
{"a": None, "b": {"b": None}},
112+
{"a": None, "b": {"b": None}},
113+
{"a": None, "b": {"b": None}},
114+
{"a": None, "b": {"b": None}},
115+
{"a": 42, "b": {"b": 1}},
116+
]
117+
118+
100119
def test_partial_with_whitespace():
101120
partial = Partial[SamplePartial]
102-
103121
# Get the actual models from chunks - must provide complete data for final validation
104-
models = list(
105-
partial.model_from_chunks(["\n", "\t", " ", '{"a": 42, "b": {"b": 1}}'])
106-
)
107-
108-
# Print actual values for debugging
109-
print(f"Number of models: {len(models)}")
122+
models = list(partial.model_from_chunks(partial_chunks))
123+
assert len(models) == len(expected_sync_models)
110124
for i, model in enumerate(models):
111-
print(f"Model {i}: {model.model_dump()}")
112-
113-
# Actual behavior: When whitespace chunks are processed, we may get models
114-
# First model has default values (nested models show their fields as None)
115-
assert models[0].model_dump() == {"a": None, "b": {"b": None}}
116-
117-
# Last model has all fields populated from JSON
118-
assert models[-1].model_dump() == {"a": 42, "b": {"b": 1}}
119-
120-
# Check we have the expected number of models (2 instead of 4)
121-
assert len(models) == 2
125+
assert model.model_dump() == expected_sync_models[i]
122126

123127

124128
@pytest.mark.asyncio
@@ -127,23 +131,15 @@ async def test_async_partial_with_whitespace():
127131

128132
# Handle any leading whitespace from the model - must provide complete data for final validation
129133
async def async_generator():
130-
for chunk in ["\n", "\t", " ", '{"a": 42, "b": {"b": 1}}']:
134+
for chunk in partial_chunks:
131135
yield chunk
132136

133-
# With completeness-based validation, nested models are constructed with None fields
134-
expected_model_dicts = [
135-
{"a": None, "b": {"b": None}},
136-
{"a": None, "b": {"b": None}},
137-
{"a": None, "b": {"b": None}},
138-
{"a": 42, "b": {"b": 1}},
139-
]
140-
141137
i = 0
142138
async for model in partial.model_from_chunks_async(async_generator()):
143-
assert model.model_dump() == expected_model_dicts[i]
139+
# Expected behavior: When whitespace chunks are processed, we should always get a model
140+
assert model.model_dump() == expected_async_models[i]
144141
i += 1
145-
146-
assert model.model_dump() == {"a": 42, "b": {"b": 1}}
142+
assert i == len(expected_async_models)
147143

148144

149145
@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="OPENAI_API_KEY not set")

uv.lock

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)