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 .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ jobs:

- name: Check formatting
run: uv run ruff format --check .

- name: Check httpx.Client() usage
run: uv run python scripts/lint_httpx_client.py
19 changes: 19 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
set quiet

default: lint format

lint:
ruff check .
python scripts/lint_httpx_client.py

format:
ruff format --check .
ruff check --fix

validate: lint format

build:
uv build

install:
uv sync --all-extras
5 changes: 3 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.1.43"
version = "0.1.44"
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 Expand Up @@ -69,7 +69,8 @@ dev = [
"pytest-asyncio>=1.0.0",
"pre-commit>=4.1.0",
"numpy>=1.24.0",
"pytest_httpx>=0.35.0"
"pytest_httpx>=0.35.0",
"rust-just>=1.39.0",
]

[tool.hatch.build.targets.wheel]
Expand Down
3 changes: 2 additions & 1 deletion src/uipath_langchain/agent/react/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from uipath.platform.guardrails import BaseGuardrail

from ..guardrails.actions import GuardrailAction
from ..tools import create_tool_node
from .guardrails.guardrails_subgraph import (
create_agent_init_guardrails_subgraph,
create_agent_terminate_guardrails_subgraph,
Expand Down Expand Up @@ -67,6 +66,8 @@ def create_agent(

Control flow tools (end_execution, raise_error) are auto-injected alongside regular tools.
"""
from ..tools import create_tool_node

if config is None:
config = AgentGraphConfig()

Expand Down
2 changes: 1 addition & 1 deletion src/uipath_langchain/agent/tools/escalation_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from uipath.eval.mocks import mockable
from uipath.platform.common import CreateEscalation

from ..react.types import AgentGraphNode, AgentTerminationSource
from .utils import sanitize_tool_name


Expand Down Expand Up @@ -82,6 +81,7 @@ async def escalation_tool_fn(
if outcome == EscalationAction.END:
output_detail = f"Escalation output: {escalation_output}"
termination_title = f"Agent run ended based on escalation outcome {outcome} with directive {escalation_action}"
from ..react.types import AgentGraphNode, AgentTerminationSource

return Command(
update={
Expand Down
5 changes: 4 additions & 1 deletion src/uipath_langchain/agent/tools/integration_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from uipath.platform.connections import ActivityMetadata, ActivityParameterLocationInfo

from uipath_langchain.agent.tools.tool_node import ToolWrapperMixin
from uipath_langchain.agent.wrappers.static_args_wrapper import get_static_args_wrapper

from .structured_tool_with_output_type import StructuredToolWithOutputType
from .utils import sanitize_dict_for_serialization, sanitize_tool_name
Expand Down Expand Up @@ -168,6 +167,10 @@ async def integration_tool_fn(**kwargs: Any):

return result

from uipath_langchain.agent.wrappers.static_args_wrapper import (
get_static_args_wrapper,
)

wrapper = get_static_args_wrapper(resource)

tool = StructuredToolWithStaticArgs(
Expand Down
2 changes: 1 addition & 1 deletion testcases/common/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Common testing utilities for UiPath testcases."""

from testcases.common.console import (
from .console import (
ConsoleTest,
PromptTest,
strip_ansi,
Expand Down
81 changes: 81 additions & 0 deletions tests/test_no_circular_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Test that all modules can be imported without circular dependency errors.

This test automatically discovers all modules in uipath_langchain and tests each
one with isolated imports to catch runtime circular imports.
"""

import importlib
import pkgutil
import sys
from typing import Iterator

import pytest


def discover_all_modules(package_name: str) -> Iterator[str]:
"""Discover all importable modules in a package recursively.

Args:
package_name: The top-level package name (e.g., 'uipath_langchain')

Yields:
Fully qualified module names (e.g., 'uipath_langchain.agent.tools')
"""
try:
package = importlib.import_module(package_name)
package_path = package.__path__
except ImportError:
return

# Recursively walk through all modules
for _importer, modname, _ispkg in pkgutil.walk_packages(
path=package_path, prefix=f"{package_name}.", onerror=lambda x: None
):
yield modname


def get_all_module_imports() -> list[str]:
"""Get all modules to test.

Returns:
List of module names to test
"""
modules = list(discover_all_modules("uipath_langchain"))

# Filter out optional dependency modules that won't be installed
exclude = {"uipath_langchain.chat.bedrock", "uipath_langchain.chat.vertex"}
return [m for m in modules if m not in exclude]


@pytest.mark.parametrize("module_name", get_all_module_imports())
def test_module_imports_with_isolation(module_name: str) -> None:
"""Test that a module can be imported in isolation.

Clears all uipath_langchain modules from sys.modules before importing to
catch circular imports that would be masked by module caching.

Args:
module_name: The fully qualified module name to test

Raises:
pytest.fail: If the module cannot be imported due to circular dependency
"""
# Clear all uipath_langchain modules from sys.modules to force fresh import
to_remove = [key for key in sys.modules.keys() if "uipath_langchain" in key]
for key in to_remove:
del sys.modules[key]

# Now try importing the module in isolation
try:
importlib.import_module(module_name)
except ImportError as e:
if "circular import" in str(e).lower():
pytest.fail(
f"Circular import in {module_name}:\n{e}",
pytrace=False,
)
# Other import errors (missing dependencies, syntax errors, etc)
pytest.fail(
f"Failed to import {module_name}:\n{e}",
pytrace=False,
)
26 changes: 25 additions & 1 deletion uv.lock

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