agno 1.7.4__py3-none-any.whl → 1.7.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +28 -14
- agno/app/fastapi/app.py +1 -1
- agno/app/fastapi/async_router.py +67 -16
- agno/app/fastapi/sync_router.py +80 -14
- agno/knowledge/agent.py +39 -2
- agno/knowledge/combined.py +1 -1
- agno/team/team.py +20 -5
- agno/tools/decorator.py +45 -2
- agno/tools/function.py +16 -12
- agno/utils/pprint.py +2 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +493 -0
- agno/workflow/v2/workflow.py +2 -4
- {agno-1.7.4.dist-info → agno-1.7.5.dist-info}/METADATA +4 -1
- {agno-1.7.4.dist-info → agno-1.7.5.dist-info}/RECORD +19 -17
- {agno-1.7.4.dist-info → agno-1.7.5.dist-info}/WHEEL +0 -0
- {agno-1.7.4.dist-info → agno-1.7.5.dist-info}/entry_points.txt +0 -0
- {agno-1.7.4.dist-info → agno-1.7.5.dist-info}/licenses/LICENSE +0 -0
- {agno-1.7.4.dist-info → agno-1.7.5.dist-info}/top_level.txt +0 -0
agno/tools/function.py
CHANGED
|
@@ -151,7 +151,7 @@ class Function(BaseModel):
|
|
|
151
151
|
param_type_hints = {
|
|
152
152
|
name: type_hints.get(name)
|
|
153
153
|
for name in sig.parameters
|
|
154
|
-
if name != "return" and name not in ["agent", "team"]
|
|
154
|
+
if name != "return" and name not in ["agent", "team", "self"]
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
# Parse docstring for parameters
|
|
@@ -177,7 +177,9 @@ class Function(BaseModel):
|
|
|
177
177
|
# If strict=True mark all fields as required
|
|
178
178
|
# See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
|
|
179
179
|
if strict:
|
|
180
|
-
parameters["required"] = [
|
|
180
|
+
parameters["required"] = [
|
|
181
|
+
name for name in parameters["properties"] if name not in ["agent", "team", "self"]
|
|
182
|
+
]
|
|
181
183
|
else:
|
|
182
184
|
# Mark a field as required if it has no default value (this would include optional fields)
|
|
183
185
|
parameters["required"] = [
|
|
@@ -235,7 +237,7 @@ class Function(BaseModel):
|
|
|
235
237
|
# log_info(f"Type hints for {self.name}: {type_hints}")
|
|
236
238
|
|
|
237
239
|
# Filter out return type and only process parameters
|
|
238
|
-
excluded_params = ["return", "agent", "team"]
|
|
240
|
+
excluded_params = ["return", "agent", "team", "self"]
|
|
239
241
|
if self.requires_user_input and self.user_input_fields:
|
|
240
242
|
if len(self.user_input_fields) == 0:
|
|
241
243
|
excluded_params.extend(list(type_hints.keys()))
|
|
@@ -337,7 +339,9 @@ class Function(BaseModel):
|
|
|
337
339
|
|
|
338
340
|
def process_schema_for_strict(self):
|
|
339
341
|
self.parameters["additionalProperties"] = False
|
|
340
|
-
self.parameters["required"] = [
|
|
342
|
+
self.parameters["required"] = [
|
|
343
|
+
name for name in self.parameters["properties"] if name not in ["agent", "team", "self"]
|
|
344
|
+
]
|
|
341
345
|
|
|
342
346
|
def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
|
|
343
347
|
"""Generate a cache key based on function name and arguments."""
|
|
@@ -554,7 +558,7 @@ class FunctionCall(BaseModel):
|
|
|
554
558
|
from functools import reduce
|
|
555
559
|
from inspect import iscoroutinefunction
|
|
556
560
|
|
|
557
|
-
def execute_entrypoint():
|
|
561
|
+
def execute_entrypoint(name, func, args):
|
|
558
562
|
"""Execute the entrypoint function."""
|
|
559
563
|
arguments = entrypoint_args.copy()
|
|
560
564
|
if self.arguments is not None:
|
|
@@ -572,9 +576,9 @@ class FunctionCall(BaseModel):
|
|
|
572
576
|
# Pass the inner function as next_func to the hook
|
|
573
577
|
# The hook will call next_func to continue the chain
|
|
574
578
|
def next_func(**kwargs):
|
|
575
|
-
return inner_func()
|
|
579
|
+
return inner_func(name, func, kwargs)
|
|
576
580
|
|
|
577
|
-
hook_args = self._build_hook_args(hook, name,
|
|
581
|
+
hook_args = self._build_hook_args(hook, name, next_func, args)
|
|
578
582
|
|
|
579
583
|
return hook(**hook_args)
|
|
580
584
|
|
|
@@ -716,7 +720,7 @@ class FunctionCall(BaseModel):
|
|
|
716
720
|
from functools import reduce
|
|
717
721
|
from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction
|
|
718
722
|
|
|
719
|
-
async def execute_entrypoint_async():
|
|
723
|
+
async def execute_entrypoint_async(name, func, args):
|
|
720
724
|
"""Execute the entrypoint function asynchronously."""
|
|
721
725
|
arguments = entrypoint_args.copy()
|
|
722
726
|
if self.arguments is not None:
|
|
@@ -729,7 +733,7 @@ class FunctionCall(BaseModel):
|
|
|
729
733
|
result = await result
|
|
730
734
|
return result
|
|
731
735
|
|
|
732
|
-
def execute_entrypoint():
|
|
736
|
+
def execute_entrypoint(name, func, args):
|
|
733
737
|
"""Execute the entrypoint function synchronously."""
|
|
734
738
|
arguments = entrypoint_args.copy()
|
|
735
739
|
if self.arguments is not None:
|
|
@@ -750,11 +754,11 @@ class FunctionCall(BaseModel):
|
|
|
750
754
|
# The hook will call next_func to continue the chain
|
|
751
755
|
async def next_func(**kwargs):
|
|
752
756
|
if iscoroutinefunction(inner_func):
|
|
753
|
-
return await inner_func()
|
|
757
|
+
return await inner_func(name, func, kwargs)
|
|
754
758
|
else:
|
|
755
|
-
return inner_func()
|
|
759
|
+
return inner_func(name, func, kwargs)
|
|
756
760
|
|
|
757
|
-
hook_args = self._build_hook_args(hook, name,
|
|
761
|
+
hook_args = self._build_hook_args(hook, name, next_func, args)
|
|
758
762
|
|
|
759
763
|
if iscoroutinefunction(hook):
|
|
760
764
|
return await hook(**hook_args)
|
agno/utils/pprint.py
CHANGED
|
@@ -163,6 +163,8 @@ async def apprint_run_response(
|
|
|
163
163
|
except Exception as e:
|
|
164
164
|
logger.warning(f"Failed to convert response to Markdown: {e}")
|
|
165
165
|
else:
|
|
166
|
+
if isinstance(streaming_response_content, JSON):
|
|
167
|
+
streaming_response_content = streaming_response_content.text + "\n" # type: ignore
|
|
166
168
|
streaming_response_content += resp.content # type: ignore
|
|
167
169
|
|
|
168
170
|
formatted_response = Markdown(streaming_response_content) if markdown else streaming_response_content # type: ignore
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
from typing import Any, Dict, Final, List, Optional, Union
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from surrealdb import (
|
|
5
|
+
AsyncHttpSurrealConnection,
|
|
6
|
+
AsyncWsSurrealConnection,
|
|
7
|
+
BlockingHttpSurrealConnection,
|
|
8
|
+
BlockingWsSurrealConnection,
|
|
9
|
+
)
|
|
10
|
+
except ImportError as e:
|
|
11
|
+
msg = "The `surrealdb` package is not installed. Please install it via `pip install surrealdb`."
|
|
12
|
+
raise ImportError(msg) from e
|
|
13
|
+
|
|
14
|
+
from agno.document import Document
|
|
15
|
+
from agno.embedder import Embedder
|
|
16
|
+
from agno.utils.log import log_debug, log_error, log_info
|
|
17
|
+
from agno.vectordb.base import VectorDb
|
|
18
|
+
from agno.vectordb.distance import Distance
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SurrealDb(VectorDb):
|
|
22
|
+
"""SurrealDB Vector Database implementation supporting both sync and async operations."""
|
|
23
|
+
|
|
24
|
+
# SQL Query Constants
|
|
25
|
+
CREATE_TABLE_QUERY: Final[str] = """
|
|
26
|
+
DEFINE TABLE IF NOT EXISTS {collection} SCHEMAFUL;
|
|
27
|
+
DEFINE FIELD IF NOT EXISTS content ON {collection} TYPE string;
|
|
28
|
+
DEFINE FIELD IF NOT EXISTS embedding ON {collection} TYPE array<float>;
|
|
29
|
+
DEFINE FIELD IF NOT EXISTS meta_data ON {collection} FLEXIBLE TYPE object;
|
|
30
|
+
DEFINE INDEX IF NOT EXISTS vector_idx ON {collection} FIELDS embedding HNSW DIMENSION {dimensions} DIST {distance};
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
DOC_EXISTS_QUERY: Final[str] = """
|
|
34
|
+
SELECT * FROM {collection}
|
|
35
|
+
WHERE content = $content
|
|
36
|
+
LIMIT 1
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
NAME_EXISTS_QUERY: Final[str] = """
|
|
40
|
+
SELECT * FROM {collection}
|
|
41
|
+
WHERE meta_data.name = $name
|
|
42
|
+
LIMIT 1
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
ID_EXISTS_QUERY: Final[str] = """
|
|
46
|
+
SELECT * FROM {collection}
|
|
47
|
+
WHERE id = $id
|
|
48
|
+
LIMIT 1
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
UPSERT_QUERY: Final[str] = """
|
|
52
|
+
UPSERT {thing}
|
|
53
|
+
SET content = $content,
|
|
54
|
+
embedding = $embedding,
|
|
55
|
+
meta_data = $meta_data
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
SEARCH_QUERY: Final[str] = """
|
|
59
|
+
SELECT
|
|
60
|
+
content,
|
|
61
|
+
meta_data,
|
|
62
|
+
vector::distance::knn() as distance
|
|
63
|
+
FROM {collection}
|
|
64
|
+
WHERE embedding <|{limit}, {search_ef}|> $query_embedding
|
|
65
|
+
{filter_condition}
|
|
66
|
+
ORDER BY distance ASC
|
|
67
|
+
LIMIT {limit};
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
INFO_DB_QUERY: Final[str] = "INFO FOR DB;"
|
|
71
|
+
DROP_TABLE_QUERY: Final[str] = "REMOVE TABLE {collection}"
|
|
72
|
+
DELETE_ALL_QUERY: Final[str] = "DELETE {collection}"
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
client: Optional[Union[BlockingWsSurrealConnection, BlockingHttpSurrealConnection]] = None,
|
|
77
|
+
async_client: Optional[Union[AsyncWsSurrealConnection, AsyncHttpSurrealConnection]] = None,
|
|
78
|
+
collection: str = "documents",
|
|
79
|
+
distance: Distance = Distance.cosine,
|
|
80
|
+
efc: int = 150,
|
|
81
|
+
m: int = 12,
|
|
82
|
+
search_ef: int = 40,
|
|
83
|
+
embedder: Optional[Embedder] = None,
|
|
84
|
+
):
|
|
85
|
+
"""Initialize SurrealDB connection.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
url: SurrealDB server URL (e.g. ws://localhost:8000/rpc)
|
|
89
|
+
client: A blocking connection, either HTTP or WS
|
|
90
|
+
async_client: An async connection, either HTTP or WS (default: None)
|
|
91
|
+
collection: Collection name to store documents (default: documents)
|
|
92
|
+
distance: Distance metric to use (default: cosine)
|
|
93
|
+
efc: HNSW construction time/accuracy trade-off (default: 150)
|
|
94
|
+
m: HNSW max number of connections per element (default: 12)
|
|
95
|
+
search_ef: HNSW search time/accuracy trade-off (default: 40)
|
|
96
|
+
embedder: Embedder instance for creating embeddings (default: OpenAIEmbedder)
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
# Embedder for embedding the document contents
|
|
100
|
+
if embedder is None:
|
|
101
|
+
from agno.embedder.openai import OpenAIEmbedder
|
|
102
|
+
|
|
103
|
+
embedder = OpenAIEmbedder()
|
|
104
|
+
log_info("Embedder not provided, using OpenAIEmbedder as default.")
|
|
105
|
+
self.embedder: Embedder = embedder
|
|
106
|
+
self.dimensions = self.embedder.dimensions
|
|
107
|
+
self.collection = collection
|
|
108
|
+
|
|
109
|
+
# Convert Distance enum to SurrealDB distance type
|
|
110
|
+
self.distance = {Distance.cosine: "COSINE", Distance.l2: "EUCLIDEAN", Distance.max_inner_product: "DOT"}[
|
|
111
|
+
distance
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
self._client: Optional[Union[BlockingHttpSurrealConnection, BlockingWsSurrealConnection]] = client
|
|
115
|
+
self._async_client: Optional[Union[AsyncWsSurrealConnection, AsyncHttpSurrealConnection]] = async_client
|
|
116
|
+
|
|
117
|
+
if self._client is None and self._async_client is None:
|
|
118
|
+
msg = "Client and async client are not provided. Please provide one of them."
|
|
119
|
+
raise RuntimeError(msg)
|
|
120
|
+
|
|
121
|
+
# HNSW index parameters
|
|
122
|
+
self.efc = efc
|
|
123
|
+
self.m = m
|
|
124
|
+
self.search_ef = search_ef
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def async_client(self) -> Union[AsyncWsSurrealConnection, AsyncHttpSurrealConnection]:
|
|
128
|
+
"""Check if the async client is initialized.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
RuntimeError: If the async client is not initialized.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
The async client.
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
if self._async_client is None:
|
|
138
|
+
msg = "Async client is not initialized"
|
|
139
|
+
raise RuntimeError(msg)
|
|
140
|
+
return self._async_client
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def client(self) -> Union[BlockingHttpSurrealConnection, BlockingWsSurrealConnection]:
|
|
144
|
+
"""Check if the client is initialized.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
The client.
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
if self._client is None:
|
|
151
|
+
msg = "Client is not initialized"
|
|
152
|
+
raise RuntimeError(msg)
|
|
153
|
+
return self._client
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _build_filter_condition(filters: Optional[Dict[str, Any]] = None) -> str:
|
|
157
|
+
"""Build filter condition for queries.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
filters: A dictionary of filters to apply to the query.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
A string representing the filter condition.
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
if not filters:
|
|
167
|
+
return ""
|
|
168
|
+
conditions = [f"meta_data.{key} = ${key}" for key in filters]
|
|
169
|
+
return "AND " + " AND ".join(conditions)
|
|
170
|
+
|
|
171
|
+
# Synchronous methods
|
|
172
|
+
def create(self) -> None:
|
|
173
|
+
"""Create the vector collection and index."""
|
|
174
|
+
if not self.exists():
|
|
175
|
+
log_debug(f"Creating collection: {self.collection}")
|
|
176
|
+
query = self.CREATE_TABLE_QUERY.format(
|
|
177
|
+
collection=self.collection,
|
|
178
|
+
distance=self.distance,
|
|
179
|
+
dimensions=self.dimensions,
|
|
180
|
+
efc=self.efc,
|
|
181
|
+
m=self.m,
|
|
182
|
+
)
|
|
183
|
+
self.client.query(query)
|
|
184
|
+
|
|
185
|
+
def doc_exists(self, document: Document) -> bool:
|
|
186
|
+
"""Check if a document exists by its content.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
document: The document to check.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if the document exists, False otherwise.
|
|
193
|
+
|
|
194
|
+
"""
|
|
195
|
+
log_debug(f"Checking if document exists: {document.content}")
|
|
196
|
+
result = self.client.query(
|
|
197
|
+
self.DOC_EXISTS_QUERY.format(collection=self.collection),
|
|
198
|
+
{"content": document.content},
|
|
199
|
+
)
|
|
200
|
+
return bool(self._extract_result(result))
|
|
201
|
+
|
|
202
|
+
def name_exists(self, name: str) -> bool:
|
|
203
|
+
"""Check if a document exists by its name.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
name: The name of the document to check.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
True if the document exists, False otherwise.
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
log_debug(f"Checking if document exists: {name}")
|
|
213
|
+
result = self.client.query(self.NAME_EXISTS_QUERY.format(collection=self.collection), {"name": name})
|
|
214
|
+
return bool(self._extract_result(result))
|
|
215
|
+
|
|
216
|
+
def insert(self, documents: List[Document], filters: Optional[Dict[str, Any]] = None) -> None:
|
|
217
|
+
"""Insert documents into the vector store.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
documents: A list of documents to insert.
|
|
221
|
+
filters: A dictionary of filters to apply to the query.
|
|
222
|
+
|
|
223
|
+
"""
|
|
224
|
+
for doc in documents:
|
|
225
|
+
doc.embed(embedder=self.embedder)
|
|
226
|
+
meta_data: Dict[str, Any] = doc.meta_data if isinstance(doc.meta_data, dict) else {}
|
|
227
|
+
data: Dict[str, Any] = {"content": doc.content, "embedding": doc.embedding, "meta_data": meta_data}
|
|
228
|
+
if filters:
|
|
229
|
+
data["meta_data"].update(filters)
|
|
230
|
+
self.client.create(self.collection, data)
|
|
231
|
+
|
|
232
|
+
def upsert(self, documents: List[Document], filters: Optional[Dict[str, Any]] = None) -> None:
|
|
233
|
+
"""Upsert documents into the vector store.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
documents: A list of documents to upsert.
|
|
237
|
+
filters: A dictionary of filters to apply to the query.
|
|
238
|
+
|
|
239
|
+
"""
|
|
240
|
+
for doc in documents:
|
|
241
|
+
doc.embed(embedder=self.embedder)
|
|
242
|
+
meta_data: Dict[str, Any] = doc.meta_data if isinstance(doc.meta_data, dict) else {}
|
|
243
|
+
data: Dict[str, Any] = {"content": doc.content, "embedding": doc.embedding, "meta_data": meta_data}
|
|
244
|
+
if filters:
|
|
245
|
+
data["meta_data"].update(filters)
|
|
246
|
+
thing = f"{self.collection}:{doc.id}" if doc.id else self.collection
|
|
247
|
+
self.client.query(self.UPSERT_QUERY.format(thing=thing), data)
|
|
248
|
+
|
|
249
|
+
def search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
|
|
250
|
+
"""Search for similar documents.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
query: The query to search for.
|
|
254
|
+
limit: The maximum number of documents to return.
|
|
255
|
+
filters: A dictionary of filters to apply to the query.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
A list of documents that are similar to the query.
|
|
259
|
+
|
|
260
|
+
"""
|
|
261
|
+
query_embedding = self.embedder.get_embedding(query)
|
|
262
|
+
if query_embedding is None:
|
|
263
|
+
log_error(f"Error getting embedding for Query: {query}")
|
|
264
|
+
return []
|
|
265
|
+
|
|
266
|
+
filter_condition = self._build_filter_condition(filters)
|
|
267
|
+
log_debug(f"Filter condition: {filter_condition}")
|
|
268
|
+
search_query = self.SEARCH_QUERY.format(
|
|
269
|
+
collection=self.collection,
|
|
270
|
+
limit=limit,
|
|
271
|
+
search_ef=self.search_ef,
|
|
272
|
+
filter_condition=filter_condition,
|
|
273
|
+
distance=self.distance,
|
|
274
|
+
)
|
|
275
|
+
log_debug(f"Search query: {search_query}")
|
|
276
|
+
response = self.client.query(
|
|
277
|
+
search_query,
|
|
278
|
+
{"query_embedding": query_embedding, **filters} if filters else {"query_embedding": query_embedding},
|
|
279
|
+
)
|
|
280
|
+
log_debug(f"Search response: {response}")
|
|
281
|
+
|
|
282
|
+
documents = []
|
|
283
|
+
for item in response:
|
|
284
|
+
if isinstance(item, dict):
|
|
285
|
+
doc = Document(
|
|
286
|
+
content=item.get("content", ""),
|
|
287
|
+
embedding=item.get("embedding", []),
|
|
288
|
+
meta_data=item.get("meta_data", {}),
|
|
289
|
+
embedder=self.embedder,
|
|
290
|
+
)
|
|
291
|
+
documents.append(doc)
|
|
292
|
+
log_debug(f"Found {len(documents)} documents")
|
|
293
|
+
return documents
|
|
294
|
+
|
|
295
|
+
def drop(self) -> None:
|
|
296
|
+
"""Drop the vector collection."""
|
|
297
|
+
log_debug(f"Dropping collection: {self.collection}")
|
|
298
|
+
self.client.query(self.DROP_TABLE_QUERY.format(collection=self.collection))
|
|
299
|
+
|
|
300
|
+
def exists(self) -> bool:
|
|
301
|
+
"""Check if the vector collection exists.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
True if the collection exists, False otherwise.
|
|
305
|
+
|
|
306
|
+
"""
|
|
307
|
+
log_debug(f"Checking if collection exists: {self.collection}")
|
|
308
|
+
response = self.client.query(self.INFO_DB_QUERY)
|
|
309
|
+
result = self._extract_result(response)
|
|
310
|
+
if isinstance(result, dict) and "tables" in result:
|
|
311
|
+
return self.collection in result["tables"]
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
def delete(self) -> bool:
|
|
315
|
+
"""Delete all documents from the vector store.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
True if the collection was deleted, False otherwise.
|
|
319
|
+
|
|
320
|
+
"""
|
|
321
|
+
self.client.query(self.DELETE_ALL_QUERY.format(collection=self.collection))
|
|
322
|
+
return True
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
def _extract_result(query_result: Union[List[Dict[str, Any]], Dict[str, Any]]) -> Union[List[Any], Dict[str, Any]]:
|
|
326
|
+
"""Extract the actual result from SurrealDB query response.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
query_result: The query result from SurrealDB.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
The actual result from SurrealDB query response.
|
|
333
|
+
|
|
334
|
+
"""
|
|
335
|
+
log_debug(f"Query result: {query_result}")
|
|
336
|
+
if isinstance(query_result, dict):
|
|
337
|
+
return query_result
|
|
338
|
+
if isinstance(query_result, list):
|
|
339
|
+
if len(query_result) > 0:
|
|
340
|
+
return query_result[0].get("result", {})
|
|
341
|
+
return []
|
|
342
|
+
return []
|
|
343
|
+
|
|
344
|
+
async def async_create(self) -> None:
|
|
345
|
+
"""Create the vector collection and index asynchronously."""
|
|
346
|
+
log_debug(f"Creating collection: {self.collection}")
|
|
347
|
+
await self.async_client.query(
|
|
348
|
+
self.CREATE_TABLE_QUERY.format(
|
|
349
|
+
collection=self.collection,
|
|
350
|
+
distance=self.distance,
|
|
351
|
+
dimensions=self.dimensions,
|
|
352
|
+
efc=self.efc,
|
|
353
|
+
m=self.m,
|
|
354
|
+
),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
async def async_doc_exists(self, document: Document) -> bool:
|
|
358
|
+
"""Check if a document exists by its content asynchronously.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
True if the document exists, False otherwise.
|
|
362
|
+
|
|
363
|
+
"""
|
|
364
|
+
response = await self.async_client.query(
|
|
365
|
+
self.DOC_EXISTS_QUERY.format(collection=self.collection),
|
|
366
|
+
{"content": document.content},
|
|
367
|
+
)
|
|
368
|
+
return bool(self._extract_result(response))
|
|
369
|
+
|
|
370
|
+
async def async_name_exists(self, name: str) -> bool:
|
|
371
|
+
"""Check if a document exists by its name asynchronously.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
True if the document exists, False otherwise.
|
|
375
|
+
|
|
376
|
+
"""
|
|
377
|
+
response = await self.async_client.query(
|
|
378
|
+
self.NAME_EXISTS_QUERY.format(collection=self.collection),
|
|
379
|
+
{"name": name},
|
|
380
|
+
)
|
|
381
|
+
return bool(self._extract_result(response))
|
|
382
|
+
|
|
383
|
+
async def async_insert(self, documents: List[Document], filters: Optional[Dict[str, Any]] = None) -> None:
|
|
384
|
+
"""Insert documents into the vector store asynchronously.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
documents: A list of documents to insert.
|
|
388
|
+
filters: A dictionary of filters to apply to the query.
|
|
389
|
+
|
|
390
|
+
"""
|
|
391
|
+
for doc in documents:
|
|
392
|
+
doc.embed(embedder=self.embedder)
|
|
393
|
+
meta_data: Dict[str, Any] = doc.meta_data if isinstance(doc.meta_data, dict) else {}
|
|
394
|
+
data: Dict[str, Any] = {"content": doc.content, "embedding": doc.embedding, "meta_data": meta_data}
|
|
395
|
+
if filters:
|
|
396
|
+
data["meta_data"].update(filters)
|
|
397
|
+
log_debug(f"Inserting document asynchronously: {doc.name} ({doc.meta_data})")
|
|
398
|
+
await self.async_client.create(self.collection, data)
|
|
399
|
+
|
|
400
|
+
async def async_upsert(self, documents: List[Document], filters: Optional[Dict[str, Any]] = None) -> None:
|
|
401
|
+
"""Upsert documents into the vector store asynchronously.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
documents: A list of documents to upsert.
|
|
405
|
+
filters: A dictionary of filters to apply to the query.
|
|
406
|
+
|
|
407
|
+
"""
|
|
408
|
+
for doc in documents:
|
|
409
|
+
doc.embed(embedder=self.embedder)
|
|
410
|
+
meta_data: Dict[str, Any] = doc.meta_data if isinstance(doc.meta_data, dict) else {}
|
|
411
|
+
data: Dict[str, Any] = {"content": doc.content, "embedding": doc.embedding, "meta_data": meta_data}
|
|
412
|
+
if filters:
|
|
413
|
+
data["meta_data"].update(filters)
|
|
414
|
+
log_debug(f"Upserting document asynchronously: {doc.name} ({doc.meta_data})")
|
|
415
|
+
thing = f"{self.collection}:{doc.id}" if doc.id else self.collection
|
|
416
|
+
await self.async_client.query(self.UPSERT_QUERY.format(thing=thing), data)
|
|
417
|
+
|
|
418
|
+
async def async_search(
|
|
419
|
+
self,
|
|
420
|
+
query: str,
|
|
421
|
+
limit: int = 5,
|
|
422
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
423
|
+
) -> List[Document]:
|
|
424
|
+
"""Search for similar documents asynchronously.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
query: The query to search for.
|
|
428
|
+
limit: The maximum number of documents to return.
|
|
429
|
+
filters: A dictionary of filters to apply to the query.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
A list of documents that are similar to the query.
|
|
433
|
+
|
|
434
|
+
"""
|
|
435
|
+
query_embedding = self.embedder.get_embedding(query)
|
|
436
|
+
if query_embedding is None:
|
|
437
|
+
log_error(f"Error getting embedding for Query: {query}")
|
|
438
|
+
return []
|
|
439
|
+
|
|
440
|
+
filter_condition = self._build_filter_condition(filters)
|
|
441
|
+
search_query = self.SEARCH_QUERY.format(
|
|
442
|
+
collection=self.collection,
|
|
443
|
+
limit=limit,
|
|
444
|
+
search_ef=self.search_ef,
|
|
445
|
+
filter_condition=filter_condition,
|
|
446
|
+
distance=self.distance,
|
|
447
|
+
)
|
|
448
|
+
response = await self.async_client.query(
|
|
449
|
+
search_query,
|
|
450
|
+
{"query_embedding": query_embedding, **filters} if filters else {"query_embedding": query_embedding},
|
|
451
|
+
)
|
|
452
|
+
log_debug(f"Search response: {response}")
|
|
453
|
+
documents = []
|
|
454
|
+
for item in response:
|
|
455
|
+
if isinstance(item, dict):
|
|
456
|
+
doc = Document(
|
|
457
|
+
content=item.get("content", ""),
|
|
458
|
+
embedding=item.get("embedding", []),
|
|
459
|
+
meta_data=item.get("meta_data", {}),
|
|
460
|
+
embedder=self.embedder,
|
|
461
|
+
)
|
|
462
|
+
documents.append(doc)
|
|
463
|
+
log_debug(f"Found {len(documents)} documents asynchronously")
|
|
464
|
+
return documents
|
|
465
|
+
|
|
466
|
+
async def async_drop(self) -> None:
|
|
467
|
+
"""Drop the vector collection asynchronously."""
|
|
468
|
+
log_debug(f"Dropping collection: {self.collection}")
|
|
469
|
+
await self.async_client.query(self.DROP_TABLE_QUERY.format(collection=self.collection))
|
|
470
|
+
|
|
471
|
+
async def async_exists(self) -> bool:
|
|
472
|
+
"""Check if the vector collection exists asynchronously.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
True if the collection exists, False otherwise.
|
|
476
|
+
|
|
477
|
+
"""
|
|
478
|
+
log_debug(f"Checking if collection exists: {self.collection}")
|
|
479
|
+
response = await self.async_client.query(self.INFO_DB_QUERY)
|
|
480
|
+
result = self._extract_result(response)
|
|
481
|
+
if isinstance(result, dict) and "tables" in result:
|
|
482
|
+
return self.collection in result["tables"]
|
|
483
|
+
return False
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def upsert_available() -> bool:
|
|
487
|
+
"""Check if upsert is available.
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
True if upsert is available, False otherwise.
|
|
491
|
+
|
|
492
|
+
"""
|
|
493
|
+
return True
|
agno/workflow/v2/workflow.py
CHANGED
|
@@ -413,9 +413,7 @@ class Workflow:
|
|
|
413
413
|
return func(**call_kwargs)
|
|
414
414
|
except TypeError as e:
|
|
415
415
|
# If signature inspection fails, fall back to original method
|
|
416
|
-
logger.warning(
|
|
417
|
-
f"Async function signature inspection failed: {e}. Falling back to original calling convention."
|
|
418
|
-
)
|
|
416
|
+
logger.warning(f"Function signature inspection failed: {e}. Falling back to original calling convention.")
|
|
419
417
|
return func(workflow, execution_input, **kwargs)
|
|
420
418
|
|
|
421
419
|
def _execute(
|
|
@@ -808,7 +806,7 @@ class Workflow:
|
|
|
808
806
|
content += str(chunk)
|
|
809
807
|
workflow_run_response.content = content
|
|
810
808
|
else:
|
|
811
|
-
workflow_run_response.content = self.
|
|
809
|
+
workflow_run_response.content = self._call_custom_function(self.steps, self, execution_input, **kwargs)
|
|
812
810
|
workflow_run_response.status = RunStatus.completed
|
|
813
811
|
|
|
814
812
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agno
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.5
|
|
4
4
|
Summary: Agno: a lightweight library for building Multi-Agent Systems
|
|
5
5
|
Author-email: Ashpreet Bedi <ashpreet@agno.com>
|
|
6
6
|
License: Copyright (c) Agno, Inc.
|
|
@@ -573,6 +573,8 @@ Provides-Extra: clickhouse
|
|
|
573
573
|
Requires-Dist: clickhouse-connect; extra == "clickhouse"
|
|
574
574
|
Provides-Extra: pinecone
|
|
575
575
|
Requires-Dist: pinecone==5.4.2; extra == "pinecone"
|
|
576
|
+
Provides-Extra: surrealdb
|
|
577
|
+
Requires-Dist: surrealdb>=1.0.4; extra == "surrealdb"
|
|
576
578
|
Provides-Extra: pdf
|
|
577
579
|
Requires-Dist: pypdf; extra == "pdf"
|
|
578
580
|
Requires-Dist: rapidocr_onnxruntime; extra == "pdf"
|
|
@@ -664,6 +666,7 @@ Requires-Dist: agno[weaviate]; extra == "vectordbs"
|
|
|
664
666
|
Requires-Dist: agno[milvusdb]; extra == "vectordbs"
|
|
665
667
|
Requires-Dist: agno[clickhouse]; extra == "vectordbs"
|
|
666
668
|
Requires-Dist: agno[pinecone]; extra == "vectordbs"
|
|
669
|
+
Requires-Dist: agno[surrealdb]; extra == "vectordbs"
|
|
667
670
|
Provides-Extra: knowledge
|
|
668
671
|
Requires-Dist: agno[pdf]; extra == "knowledge"
|
|
669
672
|
Requires-Dist: agno[docx]; extra == "knowledge"
|