Skip to content

Commit df5449a

Browse files
committed
fix: remove sql lite session storage
1 parent 3eeefb6 commit df5449a

6 files changed

Lines changed: 13 additions & 1124 deletions

File tree

packages/uipath-openai-agents/src/uipath_openai_agents/runtime/factory.py

Lines changed: 0 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Factory for creating OpenAI Agents runtimes from openai_agents.json configuration."""
22

33
import asyncio
4-
import os
54
from typing import Any
65

76
from agents import Agent
@@ -21,7 +20,6 @@
2120
UiPathOpenAIAgentsRuntimeError,
2221
)
2322
from uipath_openai_agents.runtime.runtime import UiPathOpenAIAgentRuntime
24-
from uipath_openai_agents.runtime.storage import SqliteAgentStorage
2523

2624

2725
class UiPathOpenAIAgentRuntimeFactory:
@@ -44,111 +42,13 @@ def __init__(
4442
self._agent_loaders: dict[str, OpenAiAgentLoader] = {}
4543
self._agent_lock = asyncio.Lock()
4644

47-
self._storage: SqliteAgentStorage | None = None
48-
self._storage_lock = asyncio.Lock()
49-
5045
self._setup_instrumentation()
5146

5247
def _setup_instrumentation(self) -> None:
5348
"""Setup tracing and instrumentation."""
5449
OpenAIAgentsInstrumentor().instrument()
5550
UiPathSpanUtils.register_current_span_provider(get_current_span_wrapper)
5651

57-
async def _get_or_create_storage(self) -> SqliteAgentStorage | None:
58-
"""Get or create the shared storage instance.
59-
60-
Returns:
61-
Shared storage instance, or None if storage is disabled
62-
"""
63-
async with self._storage_lock:
64-
if self._storage is None:
65-
storage_path = self._get_storage_path()
66-
if storage_path:
67-
self._storage = SqliteAgentStorage(storage_path)
68-
await self._storage.setup()
69-
return self._storage
70-
71-
def _remove_file_with_retry(self, path: str, max_attempts: int = 5) -> None:
72-
"""Remove file with retry logic for Windows file locking.
73-
74-
OpenAI SDK uses sync sqlite3 which doesn't immediately release file locks
75-
on Windows. This retry mechanism gives the OS time to release the lock.
76-
77-
Args:
78-
path: Path to file to remove
79-
max_attempts: Maximum number of retry attempts (default: 5)
80-
81-
Raises:
82-
OSError: If file cannot be removed after all retries
83-
"""
84-
import time
85-
86-
for attempt in range(max_attempts):
87-
try:
88-
os.remove(path)
89-
return # Success
90-
except PermissionError:
91-
if attempt == max_attempts - 1:
92-
# Last attempt failed, re-raise
93-
raise
94-
# Exponential backoff: 0.1s, 0.2s, 0.4s, 0.8s
95-
time.sleep(0.1 * (2**attempt))
96-
97-
def _get_storage_path(self) -> str | None:
98-
"""Get the storage path for agent state.
99-
100-
Returns:
101-
Path to SQLite database for storage, or None if storage is disabled
102-
"""
103-
if self.context.state_file_path is not None:
104-
return self.context.state_file_path
105-
106-
if self.context.runtime_dir and self.context.state_file:
107-
path = os.path.join(self.context.runtime_dir, self.context.state_file)
108-
if (
109-
not self.context.resume
110-
and self.context.job_id is None
111-
and not self.context.keep_state_file
112-
):
113-
# If not resuming and no job id, delete the previous state file
114-
if os.path.exists(path):
115-
self._remove_file_with_retry(path)
116-
os.makedirs(self.context.runtime_dir, exist_ok=True)
117-
return path
118-
119-
default_path = os.path.join("__uipath", "state.db")
120-
os.makedirs(os.path.dirname(default_path), exist_ok=True)
121-
return default_path
122-
123-
def _get_storage_path_legacy(self, runtime_id: str) -> str | None:
124-
"""
125-
Get the storage path for agent session state.
126-
127-
Args:
128-
runtime_id: Unique identifier for the runtime instance
129-
130-
Returns:
131-
Path to SQLite database for session storage, or None if storage is disabled
132-
"""
133-
if self.context.runtime_dir and self.context.state_file:
134-
# Use state file name pattern but with runtime_id
135-
base_name = os.path.splitext(self.context.state_file)[0]
136-
file_name = f"{base_name}_{runtime_id}.db"
137-
path = os.path.join(self.context.runtime_dir, file_name)
138-
139-
if not self.context.resume and self.context.job_id is None:
140-
# If not resuming and no job id, delete the previous state file
141-
if os.path.exists(path):
142-
self._remove_file_with_retry(path)
143-
144-
os.makedirs(self.context.runtime_dir, exist_ok=True)
145-
return path
146-
147-
# Default storage path
148-
default_dir = os.path.join("__uipath", "sessions")
149-
os.makedirs(default_dir, exist_ok=True)
150-
return os.path.join(default_dir, f"{runtime_id}.db")
151-
15252
def _load_config(self) -> OpenAiAgentsConfig:
15353
"""Load openai_agents.json configuration."""
15454
if self._config is None:
@@ -299,16 +199,10 @@ async def _create_runtime_instance(
299199
Returns:
300200
Configured runtime instance
301201
"""
302-
# Get shared storage instance
303-
storage = await self._get_or_create_storage()
304-
storage_path = storage.storage_path if storage else None
305-
306202
return UiPathOpenAIAgentRuntime(
307203
agent=agent,
308204
runtime_id=runtime_id,
309205
entrypoint=entrypoint,
310-
storage_path=storage_path,
311-
storage=storage,
312206
)
313207

