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.
@@ -0,0 +1,190 @@
1
+ """Async SQLite connection manager with automatic serialization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections.abc import Iterable
7
+ from contextlib import asynccontextmanager
8
+ from sqlite3 import Row
9
+ from typing import Any, AsyncIterator
10
+
11
+ import aiosqlite
12
+
13
+
14
+ class AsyncSqlite:
15
+ """Async SQLite wrapper with automatic serialization via locks.
16
+
17
+ Provides thread-safe access to a SQLite database using asyncio locks
18
+ to serialize operations. Maintains a single connection and ensures
19
+ proper WAL mode configuration.
20
+ """
21
+
22
+ def __init__(self, db_path: str, timeout: float = 30.0):
23
+ """
24
+ Initialize AsyncSQLite manager.
25
+
26
+ Args:
27
+ db_path: Path to the SQLite database file
28
+ timeout: Database connection timeout in seconds
29
+ """
30
+ self.db_path = db_path
31
+ self.timeout = timeout
32
+ self.conn: aiosqlite.Connection | None = None
33
+ self.lock = asyncio.Lock()
34
+ self.is_setup = False
35
+
36
+ async def __aenter__(self) -> AsyncSqlite:
37
+ """Async context manager entry."""
38
+ await self.connect()
39
+ return self
40
+
41
+ async def __aexit__(self, *args) -> None:
42
+ """Async context manager exit."""
43
+ await self.close()
44
+
45
+ async def connect(self) -> None:
46
+ """Establish database connection and apply initial pragmas."""
47
+ if self.conn is not None:
48
+ return
49
+
50
+ self.conn = await aiosqlite.connect(self.db_path, timeout=self.timeout)
51
+ await self._apply_connection_pragmas()
52
+
53
+ # WAL mode is persistent, set once
54
+ await self.conn.execute("PRAGMA journal_mode=WAL")
55
+ await self.conn.commit()
56
+
57
+ async def close(self) -> None:
58
+ """Close database connection."""
59
+ if self.conn:
60
+ await self.conn.close()
61
+ self.conn = None
62
+ self.is_setup = False
63
+
64
+ async def execute(
65
+ self, query: str, parameters: tuple[Any, ...] | None = None
66
+ ) -> aiosqlite.Cursor:
67
+ """
68
+ Execute a single query with automatic locking.
69
+
70
+ Args:
71
+ query: SQL query to execute
72
+ parameters: Query parameters
73
+
74
+ Returns:
75
+ Cursor with query results
76
+ """
77
+ if self.conn is None:
78
+ await self.connect()
79
+
80
+ assert self.conn is not None
81
+
82
+ async with self.lock:
83
+ return await self.conn.execute(query, parameters or ())
84
+
85
+ async def executemany(
86
+ self, query: str, parameters_list: list[tuple[Any, ...]]
87
+ ) -> None:
88
+ """
89
+ Execute a query multiple times with different parameters.
90
+
91
+ Args:
92
+ query: SQL query to execute
93
+ parameters_list: List of parameter tuples
94
+ """
95
+ if self.conn is None:
96
+ await self.connect()
97
+
98
+ assert self.conn is not None
99
+
100
+ async with self.lock:
101
+ await self.conn.executemany(query, parameters_list)
102
+ await self.conn.commit()
103
+
104
+ async def executescript(self, script: str) -> None:
105
+ """
106
+ Execute a SQL script (multiple statements).
107
+
108
+ Args:
109
+ script: SQL script to execute
110
+ """
111
+ if self.conn is None:
112
+ await self.connect()
113
+
114
+ assert self.conn is not None
115
+
116
+ async with self.lock:
117
+ await self.conn.executescript(script)
118
+ await self.conn.commit()
119
+
120
+ async def commit(self) -> None:
121
+ """Commit the current transaction."""
122
+ if self.conn is None:
123
+ return
124
+
125
+ assert self.conn is not None
126
+
127
+ async with self.lock:
128
+ await self.conn.commit()
129
+
130
+ @asynccontextmanager
131
+ async def cursor(self) -> AsyncIterator[aiosqlite.Cursor]:
132
+ """
133
+ Get a cursor with automatic locking.
134
+
135
+ Yields:
136
+ Database cursor
137
+ """
138
+ if self.conn is None:
139
+ await self.connect()
140
+
141
+ assert self.conn is not None
142
+
143
+ async with self.lock:
144
+ cursor = await self.conn.cursor()
145
+ try:
146
+ yield cursor
147
+ finally:
148
+ await cursor.close()
149
+
150
+ async def fetchone(
151
+ self, query: str, parameters: tuple[Any, ...] | None = None
152
+ ) -> Row | None:
153
+ """
154
+ Execute query and fetch one result.
155
+
156
+ Args:
157
+ query: SQL query to execute
158
+ parameters: Query parameters
159
+
160
+ Returns:
161
+ Single row or None
162
+ """
163
+ cursor = await self.execute(query, parameters)
164
+ return await cursor.fetchone()
165
+
166
+ async def fetchall(
167
+ self, query: str, parameters: tuple[Any, ...] | None = None
168
+ ) -> Iterable[Row]:
169
+ """
170
+ Execute query and fetch all results.
171
+
172
+ Args:
173
+ query: SQL query to execute
174
+ parameters: Query parameters
175
+
176
+ Returns:
177
+ List of rows
178
+ """
179
+ cursor = await self.execute(query, parameters)
180
+ return await cursor.fetchall()
181
+
182
+ async def _apply_connection_pragmas(self) -> None:
183
+ """Apply per-connection PRAGMA settings for optimal concurrency."""
184
+ if self.conn is None:
185
+ return
186
+
187
+ await self.conn.execute(f"PRAGMA busy_timeout={int(self.timeout * 1000)}")
188
+ await self.conn.execute("PRAGMA synchronous=NORMAL")
189
+ await self.conn.execute("PRAGMA cache_size=10000")
190
+ await self.conn.execute("PRAGMA temp_store=MEMORY")
@@ -0,0 +1,32 @@
1
+ """OpenInference tracing integration for OpenAI Agents."""
2
+
3
+ try:
4
+ from opentelemetry import trace
5
+
6
+ TELEMETRY_AVAILABLE = True
7
+ except ImportError:
8
+ TELEMETRY_AVAILABLE = False
9
+
10
+
11
+ if TELEMETRY_AVAILABLE:
12
+
13
+ def get_current_span_wrapper():
14
+ """Wrapper to get the current span from OpenTelemetry.
15
+
16
+ Returns:
17
+ The current OpenTelemetry span if available, None otherwise
18
+ """
19
+ return trace.get_current_span()
20
+
21
+ else:
22
+
23
+ def get_current_span_wrapper():
24
+ """Stub function when OpenTelemetry is not available.
25
+
26
+ Returns:
27
+ None since telemetry dependencies are not available
28
+ """
29
+ return None
30
+
31
+
32
+ __all__ = ["get_current_span_wrapper"]
@@ -0,0 +1,201 @@
1
+ import importlib.util
2
+ import inspect
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Any, Self
7
+
8
+ from agents import Agent
9
+ from uipath.runtime.errors import UiPathErrorCategory
10
+
11
+ from .errors import (
12
+ UiPathOpenAIAgentsErrorCode,
13
+ UiPathOpenAIAgentsRuntimeError,
14
+ )
15
+
16
+
17
+ class OpenAiAgentLoader:
18
+ """Load agent from a Python file path (e.g.: 'main.py:agent')"""
19
+
20
+ def __init__(self, name: str, file_path: str, variable_name: str):
21
+ """Initialize the openi agent loader.
22
+ Args:
23
+ name: The name of the agent.
24
+ file_path: The path to the Python file containing the agent.
25
+ variable_name: The name of the variable that contains the agent instance.
26
+ """
27
+ self.name = name
28
+ self.file_path = file_path
29
+ self.variable_name = variable_name
30
+ self._context_manager: Any = None
31
+ self._loaded_object: Any = (
32
+ None # Store original loaded object for type inference
33
+ )
34
+
35
+ @classmethod
36
+ def from_path_string(cls, name: str, file_path: str) -> Self:
37
+ """
38
+ Create an OpenAiAgentLoader from a path string.
39
+
40
+ Args:
41
+ name: Human-readable name of the agent.
42
+ path: The path string in the format 'file_path:variable_name'.
43
+
44
+ Returns:
45
+ An instance of OpenAiAgentLoader.
46
+ """
47
+ if ":" not in file_path:
48
+ raise UiPathOpenAIAgentsRuntimeError(
49
+ code=UiPathOpenAIAgentsErrorCode.CONFIG_INVALID,
50
+ title="Invalid agent path format",
51
+ detail=f"Invalid path format '{file_path}'. Expected format 'file_path:variable_name'.",
52
+ category=UiPathErrorCategory.USER,
53
+ )
54
+ file, variable = file_path.split(":", 1)
55
+ return cls(name=name, file_path=file, variable_name=variable)
56
+
57
+ async def load(self) -> Agent:
58
+ """
59
+ Load and return the agent.
60
+
61
+ Returns:
62
+ An instance of the loaded Agent.
63
+
64
+ Raises:
65
+ ValueError: If file path is outside current directory
66
+ FileNotFoundError: If file doesn't exist
67
+ ImportError: If module can't be loaded
68
+ TypeError: If loaded object isn't a valid workflow
69
+ """
70
+ # Validate and normalize paths
71
+ cwd = os.path.abspath(os.getcwd())
72
+ abs_file_path = os.path.abspath(os.path.normpath(self.file_path))
73
+
74
+ if not abs_file_path.startswith(cwd):
75
+ raise UiPathOpenAIAgentsRuntimeError(
76
+ code=UiPathOpenAIAgentsErrorCode.AGENT_VALUE_ERROR,
77
+ title="Invalid agent file path",
78
+ detail=f"Agent file path '{self.file_path}' must be within the current working directory.",
79
+ category=UiPathErrorCategory.USER,
80
+ )
81
+
82
+ if not os.path.exists(abs_file_path):
83
+ raise UiPathOpenAIAgentsRuntimeError(
84
+ code=UiPathOpenAIAgentsErrorCode.AGENT_NOT_FOUND,
85
+ title="Agent file not found",
86
+ detail=f"Agent file '{self.file_path}' does not exist.",
87
+ category=UiPathErrorCategory.USER,
88
+ )
89
+ # Ensure the current directory and src/ is in sys.path
90
+ self._setup_python_path(cwd)
91
+
92
+ # Import the module and retrieve the agent instance
93
+ module = self._import_module(abs_file_path)
94
+
95
+ # Get the agent instance from the module
96
+ agent_object = getattr(module, self.variable_name, None)
97
+ if agent_object is None:
98
+ raise UiPathOpenAIAgentsRuntimeError(
99
+ code=UiPathOpenAIAgentsErrorCode.AGENT_NOT_FOUND,
100
+ title="Agent variable not found",
101
+ detail=f"'{self.variable_name}' not found in module '{self.file_path}'.",
102
+ category=UiPathErrorCategory.USER,
103
+ )
104
+
105
+ # Store the original loaded object for type inference
106
+ self._loaded_object = agent_object
107
+
108
+ agent = await self._resolve_agent(agent_object)
109
+ if not isinstance(agent, Agent):
110
+ raise UiPathOpenAIAgentsRuntimeError(
111
+ code=UiPathOpenAIAgentsErrorCode.AGENT_TYPE_ERROR,
112
+ title="Invalid agent type",
113
+ detail=f"Expected Agent, got '{type(agent).__name__}'.",
114
+ category=UiPathErrorCategory.USER,
115
+ )
116
+
117
+ return agent
118
+
119
+ def _setup_python_path(self, cwd: str) -> None:
120
+ """Add current directory and src/ to Python path if needed."""
121
+ if cwd not in sys.path:
122
+ sys.path.insert(0, cwd)
123
+
124
+ # Support src-layout projects (mimics editable install)
125
+ src_dir = os.path.join(cwd, "src")
126
+ if os.path.isdir(src_dir) and src_dir not in sys.path:
127
+ sys.path.insert(0, src_dir)
128
+
129
+ def _import_module(self, abs_file_path: str) -> Any:
130
+ """Import a Python module from a file path."""
131
+ module_name = Path(abs_file_path).stem
132
+ spec = importlib.util.spec_from_file_location(module_name, abs_file_path)
133
+
134
+ if not spec or not spec.loader:
135
+ raise UiPathOpenAIAgentsRuntimeError(
136
+ code=UiPathOpenAIAgentsErrorCode.AGENT_IMPORT_ERROR,
137
+ title="Failed to load agent module",
138
+ detail=f"Could not load module from: {abs_file_path}",
139
+ category=UiPathErrorCategory.USER,
140
+ )
141
+
142
+ try:
143
+ module = importlib.util.module_from_spec(spec)
144
+ sys.modules[module_name] = module
145
+ spec.loader.exec_module(module)
146
+ return module
147
+ except Exception as e:
148
+ raise UiPathOpenAIAgentsRuntimeError(
149
+ code=UiPathOpenAIAgentsErrorCode.AGENT_LOAD_ERROR,
150
+ title="Failed to execute agent module",
151
+ detail=f"Error loading module from {abs_file_path}: {str(e)}",
152
+ category=UiPathErrorCategory.USER,
153
+ ) from e
154
+
155
+ async def _resolve_agent(self, agent_object: Any) -> Agent:
156
+ """
157
+ Resolve an agent object that might be:
158
+ - A direct Agent
159
+ - A function that returns an Agent
160
+ - An async function that returns an Agent
161
+ - An async context manager that yields an Agent
162
+ """
163
+ agent_instance = None
164
+ # Handle callable (sync or async)
165
+ if callable(agent_object):
166
+ if inspect.iscoroutinefunction(agent_object):
167
+ agent_instance = await agent_object()
168
+ else:
169
+ agent_instance = agent_object()
170
+ else:
171
+ agent_instance = agent_object
172
+
173
+ # Handle async context manager
174
+ if hasattr(agent_instance, "__aenter__") and callable(
175
+ agent_instance.__aenter__
176
+ ):
177
+ self._context_manager = agent_instance
178
+ return await agent_instance.__aenter__()
179
+
180
+ return agent_instance
181
+
182
+ def get_loaded_object(self) -> Any:
183
+ """
184
+ Get the original loaded object before agent resolution.
185
+
186
+ This is useful for extracting type annotations from wrapper functions.
187
+
188
+ Returns:
189
+ The original loaded object (could be an Agent, function, or callable)
190
+ """
191
+ return self._loaded_object
192
+
193
+ async def cleanup(self) -> None:
194
+ """Clean up resources (e.g., exit async context managers)."""
195
+ if self._context_manager:
196
+ try:
197
+ await self._context_manager.__aexit__(None, None, None)
198
+ except Exception as e:
199
+ print(f"Error during agent cleanup: {e}")
200
+ finally:
201
+ self._context_manager = None
@@ -0,0 +1,55 @@
1
+ import json
2
+ import os
3
+
4
+
5
+ class OpenAiAgentsConfig:
6
+ """Simple loader for OpenAi Agents configuration."""
7
+
8
+ def __init__(self, config_path: str = "openai_agents.json"):
9
+ self.config_path = config_path
10
+ self._agents: dict[str, str] | None = None
11
+
12
+ @property
13
+ def exists(self) -> bool:
14
+ """Check if the configuration file exists."""
15
+ return os.path.exists(self.config_path)
16
+
17
+ @property
18
+ def agents(self) -> dict[str, str]:
19
+ """Get agents names -> path mapping from config.
20
+
21
+ Returns: A dictionary mapping agent names to their paths.
22
+ """
23
+
24
+ if self._agents is None:
25
+ self._agents = self._load_agents()
26
+ return self._agents
27
+
28
+ @property
29
+ def entrypoint(self) -> list[str]:
30
+ """Get the entrypoint for the agents runtime.
31
+
32
+ Returns: A list representing the entrypoint command.
33
+ """
34
+ return list(self.agents.keys())
35
+
36
+ def _load_agents(self) -> dict[str, str]:
37
+ """Load agents from the configuration file."""
38
+ if not self.exists:
39
+ raise FileNotFoundError(
40
+ f"OpenAi Agents configuration file not found at {self.config_path}"
41
+ )
42
+
43
+ try:
44
+ with open(self.config_path, "r") as f:
45
+ config = json.load(f)
46
+ if "agents" not in config:
47
+ raise ValueError(
48
+ "Missing 'agents' key in openai_agents.json configuration file."
49
+ )
50
+ agents = config["agents"]
51
+ if not isinstance(agents, dict):
52
+ raise ValueError("'agents' must be a dictionary.")
53
+ return agents
54
+ except json.JSONDecodeError as e:
55
+ raise ValueError(f"Invalid JSON in '{self.config_path}': {e}") from e
@@ -0,0 +1,48 @@
1
+ """Error handling for OpenAI Agents runtime."""
2
+
3
+ from enum import Enum
4
+
5
+ from uipath.runtime.errors import (
6
+ UiPathBaseRuntimeError,
7
+ UiPathErrorCategory,
8
+ UiPathErrorCode,
9
+ )
10
+
11
+
12
+ class UiPathOpenAIAgentsErrorCode(Enum):
13
+ """Error codes specific to OpenAI Agents runtime."""
14
+
15
+ AGENT_EXECUTION_FAILURE = "AGENT_EXECUTION_FAILURE"
16
+ TIMEOUT_ERROR = "TIMEOUT_ERROR"
17
+ SERIALIZE_OUTPUT_ERROR = "SERIALIZE_OUTPUT_ERROR"
18
+
19
+ CONFIG_MISSING = "CONFIG_MISSING"
20
+ CONFIG_INVALID = "CONFIG_INVALID"
21
+
22
+ AGENT_NOT_FOUND = "AGENT_NOT_FOUND"
23
+ AGENT_TYPE_ERROR = "AGENT_TYPE_ERROR"
24
+ AGENT_VALUE_ERROR = "AGENT_VALUE_ERROR"
25
+ AGENT_LOAD_ERROR = "AGENT_LOAD_ERROR"
26
+ AGENT_IMPORT_ERROR = "AGENT_IMPORT_ERROR"
27
+
28
+
29
+ class UiPathOpenAIAgentsRuntimeError(UiPathBaseRuntimeError):
30
+ """Custom exception for OpenAI Agents runtime errors with structured error information."""
31
+
32
+ def __init__(
33
+ self,
34
+ code: UiPathOpenAIAgentsErrorCode | UiPathErrorCode,
35
+ title: str,
36
+ detail: str,
37
+ category: UiPathErrorCategory = UiPathErrorCategory.UNKNOWN,
38
+ status: int | None = None,
39
+ ):
40
+ super().__init__(
41
+ code.value, title, detail, category, status, prefix="OpenAI-Agents"
42
+ )
43
+
44
+
45
+ __all__ = [
46
+ "UiPathOpenAIAgentsErrorCode",
47
+ "UiPathOpenAIAgentsRuntimeError",
48
+ ]