Skip to content
Open
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-langchain"
version = "0.11.16"
version = "0.11.17"
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
14 changes: 8 additions & 6 deletions src/uipath_langchain/agent/react/json_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,18 @@ def _create_type_matcher(type_name: str, target_type: Any) -> Any:
"""

def matches_type(annotation: Any) -> bool:
"""Check if an annotation matches the target type name."""
"""Whether ``annotation`` refers to ``type_name``, by name or identity."""
if isinstance(annotation, ForwardRef):
return annotation.__forward_arg__ == type_name
if isinstance(annotation, str):
return annotation == type_name
if hasattr(annotation, "__name__") and annotation.__name__ == type_name:
return True
if target_type is not None and annotation is target_type:
return True
return False
# prefer the per-class marker: identity/target_type break when several
# dynamic models are built (they share the same module).
return (
getattr(annotation, "__uipath_marker_name__", None) == type_name
or getattr(annotation, "__name__", None) == type_name
or (target_type is not None and annotation is target_type)
)

return matches_type

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import inspect
import sys
from types import ModuleType
from typing import Any, Type
from typing import Any, Type, cast

from jsonschema_pydantic_converter import transform_with_modules
from pydantic import BaseModel, PydanticUndefinedAnnotation
Expand Down Expand Up @@ -53,6 +53,8 @@ def create_model(
setattr(pseudo_module, type_name, type_def)
if inspect.isclass(type_def) and issubclass(type_def, BaseModel):
type_def.__module__ = _DYNAMIC_MODULE_NAME
# per-class marker; survives the shared module being overwritten.
cast(Any, type_def).__uipath_marker_name__ = type_name

setattr(pseudo_module, model.__name__, model)
model.__module__ = _DYNAMIC_MODULE_NAME
Expand Down
25 changes: 25 additions & 0 deletions tests/agent/react/test_json_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,28 @@ class Parent(BaseModel):
)
assert result["child"]["value"] == '{"x": 1}'
assert result["child"]["data"] == {"y": 2}


class TestBuildOrderIndependence:
"""Detection must not depend on which dynamic model was built last."""

_SCHEMA = {
"type": "object",
"properties": {"doc": {"$ref": "#/$defs/Job_attachment"}},
"$defs": {
"Job_attachment": {
"type": "object",
"properties": {"ID": {"type": "string"}},
}
},
}

def test_first_model_still_detected_after_second_build(self) -> None:
"""Building a second attachment model must not blank out the first."""
first = create_model(self._SCHEMA)
assert get_json_paths_by_type(first, "__Job_attachment") == ["$.doc"]

# The second build overwrites the shared module's marker; the first
# model's per-class marker keeps its detection working.
create_model(self._SCHEMA)
assert get_json_paths_by_type(first, "__Job_attachment") == ["$.doc"]
2 changes: 1 addition & 1 deletion uv.lock

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

Loading