1717from uipath .runtime .errors import UiPathErrorCategory
1818from workflows import Workflow
1919
20- from uipath_llamaindex .runtime ._attribute_normalizer import (
21- AttributeNormalizingSpanProcessor ,
20+ from uipath_llamaindex .runtime ._telemetry import (
21+ ToolCallAttributeNormalizer ,
2222)
2323from uipath_llamaindex .runtime .config import LlamaIndexConfig
2424from uipath_llamaindex .runtime .errors import (
2525 UiPathLlamaIndexErrorCode ,
2626 UiPathLlamaIndexRuntimeError ,
2727)
2828from uipath_llamaindex .runtime .runtime import UiPathLlamaIndexRuntime
29- from uipath_llamaindex .runtime .storage import PickleResumableStorage
29+ from uipath_llamaindex .runtime .storage import SQLiteResumableStorage
3030from uipath_llamaindex .runtime .workflow import LlamaIndexWorkflowLoader
3131
3232
@@ -45,12 +45,14 @@ def __init__(
4545 """
4646 self .context = context
4747 self ._config : LlamaIndexConfig | None = None
48- self ._storage_path : str | None = None
4948
5049 self ._workflow_cache : dict [str , Workflow ] = {}
5150 self ._workflow_loaders : dict [str , LlamaIndexWorkflowLoader ] = {}
5251 self ._workflow_lock = asyncio .Lock ()
5352
53+ self ._storage_lock = asyncio .Lock ()
54+ self ._storage : SQLiteResumableStorage | None = None
55+
5456 self ._setup_instrumentation (self .context .trace_manager )
5557
5658 def _setup_instrumentation (self , trace_manager : UiPathTraceManager | None ) -> None :
@@ -60,26 +62,37 @@ def _setup_instrumentation(self, trace_manager: UiPathTraceManager | None) -> No
6062
6163 if trace_manager :
6264 trace_manager .tracer_provider .add_span_processor (
63- AttributeNormalizingSpanProcessor ()
65+ ToolCallAttributeNormalizer ()
6466 )
6567
6668 def _get_storage_path (self ) -> str :
6769 """Get the storage path for workflow state."""
68- if self ._storage_path is None :
69- if self .context .runtime_dir and self .context .state_file :
70- path = os .path .join (self .context .runtime_dir , self .context .state_file )
71- if not self .context .resume and self .context .job_id is None :
72- # If not resuming and no job id, delete the previous state file
73- if os .path .exists (path ):
74- os .remove (path )
75- os .makedirs (self .context .runtime_dir , exist_ok = True )
76- self ._storage_path = path
77- else :
78- default_path = os .path .join ("__uipath" , "state.db" )
79- os .makedirs (os .path .dirname (default_path ), exist_ok = True )
80- self ._storage_path = default_path
81-
82- return self ._storage_path
70+ if self .context .runtime_dir and self .context .state_file :
71+ path = os .path .join (self .context .runtime_dir , self .context .state_file )
72+ if not self .context .resume and self .context .job_id is None :
73+ # If not resuming and no job id, delete the previous state file
74+ if os .path .exists (path ):
75+ os .remove (path )
76+ os .makedirs (self .context .runtime_dir , exist_ok = True )
77+ return path
78+
79+ default_path = os .path .join ("__uipath" , "state.db" )
80+ os .makedirs (os .path .dirname (default_path ), exist_ok = True )
81+ return default_path
82+
83+ async def _get_storage (self ) -> SQLiteResumableStorage :
84+ """Get or create the shared storage instance."""
85+ if self ._storage is not None :
86+ return self ._storage
87+
88+ async with self ._storage_lock :
89+ if self ._storage is not None :
90+ return self ._storage
91+
92+ storage_path = self ._get_storage_path ()
93+ self ._storage = SQLiteResumableStorage (storage_path )
94+ await self ._storage .setup ()
95+ return self ._storage
8396
8497 def _load_config (self ) -> LlamaIndexConfig :
8598 """Load llama_index.json configuration."""
@@ -199,7 +212,6 @@ async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
199212 List of LlamaIndexRuntime instances, one per entrypoint
200213 """
201214 entrypoints = self .discover_entrypoints ()
202- storage_path = self ._get_storage_path ()
203215
204216 runtimes : list [UiPathRuntimeProtocol ] = []
205217 for entrypoint in entrypoints :
@@ -209,7 +221,6 @@ async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
209221 workflow = workflow ,
210222 runtime_id = entrypoint ,
211223 entrypoint = entrypoint ,
212- storage_path = storage_path ,
213224 )
214225 runtimes .append (runtime )
215226
@@ -220,7 +231,6 @@ async def _create_runtime_instance(
220231 workflow : Workflow ,
221232 runtime_id : str ,
222233 entrypoint : str ,
223- storage_path : str ,
224234 ) -> UiPathRuntimeProtocol :
225235 """
226236 Create a runtime instance from a workflow.
@@ -229,12 +239,12 @@ async def _create_runtime_instance(
229239 workflow: The workflow
230240 runtime_id: Unique identifier for the runtime instance
231241 entrypoint: Workflow entrypoint name
232- storage_path: Path for state storage
233242
234243 Returns:
235244 Configured runtime instance
236245 """
237- storage = PickleResumableStorage (storage_path )
246+
247+ storage = await self ._get_storage ()
238248
239249 base_runtime = UiPathLlamaIndexRuntime (
240250 workflow = workflow ,
@@ -265,15 +275,12 @@ async def new_runtime(
265275 Returns:
266276 Configured runtime instance with workflow
267277 """
268- storage_path = self ._get_storage_path ()
269-
270278 workflow = await self ._resolve_workflow (entrypoint )
271279
272280 return await self ._create_runtime_instance (
273281 workflow = workflow ,
274282 runtime_id = runtime_id ,
275283 entrypoint = entrypoint ,
276- storage_path = storage_path ,
277284 )
278285
279286 async def dispose (self ) -> None :
0 commit comments