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
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-langchain"
version = "0.17.1"
version = "0.17.2"
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 All @@ -20,7 +20,9 @@ dependencies = [
"httpx>=0.27.0",
"httpx2>=2.5.0, <2.10.0",
"openinference-instrumentation-langchain>=0.1.69, <0.2.0",
"jsonschema-pydantic-converter>=0.4.0",
"datamodel-code-generator>=0.76.0",
"jsonschema-pydantic-converter>=0.4.1",
"jsonschema>=4.23.0",
"jsonpath-ng>=1.7.0",
Comment thread
vldcmp-uipath marked this conversation as resolved.
"mcp==2.0.0",
"pillow>=12.1.1",
Expand Down Expand Up @@ -89,6 +91,7 @@ dev = [
"numpy>=1.24.0",
"pytest_httpx>=0.35.0",
"rust-just>=1.39.0",
"types-jsonschema>=4.23.0",
"types-protobuf<7",
"packaging>=24.0",
# tests/agent/tools/test_mcp/real_server.py hosts real MCP servers over real
Expand Down
60 changes: 60 additions & 0 deletions src/uipath_langchain/agent/react/_datamodel_code_generator_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Base model for every runtime-generated schema class.

`datamodel-code-generator` emits classes deriving from a configurable base. Every
model generated from a tool or agent schema derives from
:class:`UiPathDatamodelCodeGeneratorBaseModel`, which supplies the two
configuration options the runtime depends on:

* ``serialize_by_alias`` -- properties whose JSON names are not valid Python
identifiers are generated as sanitized fields carrying an alias. Serializing by
alias is what puts the original JSON names back on the wire, so a tool call
reaches Integration Service with the property names its schema declared.
* ``extra="allow"`` -- the default for a schema that does not say otherwise. A
schema with ``additionalProperties: false`` generates its own
``model_config``, and Pydantic merges that over this one, so ``extra`` still
ends up ``"forbid"`` there.

The base also makes a declared JSON property name usable as an attribute, which
is the half of that contract sanitizing the field name would otherwise break.
See :meth:`UiPathDatamodelCodeGeneratorBaseModel.__getattr__`.
"""

from typing import Any

from pydantic import BaseModel, ConfigDict


class UiPathDatamodelCodeGeneratorBaseModel(BaseModel):
"""Base class for models generated from JSON Schema at runtime."""

model_config = ConfigDict(serialize_by_alias=True, extra="allow")
Comment thread
vldcmp-uipath marked this conversation as resolved.

def __getattr__(self, name: str) -> Any:
"""Resolve a declared JSON property name to its sanitized field.

Serializing by alias makes ``model_dump()`` alias-keyed, and consumers read
the dumped keys straight back off the instance: LangChain's
``BaseTool._parse_input`` builds its kwargs with ``getattr(result, key)``
for every dumped key. Under the legacy backend the JSON name *was* the
field name, so that resolved; here the field is sanitized, so a property
declared ``Content-Type`` would raise ``AttributeError`` mid-tool-call.

Accepting the alias restores what callers already relied on -- the name the
schema declared works as an attribute -- so alias-unaware consumers behave
as they did before.

Only reached when normal lookup fails, so real fields, methods and extras
are untouched.
"""
try:
return super().__getattr__(name) # type: ignore[misc]
except AttributeError:
pass

for field_name, field in type(self).model_fields.items():
if field.alias == name and field_name != name:
return getattr(self, field_name)

raise AttributeError(
f"{type(self).__name__!r} object has no attribute {name!r}"
)
Loading
Loading