Skip to content

Commit d0439ec

Browse files
chore: refactor agents runtime and fix mypy (#25)
1 parent 5bcc9ab commit d0439ec

15 files changed

Lines changed: 200 additions & 254 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ maintainers = [
2323
register = "uipath_agents.middlewares:register_middleware"
2424

2525
[project.entry-points."uipath.runtime.factories"]
26-
langgraph = "uipath_agents.runtime:register_runtime_factory"
26+
agents = "uipath_agents.runtime:register_runtime_factory"
2727

2828
[project.urls]
2929
Homepage = "https://uipath.com"

src/uipath_agents/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from .middlewares import register_middleware
2+
from .runtime import register_runtime_factory
23

3-
__all__ = [
4-
"register_middleware",
5-
]
4+
__all__ = ["register_middleware", "register_runtime_factory"]

src/uipath_agents/_cli/cli_debug.py

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
from typing import Optional
44

55
from dotenv import load_dotenv
6-
from uipath._cli._debug._bridge import ConsoleDebugBridge, get_debug_bridge
7-
from uipath._cli._utils._debug import setup_debugging
6+
from uipath._cli._debug._bridge import get_debug_bridge
87
from uipath._cli._utils._studio_project import StudioClient
98
from uipath._cli.middlewares import MiddlewareResult
109
from uipath._config import UiPathConfig
@@ -18,10 +17,7 @@
1817
UiPathRuntimeFactoryProtocol,
1918
UiPathRuntimeFactoryRegistry,
2019
UiPathRuntimeProtocol,
21-
UiPathRuntimeResult,
22-
UiPathStreamOptions,
2320
)
24-
from uipath.runtime.events import UiPathRuntimeStateEvent
2521
from uipath.tracing import LlmOpsHttpExporter
2622
from uipath_langchain._cli._runtime._exception import LangGraphRuntimeError
2723

@@ -33,64 +29,15 @@
3329
logger = logging.getLogger(__name__)
3430

3531

36-
async def execute_runtime(ctx: UiPathRuntimeContext) -> UiPathRuntimeResult:
37-
with ctx:
38-
runtime: UiPathRuntimeProtocol | None = None
39-
factory: UiPathRuntimeFactoryProtocol | None = None
40-
try:
41-
factory = UiPathRuntimeFactoryRegistry.get(context=ctx)
42-
runtime = await factory.new_runtime(ctx.entrypoint, ctx.job_id or "default")
43-
options = UiPathExecuteOptions(resume=ctx.resume)
44-
ctx.result = await runtime.execute(input=ctx.get_input(), options=options)
45-
return ctx.result
46-
finally:
47-
if runtime:
48-
await runtime.dispose()
49-
if factory:
50-
await factory.dispose()
51-
52-
53-
async def debug_runtime(
54-
ctx: UiPathRuntimeContext,
55-
) -> UiPathRuntimeResult | None:
56-
with ctx:
57-
runtime: UiPathRuntimeProtocol | None = None
58-
factory: UiPathRuntimeFactoryProtocol | None = None
59-
try:
60-
factory = UiPathRuntimeFactoryRegistry.get(context=ctx)
61-
runtime = await factory.new_runtime(ctx.entrypoint, "default")
62-
debug_bridge: UiPathDebugBridgeProtocol = ConsoleDebugBridge()
63-
await debug_bridge.emit_execution_started()
64-
options = UiPathStreamOptions(resume=ctx.resume)
65-
async for event in runtime.stream(ctx.get_input(), options=options):
66-
if isinstance(event, UiPathRuntimeResult):
67-
await debug_bridge.emit_execution_completed(event)
68-
ctx.result = event
69-
elif isinstance(event, UiPathRuntimeStateEvent):
70-
await debug_bridge.emit_state_update(event)
71-
return ctx.result
72-
finally:
73-
if runtime:
74-
await runtime.dispose()
75-
if factory:
76-
await factory.dispose()
77-
78-
7932
def agents_debug_middleware(
8033
entrypoint: Optional[str],
8134
input: Optional[str],
8235
resume: bool,
8336
input_file: Optional[str],
8437
output_file: Optional[str],
85-
debug: bool,
86-
debug_port: int,
8738
**kwargs,
8839
) -> MiddlewareResult:
89-
"""Middleware to handle LangGraph execution"""
90-
91-
if not setup_debugging(debug, debug_port):
92-
logger.error(f"Failed to start debug server on port {debug_port}")
93-
40+
"""Middleware to handle Agents LangGraph execution"""
9441
_prepare_agent_run_files()
9542

9643
try:
@@ -99,6 +46,7 @@ async def execute_debug_runtime():
9946
trace_manager = UiPathTraceManager()
10047

10148
with UiPathRuntimeContext.with_defaults(
49+
entrypoint=entrypoint,
10250
input=input,
10351
input_file=input_file,
10452
output_file=output_file,
@@ -117,7 +65,7 @@ async def execute_debug_runtime():
11765
factory = UiPathRuntimeFactoryRegistry.get(context=ctx)
11866

11967
runtime = await factory.new_runtime(
120-
entrypoint, ctx.job_id or "default"
68+
ctx.entrypoint, ctx.job_id or "agents"
12169
)
12270

12371
debug_bridge: UiPathDebugBridgeProtocol = get_debug_bridge(ctx)

src/uipath_agents/_cli/cli_run.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from dotenv import load_dotenv
66
from uipath._cli._debug._bridge import ConsoleDebugBridge
77
from uipath._cli._utils._common import read_resource_overwrites_from_file
8-
from uipath._cli._utils._debug import setup_debugging
98
from uipath._cli.middlewares import MiddlewareResult
109
from uipath._utils._bindings import ResourceOverwritesContext
1110
from uipath.core import UiPathTraceManager
@@ -36,6 +35,9 @@ async def execute_runtime(ctx: UiPathRuntimeContext) -> UiPathRuntimeResult:
3635
runtime: UiPathRuntimeProtocol | None = None
3736
factory: UiPathRuntimeFactoryProtocol | None = None
3837
try:
38+
if ctx.entrypoint is None:
39+
raise ValueError("Entrypoint is required for runtime execution")
40+
3941
factory = UiPathRuntimeFactoryRegistry.get(context=ctx)
4042
runtime = await factory.new_runtime(ctx.entrypoint, ctx.job_id or "default")
4143
options = UiPathExecuteOptions(resume=ctx.resume)
@@ -55,8 +57,10 @@ async def debug_runtime(
5557
runtime: UiPathRuntimeProtocol | None = None
5658
factory: UiPathRuntimeFactoryProtocol | None = None
5759
try:
60+
if ctx.entrypoint is None:
61+
raise ValueError("Entrypoint is required for runtime debugging")
5862
factory = UiPathRuntimeFactoryRegistry.get(context=ctx)
59-
runtime = await factory.new_runtime(ctx.entrypoint, "default")
63+
runtime = await factory.new_runtime(ctx.entrypoint, "agents")
6064
debug_bridge: UiPathDebugBridgeProtocol = ConsoleDebugBridge()
6165
await debug_bridge.emit_execution_started()
6266
options = UiPathStreamOptions(resume=ctx.resume)
@@ -81,15 +85,9 @@ def agents_run_middleware(
8185
input_file: Optional[str],
8286
output_file: Optional[str],
8387
trace_file: Optional[str],
84-
debug: bool,
85-
debug_port: int,
8688
**kwargs,
8789
) -> MiddlewareResult:
88-
"""Middleware to handle LangGraph execution"""
89-
90-
if not setup_debugging(debug, debug_port):
91-
logger.error(f"Failed to start debug server on port {debug_port}")
92-
90+
"""Middleware to handle Agents LangGraph execution"""
9391
_prepare_agent_run_files()
9492

9593
try:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
AGENT_FILENAME = "agent.json"
1+
AGENT_ENTRYPOINT = "agent.json"
22
AGENT_BUILDER_FILENAME = ".agent-builder"

src/uipath_agents/_cli/runtime.py

Lines changed: 0 additions & 55 deletions
This file was deleted.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import os
2+
3+
from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor
4+
from opentelemetry.instrumentation.asyncio import AsyncioInstrumentor
5+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
6+
from opentelemetry.instrumentation.sqlite3 import SQLite3Instrumentor
7+
from uipath.runtime import UiPathRuntimeContext, UiPathRuntimeProtocol
8+
from uipath_langchain._cli._runtime._factory import LangGraphRuntimeFactory
9+
10+
from ..constants import AGENT_ENTRYPOINT
11+
from .runtime import create_agent_langgraph_runtime
12+
13+
_telemetry_initialized = False
14+
15+
16+
def _configure_agents_telemetry() -> None:
17+
"""Configure telemetry for agents. Idempotent - only runs once."""
18+
global _telemetry_initialized
19+
if _telemetry_initialized:
20+
return
21+
22+
os.environ.setdefault("OTEL_SERVICE_NAME", "uipath-agents")
23+
AsyncioInstrumentor().instrument()
24+
HTTPXClientInstrumentor().instrument()
25+
AioHttpClientInstrumentor().instrument()
26+
SQLite3Instrumentor().instrument()
27+
_telemetry_initialized = True
28+
29+
30+
class AgentRuntimeFactory(LangGraphRuntimeFactory):
31+
"""Factory for creating Agent runtimes from agent.json configuration."""
32+
33+
def __init__(self, context: UiPathRuntimeContext) -> None:
34+
"""Initialize the factory.
35+
36+
Args:
37+
context: UiPathRuntimeContext to use for runtime creation
38+
"""
39+
super().__init__(context)
40+
_configure_agents_telemetry()
41+
42+
def discover_entrypoints(self) -> list[str]:
43+
"""Discover the Agent entrypoint agent.json"""
44+
return [AGENT_ENTRYPOINT]
45+
46+
async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
47+
"""Discover runtime instances for all entrypoints.
48+
49+
Returns:
50+
List of AgentLangGraphRuntime instances
51+
"""
52+
entrypoints = self.discover_entrypoints()
53+
memory = await self._get_memory()
54+
55+
runtimes: list[UiPathRuntimeProtocol] = []
56+
for entrypoint in entrypoints:
57+
runtime = create_agent_langgraph_runtime(
58+
entrypoint, entrypoint, self.context, memory
59+
)
60+
runtimes.append(runtime)
61+
62+
return runtimes
63+
64+
async def new_runtime(
65+
self, entrypoint: str, runtime_id: str
66+
) -> UiPathRuntimeProtocol:
67+
"""Create a new Agent runtime instance.
68+
69+
Args:
70+
entrypoint: Agent entrypoint (agent.json)
71+
runtime_id: Unique identifier for the runtime instance
72+
73+
Returns:
74+
Configured AgentLangGraphRuntime instance
75+
"""
76+
memory = await self._get_memory()
77+
return create_agent_langgraph_runtime(
78+
runtime_id, entrypoint, self.context, memory
79+
)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import logging
2+
from pathlib import Path
3+
from typing import Any
4+
5+
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
6+
from uipath.runtime import (
7+
UiPathRuntimeContext,
8+
)
9+
from uipath.runtime.schema import UiPathRuntimeSchema
10+
from uipath_langchain._cli._runtime._runtime import (
11+
LangGraphRuntime,
12+
)
13+
14+
from uipath_agents.agent_graph_builder import build_agent_graph
15+
16+
from ..utils import load_agent_configuration
17+
from .utils import validate_json_against_json_schema
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
class AgentLangGraphRuntime(LangGraphRuntime):
23+
"""Agents runtime extending LangGraph base runtime."""
24+
25+
def __init__(
26+
self,
27+
runtime_id: str,
28+
graph_resolver: Any,
29+
memory: AsyncSqliteSaver,
30+
entrypoint: str,
31+
) -> None:
32+
super().__init__(runtime_id, graph_resolver, memory)
33+
self.entrypoint = entrypoint
34+
35+
async def get_schema(self) -> UiPathRuntimeSchema:
36+
"""Return the runtime schema for this agent."""
37+
agent_json_path = Path.cwd() / self.entrypoint
38+
agent_definition = load_agent_configuration(agent_json_path)
39+
40+
return UiPathRuntimeSchema(
41+
filePath=self.entrypoint,
42+
uniqueId=self.runtime_id,
43+
type="agent",
44+
input=agent_definition.input_schema or {},
45+
output=agent_definition.output_schema or {},
46+
)
47+
48+
49+
def create_agent_langgraph_runtime(
50+
runtime_id: str,
51+
entrypoint: str,
52+
ctx: UiPathRuntimeContext,
53+
memory: AsyncSqliteSaver,
54+
) -> AgentLangGraphRuntime:
55+
"""Create runtime for agents with input validation.
56+
57+
Args:
58+
runtime_id: Unique identifier for the runtime instance
59+
entrypoint: Agent file path containing the Agent definition
60+
ctx: Runtime context containing input, resume flag, and other metadata
61+
memory: AsyncSqliteSaver instance for checkpoint/state management
62+
63+
Returns:
64+
AgentLangGraphRuntime instance configured for the agent
65+
"""
66+
67+
async def graph_resolver():
68+
"""Load agent config, validate input on new runs, and build graph."""
69+
agent_json_path = Path.cwd() / entrypoint
70+
agent_definition = load_agent_configuration(agent_json_path)
71+
72+
agent_input: dict[str, Any] = {}
73+
if not ctx.resume:
74+
agent_input = validate_json_against_json_schema(
75+
agent_definition.input_schema, ctx.input
76+
)
77+
78+
return await build_agent_graph(agent_definition, input_data=agent_input)
79+
80+
return AgentLangGraphRuntime(runtime_id, graph_resolver, memory, entrypoint)

src/uipath_agents/_cli/json_schema_utils.py renamed to src/uipath_agents/_cli/runtime/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pydantic import ValidationError
44
from uipath.utils.dynamic_schema import jsonschema_to_pydantic
55

6-
from .exceptions import InputValidationError
6+
from ..exceptions import InputValidationError
77

88

99
def validate_json_against_json_schema(

0 commit comments

Comments
 (0)