uipath-openai-agents 0.0.1__py3-none-any.whl → 0.0.3__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 +24 -3
- uipath_openai_agents/_cli/_templates/AGENTS.md.template +1 -1
- uipath_openai_agents/_cli/_templates/main.py.template +13 -7
- uipath_openai_agents/_cli/cli_new.py +2 -6
- uipath_openai_agents/chat/__init__.py +30 -3
- uipath_openai_agents/chat/supported_models.py +42 -60
- uipath_openai_agents/runtime/__init__.py +34 -8
- uipath_openai_agents/runtime/_serialize.py +4 -4
- uipath_openai_agents/runtime/agent.py +0 -17
- uipath_openai_agents/runtime/factory.py +0 -120
- uipath_openai_agents/runtime/runtime.py +21 -205
- uipath_openai_agents/runtime/schema.py +124 -172
- uipath_openai_agents-0.0.3.dist-info/METADATA +146 -0
- uipath_openai_agents-0.0.3.dist-info/RECORD +23 -0
- uipath_openai_agents/runtime/_sqlite.py +0 -190
- uipath_openai_agents/runtime/_telemetry.py +0 -32
- uipath_openai_agents/runtime/storage.py +0 -357
- uipath_openai_agents-0.0.1.dist-info/METADATA +0 -53
- uipath_openai_agents-0.0.1.dist-info/RECORD +0 -26
- {uipath_openai_agents-0.0.1.dist-info → uipath_openai_agents-0.0.3.dist-info}/WHEEL +0 -0
- {uipath_openai_agents-0.0.1.dist-info → uipath_openai_agents-0.0.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
"""Runtime class for executing OpenAI Agents within the UiPath framework."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import os
|
|
5
4
|
from typing import Any, AsyncGenerator
|
|
6
5
|
from uuid import uuid4
|
|
7
6
|
|
|
8
|
-
from agents import
|
|
9
|
-
Agent,
|
|
10
|
-
Runner,
|
|
11
|
-
SQLiteSession,
|
|
12
|
-
)
|
|
7
|
+
from agents import Agent, Runner
|
|
13
8
|
from uipath.runtime import (
|
|
14
9
|
UiPathExecuteOptions,
|
|
15
10
|
UiPathRuntimeResult,
|
|
@@ -27,7 +22,6 @@ from uipath.runtime.schema import UiPathRuntimeSchema
|
|
|
27
22
|
from ._serialize import serialize_output
|
|
28
23
|
from .errors import UiPathOpenAIAgentsErrorCode, UiPathOpenAIAgentsRuntimeError
|
|
29
24
|
from .schema import get_agent_schema, get_entrypoints_schema
|
|
30
|
-
from .storage import SqliteAgentStorage
|
|
31
25
|
|
|
32
26
|
|
|
33
27
|
class UiPathOpenAIAgentRuntime:
|
|
@@ -40,9 +34,6 @@ class UiPathOpenAIAgentRuntime:
|
|
|
40
34
|
agent: Agent,
|
|
41
35
|
runtime_id: str | None = None,
|
|
42
36
|
entrypoint: str | None = None,
|
|
43
|
-
storage_path: str | None = None,
|
|
44
|
-
loaded_object: Any | None = None,
|
|
45
|
-
storage: SqliteAgentStorage | None = None,
|
|
46
37
|
):
|
|
47
38
|
"""
|
|
48
39
|
Initialize the runtime.
|
|
@@ -51,86 +42,10 @@ class UiPathOpenAIAgentRuntime:
|
|
|
51
42
|
agent: The OpenAI Agent to execute
|
|
52
43
|
runtime_id: Unique identifier for this runtime instance
|
|
53
44
|
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
45
|
"""
|
|
58
46
|
self.agent: Agent = agent
|
|
59
47
|
self.runtime_id: str = runtime_id or "default"
|
|
60
48
|
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
49
|
|
|
135
50
|
async def execute(
|
|
136
51
|
self,
|
|
@@ -207,54 +122,27 @@ class UiPathOpenAIAgentRuntime:
|
|
|
207
122
|
Runtime events if stream_events=True, then final result
|
|
208
123
|
"""
|
|
209
124
|
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
125
|
|
|
218
126
|
# Run the agent with streaming if events requested
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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)
|
|
127
|
+
if stream_events:
|
|
128
|
+
# Use streaming for events
|
|
129
|
+
async for event_or_result in self._run_agent_streamed(
|
|
130
|
+
agent_input, options, stream_events
|
|
131
|
+
):
|
|
132
|
+
yield event_or_result
|
|
133
|
+
else:
|
|
134
|
+
# Use non-streaming for simple execution
|
|
135
|
+
result = await Runner.run(
|
|
136
|
+
starting_agent=self.agent,
|
|
137
|
+
input=agent_input,
|
|
138
|
+
)
|
|
139
|
+
yield self._create_success_result(result.final_output)
|
|
251
140
|
|
|
252
141
|
async def _run_agent_streamed(
|
|
253
142
|
self,
|
|
254
143
|
agent_input: str | list[Any],
|
|
255
144
|
options: UiPathExecuteOptions | UiPathStreamOptions | None,
|
|
256
145
|
stream_events: bool,
|
|
257
|
-
session: SQLiteSession | None,
|
|
258
146
|
) -> AsyncGenerator[UiPathRuntimeEvent | UiPathRuntimeResult, None]:
|
|
259
147
|
"""
|
|
260
148
|
Run agent using streaming API to enable event streaming.
|
|
@@ -272,7 +160,6 @@ class UiPathOpenAIAgentRuntime:
|
|
|
272
160
|
result = Runner.run_streamed(
|
|
273
161
|
starting_agent=self.agent,
|
|
274
162
|
input=agent_input,
|
|
275
|
-
session=session,
|
|
276
163
|
)
|
|
277
164
|
|
|
278
165
|
# Stream events from the agent
|
|
@@ -335,49 +222,17 @@ class UiPathOpenAIAgentRuntime:
|
|
|
335
222
|
"""
|
|
336
223
|
Prepare agent input from UiPath input dictionary.
|
|
337
224
|
|
|
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
225
|
"""
|
|
354
226
|
if not input:
|
|
355
|
-
|
|
356
|
-
"Input is required. Provide either 'message' (string) or 'messages' (list of message dicts)"
|
|
357
|
-
)
|
|
227
|
+
return ""
|
|
358
228
|
|
|
359
|
-
|
|
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
|
-
)
|
|
229
|
+
messages = input.get("messages", "")
|
|
369
230
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
message = input["message"]
|
|
373
|
-
# Return as string (OpenAI Agents SDK handles string → message conversion)
|
|
374
|
-
return str(message)
|
|
231
|
+
if isinstance(messages, (str, list)):
|
|
232
|
+
return messages
|
|
375
233
|
|
|
376
|
-
#
|
|
377
|
-
|
|
378
|
-
"Input must contain either 'message' (string) or 'messages' (list of message dicts). "
|
|
379
|
-
f"Got keys: {list(input.keys())}"
|
|
380
|
-
)
|
|
234
|
+
# Fallback to empty string for unexpected types
|
|
235
|
+
return ""
|
|
381
236
|
|
|
382
237
|
def _serialize_message(self, message: Any) -> dict[str, Any]:
|
|
383
238
|
"""
|
|
@@ -477,7 +332,7 @@ class UiPathOpenAIAgentRuntime:
|
|
|
477
332
|
Returns:
|
|
478
333
|
UiPathRuntimeSchema with input/output schemas and graph structure
|
|
479
334
|
"""
|
|
480
|
-
entrypoints_schema = get_entrypoints_schema(self.agent
|
|
335
|
+
entrypoints_schema = get_entrypoints_schema(self.agent)
|
|
481
336
|
|
|
482
337
|
return UiPathRuntimeSchema(
|
|
483
338
|
filePath=self.entrypoint,
|
|
@@ -488,45 +343,6 @@ class UiPathOpenAIAgentRuntime:
|
|
|
488
343
|
graph=get_agent_schema(self.agent),
|
|
489
344
|
)
|
|
490
345
|
|
|
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
346
|
async def dispose(self) -> None:
|
|
529
347
|
"""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
348
|
pass
|