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 , TypeVar , override
159
1610from langchain_core .documents import Document
1711from langchain_core .embeddings import Embeddings
1812from langchain_core .vectorstores import VectorStore
1913from uipath import UiPath
20-
21- VST = TypeVar ("VST" , bound = "ContextGroundingVectorStore" )
22-
14+ from uipath .models .context_grounding import ContextGroundingQueryResponse
2315
2416class ContextGroundingVectorStore (VectorStore ):
2517 """Vector store that uses UiPath Context Grounding (ECS) as a backend.
2618
2719 This class provides a straightforward implementation that connects to the
2820 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- )
4221 """
4322
23+
4424 def __init__ (
4525 self ,
4626 index_name : str ,
47- folder_path : Optional [ str ] = None ,
48- uipath_sdk : Optional [ UiPath ] = None ,
27+ uipath_sdk : UiPath | None = None ,
28+ folder_path : str | None = None ,
4929 ):
5030 """Initialize the ContextGroundingVectorStore.
5131
5232 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.
33+ index_name: Name of the context grounding index to use (schema name)
34+ uipath_sdk: Optional UiPath SDK instance.
35+ folder_path: Optional folder path for folder-scoped operations
5536 """
37+ self .index_name = index_name
38+ self .folder_path = folder_path
39+
5640 self .index_name = index_name
5741 self .folder_path = folder_path
5842 self .sdk = uipath_sdk or UiPath ()
5943
44+ # VectorStore implementation methods
45+
46+ @override
6047 def similarity_search_with_score (
6148 self , query : str , k : int = 4 , ** kwargs : Any
6249 ) -> list [tuple [Document , float ]]:
6350 """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.
6551
6652 Args:
6753 query: The query string
@@ -70,52 +56,22 @@ def similarity_search_with_score(
7056 Returns:
7157 list of tuples of (document, score)
7258 """
73- # Call the UiPath SDK to perform the search
74- results = self .sdk .context_grounding .search (
59+ # Use the context grounding service to perform search
60+ results : list [ ContextGroundingQueryResponse ] = self .sdk .context_grounding .search (
7561 name = self .index_name ,
7662 query = query ,
7763 number_of_results = k ,
7864 folder_path = self .folder_path ,
7965 )
8066
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 ,
103- )
104-
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
@@ -196,14 +124,11 @@ async def asimilarity_search_with_relevance_scores(
196124 """
197125 return [
198126 (doc , 1.0 - score )
199- for doc , score in await self .asimilarity_search_with_score (
200- query , k , ** kwargs
201- )
127+ for doc , score in await self .asimilarity_search_with_score (query , k , ** kwargs )
202128 ]
203129
204- def similarity_search (
205- self , query : str , k : int = 4 , ** kwargs : Any
206- ) -> list [Document ]:
130+ @override
131+ def similarity_search (self , query : str , k : int = 4 , ** kwargs : Any ) -> list [Document ]:
207132 """Return documents most similar to the query.
208133
209134 Args:
@@ -216,9 +141,8 @@ def similarity_search(
216141 docs_and_scores = self .similarity_search_with_score (query , k , ** kwargs )
217142 return [doc for doc , _ in docs_and_scores ]
218143
219- async def asimilarity_search (
220- self , query : str , k : int = 4 , ** kwargs : Any
221- ) -> list [Document ]:
144+ @override
145+ async def asimilarity_search (self , query : str , k : int = 4 , ** kwargs : Any ) -> list [Document ]:
222146 """Asynchronously return documents most similar to the query.
223147
224148 Args:
@@ -231,38 +155,85 @@ async def asimilarity_search(
231155 docs_and_scores = await self .asimilarity_search_with_score (query , k , ** kwargs )
232156 return [doc for doc , _ in docs_and_scores ]
233157
158+ def _convert_results_to_documents (
159+ self , results : list [ContextGroundingQueryResponse ]
160+ ) -> list [tuple [Document , float ]]:
161+ """Convert API results to Document objects with scores.
162+
163+ Args:
164+ results: List of ContextGroundingQueryResponse objects
165+
166+ Returns:
167+ List of tuples containing (Document, score)
168+ """
169+ docs_with_scores = []
170+
171+ for result in results :
172+ # Create metadata from result fields
173+ metadata = {}
174+
175+ # Add string fields with proper defaults
176+ if result .source :
177+ metadata ["source" ] = str (result .source )
178+ if result .reference :
179+ metadata ["reference" ] = str (result .reference )
180+ if result .page_number :
181+ metadata ["page_number" ] = str (result .page_number )
182+ if result .source_document_id :
183+ metadata ["source_document_id" ] = str (result .source_document_id )
184+ if result .caption :
185+ metadata ["caption" ] = str (result .caption )
186+
187+ # Add any operation metadata if available
188+ if result .metadata :
189+ if result .metadata .operation_id :
190+ metadata ["operation_id" ] = str (result .metadata .operation_id )
191+ if result .metadata .strategy :
192+ metadata ["strategy" ] = str (result .metadata .strategy )
193+
194+ # Create a Document with the content and metadata
195+ doc = Document (
196+ page_content = result .content or "" ,
197+ metadata = metadata ,
198+ )
199+
200+ # Convert score to distance (1 - score)
201+ score = 1.0 - float (result .score or 0.0 )
202+
203+ docs_with_scores .append ((doc , score ))
204+
205+ return docs_with_scores
206+
234207 @classmethod
208+ @override
235209 def from_texts (
236- cls : type [ VST ] ,
210+ cls ,
237211 texts : list [str ],
238212 embedding : Embeddings ,
239- metadatas : Optional [ list [dict [ str , Any ]]] = None ,
213+ metadatas : list [dict ] | None = None ,
240214 ** kwargs : Any ,
241- ) -> VST :
215+ ) -> Self :
242216 """This method is required by the VectorStore abstract class, but is not supported
243217 by ContextGroundingVectorStore which is read-only.
244218
245219 Raises:
246220 NotImplementedError: This method is not supported by ContextGroundingVectorStore
247221 """
248222 raise NotImplementedError (
249- "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding. "
250- "Creating a vector store from texts is not supported."
223+ "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
251224 )
252225
253- # Other required methods with minimal implementation to satisfy the interface
226+ @ override
254227 def add_texts (
255- self ,
256- texts : Iterable [str ],
257- metadatas : Optional [list [dict [str , Any ]]] = None ,
258- ** kwargs : Any ,
228+ self , texts : Iterable [str ], metadatas : list [dict ] | None = None , ** kwargs : Any
259229 ) -> list [str ]:
260230 """Not implemented for ContextGroundingVectorStore as this is a read-only wrapper."""
261231 raise NotImplementedError (
262232 "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
263- )
233+ ) # type: ignore[unreachable]
264234
265- def delete (self , ids : Optional [list [str ]] = None , ** kwargs : Any ) -> Optional [bool ]:
235+ @override
236+ def delete (self , ids : list [str ] | None = None , ** kwargs : Any ) -> bool | None :
266237 """Not implemented for ContextGroundingVectorStore as this is a read-only wrapper."""
267238 raise NotImplementedError (
268239 "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
0 commit comments