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 , 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" )
14+ from uipath .models .context_grounding import ContextGroundingQueryResponse
2215
2316
2417class ContextGroundingVectorStore (VectorStore ):
2518 """Vector store that uses UiPath Context Grounding (ECS) as a backend.
2619
2720 This class provides a straightforward implementation that connects to the
2821 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- )
4222 """
4323
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 """
5637 self .index_name = index_name
5738 self .folder_path = folder_path
5839 self .sdk = uipath_sdk or UiPath ()
5940
41+ # VectorStore implementation methods
42+
43+ @override
6044 def similarity_search_with_score (
6145 self , query : str , k : int = 4 , ** kwargs : Any
6246 ) -> list [tuple [Document , float ]]:
6347 """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.
6548
6649 Args:
6750 query: The query string
@@ -70,52 +53,24 @@ def similarity_search_with_score(
7053 Returns:
7154 list of tuples of (document, score)
7255 """
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 ,
56+ # Use the context grounding service to perform search
57+ results : list [ContextGroundingQueryResponse ] = (
58+ self .sdk .context_grounding .search (
59+ name = self .index_name ,
60+ query = query ,
61+ number_of_results = k ,
62+ folder_path = self .folder_path ,
10363 )
64+ )
10465
105- score = 1.0 - float (result .score )
106-
107- docs_with_scores .append ((doc , score ))
108-
109- return docs_with_scores
66+ return self ._convert_results_to_documents (results )
11067
68+ @override
11169 def similarity_search_with_relevance_scores (
11270 self , query : str , k : int = 4 , ** kwargs : Any
11371 ) -> list [tuple [Document , float ]]:
11472 """Return documents along with their relevance scores on a scale from 0 to 1.
11573
116- This directly uses the scores provided by the Context Grounding API,
117- which are already normalized between 0 and 1.
118-
11974 Args:
12075 query: The query string
12176 k: Number of documents to return (default=4)
@@ -128,6 +83,7 @@ def similarity_search_with_relevance_scores(
12883 for doc , score in self .similarity_search_with_score (query , k , ** kwargs )
12984 ]
13085
86+ @override
13187 async def asimilarity_search_with_score (
13288 self , query : str , k : int = 4 , ** kwargs : Any
13389 ) -> list [tuple [Document , float ]]:
@@ -140,52 +96,23 @@ async def asimilarity_search_with_score(
14096 Returns:
14197 list of tuples of (document, score)
14298 """
143- # Call the UiPath SDK to perform the search asynchronously
144- results = await self .sdk .context_grounding .search_async (
99+ # Use the context grounding service to perform async search
100+ results : list [
101+ ContextGroundingQueryResponse
102+ ] = await self .sdk .context_grounding .search_async (
145103 name = self .index_name ,
146104 query = query ,
147105 number_of_results = k ,
148106 folder_path = self .folder_path ,
149107 )
150108
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
109+ return self ._convert_results_to_documents (results )
181110
111+ @override
182112 async def asimilarity_search_with_relevance_scores (
183113 self , query : str , k : int = 4 , ** kwargs : Any
184114 ) -> 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.
115+ """Asynchronously return documents along with their relevance scores.
189116
190117 Args:
191118 query: The query string
@@ -201,6 +128,7 @@ async def asimilarity_search_with_relevance_scores(
201128 )
202129 ]
203130
131+ @override
204132 def similarity_search (
205133 self , query : str , k : int = 4 , ** kwargs : Any
206134 ) -> list [Document ]:
@@ -216,6 +144,7 @@ def similarity_search(
216144 docs_and_scores = self .similarity_search_with_score (query , k , ** kwargs )
217145 return [doc for doc , _ in docs_and_scores ]
218146
147+ @override
219148 async def asimilarity_search (
220149 self , query : str , k : int = 4 , ** kwargs : Any
221150 ) -> list [Document ]:
@@ -231,38 +160,85 @@ async def asimilarity_search(
231160 docs_and_scores = await self .asimilarity_search_with_score (query , k , ** kwargs )
232161 return [doc for doc , _ in docs_and_scores ]
233162
163+ def _convert_results_to_documents (
164+ self , results : list [ContextGroundingQueryResponse ]
165+ ) -> list [tuple [Document , float ]]:
166+ """Convert API results to Document objects with scores.
167+
168+ Args:
169+ results: List of ContextGroundingQueryResponse objects
170+
171+ Returns:
172+ List of tuples containing (Document, score)
173+ """
174+ docs_with_scores = []
175+
176+ for result in results :
177+ # Create metadata from result fields
178+ metadata = {}
179+
180+ # Add string fields with proper defaults
181+ if result .source :
182+ metadata ["source" ] = str (result .source )
183+ if result .reference :
184+ metadata ["reference" ] = str (result .reference )
185+ if result .page_number :
186+ metadata ["page_number" ] = str (result .page_number )
187+ if result .source_document_id :
188+ metadata ["source_document_id" ] = str (result .source_document_id )
189+ if result .caption :
190+ metadata ["caption" ] = str (result .caption )
191+
192+ # Add any operation metadata if available
193+ if result .metadata :
194+ if result .metadata .operation_id :
195+ metadata ["operation_id" ] = str (result .metadata .operation_id )
196+ if result .metadata .strategy :
197+ metadata ["strategy" ] = str (result .metadata .strategy )
198+
199+ # Create a Document with the content and metadata
200+ doc = Document (
201+ page_content = result .content or "" ,
202+ metadata = metadata ,
203+ )
204+
205+ # Convert score to distance (1 - score)
206+ score = 1.0 - float (result .score or 0.0 )
207+
208+ docs_with_scores .append ((doc , score ))
209+
210+ return docs_with_scores
211+
234212 @classmethod
213+ @override
235214 def from_texts (
236- cls : type [ VST ] ,
215+ cls ,
237216 texts : list [str ],
238217 embedding : Embeddings ,
239- metadatas : Optional [ list [dict [ str , Any ]]] = None ,
218+ metadatas : list [dict ] | None = None ,
240219 ** kwargs : Any ,
241- ) -> VST :
220+ ) -> Self :
242221 """This method is required by the VectorStore abstract class, but is not supported
243222 by ContextGroundingVectorStore which is read-only.
244223
245224 Raises:
246225 NotImplementedError: This method is not supported by ContextGroundingVectorStore
247226 """
248227 raise NotImplementedError (
249- "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding. "
250- "Creating a vector store from texts is not supported."
228+ "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
251229 )
252230
253- # Other required methods with minimal implementation to satisfy the interface
231+ @ override
254232 def add_texts (
255- self ,
256- texts : Iterable [str ],
257- metadatas : Optional [list [dict [str , Any ]]] = None ,
258- ** kwargs : Any ,
233+ self , texts : Iterable [str ], metadatas : list [dict ] | None = None , ** kwargs : Any
259234 ) -> list [str ]:
260235 """Not implemented for ContextGroundingVectorStore as this is a read-only wrapper."""
261236 raise NotImplementedError (
262237 "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
263- )
238+ ) # type: ignore[unreachable]
264239
265- def delete (self , ids : Optional [list [str ]] = None , ** kwargs : Any ) -> Optional [bool ]:
240+ @override
241+ def delete (self , ids : list [str ] | None = None , ** kwargs : Any ) -> bool | None :
266242 """Not implemented for ContextGroundingVectorStore as this is a read-only wrapper."""
267243 raise NotImplementedError (
268244 "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
0 commit comments