fix: handle BaseModel input in convert_to_model

This commit is contained in:
Greyson LaLonde
2026-05-03 14:17:03 +08:00
committed by GitHub
parent 3403f3cba9
commit 17e82743f6
5 changed files with 58 additions and 5 deletions

View File

@@ -1110,7 +1110,7 @@ Follow these guidelines:
)
def _export_output(
self, result: str
self, result: str | BaseModel
) -> tuple[BaseModel | None, dict[str, Any] | None]:
pydantic_output: BaseModel | None = None
json_output: dict[str, Any] | None = None

View File

@@ -153,16 +153,18 @@ class Converter(OutputConverter):
def convert_to_model(
result: str,
result: str | BaseModel,
output_pydantic: type[BaseModel] | None,
output_json: type[BaseModel] | None,
agent: Agent | BaseAgent | None = None,
converter_cls: type[Converter] | None = None,
) -> dict[str, Any] | BaseModel | str:
"""Convert a result string to a Pydantic model or JSON.
"""Convert a result to a Pydantic model or JSON.
Args:
result: The result string to convert.
result: The result to convert. Usually a JSON string, but a Pydantic
instance is also accepted when an upstream caller already produced
a structured object.
output_pydantic: The Pydantic model class to convert to.
output_json: The Pydantic model class to convert to JSON.
agent: The agent instance.
@@ -175,6 +177,11 @@ def convert_to_model(
if model is None:
return result
if isinstance(result, BaseModel):
if isinstance(result, model):
return result.model_dump() if output_json else result
result = result.model_dump_json()
if converter_cls:
return convert_with_instructions(
result=result,

View File

@@ -690,6 +690,27 @@ def test_multiple_guardrails_with_pydantic_output():
assert parsed["processed"] is True
def test_export_output_accepts_pydantic_input():
"""Regression test for #5458: _export_output must not crash with TypeError
when called with a Pydantic instance (e.g. when an upstream caller passes
an already-converted model from a context task)."""
from pydantic import BaseModel
class StructuredResult(BaseModel):
value: str
task = create_smart_task(
description="Test pydantic export",
expected_output="Structured output",
output_pydantic=StructuredResult,
)
instance = StructuredResult(value="ok")
pydantic_output, json_output = task._export_output(instance)
assert pydantic_output is instance
assert json_output is None
def test_guardrails_vs_single_guardrail_mutual_exclusion():
"""Test that guardrails list nullifies single guardrail."""

View File

@@ -87,6 +87,31 @@ def test_convert_to_model_with_no_model() -> None:
assert output == "Plain text"
def test_convert_to_model_with_basemodel_input_matching_pydantic() -> None:
instance = SimpleModel(name="John", age=30)
output = convert_to_model(instance, SimpleModel, None, None)
assert output is instance
def test_convert_to_model_with_basemodel_input_matching_json() -> None:
instance = SimpleModel(name="John", age=30)
output = convert_to_model(instance, None, SimpleModel, None)
assert output == {"name": "John", "age": 30}
def test_convert_to_model_with_basemodel_input_different_class() -> None:
class OtherModel(BaseModel):
name: str
age: int
extra: str = "default"
instance = OtherModel(name="John", age=30, extra="ignored")
output = convert_to_model(instance, SimpleModel, None, None)
assert isinstance(output, SimpleModel)
assert output.name == "John"
assert output.age == 30
def test_convert_to_model_with_special_characters() -> None:
json_string_test = """
{

2
uv.lock generated
View File

@@ -13,7 +13,7 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-04-28T07:00:00Z"
exclude-newer = "2026-04-27T16:00:00Z"
[manifest]
members = [