Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions py/src/braintrust/integrations/instructor/test_instructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,32 @@ def test_instructor_openai_retries_then_succeeds(self, setup_logger, memory_logg
for k in ("tokens", "prompt_tokens", "completion_tokens", "total_tokens"):
assert k not in parent_metrics

@pytest.mark.vcr
def test_instructor_openai_create_partial_logs_final_snapshot(self, setup_logger, memory_logger):
"""Partial snapshots are yielded to callers, but only the final one is logged."""
from braintrust import wrap_openai

client = wrap_openai(_make_openai_client())
patched = instructor.from_openai(client, mode=instructor.Mode.TOOLS)

result = list(
patched.chat.completions.create_partial(
model="gpt-4o-mini",
response_model=Person,
max_retries=3,
messages=[{"role": "user", "content": "Extract Grace, age 45."}],
)
)
assert len(result) > 1, "create_partial should yield progressive snapshots"
assert _dump(result[-1]) == {"name": "Grace", "age": 45}

spans = _all_spans(memory_logger)
task_spans = find_spans_by_type(spans, SpanTypeAttribute.TASK)
assert len(task_spans) == 1
parent = task_spans[0]
assert parent["span_attributes"]["name"] == "instructor.create_partial"
assert _dump(parent["output"]) == _dump(result[-1])


class TestInstructorAutoInstrumentSubprocess:
"""auto_instrument() must instrument Instructor in a fresh subprocess too."""
Expand Down
24 changes: 16 additions & 8 deletions py/src/braintrust/integrations/instructor/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
- ``name`` : ``instructor.create`` (or ``.create_with_completion`` etc.)
- ``input`` : ``{"response_model": <name>, "mode": <str>, "messages": [...]}``
- ``output``: the extracted Pydantic model (or list of models for
iterable/partial helpers). This is Instructor's product and is not
present on any provider child span.
iterable helpers). Partial helpers log only their final snapshot. This is
Instructor's product and is not present on any provider child span.
- ``metadata``: ``model``, ``provider``, ``response_model`` (class name),
``mode``, ``max_retries``, ``retry_count``, ``validation_errors``. The
``model`` / ``provider`` pair carries attribution for the framework span
Expand Down Expand Up @@ -310,7 +310,7 @@ async def _wrapper(wrapped: Any, instance: Any, args: Any, kwargs: Any) -> Any:
return _wrapper


def _make_sync_iterable_wrapper(span_name: str):
def _make_sync_iterable_wrapper(span_name: str, *, final_only: bool = False):
def _wrapper(wrapped: Any, instance: Any, args: Any, kwargs: Any) -> Any:
response_model = _extract_response_model(args, kwargs)
max_retries = kwargs.get("max_retries", 3)
Expand All @@ -328,7 +328,11 @@ def _iterate() -> Iterator[Any]:
try:
result = wrapped(*args, **kwargs)
for item in result:
output.append(_extract_output(item))
extracted = _extract_output(item)
if final_only:
output = extracted
else:
output.append(extracted)
yield item
except Exception as exc:
span.log(
Expand Down Expand Up @@ -360,7 +364,7 @@ def _iterate() -> Iterator[Any]:
return _wrapper


def _make_async_iterable_wrapper(span_name: str):
def _make_async_iterable_wrapper(span_name: str, *, final_only: bool = False):
def _wrapper(wrapped: Any, instance: Any, args: Any, kwargs: Any) -> Any:
response_model = _extract_response_model(args, kwargs)
max_retries = kwargs.get("max_retries", 3)
Expand All @@ -380,7 +384,11 @@ async def _iterate() -> AsyncIterator[Any]:
if inspect.isawaitable(result):
result = await result
async for item in result:
output.append(_extract_output(item))
extracted = _extract_output(item)
if final_only:
output = extracted
else:
output.append(extracted)
yield item
except Exception as exc:
span.log(
Expand Down Expand Up @@ -416,10 +424,10 @@ async def _iterate() -> AsyncIterator[Any]:
# patcher targets in ``patchers.py``.
_create_wrapper = _make_sync_wrapper("instructor.create")
_create_with_completion_wrapper = _make_sync_wrapper("instructor.create_with_completion")
_create_partial_wrapper = _make_sync_iterable_wrapper("instructor.create_partial")
_create_partial_wrapper = _make_sync_iterable_wrapper("instructor.create_partial", final_only=True)
_create_iterable_wrapper = _make_sync_iterable_wrapper("instructor.create_iterable")

_async_create_wrapper = _make_async_wrapper("instructor.create")
_async_create_with_completion_wrapper = _make_async_wrapper("instructor.create_with_completion")
_async_create_partial_wrapper = _make_async_iterable_wrapper("instructor.create_partial")
_async_create_partial_wrapper = _make_async_iterable_wrapper("instructor.create_partial", final_only=True)
_async_create_iterable_wrapper = _make_async_iterable_wrapper("instructor.create_iterable")
Loading