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
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "uipath-langchain"
version = "0.3.3"
version = "0.3.4"
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"
dependencies = [
"uipath>=2.4.0, <2.5.0",
"uipath-runtime>=0.4.0, <0.5.0",
"uipath>=2.4.14, <2.5.0",
"uipath-runtime>=0.4.1, <0.5.0",
"langgraph>=1.0.0, <2.0.0",
"langchain-core>=1.2.5, <2.0.0",
"aiosqlite==0.21.0",
Expand Down
129 changes: 25 additions & 104 deletions src/uipath_langchain/runtime/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
UiPathRuntimeEdge,
UiPathRuntimeGraph,
UiPathRuntimeNode,
transform_attachments,
transform_nullable_types,
transform_references,
)

try:
Expand Down Expand Up @@ -322,120 +325,38 @@ def get_entrypoints_schema(
}

if hasattr(graph, "input_schema"):
if hasattr(graph.input_schema, "model_json_schema"):
input_schema = graph.input_schema.model_json_schema()
unpacked_ref_def_properties, input_circular_dependency = _resolve_refs(
input_schema
)
input_schema = graph.get_input_jsonschema()
unpacked_ref_def_properties, input_circular_dependency = transform_references(
input_schema
)

# Process the schema to handle nullable types
processed_properties = _process_nullable_types(
unpacked_ref_def_properties.get("properties", {})
)
# Process the schema to handle nullable types
processed_properties = transform_nullable_types(
unpacked_ref_def_properties.get("properties", {})
)

schema["input"]["properties"] = processed_properties
schema["input"]["required"] = unpacked_ref_def_properties.get(
"required", []
)
schema["input"]["properties"] = processed_properties
schema["input"]["required"] = unpacked_ref_def_properties.get("required", [])
schema["input"] = transform_attachments(schema["input"])

if hasattr(graph, "output_schema"):
if hasattr(graph.output_schema, "model_json_schema"):
output_schema = graph.output_schema.model_json_schema()
unpacked_ref_def_properties, output_circular_dependency = _resolve_refs(
output_schema
)
output_schema = graph.get_output_jsonschema()
unpacked_ref_def_properties, output_circular_dependency = transform_references(
output_schema
)

# Process the schema to handle nullable types
processed_properties = _process_nullable_types(
unpacked_ref_def_properties.get("properties", {})
)
# Process the schema to handle nullable types
processed_properties = transform_nullable_types(
unpacked_ref_def_properties.get("properties", {})
)

schema["output"]["properties"] = processed_properties
schema["output"]["required"] = unpacked_ref_def_properties.get(
"required", []
)
schema["output"]["properties"] = processed_properties
schema["output"]["required"] = unpacked_ref_def_properties.get("required", [])
schema["output"] = transform_attachments(schema["output"])

return SchemaDetails(schema, input_circular_dependency, output_circular_dependency)


def _resolve_refs(schema, root=None, visited=None):
"""Recursively resolves $ref references in a JSON schema, handling circular references.

Returns:
tuple: (resolved_schema, has_circular_dependency)
"""
if root is None:
root = schema

if visited is None:
visited = set()

has_circular = False

if isinstance(schema, dict):
if "$ref" in schema:
ref_path = schema["$ref"]

if ref_path in visited:
# Circular dependency detected
return {
"type": "object",
"description": f"Circular reference to {ref_path}",
}, True

visited.add(ref_path)

# Resolve the reference
ref_parts = ref_path.lstrip("#/").split("/")
ref_schema = root
for part in ref_parts:
ref_schema = ref_schema.get(part, {})

result, circular = _resolve_refs(ref_schema, root, visited)
has_circular = has_circular or circular

# Remove from visited after resolution (allows the same ref in different branches)
visited.discard(ref_path)

return result, has_circular

resolved_dict = {}
for k, v in schema.items():
resolved_value, circular = _resolve_refs(v, root, visited)
resolved_dict[k] = resolved_value
has_circular = has_circular or circular
return resolved_dict, has_circular

elif isinstance(schema, list):
resolved_list = []
for item in schema:
resolved_item, circular = _resolve_refs(item, root, visited)
resolved_list.append(resolved_item)
has_circular = has_circular or circular
return resolved_list, has_circular

return schema, False


def _process_nullable_types(
schema: dict[str, Any] | list[Any] | Any,
) -> dict[str, Any] | list[Any]:
"""Process the schema to handle nullable types by removing anyOf with null and keeping the base type."""
if isinstance(schema, dict):
if "anyOf" in schema and len(schema["anyOf"]) == 2:
types = [t.get("type") for t in schema["anyOf"]]
if "null" in types:
non_null_type = next(
t for t in schema["anyOf"] if t.get("type") != "null"
)
return non_null_type

return {k: _process_nullable_types(v) for k, v in schema.items()}
elif isinstance(schema, list):
return [_process_nullable_types(item) for item in schema]
return schema


__all__ = [
"get_graph_schema",
"get_entrypoints_schema",
Expand Down
Loading