Skip to content

Commit a7fc34b

Browse files
committed
Update context grounding vector store to be compatible with gym
1 parent aefbe61 commit a7fc34b

File tree

1 file changed

+101
-112
lines changed

1 file changed

+101
-112
lines changed

src/uipath_langchain/vectorstores/context_grounding_vectorstore.py

Lines changed: 101 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -7,61 +7,64 @@
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

1312
from collections.abc import Iterable
14-
from typing import Any, Optional, TypeVar
13+
from typing import Any, Self, TypeVar, override
1514

1615
from langchain_core.documents import Document
1716
from langchain_core.embeddings import Embeddings
1817
from langchain_core.vectorstores import VectorStore
1918
from uipath import UiPath
19+
from uipath._services.context_grounding_service import ContextGroundingService
20+
from uipath.models.context_grounding import ContextGroundingQueryResponse
2021

2122
VST = TypeVar("VST", bound="ContextGroundingVectorStore")
2223

23-
2424
class 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

Comments
 (0)