uipath-openai-agents 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uipath_openai_agents/__init__.py +7 -0
- uipath_openai_agents/_cli/__init__.py +1 -0
- uipath_openai_agents/_cli/_templates/AGENTS.md.template +21 -0
- uipath_openai_agents/_cli/_templates/main.py.template +28 -0
- uipath_openai_agents/_cli/_templates/openai_agents.json.template +5 -0
- uipath_openai_agents/_cli/cli_new.py +81 -0
- uipath_openai_agents/chat/__init__.py +5 -0
- uipath_openai_agents/chat/openai.py +242 -0
- uipath_openai_agents/chat/supported_models.py +78 -0
- uipath_openai_agents/middlewares.py +8 -0
- uipath_openai_agents/py.typed +0 -0
- uipath_openai_agents/runtime/__init__.py +40 -0
- uipath_openai_agents/runtime/_serialize.py +51 -0
- uipath_openai_agents/runtime/_sqlite.py +190 -0
- uipath_openai_agents/runtime/_telemetry.py +32 -0
- uipath_openai_agents/runtime/agent.py +201 -0
- uipath_openai_agents/runtime/config.py +55 -0
- uipath_openai_agents/runtime/errors.py +48 -0
- uipath_openai_agents/runtime/factory.py +353 -0
- uipath_openai_agents/runtime/runtime.py +532 -0
- uipath_openai_agents/runtime/schema.py +490 -0
- uipath_openai_agents/runtime/storage.py +357 -0
- uipath_openai_agents-0.0.1.dist-info/METADATA +53 -0
- uipath_openai_agents-0.0.1.dist-info/RECORD +26 -0
- uipath_openai_agents-0.0.1.dist-info/WHEEL +4 -0
- uipath_openai_agents-0.0.1.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"""Runtime class for executing OpenAI Agents within the UiPath framework."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, AsyncGenerator
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
from agents import (
|
|
9
|
+
Agent,
|
|
10
|
+
Runner,
|
|
11
|
+
SQLiteSession,
|
|
12
|
+
)
|
|
13
|
+
from uipath.runtime import (
|
|
14
|
+
UiPathExecuteOptions,
|
|
15
|
+
UiPathRuntimeResult,
|
|
16
|
+
UiPathRuntimeStatus,
|
|
17
|
+
UiPathStreamOptions,
|
|
18
|
+
)
|
|
19
|
+
from uipath.runtime.errors import UiPathErrorCategory, UiPathErrorCode
|
|
20
|
+
from uipath.runtime.events import (
|
|
21
|
+
UiPathRuntimeEvent,
|
|
22
|
+
UiPathRuntimeMessageEvent,
|
|
23
|
+
UiPathRuntimeStateEvent,
|
|
24
|
+
)
|
|
25
|
+
from uipath.runtime.schema import UiPathRuntimeSchema
|
|
26
|
+
|
|
27
|
+
from ._serialize import serialize_output
|
|
28
|
+
from .errors import UiPathOpenAIAgentsErrorCode, UiPathOpenAIAgentsRuntimeError
|
|
29
|
+
from .schema import get_agent_schema, get_entrypoints_schema
|
|
30
|
+
from .storage import SqliteAgentStorage
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class UiPathOpenAIAgentRuntime:
|
|
34
|
+
"""
|
|
35
|
+
A runtime class for executing OpenAI Agents within the UiPath framework.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
agent: Agent,
|
|
41
|
+
runtime_id: str | None = None,
|
|
42
|
+
entrypoint: str | None = None,
|
|
43
|
+
storage_path: str | None = None,
|
|
44
|
+
loaded_object: Any | None = None,
|
|
45
|
+
storage: SqliteAgentStorage | None = None,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Initialize the runtime.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
agent: The OpenAI Agent to execute
|
|
52
|
+
runtime_id: Unique identifier for this runtime instance
|
|
53
|
+
entrypoint: Optional entrypoint name (for schema generation)
|
|
54
|
+
storage_path: Path to SQLite database for session persistence
|
|
55
|
+
loaded_object: Original loaded object (for schema inference)
|
|
56
|
+
storage: Optional storage instance for state persistence
|
|
57
|
+
"""
|
|
58
|
+
self.agent: Agent = agent
|
|
59
|
+
self.runtime_id: str = runtime_id or "default"
|
|
60
|
+
self.entrypoint: str | None = entrypoint
|
|
61
|
+
self.storage_path: str | None = storage_path
|
|
62
|
+
self.loaded_object: Any | None = loaded_object
|
|
63
|
+
self.storage: SqliteAgentStorage | None = storage
|
|
64
|
+
|
|
65
|
+
# Configure OpenAI Agents SDK to use Responses API
|
|
66
|
+
# UiPath supports both APIs via X-UiPath-LlmGateway-ApiFlavor header
|
|
67
|
+
# Using responses API for enhanced agent capabilities (conversation state, reasoning)
|
|
68
|
+
from agents import set_default_openai_api
|
|
69
|
+
|
|
70
|
+
set_default_openai_api("responses")
|
|
71
|
+
|
|
72
|
+
# Inject UiPath OpenAI client if UiPath credentials are available
|
|
73
|
+
self._setup_uipath_client()
|
|
74
|
+
|
|
75
|
+
def _setup_uipath_client(self) -> None:
|
|
76
|
+
"""Set up UiPath OpenAI client for agents to use UiPath gateway.
|
|
77
|
+
|
|
78
|
+
This injects the UiPath OpenAI client into the OpenAI Agents SDK
|
|
79
|
+
so all agents use the UiPath LLM Gateway instead of direct OpenAI.
|
|
80
|
+
|
|
81
|
+
The model is automatically extracted from the agent's `model` parameter.
|
|
82
|
+
If not specified in Agent(), the SDK uses agents.models.get_default_model().
|
|
83
|
+
|
|
84
|
+
If UiPath credentials are not available, falls back to default OpenAI client.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
# Import here to avoid circular dependency
|
|
88
|
+
from uipath_openai_agents.chat import UiPathChatOpenAI
|
|
89
|
+
|
|
90
|
+
# Check if UiPath credentials are available
|
|
91
|
+
org_id = os.getenv("UIPATH_ORGANIZATION_ID")
|
|
92
|
+
tenant_id = os.getenv("UIPATH_TENANT_ID")
|
|
93
|
+
token = os.getenv("UIPATH_ACCESS_TOKEN")
|
|
94
|
+
uipath_url = os.getenv("UIPATH_URL")
|
|
95
|
+
|
|
96
|
+
if org_id and tenant_id and token and uipath_url:
|
|
97
|
+
# Extract model from agent definition
|
|
98
|
+
from agents.models import get_default_model
|
|
99
|
+
|
|
100
|
+
from uipath_openai_agents.chat.supported_models import OpenAIModels
|
|
101
|
+
|
|
102
|
+
if hasattr(self.agent, "model") and self.agent.model:
|
|
103
|
+
model_name = str(self.agent.model)
|
|
104
|
+
else:
|
|
105
|
+
model_name = get_default_model()
|
|
106
|
+
|
|
107
|
+
# Normalize generic model names to UiPath-specific versions
|
|
108
|
+
model_name = OpenAIModels.normalize_model_name(model_name)
|
|
109
|
+
|
|
110
|
+
# Update agent's model to normalized version so SDK sends correct model in body
|
|
111
|
+
self.agent.model = model_name
|
|
112
|
+
|
|
113
|
+
# Create UiPath OpenAI client
|
|
114
|
+
uipath_client = UiPathChatOpenAI(
|
|
115
|
+
token=token,
|
|
116
|
+
org_id=org_id,
|
|
117
|
+
tenant_id=tenant_id,
|
|
118
|
+
model_name=model_name,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Inject into OpenAI Agents SDK
|
|
122
|
+
# This makes all agents use UiPath gateway
|
|
123
|
+
from agents.models import _openai_shared
|
|
124
|
+
|
|
125
|
+
_openai_shared.set_default_openai_client(uipath_client.async_client)
|
|
126
|
+
|
|
127
|
+
except ImportError:
|
|
128
|
+
# UiPath chat module not available, skip injection
|
|
129
|
+
pass
|
|
130
|
+
except Exception:
|
|
131
|
+
# If injection fails, fall back to default OpenAI client
|
|
132
|
+
# Agents will use OPENAI_API_KEY if set
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
async def execute(
|
|
136
|
+
self,
|
|
137
|
+
input: dict[str, Any] | None = None,
|
|
138
|
+
options: UiPathExecuteOptions | None = None,
|
|
139
|
+
) -> UiPathRuntimeResult:
|
|
140
|
+
"""
|
|
141
|
+
Execute the agent with the provided input and configuration.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
input: Input dictionary containing the message for the agent
|
|
145
|
+
options: Execution options
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
UiPathRuntimeResult with the agent's output
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
UiPathOpenAIAgentRuntimeError: If execution fails
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
result: UiPathRuntimeResult | None = None
|
|
155
|
+
async for event in self._run_agent(input, options, stream_events=False):
|
|
156
|
+
if isinstance(event, UiPathRuntimeResult):
|
|
157
|
+
result = event
|
|
158
|
+
|
|
159
|
+
if result is None:
|
|
160
|
+
raise RuntimeError("Agent completed without returning a result")
|
|
161
|
+
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
raise self._create_runtime_error(e) from e
|
|
166
|
+
|
|
167
|
+
async def stream(
|
|
168
|
+
self,
|
|
169
|
+
input: dict[str, Any] | None = None,
|
|
170
|
+
options: UiPathStreamOptions | None = None,
|
|
171
|
+
) -> AsyncGenerator[UiPathRuntimeEvent, None]:
|
|
172
|
+
"""
|
|
173
|
+
Stream agent execution events in real-time.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
input: Input dictionary containing the message for the agent
|
|
177
|
+
options: Stream options
|
|
178
|
+
|
|
179
|
+
Yields:
|
|
180
|
+
UiPathRuntimeEvent instances during execution,
|
|
181
|
+
then the final UiPathRuntimeResult
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
UiPathOpenAIAgentRuntimeError: If execution fails
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
async for event in self._run_agent(input, options, stream_events=True):
|
|
188
|
+
yield event
|
|
189
|
+
except Exception as e:
|
|
190
|
+
raise self._create_runtime_error(e) from e
|
|
191
|
+
|
|
192
|
+
async def _run_agent(
|
|
193
|
+
self,
|
|
194
|
+
input: dict[str, Any] | None,
|
|
195
|
+
options: UiPathExecuteOptions | UiPathStreamOptions | None,
|
|
196
|
+
stream_events: bool,
|
|
197
|
+
) -> AsyncGenerator[UiPathRuntimeEvent | UiPathRuntimeResult, None]:
|
|
198
|
+
"""
|
|
199
|
+
Core agent execution logic used by both execute() and stream().
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
input: Input dictionary
|
|
203
|
+
options: Execution/stream options
|
|
204
|
+
stream_events: Whether to stream events during execution
|
|
205
|
+
|
|
206
|
+
Yields:
|
|
207
|
+
Runtime events if stream_events=True, then final result
|
|
208
|
+
"""
|
|
209
|
+
agent_input = self._prepare_agent_input(input)
|
|
210
|
+
is_resuming = bool(options and options.resume)
|
|
211
|
+
|
|
212
|
+
# Create session for state persistence (local to this run)
|
|
213
|
+
# SQLiteSession automatically loads existing data from the database when created
|
|
214
|
+
session: SQLiteSession | None = None
|
|
215
|
+
if self.storage_path:
|
|
216
|
+
session = SQLiteSession(self.runtime_id, self.storage_path)
|
|
217
|
+
|
|
218
|
+
# Run the agent with streaming if events requested
|
|
219
|
+
try:
|
|
220
|
+
if stream_events:
|
|
221
|
+
# Use streaming for events
|
|
222
|
+
async for event_or_result in self._run_agent_streamed(
|
|
223
|
+
agent_input, options, stream_events, session
|
|
224
|
+
):
|
|
225
|
+
yield event_or_result
|
|
226
|
+
else:
|
|
227
|
+
# Use non-streaming for simple execution
|
|
228
|
+
result = await Runner.run(
|
|
229
|
+
starting_agent=self.agent,
|
|
230
|
+
input=agent_input,
|
|
231
|
+
session=session,
|
|
232
|
+
)
|
|
233
|
+
yield self._create_success_result(result.final_output)
|
|
234
|
+
|
|
235
|
+
except Exception:
|
|
236
|
+
# Clean up session on error
|
|
237
|
+
if session and self.storage_path and not is_resuming:
|
|
238
|
+
# Delete incomplete session
|
|
239
|
+
try:
|
|
240
|
+
import os
|
|
241
|
+
|
|
242
|
+
if os.path.exists(self.storage_path):
|
|
243
|
+
os.remove(self.storage_path)
|
|
244
|
+
except Exception:
|
|
245
|
+
pass # Best effort cleanup
|
|
246
|
+
raise
|
|
247
|
+
finally:
|
|
248
|
+
# Always close session after run completes with proper WAL checkpoint
|
|
249
|
+
if session:
|
|
250
|
+
self._close_session_with_checkpoint(session)
|
|
251
|
+
|
|
252
|
+
async def _run_agent_streamed(
|
|
253
|
+
self,
|
|
254
|
+
agent_input: str | list[Any],
|
|
255
|
+
options: UiPathExecuteOptions | UiPathStreamOptions | None,
|
|
256
|
+
stream_events: bool,
|
|
257
|
+
session: SQLiteSession | None,
|
|
258
|
+
) -> AsyncGenerator[UiPathRuntimeEvent | UiPathRuntimeResult, None]:
|
|
259
|
+
"""
|
|
260
|
+
Run agent using streaming API to enable event streaming.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
agent_input: Prepared agent input (string or list of messages)
|
|
264
|
+
options: Execution/stream options
|
|
265
|
+
stream_events: Whether to yield streaming events to caller
|
|
266
|
+
|
|
267
|
+
Yields:
|
|
268
|
+
Runtime events if stream_events=True, then final result
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
# Use Runner.run_streamed() for streaming events (returns RunResultStreaming directly)
|
|
272
|
+
result = Runner.run_streamed(
|
|
273
|
+
starting_agent=self.agent,
|
|
274
|
+
input=agent_input,
|
|
275
|
+
session=session,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Stream events from the agent
|
|
279
|
+
async for event in result.stream_events():
|
|
280
|
+
# Emit the event to caller if streaming is enabled
|
|
281
|
+
if stream_events:
|
|
282
|
+
runtime_event = self._convert_stream_event_to_runtime_event(event)
|
|
283
|
+
if runtime_event:
|
|
284
|
+
yield runtime_event
|
|
285
|
+
|
|
286
|
+
# Stream complete - yield final result
|
|
287
|
+
yield self._create_success_result(result.final_output)
|
|
288
|
+
|
|
289
|
+
def _convert_stream_event_to_runtime_event(
|
|
290
|
+
self,
|
|
291
|
+
event: Any,
|
|
292
|
+
) -> UiPathRuntimeEvent | None:
|
|
293
|
+
"""
|
|
294
|
+
Convert OpenAI streaming event to UiPath runtime event.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
event: Streaming event from Runner.run_streamed()
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
UiPathRuntimeEvent or None if event should be filtered
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
event_type = getattr(event, "type", None)
|
|
304
|
+
event_name = getattr(event, "name", None)
|
|
305
|
+
|
|
306
|
+
# Handle run item events (messages, tool calls, etc.)
|
|
307
|
+
if event_type == "run_item_stream_event":
|
|
308
|
+
event_item = getattr(event, "item", None)
|
|
309
|
+
if event_item:
|
|
310
|
+
# Determine if this is a message or state event
|
|
311
|
+
if event_name in ["message_output_created", "reasoning_item_created"]:
|
|
312
|
+
return UiPathRuntimeMessageEvent(
|
|
313
|
+
payload=serialize_output(event_item),
|
|
314
|
+
metadata={"event_name": event_name},
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
return UiPathRuntimeStateEvent(
|
|
318
|
+
payload=serialize_output(event_item),
|
|
319
|
+
metadata={"event_name": event_name},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Handle agent updated events
|
|
323
|
+
if event_type == "agent_updated_stream_event":
|
|
324
|
+
new_agent = getattr(event, "new_agent", None)
|
|
325
|
+
if new_agent:
|
|
326
|
+
return UiPathRuntimeStateEvent(
|
|
327
|
+
payload={"agent_name": getattr(new_agent, "name", "unknown")},
|
|
328
|
+
metadata={"event_type": "agent_updated"},
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Filter out raw response events (too granular)
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
def _prepare_agent_input(self, input: dict[str, Any] | None) -> str | list[Any]:
|
|
335
|
+
"""
|
|
336
|
+
Prepare agent input from UiPath input dictionary.
|
|
337
|
+
|
|
338
|
+
Supports two input formats:
|
|
339
|
+
- {"message": "text"} → returns string for Runner.run()
|
|
340
|
+
- {"messages": [...]} → returns list of message dicts for Runner.run()
|
|
341
|
+
|
|
342
|
+
Note: When using sessions, string input is preferred as it doesn't
|
|
343
|
+
require a session_input_callback.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
input: Input dictionary from UiPath
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
String or list for Runner.run() input parameter
|
|
350
|
+
|
|
351
|
+
Raises:
|
|
352
|
+
ValueError: If input doesn't contain "message" or "messages" field
|
|
353
|
+
"""
|
|
354
|
+
if not input:
|
|
355
|
+
raise ValueError(
|
|
356
|
+
"Input is required. Provide either 'message' (string) or 'messages' (list of message dicts)"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Check for "messages" field (list of message dicts)
|
|
360
|
+
if "messages" in input:
|
|
361
|
+
messages = input["messages"]
|
|
362
|
+
# Ensure it's a list
|
|
363
|
+
if isinstance(messages, list):
|
|
364
|
+
return messages
|
|
365
|
+
else:
|
|
366
|
+
raise ValueError(
|
|
367
|
+
"'messages' field must be a list of message dictionaries"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Check for "message" field (simple string)
|
|
371
|
+
if "message" in input:
|
|
372
|
+
message = input["message"]
|
|
373
|
+
# Return as string (OpenAI Agents SDK handles string → message conversion)
|
|
374
|
+
return str(message)
|
|
375
|
+
|
|
376
|
+
# No valid field found
|
|
377
|
+
raise ValueError(
|
|
378
|
+
"Input must contain either 'message' (string) or 'messages' (list of message dicts). "
|
|
379
|
+
f"Got keys: {list(input.keys())}"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def _serialize_message(self, message: Any) -> dict[str, Any]:
|
|
383
|
+
"""
|
|
384
|
+
Serialize an agent message for event streaming.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
message: Message object from the agent
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Dictionary representation of the message
|
|
391
|
+
"""
|
|
392
|
+
serialized = serialize_output(message)
|
|
393
|
+
|
|
394
|
+
# Ensure the result is a dictionary
|
|
395
|
+
if isinstance(serialized, dict):
|
|
396
|
+
return serialized
|
|
397
|
+
|
|
398
|
+
# Fallback to wrapping in a content field
|
|
399
|
+
return {"content": serialized}
|
|
400
|
+
|
|
401
|
+
def _create_success_result(self, output: Any) -> UiPathRuntimeResult:
|
|
402
|
+
"""
|
|
403
|
+
Create result for successful completion.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
output: The agent's output
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
UiPathRuntimeResult with serialized output
|
|
410
|
+
"""
|
|
411
|
+
# Serialize output
|
|
412
|
+
serialized_output = self._serialize_output(output)
|
|
413
|
+
|
|
414
|
+
# Ensure output is a dictionary
|
|
415
|
+
if not isinstance(serialized_output, dict):
|
|
416
|
+
serialized_output = {"result": serialized_output}
|
|
417
|
+
|
|
418
|
+
return UiPathRuntimeResult(
|
|
419
|
+
output=serialized_output,
|
|
420
|
+
status=UiPathRuntimeStatus.SUCCESSFUL,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def _serialize_output(self, output: Any) -> Any:
|
|
424
|
+
"""
|
|
425
|
+
Serialize agent output to a JSON-compatible format.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
output: Output from the agent
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
JSON-compatible representation
|
|
432
|
+
"""
|
|
433
|
+
return serialize_output(output)
|
|
434
|
+
|
|
435
|
+
def _create_runtime_error(self, e: Exception) -> UiPathOpenAIAgentsRuntimeError:
|
|
436
|
+
"""
|
|
437
|
+
Handle execution errors and create appropriate runtime error.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
e: The exception that occurred
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
UiPathOpenAIAgentsRuntimeError with appropriate error code
|
|
444
|
+
"""
|
|
445
|
+
if isinstance(e, UiPathOpenAIAgentsRuntimeError):
|
|
446
|
+
return e
|
|
447
|
+
|
|
448
|
+
detail = f"Error: {str(e)}"
|
|
449
|
+
|
|
450
|
+
if isinstance(e, json.JSONDecodeError):
|
|
451
|
+
return UiPathOpenAIAgentsRuntimeError(
|
|
452
|
+
UiPathErrorCode.INPUT_INVALID_JSON,
|
|
453
|
+
"Invalid JSON input",
|
|
454
|
+
detail,
|
|
455
|
+
UiPathErrorCategory.USER,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
if isinstance(e, TimeoutError):
|
|
459
|
+
return UiPathOpenAIAgentsRuntimeError(
|
|
460
|
+
UiPathOpenAIAgentsErrorCode.TIMEOUT_ERROR,
|
|
461
|
+
"Agent execution timed out",
|
|
462
|
+
detail,
|
|
463
|
+
UiPathErrorCategory.USER,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
return UiPathOpenAIAgentsRuntimeError(
|
|
467
|
+
UiPathOpenAIAgentsErrorCode.AGENT_EXECUTION_FAILURE,
|
|
468
|
+
"Agent execution failed",
|
|
469
|
+
detail,
|
|
470
|
+
UiPathErrorCategory.USER,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
async def get_schema(self) -> UiPathRuntimeSchema:
|
|
474
|
+
"""
|
|
475
|
+
Get schema for this OpenAI Agent runtime.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
UiPathRuntimeSchema with input/output schemas and graph structure
|
|
479
|
+
"""
|
|
480
|
+
entrypoints_schema = get_entrypoints_schema(self.agent, self.loaded_object)
|
|
481
|
+
|
|
482
|
+
return UiPathRuntimeSchema(
|
|
483
|
+
filePath=self.entrypoint,
|
|
484
|
+
uniqueId=str(uuid4()),
|
|
485
|
+
type="agent",
|
|
486
|
+
input=entrypoints_schema.get("input", {}),
|
|
487
|
+
output=entrypoints_schema.get("output", {}),
|
|
488
|
+
graph=get_agent_schema(self.agent),
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def _close_session_with_checkpoint(self, session: SQLiteSession) -> None:
|
|
492
|
+
"""Close SQLite session with WAL checkpoint to release file locks.
|
|
493
|
+
|
|
494
|
+
OpenAI SDK uses sync sqlite3 which doesn't release file locks on Windows
|
|
495
|
+
without explicit WAL checkpoint. This is especially important for cleanup.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
session: The SQLiteSession to close
|
|
499
|
+
"""
|
|
500
|
+
try:
|
|
501
|
+
# Get the underlying connection
|
|
502
|
+
conn = session._get_connection()
|
|
503
|
+
|
|
504
|
+
# Commit any pending transactions
|
|
505
|
+
try:
|
|
506
|
+
conn.commit()
|
|
507
|
+
except Exception:
|
|
508
|
+
pass # Best effort
|
|
509
|
+
|
|
510
|
+
# Force WAL checkpoint to release shared memory files
|
|
511
|
+
# This is especially important on Windows
|
|
512
|
+
try:
|
|
513
|
+
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
|
|
514
|
+
conn.commit()
|
|
515
|
+
except Exception:
|
|
516
|
+
pass # Best effort
|
|
517
|
+
|
|
518
|
+
except Exception:
|
|
519
|
+
pass # Best effort cleanup
|
|
520
|
+
|
|
521
|
+
finally:
|
|
522
|
+
# Always call the session's close method
|
|
523
|
+
try:
|
|
524
|
+
session.close()
|
|
525
|
+
except Exception:
|
|
526
|
+
pass # Best effort
|
|
527
|
+
|
|
528
|
+
async def dispose(self) -> None:
|
|
529
|
+
"""Cleanup runtime resources."""
|
|
530
|
+
# Sessions are closed immediately after each run in _run_agent()
|
|
531
|
+
# Storage is shared across runtimes and managed by the factory
|
|
532
|
+
pass
|