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
9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
[project]
name = "uipath-langchain"
version = "0.17.8"
version = "0.17.9"
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.14.13, <2.15.0",
# TODO(release): restore ">=2.14.15, <2.15.0" and drop the testpypi
# source once uipath 2.14.15 (UiPath/uipath-python#1886) is on PyPI.
"uipath==2.14.15.dev1018867616",
"uipath-core>=0.5.29, <0.6.0",
"uipath-platform>=0.2.28, <0.3.0",
"uipath-runtime>=0.13.0, <0.14.0",
Expand Down Expand Up @@ -182,3 +184,6 @@ name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

[tool.uv.sources]
uipath = { index = "testpypi" }
10 changes: 9 additions & 1 deletion src/uipath_langchain/_cli/cli_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import click
from uipath._cli._utils._console import ConsoleLogger
from uipath._cli.middlewares import MiddlewareResult
from uipath._cli.models.agent_frameworks import AgentFramework
from uipath._cli.models.project_types import ProjectType

console = ConsoleLogger()

Expand Down Expand Up @@ -47,8 +49,14 @@ def generate_pyproject(target_directory, project_name):
f.write(toml_content)


def langgraph_new_middleware(name: str) -> MiddlewareResult:
def langgraph_new_middleware(
name: str,
project_type: ProjectType = ProjectType.AUTO,
agent_framework: AgentFramework | None = None,
) -> MiddlewareResult:
"""Middleware to create demo langchain agent"""
if not AgentFramework.LANGCHAIN.claims_scaffold(project_type, agent_framework):
return MiddlewareResult(should_continue=True)

directory = os.getcwd()

Expand Down
93 changes: 93 additions & 0 deletions tests/cli/test_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import pytest
from packaging.specifiers import SpecifierSet
from packaging.version import Version
from uipath._cli.models.agent_frameworks import AgentFramework
from uipath._cli.models.project_types import ProjectType

from uipath_langchain._cli.cli_new import (
UIPATH_LANGCHAIN_SCAFFOLD_MINOR,
Expand All @@ -14,6 +16,97 @@
PIN_RE = re.compile(r'"uipath-langchain\[bedrock,vertex\]([^"]*)"')


class TestProjectTypeGate:
"""The middleware only claims agent scaffolds; everything else passes through."""

def test_function_type_passes_through(
self, tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""project_type='function' must defer to the base `uipath new` scaffold.

Regression guard for uipath-python#1543: installing uipath-langchain
used to hijack `uipath new` unconditionally, making the base function
scaffold unreachable.
"""
monkeypatch.chdir(tmp_path)
result = langgraph_new_middleware("demo", project_type=ProjectType.FUNCTION)
assert result.should_continue is True
assert not os.path.exists("main.py")
assert not os.path.exists("langgraph.json")
assert not os.path.exists("pyproject.toml")

def test_auto_type_scaffolds_agent(
self, tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""--type auto (the default): an installed framework claims the scaffold."""
monkeypatch.chdir(tmp_path)
result = langgraph_new_middleware("demo", project_type=ProjectType.AUTO)
assert result.should_continue is False
assert os.path.exists("main.py")
assert os.path.exists("langgraph.json")

def test_auto_type_plain_string_scaffolds_agent(
self, tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Raw-string 'auto' from other callers is claimed the same way."""
monkeypatch.chdir(tmp_path)
# Deliberately off-type: the point is that a plain string still gates.
result = langgraph_new_middleware("demo", project_type="auto") # type: ignore[arg-type]
assert result.should_continue is False
assert os.path.exists("langgraph.json")

def test_agent_type_scaffolds_agent(
self, tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
result = langgraph_new_middleware(
"demo",
project_type=ProjectType.AGENT,
agent_framework=AgentFramework.LANGCHAIN,
)
assert result.should_continue is False
assert os.path.exists("main.py")
assert os.path.exists("langgraph.json")

def test_other_agent_framework_passes_through(
self, tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Another framework's scaffold must be left to its own integration."""
monkeypatch.chdir(tmp_path)
result = langgraph_new_middleware(
"demo",
project_type=ProjectType.AGENT,
agent_framework=AgentFramework.PYDANTIC_AI,
)
assert result.should_continue is True
assert not os.path.exists("main.py")
assert not os.path.exists("langgraph.json")
assert not os.path.exists("pyproject.toml")

def test_plain_string_arguments_still_gate_correctly(
self, tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Callers passing raw strings (older CLIs) must hit the same gate."""
monkeypatch.chdir(tmp_path)
# Deliberately off-type: the point is that plain strings still gate.
result = langgraph_new_middleware(
"demo",
project_type="agent", # type: ignore[arg-type]
agent_framework="pydantic-ai", # type: ignore[arg-type]
)
assert result.should_continue is True
assert not os.path.exists("langgraph.json")

def test_default_type_stays_agent_for_old_base_cli(
self, tmp_path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Older `uipath` versions call without project_type; keep scaffolding an agent."""
monkeypatch.chdir(tmp_path)
result = langgraph_new_middleware("demo")
assert result.should_continue is False
assert os.path.exists("langgraph.json")


class TestUipathLangchainScaffoldPin:
"""The scaffolded pin must admit the uipath-langchain release it ships with."""

Expand Down
22 changes: 11 additions & 11 deletions uv.lock

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

Loading