Skip to content

Commit b79e226

Browse files
committed
Update context grounding vector store to be compatible with gym. Update version to 0.0.147
1 parent 868fd91 commit b79e226

File tree

2 files changed

+90
-114
lines changed

2 files changed

+90
-114
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.0.146"
3+
version = "0.0.147"
44
description = "UiPath Langchain"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.10"

src/uipath_langchain/vectorstores/context_grounding_vectorstore.py

Lines changed: 89 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,49 @@
22
Vector store implementation that connects to UiPath Context Grounding as a backend.
33
44
This 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

137
from collections.abc import Iterable
14-
from typing import Any, Optional, TypeVar
8+
from typing import Any, Self, override
159

1610
from langchain_core.documents import Document
1711
from langchain_core.embeddings import Embeddings
1812
from langchain_core.vectorstores import VectorStore
1913
from uipath import UiPath
20-
21-
VST = TypeVar("VST", bound="ContextGroundingVectorStore")
14+
from uipath.models.context_grounding import ContextGroundingQueryResponse
2215

2316

2417
class 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

Comments
 (0)