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,13 +1,13 @@
[project]
name = "uipath-langchain"
version = "0.7.6"
version = "0.7.7"
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.9.10, <2.10.0",
"uipath>=2.9.12, <2.10.0",
"uipath-core>=0.5.2, <0.6.0",
"uipath-platform>=0.0.1, < 0.1.0",
"uipath-platform>=0.0.4, < 0.1.0",
"uipath-runtime>=0.9.1, <0.10.0",
"langgraph>=1.0.0, <2.0.0",
"langchain-core>=1.2.11, <2.0.0",
Expand Down
6 changes: 6 additions & 0 deletions src/uipath_langchain/agent/tools/context_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ async def context_tool_fn(query: Optional[str] = None) -> dict[str, Any]:
metadata={
"tool_type": "context",
"display_name": resource.name,
"index_name": resource.index_name,
"context_retrieval_mode": resource.settings.retrieval_mode,
},
)

Expand Down Expand Up @@ -202,6 +204,8 @@ async def create_deep_rag():
metadata={
"tool_type": "context",
"display_name": resource.name,
"index_name": resource.index_name,
"context_retrieval_mode": resource.settings.retrieval_mode,
},
)

Expand Down Expand Up @@ -335,6 +339,8 @@ async def context_batch_transform_wrapper(
metadata={
"tool_type": "context",
"display_name": resource.name,
"index_name": resource.index_name,
"context_retrieval_mode": resource.settings.retrieval_mode,
},
)
tool.set_tool_wrappers(awrapper=context_batch_transform_wrapper)
Expand Down
7 changes: 7 additions & 0 deletions src/uipath_langchain/agent/tools/escalation_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ class EscalationToolOutput(BaseModel):
action: Literal["approve", "reject"]
data: output_model

_bts_context: dict[str, Any] = {}

async def escalation_tool_fn(**kwargs: Any) -> dict[str, Any]:
recipient: TaskRecipient | None = (
await resolve_recipient_value(channel.recipients[0])
Expand Down Expand Up @@ -191,6 +193,10 @@ async def create_escalation_task():
is_actionable_message_enabled=channel.properties.is_actionable_message_enabled,
actionable_message_metadata=channel.properties.actionable_message_meta_data,
)

if created_task.id is not None:
_bts_context["task_key"] = str(created_task.id)

return WaitEscalation(
action=created_task,
app_folder_path=channel.properties.folder_name,
Expand Down Expand Up @@ -283,6 +289,7 @@ async def escalation_wrapper(
"recipient": None,
"args_schema": input_model,
"output_schema": output_model,
"_bts_context": _bts_context,
},
)
tool.set_tool_wrappers(awrapper=escalation_wrapper)
Expand Down
2 changes: 2 additions & 0 deletions src/uipath_langchain/agent/tools/integration_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ async def integration_tool_wrapper(
metadata={
"tool_type": "integration",
"display_name": resource.name,
"connector_key": resource.properties.connection.id,
"connector_name": resource.properties.connection.name,
},
)
tool.set_tool_wrappers(awrapper=integration_tool_wrapper)
Expand Down
15 changes: 14 additions & 1 deletion src/uipath_langchain/agent/tools/process_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from langchain.tools import BaseTool
from langchain_core.messages import ToolCall
from langchain_core.tools import StructuredTool
from uipath.agent.models.agent import AgentProcessToolResourceConfig
from uipath.agent.models.agent import AgentProcessToolResourceConfig, AgentToolType
from uipath.eval.mocks import mockable
from uipath.platform import UiPath
from uipath.platform.common import WaitJob
Expand Down Expand Up @@ -38,6 +38,7 @@ def create_process_tool(resource: AgentProcessToolResourceConfig) -> StructuredT
output_model: Any = create_model(resource.output_schema)

_span_context: dict[str, Any] = {}
_bts_context: dict[str, Any] = {}

async def process_tool_fn(**kwargs: Any):
attachments = get_job_attachments(input_model, kwargs)
Expand All @@ -52,6 +53,7 @@ async def process_tool_fn(**kwargs: Any):
)
async def invoke_process(**_tool_kwargs: Any):
parent_span_id = _span_context.pop("parent_span_id", None)
parent_operation_id = _bts_context.pop("parent_operation_id", None)

@durable_interrupt
async def start_job():
Expand All @@ -62,7 +64,17 @@ async def start_job():
folder_path=folder_path,
attachments=attachments,
parent_span_id=parent_span_id,
parent_operation_id=parent_operation_id,
)

if job.key:
bts_key = (
"wait_for_agent_job_key"
if resource.type == AgentToolType.AGENT
else "wait_for_job_key"
)
_bts_context[bts_key] = str(job.key)

return WaitJob(job=job, process_folder_key=job.folder_key)

return await start_job()
Expand Down Expand Up @@ -92,6 +104,7 @@ async def process_tool_wrapper(
"args_schema": input_model,
"output_schema": output_model,
"_span_context": _span_context,
"_bts_context": _bts_context,
},
argument_properties=resource.argument_properties,
)
Expand Down
8 changes: 8 additions & 0 deletions tests/agent/tools/test_process_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ async def test_invoke_calls_processes_invoke_async(
):
"""Test that invoking the tool calls client.processes.invoke_async."""
mock_job = MagicMock(spec=Job)
mock_job.key = "job-key-123"
mock_job.folder_key = "folder-key-123"

mock_client = MagicMock()
Expand All @@ -138,6 +139,7 @@ async def test_invoke_calls_processes_invoke_async(
folder_path="/Shared/MyFolder",
attachments=[],
parent_span_id=None,
parent_operation_id=None,
)

@pytest.mark.asyncio
Expand All @@ -148,6 +150,7 @@ async def test_invoke_interrupts_with_wait_job(
):
"""Test that after invoking, the tool interrupts with WaitJob."""
mock_job = MagicMock(spec=Job)
mock_job.key = "job-key-456"
mock_job.folder_key = "folder-key-456"

mock_client = MagicMock()
Expand All @@ -173,6 +176,7 @@ async def test_invoke_passes_input_arguments(
):
"""Test that input arguments are correctly passed to invoke_async."""
mock_job = MagicMock(spec=Job)
mock_job.key = "job-key"
mock_job.folder_key = "folder-key"

mock_client = MagicMock()
Expand All @@ -197,6 +201,7 @@ async def test_invoke_returns_interrupt_value(
):
"""Test that the tool returns the value from interrupt()."""
mock_job = MagicMock(spec=Job)
mock_job.key = "job-key"
mock_job.folder_key = "folder-key"

mock_client = MagicMock()
Expand All @@ -222,6 +227,7 @@ async def test_span_context_parent_span_id_passed_to_invoke(
):
"""Test that parent_span_id from _span_context is forwarded to invoke_async."""
mock_job = MagicMock(spec=Job)
mock_job.key = "job-key"
mock_job.folder_key = "folder-key"

mock_client = MagicMock()
Expand Down Expand Up @@ -249,6 +255,7 @@ async def test_span_context_consumed_after_invoke(
):
"""Test that parent_span_id is popped (consumed) from _span_context after use."""
mock_job = MagicMock(spec=Job)
mock_job.key = "job-key"
mock_job.folder_key = "folder-key"

mock_client = MagicMock()
Expand All @@ -274,6 +281,7 @@ async def test_span_context_defaults_to_none_when_empty(
):
"""Test that parent_span_id defaults to None when _span_context is empty."""
mock_job = MagicMock(spec=Job)
mock_job.key = "job-key"
mock_job.folder_key = "folder-key"

mock_client = MagicMock()
Expand Down
24 changes: 12 additions & 12 deletions uv.lock

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