Skip to content
Draft
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
10 changes: 7 additions & 3 deletions src/uipath_langchain/agent/react/json_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,14 @@ def _coerce_field(key: str, value: Any, schema: type[BaseModel] | None) -> Any:

if get_origin(annotation) is list:
item_args = get_args(annotation)
item_schema = None
if item_args and _is_pydantic_model(item_args[0]):
item_schema = item_args[0]
if isinstance(value, list):
# str-typed items are protected like a scalar str field: a list[str]
# element that merely looks like JSON must stay a string.
if item_args and _unwrap_optional(item_args[0]) is str:
return value
item_schema = (
item_args[0] if item_args and _is_pydantic_model(item_args[0]) else None
)
return [coerce_json_strings(item, item_schema) for item in value]

return coerce_json_strings(value)
Expand Down
37 changes: 37 additions & 0 deletions tests/agent/react/test_json_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,40 @@ def test_get_job_attachments_after_second_build(self) -> None:

attachments = get_job_attachments(first, self._data())
assert [str(att.id) for att in attachments] == [self._ATTACHMENT_ID]


# -- coerce_json_strings: list[str] items are protected by the schema ----------


class TestCoerceListOfStrProtected:
"""The elements of a list[str] field are str-typed per the schema and must be
left untouched, exactly as a scalar str field is — even when an element looks
like JSON or a Python repr."""

def test_list_str_items_not_coerced(self) -> None:
class Schema(BaseModel):
tags: list[str]

data = {"tags": ['{"not": "a dict"}', "hello", "[1, 2]"]}
assert coerce_json_strings(data, Schema) == {
"tags": ['{"not": "a dict"}', "hello", "[1, 2]"]
}

def test_optional_list_str_items_not_coerced(self) -> None:
class Schema(BaseModel):
tags: Optional[list[str]] = None

data = {"tags": ["{'a': 1}"]}
assert coerce_json_strings(data, Schema) == {"tags": ["{'a': 1}"]}

def test_list_model_items_still_coerced(self) -> None:
"""Regression guard: model-typed list items must still be coerced."""

class Item(BaseModel):
data: dict[str, Any] | None = None

class Schema(BaseModel):
items: list[Item]

data = {"items": [{"data": '{"size": 1}'}]}
assert coerce_json_strings(data, Schema) == {"items": [{"data": {"size": 1}}]}
Loading