agno 2.3.23__py3-none-any.whl → 2.3.24__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 +6 -0
- agno/db/mongo/mongo.py +9 -1
- agno/knowledge/chunking/markdown.py +94 -8
- agno/knowledge/chunking/semantic.py +2 -2
- agno/knowledge/knowledge.py +215 -207
- agno/models/base.py +28 -8
- agno/os/routers/knowledge/knowledge.py +19 -3
- agno/os/utils.py +1 -1
- agno/team/team.py +5 -3
- agno/tools/crawl4ai.py +3 -0
- agno/tools/file.py +14 -13
- agno/tools/function.py +9 -1
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/python.py +14 -6
- agno/tools/toolkit.py +33 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/chromadb.py +1 -1
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +1 -1
- agno/vectordb/milvus/milvus.py +1 -1
- agno/vectordb/mongodb/mongodb.py +13 -3
- agno/vectordb/pgvector/pgvector.py +1 -1
- agno/vectordb/pineconedb/pineconedb.py +2 -2
- agno/vectordb/qdrant/qdrant.py +1 -1
- agno/vectordb/redis/redisdb.py +2 -2
- agno/vectordb/singlestore/singlestore.py +1 -1
- agno/vectordb/surrealdb/surrealdb.py +2 -2
- agno/vectordb/weaviate/weaviate.py +1 -1
- {agno-2.3.23.dist-info → agno-2.3.24.dist-info}/METADATA +1 -1
- {agno-2.3.23.dist-info → agno-2.3.24.dist-info}/RECORD +33 -33
- {agno-2.3.23.dist-info → agno-2.3.24.dist-info}/WHEEL +0 -0
- {agno-2.3.23.dist-info → agno-2.3.24.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.23.dist-info → agno-2.3.24.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional, Union
|
|
|
5
5
|
|
|
6
6
|
from fastapi import APIRouter, BackgroundTasks, Depends, File, Form, HTTPException, Path, Query, Request, UploadFile
|
|
7
7
|
|
|
8
|
+
from agno.db.base import AsyncBaseDb
|
|
8
9
|
from agno.knowledge.content import Content, FileData
|
|
9
10
|
from agno.knowledge.knowledge import Knowledge
|
|
10
11
|
from agno.knowledge.reader import ReaderFactory
|
|
@@ -36,7 +37,7 @@ from agno.os.schema import (
|
|
|
36
37
|
from agno.os.settings import AgnoAPISettings
|
|
37
38
|
from agno.os.utils import get_knowledge_instance_by_db_id
|
|
38
39
|
from agno.remote.base import RemoteKnowledge
|
|
39
|
-
from agno.utils.log import log_debug, log_info
|
|
40
|
+
from agno.utils.log import log_debug, log_error, log_info
|
|
40
41
|
from agno.utils.string import generate_id
|
|
41
42
|
|
|
42
43
|
logger = logging.getLogger(__name__)
|
|
@@ -297,7 +298,17 @@ def attach_routes(router: APIRouter, knowledge_instances: List[Union[Knowledge,
|
|
|
297
298
|
else:
|
|
298
299
|
raise HTTPException(status_code=400, detail=f"Invalid reader_id: {update_data.reader_id}")
|
|
299
300
|
|
|
300
|
-
|
|
301
|
+
# Use async patch method if contents_db is an AsyncBaseDb, otherwise use sync patch method
|
|
302
|
+
updated_content_dict = None
|
|
303
|
+
try:
|
|
304
|
+
if knowledge.contents_db is not None and isinstance(knowledge.contents_db, AsyncBaseDb):
|
|
305
|
+
updated_content_dict = await knowledge.apatch_content(content)
|
|
306
|
+
else:
|
|
307
|
+
updated_content_dict = knowledge.patch_content(content)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
log_error(f"Error updating content: {str(e)}")
|
|
310
|
+
raise HTTPException(status_code=500, detail=f"Error updating content: {str(e)}")
|
|
311
|
+
|
|
301
312
|
if not updated_content_dict:
|
|
302
313
|
raise HTTPException(status_code=404, detail=f"Content not found: {content_id}")
|
|
303
314
|
|
|
@@ -1090,7 +1101,12 @@ async def process_content(
|
|
|
1090
1101
|
|
|
1091
1102
|
content.status = KnowledgeContentStatus.FAILED
|
|
1092
1103
|
content.status_message = str(e)
|
|
1093
|
-
|
|
1104
|
+
# Use async patch method if contents_db is an AsyncBaseDb, otherwise use sync patch method
|
|
1105
|
+
if knowledge.contents_db is not None and isinstance(knowledge.contents_db, AsyncBaseDb):
|
|
1106
|
+
await knowledge.apatch_content(content)
|
|
1107
|
+
else:
|
|
1108
|
+
knowledge.patch_content(content)
|
|
1109
|
+
|
|
1094
1110
|
except Exception:
|
|
1095
1111
|
# Swallow any secondary errors to avoid crashing the background task
|
|
1096
1112
|
pass
|
agno/os/utils.py
CHANGED
agno/team/team.py
CHANGED
|
@@ -1569,6 +1569,7 @@ class Team:
|
|
|
1569
1569
|
tools=_tools,
|
|
1570
1570
|
tool_choice=self.tool_choice,
|
|
1571
1571
|
tool_call_limit=self.tool_call_limit,
|
|
1572
|
+
run_response=run_response,
|
|
1572
1573
|
send_media_to_model=self.send_media_to_model,
|
|
1573
1574
|
compression_manager=self.compression_manager if self.compress_tool_results else None,
|
|
1574
1575
|
)
|
|
@@ -3392,6 +3393,7 @@ class Team:
|
|
|
3392
3393
|
tool_choice=self.tool_choice,
|
|
3393
3394
|
tool_call_limit=self.tool_call_limit,
|
|
3394
3395
|
stream_model_response=stream_model_response,
|
|
3396
|
+
run_response=run_response,
|
|
3395
3397
|
send_media_to_model=self.send_media_to_model,
|
|
3396
3398
|
compression_manager=self.compression_manager if self.compress_tool_results else None,
|
|
3397
3399
|
):
|
|
@@ -8447,7 +8449,7 @@ class Team:
|
|
|
8447
8449
|
gen_session_name_prompt = "Team Conversation\n"
|
|
8448
8450
|
|
|
8449
8451
|
# Get team session messages for generating the name
|
|
8450
|
-
messages_for_generating_session_name =
|
|
8452
|
+
messages_for_generating_session_name = session.get_messages()
|
|
8451
8453
|
|
|
8452
8454
|
for message in messages_for_generating_session_name:
|
|
8453
8455
|
gen_session_name_prompt += f"{message.role.upper()}: {message.content}\n"
|
|
@@ -8997,7 +8999,7 @@ class Team:
|
|
|
8997
8999
|
log_warning("No valid filters remain after validation. Search will proceed without filters.")
|
|
8998
9000
|
|
|
8999
9001
|
if invalid_keys == [] and valid_filters == {}:
|
|
9000
|
-
|
|
9002
|
+
log_debug("No valid filters provided. Search will proceed without filters.")
|
|
9001
9003
|
filters = None
|
|
9002
9004
|
|
|
9003
9005
|
if self.knowledge_retriever is not None and callable(self.knowledge_retriever):
|
|
@@ -9073,7 +9075,7 @@ class Team:
|
|
|
9073
9075
|
log_warning("No valid filters remain after validation. Search will proceed without filters.")
|
|
9074
9076
|
|
|
9075
9077
|
if invalid_keys == [] and valid_filters == {}:
|
|
9076
|
-
|
|
9078
|
+
log_debug("No valid filters provided. Search will proceed without filters.")
|
|
9077
9079
|
filters = None
|
|
9078
9080
|
|
|
9079
9081
|
if self.knowledge_retriever is not None and callable(self.knowledge_retriever):
|
agno/tools/crawl4ai.py
CHANGED
|
@@ -20,6 +20,7 @@ class Crawl4aiTools(Toolkit):
|
|
|
20
20
|
bm25_threshold: float = 1.0,
|
|
21
21
|
headless: bool = True,
|
|
22
22
|
wait_until: str = "domcontentloaded",
|
|
23
|
+
proxy_config: Optional[Dict[str, Any]] = None,
|
|
23
24
|
enable_crawl: bool = True,
|
|
24
25
|
all: bool = False,
|
|
25
26
|
**kwargs,
|
|
@@ -36,6 +37,7 @@ class Crawl4aiTools(Toolkit):
|
|
|
36
37
|
self.bm25_threshold = bm25_threshold
|
|
37
38
|
self.wait_until = wait_until
|
|
38
39
|
self.headless = headless
|
|
40
|
+
self.proxy_config = proxy_config or {}
|
|
39
41
|
|
|
40
42
|
def _build_config(self, search_query: Optional[str] = None) -> Dict[str, Any]:
|
|
41
43
|
"""Build CrawlerRunConfig parameters from toolkit settings."""
|
|
@@ -103,6 +105,7 @@ class Crawl4aiTools(Toolkit):
|
|
|
103
105
|
browser_config = BrowserConfig(
|
|
104
106
|
headless=self.headless,
|
|
105
107
|
verbose=False,
|
|
108
|
+
**self.proxy_config,
|
|
106
109
|
)
|
|
107
110
|
|
|
108
111
|
async with AsyncWebCrawler(config=browser_config) as crawler:
|
agno/tools/file.py
CHANGED
|
@@ -24,8 +24,7 @@ class FileTools(Toolkit):
|
|
|
24
24
|
all: bool = False,
|
|
25
25
|
**kwargs,
|
|
26
26
|
):
|
|
27
|
-
self.base_dir: Path = base_dir or Path.cwd()
|
|
28
|
-
self.base_dir = self.base_dir.resolve()
|
|
27
|
+
self.base_dir: Path = (base_dir or Path.cwd()).resolve()
|
|
29
28
|
|
|
30
29
|
tools: List[Any] = []
|
|
31
30
|
self.max_file_length = max_file_length
|
|
@@ -49,6 +48,19 @@ class FileTools(Toolkit):
|
|
|
49
48
|
|
|
50
49
|
super().__init__(name="file_tools", tools=tools, **kwargs)
|
|
51
50
|
|
|
51
|
+
def check_escape(self, relative_path: str) -> Tuple[bool, Path]:
|
|
52
|
+
"""Check if the file path is within the base directory.
|
|
53
|
+
|
|
54
|
+
Alias for _check_path maintained for backward compatibility.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
relative_path: The file name or relative path to check.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Tuple of (is_safe, resolved_path). If not safe, returns base_dir as the path.
|
|
61
|
+
"""
|
|
62
|
+
return self._check_path(relative_path, self.base_dir)
|
|
63
|
+
|
|
52
64
|
def save_file(self, contents: str, file_name: str, overwrite: bool = True, encoding: str = "utf-8") -> str:
|
|
53
65
|
"""Saves the contents to a file called `file_name` and returns the file name if successful.
|
|
54
66
|
|
|
@@ -173,17 +185,6 @@ class FileTools(Toolkit):
|
|
|
173
185
|
log_error(f"Error removing {file_name}: {e}")
|
|
174
186
|
return f"Error removing file: {e}"
|
|
175
187
|
|
|
176
|
-
def check_escape(self, relative_path: str) -> Tuple[bool, Path]:
|
|
177
|
-
d = self.base_dir.joinpath(Path(relative_path)).resolve()
|
|
178
|
-
if self.base_dir == d:
|
|
179
|
-
return True, d
|
|
180
|
-
try:
|
|
181
|
-
d.relative_to(self.base_dir)
|
|
182
|
-
except ValueError:
|
|
183
|
-
log_error("Attempted to escape base_dir")
|
|
184
|
-
return False, self.base_dir
|
|
185
|
-
return True, d
|
|
186
|
-
|
|
187
188
|
def list_files(self, **kwargs) -> str:
|
|
188
189
|
"""Returns a list of files in directory
|
|
189
190
|
:param directory: (Optional) name of directory to list.
|
agno/tools/function.py
CHANGED
|
@@ -54,9 +54,17 @@ class UserInputField:
|
|
|
54
54
|
|
|
55
55
|
@classmethod
|
|
56
56
|
def from_dict(cls, data: Dict[str, Any]) -> "UserInputField":
|
|
57
|
+
type_mapping = {"str": str, "int": int, "float": float, "bool": bool, "list": list, "dict": dict}
|
|
58
|
+
field_type_raw = data["field_type"]
|
|
59
|
+
if isinstance(field_type_raw, str):
|
|
60
|
+
field_type = type_mapping.get(field_type_raw, str)
|
|
61
|
+
elif isinstance(field_type_raw, type):
|
|
62
|
+
field_type = field_type_raw
|
|
63
|
+
else:
|
|
64
|
+
field_type = str
|
|
57
65
|
return cls(
|
|
58
66
|
name=data["name"],
|
|
59
|
-
field_type=
|
|
67
|
+
field_type=field_type,
|
|
60
68
|
description=data["description"],
|
|
61
69
|
value=data["value"],
|
|
62
70
|
)
|
agno/tools/mlx_transcribe.py
CHANGED
|
@@ -17,7 +17,7 @@ provides high-quality transcription capabilities.
|
|
|
17
17
|
|
|
18
18
|
import json
|
|
19
19
|
from pathlib import Path
|
|
20
|
-
from typing import Any, Dict, List, Optional,
|
|
20
|
+
from typing import Any, Dict, List, Optional, Union
|
|
21
21
|
|
|
22
22
|
from agno.tools import Toolkit
|
|
23
23
|
from agno.utils.log import log_info, logger
|
|
@@ -33,9 +33,10 @@ class MLXTranscribeTools(Toolkit):
|
|
|
33
33
|
self,
|
|
34
34
|
base_dir: Optional[Path] = None,
|
|
35
35
|
enable_read_files_in_base_dir: bool = True,
|
|
36
|
+
restrict_to_base_dir: bool = True,
|
|
36
37
|
path_or_hf_repo: str = "mlx-community/whisper-large-v3-turbo",
|
|
37
38
|
verbose: Optional[bool] = None,
|
|
38
|
-
temperature: Optional[Union[float,
|
|
39
|
+
temperature: Optional[Union[float, tuple[float, ...]]] = None,
|
|
39
40
|
compression_ratio_threshold: Optional[float] = None,
|
|
40
41
|
logprob_threshold: Optional[float] = None,
|
|
41
42
|
no_speech_threshold: Optional[float] = None,
|
|
@@ -50,10 +51,11 @@ class MLXTranscribeTools(Toolkit):
|
|
|
50
51
|
all: bool = False,
|
|
51
52
|
**kwargs,
|
|
52
53
|
):
|
|
53
|
-
self.base_dir: Path = base_dir or Path.cwd()
|
|
54
|
+
self.base_dir: Path = (base_dir or Path.cwd()).resolve()
|
|
55
|
+
self.restrict_to_base_dir = restrict_to_base_dir
|
|
54
56
|
self.path_or_hf_repo: str = path_or_hf_repo
|
|
55
57
|
self.verbose: Optional[bool] = verbose
|
|
56
|
-
self.temperature: Optional[Union[float,
|
|
58
|
+
self.temperature: Optional[Union[float, tuple[float, ...]]] = temperature
|
|
57
59
|
self.compression_ratio_threshold: Optional[float] = compression_ratio_threshold
|
|
58
60
|
self.logprob_threshold: Optional[float] = logprob_threshold
|
|
59
61
|
self.no_speech_threshold: Optional[float] = no_speech_threshold
|
|
@@ -83,9 +85,10 @@ class MLXTranscribeTools(Toolkit):
|
|
|
83
85
|
str: The transcribed text or an error message if the transcription fails.
|
|
84
86
|
"""
|
|
85
87
|
try:
|
|
86
|
-
|
|
87
|
-
if
|
|
88
|
-
return "
|
|
88
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
89
|
+
if not safe:
|
|
90
|
+
return f"Error: Path '{file_name}' is outside the allowed base directory"
|
|
91
|
+
audio_file_path = str(file_path)
|
|
89
92
|
|
|
90
93
|
log_info(f"Transcribing audio file {audio_file_path}")
|
|
91
94
|
transcription_kwargs: Dict[str, Any] = {
|
agno/tools/python.py
CHANGED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any, List, Optional
|
|
5
5
|
|
|
6
6
|
from agno.tools import Toolkit
|
|
7
|
-
from agno.utils.log import log_debug, log_info, logger
|
|
7
|
+
from agno.utils.log import log_debug, log_error, log_info, logger
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@functools.lru_cache(maxsize=None)
|
|
@@ -18,9 +18,11 @@ class PythonTools(Toolkit):
|
|
|
18
18
|
base_dir: Optional[Path] = None,
|
|
19
19
|
safe_globals: Optional[dict] = None,
|
|
20
20
|
safe_locals: Optional[dict] = None,
|
|
21
|
+
restrict_to_base_dir: bool = True,
|
|
21
22
|
**kwargs,
|
|
22
23
|
):
|
|
23
|
-
self.base_dir: Path = base_dir or Path.cwd()
|
|
24
|
+
self.base_dir: Path = (base_dir or Path.cwd()).resolve()
|
|
25
|
+
self.restrict_to_base_dir = restrict_to_base_dir
|
|
24
26
|
|
|
25
27
|
# Restricted global and local scope
|
|
26
28
|
self.safe_globals: dict = safe_globals or globals()
|
|
@@ -55,7 +57,9 @@ class PythonTools(Toolkit):
|
|
|
55
57
|
"""
|
|
56
58
|
try:
|
|
57
59
|
warn()
|
|
58
|
-
file_path = self.base_dir.
|
|
60
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
61
|
+
if not safe:
|
|
62
|
+
return f"Error: Path '{file_name}' is outside the allowed base directory"
|
|
59
63
|
log_debug(f"Saving code to {file_path}")
|
|
60
64
|
if not file_path.parent.exists():
|
|
61
65
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -89,8 +93,9 @@ class PythonTools(Toolkit):
|
|
|
89
93
|
"""
|
|
90
94
|
try:
|
|
91
95
|
warn()
|
|
92
|
-
file_path = self.base_dir.
|
|
93
|
-
|
|
96
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
97
|
+
if not safe:
|
|
98
|
+
return f"Error: Path '{file_name}' is outside the allowed base directory"
|
|
94
99
|
log_info(f"Running {file_path}")
|
|
95
100
|
globals_after_run = runpy.run_path(str(file_path), init_globals=self.safe_globals, run_name="__main__")
|
|
96
101
|
if variable_to_return:
|
|
@@ -113,7 +118,10 @@ class PythonTools(Toolkit):
|
|
|
113
118
|
"""
|
|
114
119
|
try:
|
|
115
120
|
log_info(f"Reading file: {file_name}")
|
|
116
|
-
file_path = self.base_dir.
|
|
121
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
122
|
+
if not safe:
|
|
123
|
+
log_error(f"Attempted to read file outside base directory: {file_name}")
|
|
124
|
+
return "Error reading file: path outside allowed directory"
|
|
117
125
|
contents = file_path.read_text(encoding="utf-8")
|
|
118
126
|
return str(contents)
|
|
119
127
|
except Exception as e:
|
agno/tools/toolkit.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from collections import OrderedDict
|
|
2
2
|
from inspect import iscoroutinefunction
|
|
3
|
-
from
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
|
|
4
5
|
|
|
5
6
|
from agno.tools.function import Function
|
|
6
|
-
from agno.utils.log import log_debug, log_warning, logger
|
|
7
|
+
from agno.utils.log import log_debug, log_error, log_warning, logger
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class Toolkit:
|
|
@@ -343,6 +344,36 @@ class Toolkit:
|
|
|
343
344
|
"""
|
|
344
345
|
pass
|
|
345
346
|
|
|
347
|
+
def _check_path(self, file_name: str, base_dir: Path, restrict_to_base_dir: bool = True) -> Tuple[bool, Path]:
|
|
348
|
+
"""Check if the file path is within the base directory.
|
|
349
|
+
|
|
350
|
+
This method validates that a given file path resolves to a location
|
|
351
|
+
within the specified base_dir, preventing directory traversal attacks.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
file_name: The file name or relative path to check.
|
|
355
|
+
base_dir: The base directory to validate against.
|
|
356
|
+
restrict_to_base_dir: If True, reject paths outside base_dir.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Tuple of (is_safe, resolved_path). If not safe, returns base_dir as the path.
|
|
360
|
+
"""
|
|
361
|
+
file_path = base_dir.joinpath(file_name).resolve()
|
|
362
|
+
|
|
363
|
+
if not restrict_to_base_dir:
|
|
364
|
+
return True, file_path
|
|
365
|
+
|
|
366
|
+
if base_dir == file_path:
|
|
367
|
+
return True, file_path
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
file_path.relative_to(base_dir)
|
|
371
|
+
except ValueError:
|
|
372
|
+
log_error(f"Path escapes base directory: {file_name}")
|
|
373
|
+
return False, base_dir
|
|
374
|
+
|
|
375
|
+
return True, file_path
|
|
376
|
+
|
|
346
377
|
def __repr__(self):
|
|
347
378
|
return f"<{self.__class__.__name__} name={self.name} functions={list(self.functions.keys())}>"
|
|
348
379
|
|
|
@@ -32,7 +32,7 @@ class Cassandra(VectorDb):
|
|
|
32
32
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
33
33
|
|
|
34
34
|
embedder = OpenAIEmbedder()
|
|
35
|
-
|
|
35
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
36
36
|
# Initialize base class with name and description
|
|
37
37
|
super().__init__(name=name, description=description)
|
|
38
38
|
|
agno/vectordb/chroma/chromadb.py
CHANGED
|
@@ -117,7 +117,7 @@ class ChromaDb(VectorDb):
|
|
|
117
117
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
118
118
|
|
|
119
119
|
embedder = OpenAIEmbedder()
|
|
120
|
-
|
|
120
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
121
121
|
self.embedder: Embedder = embedder
|
|
122
122
|
# Distance metric
|
|
123
123
|
self.distance: Distance = distance
|
|
@@ -71,7 +71,7 @@ class Clickhouse(VectorDb):
|
|
|
71
71
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
72
72
|
|
|
73
73
|
_embedder = OpenAIEmbedder()
|
|
74
|
-
|
|
74
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
75
75
|
self.embedder: Embedder = _embedder
|
|
76
76
|
self.dimensions: Optional[int] = self.embedder.dimensions
|
|
77
77
|
|
|
@@ -100,7 +100,7 @@ class CouchbaseSearch(VectorDb):
|
|
|
100
100
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
101
101
|
|
|
102
102
|
embedder = OpenAIEmbedder()
|
|
103
|
-
|
|
103
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
104
104
|
self.embedder = embedder
|
|
105
105
|
self.overwrite = overwrite
|
|
106
106
|
self.is_global_level_index = is_global_level_index
|
agno/vectordb/milvus/milvus.py
CHANGED
|
@@ -89,7 +89,7 @@ class Milvus(VectorDb):
|
|
|
89
89
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
90
90
|
|
|
91
91
|
embedder = OpenAIEmbedder()
|
|
92
|
-
|
|
92
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
93
93
|
self.embedder: Embedder = embedder
|
|
94
94
|
self.dimensions: Optional[int] = self.embedder.dimensions
|
|
95
95
|
|
agno/vectordb/mongodb/mongodb.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import time
|
|
3
|
+
from importlib import metadata
|
|
3
4
|
from typing import Any, Dict, List, Optional, Union
|
|
4
5
|
|
|
5
6
|
from bson import ObjectId
|
|
@@ -20,11 +21,14 @@ except ImportError:
|
|
|
20
21
|
try:
|
|
21
22
|
from pymongo import AsyncMongoClient, MongoClient, errors
|
|
22
23
|
from pymongo.collection import Collection
|
|
24
|
+
from pymongo.driver_info import DriverInfo
|
|
23
25
|
from pymongo.operations import SearchIndexModel
|
|
24
26
|
|
|
25
27
|
except ImportError:
|
|
26
28
|
raise ImportError("`pymongo` not installed. Please install using `pip install pymongo`")
|
|
27
29
|
|
|
30
|
+
DRIVER_METADATA = DriverInfo(name="Agno", version=metadata.version("agno"))
|
|
31
|
+
|
|
28
32
|
|
|
29
33
|
class MongoDb(VectorDb):
|
|
30
34
|
"""
|
|
@@ -110,7 +114,7 @@ class MongoDb(VectorDb):
|
|
|
110
114
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
111
115
|
|
|
112
116
|
embedder = OpenAIEmbedder()
|
|
113
|
-
|
|
117
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
114
118
|
self.embedder = embedder
|
|
115
119
|
|
|
116
120
|
self.distance_metric = distance_metric
|
|
@@ -135,6 +139,11 @@ class MongoDb(VectorDb):
|
|
|
135
139
|
self._async_db = None
|
|
136
140
|
self._async_collection: Optional[Collection] = None
|
|
137
141
|
|
|
142
|
+
if self._client is not None:
|
|
143
|
+
# append_metadata was added in PyMongo 4.14.0, but is a valid database name on earlier versions
|
|
144
|
+
if callable(self._client.append_metadata):
|
|
145
|
+
self._client.append_metadata(DRIVER_METADATA)
|
|
146
|
+
|
|
138
147
|
def _get_client(self) -> MongoClient:
|
|
139
148
|
"""Create or retrieve the MongoDB client."""
|
|
140
149
|
if self._client is None:
|
|
@@ -157,7 +166,7 @@ class MongoDb(VectorDb):
|
|
|
157
166
|
warnings.filterwarnings(
|
|
158
167
|
"ignore", category=UserWarning, message=".*connected to a CosmosDB cluster.*"
|
|
159
168
|
)
|
|
160
|
-
self._client = MongoClient(self.connection_string, **cosmos_kwargs) # type: ignore
|
|
169
|
+
self._client = MongoClient(self.connection_string, **cosmos_kwargs, driver=DRIVER_METADATA) # type: ignore
|
|
161
170
|
|
|
162
171
|
self._client.admin.command("ping")
|
|
163
172
|
|
|
@@ -173,7 +182,7 @@ class MongoDb(VectorDb):
|
|
|
173
182
|
else:
|
|
174
183
|
try:
|
|
175
184
|
log_debug("Creating MongoDB Client")
|
|
176
|
-
self._client = MongoClient(self.connection_string, **self.kwargs)
|
|
185
|
+
self._client = MongoClient(self.connection_string, **self.kwargs, driver=DRIVER_METADATA) # type: ignore
|
|
177
186
|
# Trigger a connection to verify the client
|
|
178
187
|
self._client.admin.command("ping")
|
|
179
188
|
log_info("Connected to MongoDB successfully.")
|
|
@@ -195,6 +204,7 @@ class MongoDb(VectorDb):
|
|
|
195
204
|
maxPoolSize=self.kwargs.get("maxPoolSize", 100),
|
|
196
205
|
retryWrites=self.kwargs.get("retryWrites", True),
|
|
197
206
|
serverSelectionTimeoutMS=5000,
|
|
207
|
+
driver=DRIVER_METADATA,
|
|
198
208
|
)
|
|
199
209
|
# Verify connection
|
|
200
210
|
try:
|
|
@@ -122,7 +122,7 @@ class PgVector(VectorDb):
|
|
|
122
122
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
123
123
|
|
|
124
124
|
embedder = OpenAIEmbedder()
|
|
125
|
-
|
|
125
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
126
126
|
self.embedder: Embedder = embedder
|
|
127
127
|
self.dimensions: Optional[int] = self.embedder.dimensions
|
|
128
128
|
|
|
@@ -26,7 +26,7 @@ from agno.filters import FilterExpr
|
|
|
26
26
|
from agno.knowledge.document import Document
|
|
27
27
|
from agno.knowledge.embedder import Embedder
|
|
28
28
|
from agno.knowledge.reranker.base import Reranker
|
|
29
|
-
from agno.utils.log import log_debug,
|
|
29
|
+
from agno.utils.log import log_debug, log_warning, logger
|
|
30
30
|
from agno.vectordb.base import VectorDb
|
|
31
31
|
|
|
32
32
|
|
|
@@ -136,7 +136,7 @@ class PineconeDb(VectorDb):
|
|
|
136
136
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
137
137
|
|
|
138
138
|
_embedder = OpenAIEmbedder()
|
|
139
|
-
|
|
139
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
140
140
|
self.embedder: Embedder = _embedder
|
|
141
141
|
self.reranker: Optional[Reranker] = reranker
|
|
142
142
|
|
agno/vectordb/qdrant/qdrant.py
CHANGED
|
@@ -102,7 +102,7 @@ class Qdrant(VectorDb):
|
|
|
102
102
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
103
103
|
|
|
104
104
|
embedder = OpenAIEmbedder()
|
|
105
|
-
|
|
105
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
106
106
|
|
|
107
107
|
self.embedder: Embedder = embedder
|
|
108
108
|
self.dimensions: Optional[int] = self.embedder.dimensions
|
agno/vectordb/redis/redisdb.py
CHANGED
|
@@ -16,7 +16,7 @@ from agno.filters import FilterExpr
|
|
|
16
16
|
from agno.knowledge.document import Document
|
|
17
17
|
from agno.knowledge.embedder import Embedder
|
|
18
18
|
from agno.knowledge.reranker.base import Reranker
|
|
19
|
-
from agno.utils.log import log_debug, log_error,
|
|
19
|
+
from agno.utils.log import log_debug, log_error, log_warning
|
|
20
20
|
from agno.utils.string import hash_string_sha256
|
|
21
21
|
from agno.vectordb.base import VectorDb
|
|
22
22
|
from agno.vectordb.distance import Distance
|
|
@@ -80,7 +80,7 @@ class RedisDB(VectorDb):
|
|
|
80
80
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
81
81
|
|
|
82
82
|
embedder = OpenAIEmbedder()
|
|
83
|
-
|
|
83
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
84
84
|
|
|
85
85
|
self.embedder: Embedder = embedder
|
|
86
86
|
self.dimensions: Optional[int] = self.embedder.dimensions
|
|
@@ -56,7 +56,7 @@ class SingleStore(VectorDb):
|
|
|
56
56
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
57
57
|
|
|
58
58
|
embedder = OpenAIEmbedder()
|
|
59
|
-
|
|
59
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
60
60
|
self.embedder: Embedder = embedder
|
|
61
61
|
self.dimensions: Optional[int] = self.embedder.dimensions
|
|
62
62
|
|
|
@@ -14,7 +14,7 @@ except ImportError as e:
|
|
|
14
14
|
from agno.filters import FilterExpr
|
|
15
15
|
from agno.knowledge.document import Document
|
|
16
16
|
from agno.knowledge.embedder import Embedder
|
|
17
|
-
from agno.utils.log import log_debug, log_error,
|
|
17
|
+
from agno.utils.log import log_debug, log_error, log_warning
|
|
18
18
|
from agno.vectordb.base import VectorDb
|
|
19
19
|
from agno.vectordb.distance import Distance
|
|
20
20
|
|
|
@@ -135,7 +135,7 @@ class SurrealDb(VectorDb):
|
|
|
135
135
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
136
136
|
|
|
137
137
|
embedder = OpenAIEmbedder()
|
|
138
|
-
|
|
138
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
139
139
|
self.embedder: Embedder = embedder
|
|
140
140
|
self.dimensions = self.embedder.dimensions
|
|
141
141
|
self.collection = collection
|
|
@@ -81,7 +81,7 @@ class Weaviate(VectorDb):
|
|
|
81
81
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
82
82
|
|
|
83
83
|
embedder = OpenAIEmbedder()
|
|
84
|
-
|
|
84
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
85
85
|
self.embedder: Embedder = embedder
|
|
86
86
|
|
|
87
87
|
# Search setup
|