314208
async def new_runtime(
@@ -340,8 +234,3 @@ async def dispose(self) -> None:
340234

341235
self._agent_loaders.clear()
342236
self._agent_cache.clear()
343-
344-
# Dispose shared storage
345-
if self._storage:
346-
await self._storage.dispose()
347-
self._storage = None

packages/uipath-openai-agents/src/uipath_openai_agents/runtime/runtime.py

Lines changed: 13 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from agents import (
99
Agent,
1010
Runner,
11-
SQLiteSession,
1211
)
1312
from uipath.runtime import (
1413
UiPathExecuteOptions,
@@ -27,7 +26,6 @@
2726
from ._serialize import serialize_output
2827
from .errors import UiPathOpenAIAgentsErrorCode, UiPathOpenAIAgentsRuntimeError
2928
from .schema import get_agent_schema, get_entrypoints_schema
30-
from .storage import SqliteAgentStorage
3129

3230

3331
class UiPathOpenAIAgentRuntime:
@@ -40,8 +38,6 @@ def __init__(
4038
agent: Agent,
4139
runtime_id: str | None = None,
4240
entrypoint: str | None = None,
43-
storage_path: str | None = None,
44-
storage: SqliteAgentStorage | None = None,
4541
):
4642
"""
4743
Initialize the runtime.
@@ -50,14 +46,10 @@ def __init__(
5046
agent: The OpenAI Agent to execute
5147
runtime_id: Unique identifier for this runtime instance
5248
entrypoint: Optional entrypoint name (for schema generation)
53-
storage_path: Path to SQLite database for session persistence
54-
storage: Optional storage instance for state persistence
5549
"""
5650
self.agent: Agent = agent
5751
self.runtime_id: str = runtime_id or "default"
5852
self.entrypoint: str | None = entrypoint
59-
self.storage_path: str | None = storage_path
60-
self.storage: SqliteAgentStorage | None = storage
6153

6254
# Configure OpenAI Agents SDK to use Responses API
6355
# UiPath supports both APIs via X-UiPath-LlmGateway-ApiFlavor header
@@ -204,54 +196,27 @@ async def _run_agent(
204196
Runtime events if stream_events=True, then final result
205197
"""
206198
agent_input = self._prepare_agent_input(input)
207-
is_resuming = bool(options and options.resume)
208-
209-
# Create session for state persistence (local to this run)
210-
# SQLiteSession automatically loads existing data from the database when created
211-
session: SQLiteSession | None = None
212-
if self.storage_path:
213-
session = SQLiteSession(self.runtime_id, self.storage_path)
214199

215200
# Run the agent with streaming if events requested
216-
try:
217-
if stream_events:
218-
# Use streaming for events
219-
async for event_or_result in self._run_agent_streamed(
220-
agent_input, options, stream_events, session
221-
):
222-
yield event_or_result
223-
else:
224-
# Use non-streaming for simple execution
225-
result = await Runner.run(
226-
starting_agent=self.agent,
227-
input=agent_input,
228-
session=session,
229-
)
230-
yield self._create_success_result(result.final_output)
231-
232-
except Exception:
233-
# Clean up session on error
234-
if session and self.storage_path and not is_resuming:
235-
# Delete incomplete session
236-
try:
237-
import os
238-
239-
if os.path.exists(self.storage_path):
240-
os.remove(self.storage_path)
241-
except Exception:
242-
pass # Best effort cleanup
243-
raise
244-
finally:
245-
# Always close session after run completes with proper WAL checkpoint
246-
if session:
247-
self._close_session_with_checkpoint(session)
201+
if stream_events:
202+
# Use streaming for events
203+
async for event_or_result in self._run_agent_streamed(
204+
agent_input, options, stream_events
205+
):
206+
yield event_or_result
207+
else:
208+
# Use non-streaming for simple execution
209+
result = await Runner.run(
210+
starting_agent=self.agent,
211+
input=agent_input,
212+
)
213+
yield self._create_success_result(result.final_output)
248214

249215
async def _run_agent_streamed(
250216
self,
251217
agent_input: str | list[Any],
252218
options: UiPathExecuteOptions | UiPathStreamOptions | None,
253219
stream_events: bool,
254-
session: SQLiteSession | None,
255220
) -> AsyncGenerator[UiPathRuntimeEvent | UiPathRuntimeResult, None]:
256221
"""
257222
Run agent using streaming API to enable event streaming.
@@ -269,7 +234,6 @@ async def _run_agent_streamed(
269234
result = Runner.run_streamed(
270235
starting_agent=self.agent,
271236
input=agent_input,
272-
session=session,
273237
)
274238

275239
# Stream events from the agent
@@ -485,45 +449,6 @@ async def get_schema(self) -> UiPathRuntimeSchema:
485449
graph=get_agent_schema(self.agent),
486450
)
487451

488-
def _close_session_with_checkpoint(self, session: SQLiteSession) -> None:
489-
"""Close SQLite session with WAL checkpoint to release file locks.
490-
491-
OpenAI SDK uses sync sqlite3 which doesn't release file locks on Windows
492-
without explicit WAL checkpoint. This is especially important for cleanup.
493-
494-
Args:
495-
session: The SQLiteSession to close
496-
"""
497-
try:
498-
# Get the underlying connection
499-
conn = session._get_connection()
500-
501-
# Commit any pending transactions
502-
try:
503-
conn.commit()
504-
except Exception:
505-
pass # Best effort
506-
507-
# Force WAL checkpoint to release shared memory files
508-
# This is especially important on Windows
509-
try:
510-
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
511-
conn.commit()
512-
except Exception:
513-
pass # Best effort
514-
515-
except Exception:
516-
pass # Best effort cleanup
517-
518-
finally:
519-
# Always call the session's close method
520-
try:
521-
session.close()
522-
except Exception:
523-
pass # Best effort
524-
525452
async def dispose(self) -> None:
526453
"""Cleanup runtime resources."""
527-
# Sessions are closed immediately after each run in _run_agent()
528-
# Storage is shared across runtimes and managed by the factory
529454
pass

0 commit comments

Comments
 (0)