77### - UIPATH_URL="https://alpha.uipath.com/{ORG_ID}/{TENANT_ID}"
88### - UIPATH_ACCESS_TOKEN={BEARER_TOKEN_WITH_CONTEXT_GROUNDING_PERMISSIONS}
99### - UIPATH_FOLDER_PATH="" - this can be left empty
10- ### - UIPATH_FOLDER_KEY="" - this can be left empty
1110"""
1211
1312from collections .abc import Iterable
14- from typing import Any , Optional , TypeVar
13+ from typing import Any , Self , TypeVar , override
1514
1615from langchain_core .documents import Document
1716from langchain_core .embeddings import Embeddings
1817from langchain_core .vectorstores import VectorStore
1918from uipath import UiPath
19+ from uipath ._services .context_grounding_service import ContextGroundingService
20+ from uipath .models .context_grounding import ContextGroundingQueryResponse
2021
2122VST = TypeVar ("VST" , bound = "ContextGroundingVectorStore" )
2223
23-
2424class ContextGroundingVectorStore (VectorStore ):
2525 """Vector store that uses UiPath Context Grounding (ECS) as a backend.
2626
2727 This class provides a straightforward implementation that connects to the
2828 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- )
4229 """
4330
31+
4432 def __init__ (
4533 self ,
4634 index_name : str ,
47- folder_path : Optional [ str ] = None ,
48- uipath_sdk : Optional [ UiPath ] = None ,
35+ uipath_sdk : UiPath | None = None ,
36+ folder_path : str | None = None ,
4937 ):
5038 """Initialize the ContextGroundingVectorStore.
5139
5240 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.
41+ index_name: Name of the context grounding index to use (schema name)
42+ uipath_sdk: Optional UiPath SDK instance. If provided, uipath_url and access_token are ignored
43+ folder_path: Optional folder path for folder-scoped operations
5544 """
45+ self .index_name = index_name
46+ self .folder_path = folder_path
47+
5648 self .index_name = index_name
5749 self .folder_path = folder_path
5850 self .sdk = uipath_sdk or UiPath ()
5951
52+ self .uipath_url = self .sdk ._config .base_url
53+ config = self .sdk ._config
54+ execution_context = self .sdk ._execution_context
55+
56+ # Create ContextGroundingService instance (composition over inheritance)
57+ self ._context_grounding_service = ContextGroundingService (
58+ config , execution_context , self .sdk .folders , self .sdk .buckets
59+ )
60+
61+ # VectorStore implementation methods
62+
63+ @override
6064 def similarity_search_with_score (
6165 self , query : str , k : int = 4 , ** kwargs : Any
6266 ) -> list [tuple [Document , float ]]:
6367 """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.
6568
6669 Args:
6770 query: The query string
@@ -70,52 +73,22 @@ def similarity_search_with_score(
7073 Returns:
7174 list of tuples of (document, score)
7275 """
73- # Call the UiPath SDK to perform the search
74- results = self .sdk . context_grounding .search (
76+ # Use the context grounding service to perform search
77+ results : list [ ContextGroundingQueryResponse ] = self ._context_grounding_service .search (
7578 name = self .index_name ,
7679 query = query ,
7780 number_of_results = k ,
7881 folder_path = self .folder_path ,
7982 )
8083
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
84+ return self ._convert_results_to_documents (results )
11085
86+ @override
11187 def similarity_search_with_relevance_scores (
11288 self , query : str , k : int = 4 , ** kwargs : Any
11389 ) -> list [tuple [Document , float ]]:
11490 """Return documents along with their relevance scores on a scale from 0 to 1.
11591
116- This directly uses the scores provided by the Context Grounding API,
117- which are already normalized between 0 and 1.
118-
11992 Args:
12093 query: The query string
12194 k: Number of documents to return (default=4)
@@ -128,6 +101,7 @@ def similarity_search_with_relevance_scores(
128101 for doc , score in self .similarity_search_with_score (query , k , ** kwargs )
129102 ]
130103
104+ @override
131105 async def asimilarity_search_with_score (
132106 self , query : str , k : int = 4 , ** kwargs : Any
133107 ) -> list [tuple [Document , float ]]:
@@ -140,52 +114,23 @@ async def asimilarity_search_with_score(
140114 Returns:
141115 list of tuples of (document, score)
142116 """
143- # Call the UiPath SDK to perform the search asynchronously
144- results = await self .sdk .context_grounding .search_async (
117+ # Use the context grounding service to perform async search
118+ results : list [
119+ ContextGroundingQueryResponse
120+ ] = await self ._context_grounding_service .search_async (
145121 name = self .index_name ,
146122 query = query ,
147123 number_of_results = k ,
148124 folder_path = self .folder_path ,
149125 )
150126
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
127+ return self ._convert_results_to_documents (results )
181128
129+ @override
182130 async def asimilarity_search_with_relevance_scores (
183131 self , query : str , k : int = 4 , ** kwargs : Any
184132 ) -> 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.
133+ """Asynchronously return documents along with their relevance scores.
189134
190135 Args:
191136 query: The query string
@@ -196,14 +141,11 @@ async def asimilarity_search_with_relevance_scores(
196141 """
197142 return [
198143 (doc , 1.0 - score )
199- for doc , score in await self .asimilarity_search_with_score (
200- query , k , ** kwargs
201- )
144+ for doc , score in await self .asimilarity_search_with_score (query , k , ** kwargs )
202145 ]
203146
204- def similarity_search (
205- self , query : str , k : int = 4 , ** kwargs : Any
206- ) -> list [Document ]:
147+ @override
148+ def similarity_search (self , query : str , k : int = 4 , ** kwargs : Any ) -> list [Document ]:
207149 """Return documents most similar to the query.
208150
209151 Args:
@@ -216,9 +158,8 @@ def similarity_search(
216158 docs_and_scores = self .similarity_search_with_score (query , k , ** kwargs )
217159 return [doc for doc , _ in docs_and_scores ]
218160
219- async def asimilarity_search (
220- self , query : str , k : int = 4 , ** kwargs : Any
221- ) -> list [Document ]:
161+ @override
162+ async def asimilarity_search (self , query : str , k : int = 4 , ** kwargs : Any ) -> list [Document ]:
222163 """Asynchronously return documents most similar to the query.
223164
224165 Args:
@@ -231,38 +172,86 @@ async def asimilarity_search(
231172 docs_and_scores = await self .asimilarity_search_with_score (query , k , ** kwargs )
232173 return [doc for doc , _ in docs_and_scores ]
233174
175+ def _convert_results_to_documents (
176+ self , results : list [ContextGroundingQueryResponse ]
177+ ) -> list [tuple [Document , float ]]:
178+ """Convert API results to Document objects with scores.
179+
180+ Args:
181+ results: List of ContextGroundingQueryResponse objects
182+
183+ Returns:
184+ List of tuples containing (Document, score)
185+ """
186+ docs_with_scores = []
187+
188+ for result in results :
189+ # Create metadata from result fields
190+ metadata = {}
191+
192+ # Add string fields with proper defaults
193+ if result .source :
194+ metadata ["source" ] = str (result .source )
195+ if result .reference :
196+ metadata ["reference" ] = str (result .reference )
197+ if result .page_number :
198+ metadata ["page_number" ] = str (result .page_number )
199+ if result .source_document_id :
200+ metadata ["source_document_id" ] = str (result .source_document_id )
201+ if result .caption :
202+ metadata ["caption" ] = str (result .caption )
203+
204+ # Add any operation metadata if available
205+ if result .metadata :
206+ if result .metadata .operation_id :
207+ metadata ["operation_id" ] = str (result .metadata .operation_id )
208+ if result .metadata .strategy :
209+ metadata ["strategy" ] = str (result .metadata .strategy )
210+
211+ # Create a Document with the content and metadata
212+ doc = Document (
213+ page_content = result .content or "" ,
214+ metadata = metadata ,
215+ )
216+
217+ # Convert score to distance (1 - score)
218+ score = 1.0 - float (result .score or 0.0 )
219+
220+ docs_with_scores .append ((doc , score ))
221+
222+ return docs_with_scores
223+
234224 @classmethod
225+ @override
235226 def from_texts (
236- cls : type [ VST ] ,
227+ cls ,
237228 texts : list [str ],
238229 embedding : Embeddings ,
239- metadatas : Optional [ list [dict [ str , Any ]]] = None ,
230+ metadatas : list [dict ] | None = None ,
240231 ** kwargs : Any ,
241- ) -> VST :
232+ ) -> Self :
242233 """This method is required by the VectorStore abstract class, but is not supported
243234 by ContextGroundingVectorStore which is read-only.
244235
245236 Raises:
246237 NotImplementedError: This method is not supported by ContextGroundingVectorStore
247238 """
248239 raise NotImplementedError (
249- "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding. "
250- "Creating a vector store from texts is not supported."
240+ "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
241+ +
251242 )
252243
253- # Other required methods with minimal implementation to satisfy the interface
244+ @ override
254245 def add_texts (
255- self ,
256- texts : Iterable [str ],
257- metadatas : Optional [list [dict [str , Any ]]] = None ,
258- ** kwargs : Any ,
246+ self , texts : Iterable [str ], metadatas : list [dict ] | None = None , ** kwargs : Any
259247 ) -> list [str ]:
260248 """Not implemented for ContextGroundingVectorStore as this is a read-only wrapper."""
261249 raise NotImplementedError (
262250 "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
263- )
251+ ) # type: ignore[unreachable]
264252
265- def delete (self , ids : Optional [list [str ]] = None , ** kwargs : Any ) -> Optional [bool ]:
253+ @override
254+ def delete (self , ids : list [str ] | None = None , ** kwargs : Any ) -> bool | None :
266255 """Not implemented for ContextGroundingVectorStore as this is a read-only wrapper."""
267256 raise NotImplementedError (
268257 "ContextGroundingVectorStore is a read-only wrapper for UiPath Context Grounding."
0 commit comments