22Vector store implementation that connects to UiPath Context Grounding as a backend.
33
44This is a read-only vector store that uses the UiPath Context Grounding API to retrieve documents.
5-
6- You need to set the following environment variables (also see .env.example):
7- ### - UIPATH_URL="https://alpha.uipath.com/{ORG_ID}/{TENANT_ID}"
8- ### - UIPATH_ACCESS_TOKEN={BEARER_TOKEN_WITH_CONTEXT_GROUNDING_PERMISSIONS}
9- ### - UIPATH_FOLDER_PATH="" - this can be left empty
10- ### - UIPATH_FOLDER_KEY="" - this can be left empty
115"""
126
137from collections .abc import Iterable
14- from typing import Any , Optional , TypeVar
8+ from typing import Any , Self
159
1610from langchain_core .documents import Document
1711from langchain_core .embeddings import Embeddings
1812from langchain_core .vectorstores import VectorStore
13+ from typing_extensions import override
1914from uipath import UiPath
20-
21- VST = TypeVar ("VST" , bound = "ContextGroundingVectorStore" )
15+ from uipath .models .context_grounding import ContextGroundingQueryResponse
2216
2317
2418class ContextGroundingVectorStore (VectorStore ):
2519 """Vector store that uses UiPath Context Grounding (ECS) as a backend.
2620
2721 This class provides a straightforward implementation that connects to the
2822 UiPath Context Grounding API for semantic searching.
29-
30- Example:
31- .. code-block:: python
32-
33- from uipath_agents_gym.tools.ecs_vectorstore import ContextGroundingVectorStore
34-
35- # Initialize the vector store with an index name
36- vectorstore = ContextGroundingVectorStore(index_name="ECCN")
37-
38- # Perform similarity search
39- docs_with_scores = vectorstore.similarity_search_with_score(
40- "How do I process an invoice?", k=5
41- )
4223 """
4324
4425 def __init__ (
4526 self ,
4627 index_name : str ,
47- folder_path : Optional [ str ] = None ,
48- uipath_sdk : Optional [ UiPath ] = None ,
28+ uipath_sdk : UiPath | None = None ,
29+ folder_path : str | None = None ,
4930 ):
5031 """Initialize the ContextGroundingVectorStore.
5132
5233 Args:
53- index_name: Name of the context grounding index to use
54- uipath_sdk: Optional SDK instance to use. If not provided, a new instance will be created.
34+ index_name: Name of the context grounding index to use (schema name)
35+ uipath_sdk: Optional UiPath SDK instance.
36+ folder_path: Optional folder path for folder-scoped operations
5537 """
5638 self .index_name = index_name
5739 self .folder_path = folder_path
5840 self .sdk = uipath_sdk or UiPath ()
5941
42+ # VectorStore implementation methods
43+
44+ @override
6045 def similarity_search_with_score (
6146 self , query : str , k : int = 4 , ** kwargs : Any
6247 ) -> list [tuple [Document , float ]]:
6348 """Return documents most similar to the query along with the distances.
64- The distance is 1 - score, where score is the relevance score returned by the Context Grounding API.
6549
6650 Args:
6751 query: The query string
@@ -70,52 +54,24 @@ def similarity_search_with_score(
7054 Returns:
7155 list of tuples of (document, score)
7256 """
73- # Call the UiPath SDK to perform the search
74- results = self .sdk .context_grounding .search (
75- name = self .index_name ,
76- query = query ,
77- number_of_results = k ,
78- folder_path = self .folder_path ,
79- )
80-
81- # Convert the results to Documents with scores
82- docs_with_scores = []
83- for result in results :
84- # Create metadata from result fields
85- metadata = {
86- "source" : result .source ,
87- "id" : result .id ,
88- "reference" : result .reference ,
89- "page_number" : result .page_number ,
90- "source_document_id" : result .source_document_id ,
91- "caption" : result .caption ,
92- }
93-
94- # Add any operation metadata if available
95- if result .metadata :
96- metadata ["operation_id" ] = result .metadata .operation_id
97- metadata ["strategy" ] = result .metadata .strategy
98-
99- # Create a Document with the content and metadata
100- doc = Document (
101- page_content = result .content ,
102- metadata = metadata ,
57+ # Use the context grounding service to perform search
58+ results : list [ContextGroundingQueryResponse ] = (
59+ self .sdk .context_grounding .search (
60+ name = self .index_name ,
61+ query = query ,
62+ number_of_results = k ,
63+ folder_path = self .folder_path ,
10364 )
65+ )
10466
105- score = 1.0 - float (result .score )
106-
107- docs_with_scores .append ((doc , score ))
108-
109- return docs_with_scores
67+ return self ._convert_results_to_documents (results )
11068
69+ @override
11170 def similarity_search_with_relevance_scores (
11271 self , query : str , k : int = 4 , ** kwargs : Any
11372 ) -> list [tuple [Document , float ]]:
11473 """Return documents along with their relevance scores on a scale from 0 to 1.
11574
116- This directly uses the scores provided by the Context Grounding API,
117- which are already normalized between 0 and 1.
118-
11975 Args:
12076 query: The query string
12177 k: Number of documents to return (default=4)
@@ -128,6 +84,7 @@ def similarity_search_with_relevance_scores(
12884 for doc , score in self .similarity_search_with_score (query , k , ** kwargs )
12985 ]
13086
87+ @override
13188 async def asimilarity_search_with_score (
13289 self , query : str , k : int = 4 , ** kwargs : Any
13390 ) -> list [tuple [Document , float ]]:
@@ -140,52 +97,23 @@ async def asimilarity_search_with_score(
14097 Returns:
14198 list of tuples of (document, score)
14299 """
143- # Call the UiPath SDK to perform the search asynchronously
144- results = await self .sdk .context_grounding .search_async (
100+ # Use the context grounding service to perform async search
101+ results : list [
102+ ContextGroundingQueryResponse
103+ ] = await self .sdk .context_grounding .search_async (
145104 name = self .index_name ,
146105 query = query ,
147106 number_of_results = k ,
148107 folder_path = self .folder_path ,
149108 )
150109
151- # Convert the results to Documents with scores
152- docs_with_scores = []
153- for result in results :
154- # Create metadata from result fields
155- metadata = {
156- "source" : result .source ,
157- "id" : result .id ,
158- "reference" : result .reference ,
159- "page_number" : result .page_number ,
160- "source_document_id" : result .source_document_id ,
161- "caption" : result .caption ,
162- }
163-
164- # Add any operation metadata if available
165- if result .metadata :
166- metadata ["operation_id" ] = result .metadata .operation_id
167- metadata ["strategy" ] = result .metadata .strategy
168-
169- # Create a Document with the content and metadata
170- doc = Document (
171- page_content = result .content ,
172- metadata = metadata ,
173- )
174-
175- # Get the distance score as 1 - ecs_score
176- score = 1.0 - float (result .score )
177-
178- docs_with_scores .append ((doc , score ))
179-
180- return docs_with_scores
110+ return self ._convert_results_to_documents (results )
181111
112+ @override
182113 async def asimilarity_search_with_relevance_scores (
183114 self , query : str , k : int = 4 , ** kwargs : Any
184115 ) -> list [tuple [Document , float ]]:
185- """Asynchronously return documents along with their relevance scores on a scale from 0 to 1.
186-
187- This directly uses the scores provided by the Context Grounding API,
188- which are already normalized between 0 and 1.
116+ """Asynchronously return documents along with their relevance scores.
189117
190118 Args:
191119 query: The query string
@@ -201,6 +129,7 @@ async def asimilarity_search_with_relevance_scores(
201129 )
202130 ]
203131
132+ @override
204133 def similarity_search (
205134 self , query : str , k : int = 4 , ** kwargs : Any
206135 ) -> list [Document ]:
@@ -216,6 +145,7 @@ def similarity_search(
216145 docs_and_scores = self .similarity_search_with_score (query , k , ** kwargs )
217146 return [doc for doc , _ in docs_and_scores ]
218147
148+ @override
219149 async def asimilarity_search (
220150 self , query : str , k : int = 4 , ** kwargs : Any
221151 ) -> list [Document ]:
@@ -231,38 +161,88 @@ async def asimilarity_search(
231161 docs_and_scores = await self .asimilarity_search_with_score (query , k , ** kwargs )
232162 return [doc for doc , _ in docs_and_scores ]
233163
164+ def _convert_results_to_documents (
165+ self , results : list [ContextGroundingQueryResponse ]
166+ ) -> list [tuple [Document , float ]]:
167+ """Convert API results to Document objects with scores.
168+
169+ Args:
170+ results: List of ContextGroundingQueryResponse objects
171+
172+ Returns:
173+ List of tuples containing (Document, score)
174+ """
175+ docs_with_scores = []
176+
177+ for result in results :
178+ # Create metadata from result fields
179+ metadata = {}
180+
181+ # Add string fields with proper defaults
182+ if result .source :
183+ metadata ["source" ] = str (result .source )
184+ if result .reference :
185+ metadata ["reference" ] = str (result .reference )
186+ if result .page_number :
187+ metadata ["page_number" ] = str (result .page_number )
188+ if result .source_document_id :
189+ metadata ["source_document_id" ] = str (result .source_document_id )
190+ if result .caption :
191+ metadata ["caption" ] = str (result .caption )
192+
193+ # Add any operation metadata if available
194+ if result .metadata :
195+ if result .metadata .operation_id :
196+ metadata ["operation_id" ] = str (result .metadata .operation_id )
197+ if result .metadata .strategy :
198+ metadata ["strategy" ] = str (result .metadata .strategy )
199+
200+ # Create a Document with the content and metadata
201+ doc = Document (
202+ page_content = result .content or "" ,
203+ metadata = metadata ,
204+ )
205+
206+ # Convert score to distance (1 - score)
207+ score = 1.0 - float (result .score or 0.0 )
208+
209+ docs_with_scores .append ((doc , score ))
210+
211+ return docs_with_scores
212+
234213 @classmethod
214+ @override
235215 def from_texts (
236- cls : type [ VST ] ,
216+ cls ,
237217 texts : list [str ],
238218 embedding : Embeddings ,
239- metadatas : Optional [ list [dict [str , Any ]]] = None ,
219+ metadatas : list [dict [str , Any ]] | None = None ,
240220 ** kwargs : Any ,
241- ) -> VST :
221+ ) -> Self :
242222 """This method is required by the VectorStore abstract class, but is not supported
243223 by ContextGroundingVectorStore which is read-only.
244224
245225 Raises:
246226 NotImplementedError: This method is not supported by ContextGroundingVectorStore
247227 """
248228 raise NotImplementedError (
249- "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding. "
250- "Creating a vector store from texts is not supported."
229+ "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
251230 )
252231
253- # Other required methods with minimal implementation to satisfy the interface
232+ @ override
254233 def add_texts (
255234 self ,
256235 texts : Iterable [str ],
257- metadatas : Optional [ list [dict [str , Any ]]] = None ,
236+ metadatas : list [dict [str , Any ]] | None = None ,
258237 ** kwargs : Any ,
259238 ) -> list [str ]:
260239 """Not implemented for ContextGroundingVectorStore as this is a read-only wrapper."""
261240 raise NotImplementedError (
262241 "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
263242 )
264243
265- def delete (self , ids : Optional [list [str ]] = None , ** kwargs : Any ) -> Optional [bool ]:
244+ @override
245+ def delete (self , ids : list [str ] | None = None , ** kwargs : Any ) -> bool | None :
266246 """Not implemented for ContextGroundingVectorStore as this is a read-only wrapper."""
267247 raise NotImplementedError (
268248 "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
0 commit comments