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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### New features

* `.stream()` and `.stream_async()` now support a `data_model` parameter for structured data extraction while streaming. (#262)
* `.to_solver()` now supports a `data_model` parameter for structured data extraction in evals. When provided, the solver uses `.chat_structured()` instead of `.chat()` and outputs JSON-serialized data. (#264)

## [0.15.0] - 2026-01-06

Expand Down
23 changes: 20 additions & 3 deletions chatlas/_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,7 @@ def to_solver(
*,
include_system_prompt: bool = False,
include_turns: bool = False,
data_model: type[BaseModel] | None = None,
):
"""
Create an InspectAI solver from this chat.
Expand All @@ -847,6 +848,11 @@ def to_solver(

Parameters
----------
data_model
A Pydantic model describing the structure of the data to extract.
When provided, the solver will use `.chat_structured()` instead of
`.chat()` to generate responses, and the output completion will be
JSON serialized from the model instance.
include_system_prompt
Whether to include the system prompt in the solver's starting
messages.
Expand Down Expand Up @@ -977,8 +983,14 @@ async def solve(state: InspectTaskState, generate):
input_content = [input_content]
input_content = [inspect_content_as_chatlas(x) for x in input_content]

# Generate the response (this can generate multiple turns!)
await chat_instance.chat_async(*input_content, echo="none")
# Generate the response
structured_result: BaseModel | None = None
if data_model is not None:
structured_result = await chat_instance.chat_structured_async(
*input_content, data_model=data_model, echo="none"
)
else:
await chat_instance.chat_async(*input_content, echo="none")

# Map change in chatlas Turn state back to Inspect message.state
# (Note: we skip the user prompt turn since it's already included)
Expand All @@ -1001,10 +1013,15 @@ async def solve(state: InspectTaskState, generate):
"Expected the last message in InspectAI state to be an assistant message"
)

if structured_result is not None:
completion = structured_result.model_dump_json()
else:
completion = turns[-1].text

state.output = imodel.ModelOutput(
model=model,
choices=[imodel.ChatCompletionChoice(message=last_message)],
completion=turns[-1].text,
completion=completion,
usage=usage,
time=time.perf_counter() - start_time,
)
Expand Down
Loading