agno 2.3.22__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 +28 -1
- agno/agent/remote.py +1 -1
- agno/db/mongo/mongo.py +9 -1
- agno/db/mysql/async_mysql.py +5 -7
- agno/db/mysql/mysql.py +5 -7
- agno/db/mysql/schemas.py +39 -21
- agno/db/postgres/async_postgres.py +10 -2
- agno/db/postgres/postgres.py +5 -7
- agno/db/postgres/schemas.py +39 -21
- agno/db/singlestore/schemas.py +41 -21
- agno/db/singlestore/singlestore.py +14 -3
- agno/db/sqlite/async_sqlite.py +7 -2
- agno/db/sqlite/schemas.py +36 -21
- agno/db/sqlite/sqlite.py +3 -7
- agno/knowledge/chunking/markdown.py +94 -8
- agno/knowledge/chunking/semantic.py +2 -2
- agno/knowledge/knowledge.py +215 -207
- agno/models/base.py +32 -8
- agno/models/google/gemini.py +27 -4
- agno/os/routers/agents/router.py +1 -1
- agno/os/routers/evals/evals.py +2 -2
- agno/os/routers/knowledge/knowledge.py +21 -5
- agno/os/routers/knowledge/schemas.py +1 -1
- agno/os/routers/memory/memory.py +4 -4
- agno/os/routers/session/session.py +2 -2
- agno/os/routers/teams/router.py +2 -2
- agno/os/routers/traces/traces.py +3 -3
- agno/os/routers/workflows/router.py +1 -1
- agno/os/schema.py +1 -1
- agno/os/utils.py +1 -1
- agno/remote/base.py +1 -1
- agno/team/remote.py +1 -1
- agno/team/team.py +24 -4
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +150 -13
- agno/tools/crawl4ai.py +3 -0
- agno/tools/file.py +14 -13
- agno/tools/function.py +15 -2
- agno/tools/mcp/mcp.py +1 -0
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/python.py +14 -6
- agno/tools/toolkit.py +122 -23
- 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/workflow/remote.py +1 -1
- agno/workflow/workflow.py +14 -0
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/METADATA +1 -1
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/RECORD +62 -62
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/WHEEL +0 -0
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/top_level.txt +0 -0
agno/db/sqlite/schemas.py
CHANGED
|
@@ -111,25 +111,36 @@ TRACE_TABLE_SCHEMA = {
|
|
|
111
111
|
"created_at": {"type": String, "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
114
|
+
|
|
115
|
+
def _get_span_table_schema(traces_table_name: str = "agno_traces") -> dict[str, Any]:
|
|
116
|
+
"""Get the span table schema with the correct foreign key reference.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
traces_table_name: The name of the traces table to reference in the foreign key.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The span table schema dictionary.
|
|
123
|
+
"""
|
|
124
|
+
return {
|
|
125
|
+
"span_id": {"type": String, "primary_key": True, "nullable": False},
|
|
126
|
+
"trace_id": {
|
|
127
|
+
"type": String,
|
|
128
|
+
"nullable": False,
|
|
129
|
+
"index": True,
|
|
130
|
+
"foreign_key": f"{traces_table_name}.trace_id",
|
|
131
|
+
},
|
|
132
|
+
"parent_span_id": {"type": String, "nullable": True, "index": True},
|
|
133
|
+
"name": {"type": String, "nullable": False},
|
|
134
|
+
"span_kind": {"type": String, "nullable": False},
|
|
135
|
+
"status_code": {"type": String, "nullable": False},
|
|
136
|
+
"status_message": {"type": String, "nullable": True},
|
|
137
|
+
"start_time": {"type": String, "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
138
|
+
"end_time": {"type": String, "nullable": False}, # ISO 8601 datetime string
|
|
139
|
+
"duration_ms": {"type": BigInteger, "nullable": False},
|
|
140
|
+
"attributes": {"type": JSON, "nullable": True},
|
|
141
|
+
"created_at": {"type": String, "nullable": False, "index": True}, # ISO 8601 datetime string
|
|
142
|
+
}
|
|
143
|
+
|
|
133
144
|
|
|
134
145
|
CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
|
|
135
146
|
"id": {"type": String, "primary_key": True, "nullable": False},
|
|
@@ -152,16 +163,21 @@ VERSIONS_TABLE_SCHEMA = {
|
|
|
152
163
|
}
|
|
153
164
|
|
|
154
165
|
|
|
155
|
-
def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
166
|
+
def get_table_schema_definition(table_type: str, traces_table_name: str = "agno_traces") -> dict[str, Any]:
|
|
156
167
|
"""
|
|
157
168
|
Get the expected schema definition for the given table.
|
|
158
169
|
|
|
159
170
|
Args:
|
|
160
171
|
table_type (str): The type of table to get the schema for.
|
|
172
|
+
traces_table_name (str): The name of the traces table (used for spans foreign key).
|
|
161
173
|
|
|
162
174
|
Returns:
|
|
163
175
|
Dict[str, Any]: Dictionary containing column definitions for the table
|
|
164
176
|
"""
|
|
177
|
+
# Handle spans table specially to resolve the foreign key reference
|
|
178
|
+
if table_type == "spans":
|
|
179
|
+
return _get_span_table_schema(traces_table_name)
|
|
180
|
+
|
|
165
181
|
schemas = {
|
|
166
182
|
"sessions": SESSION_TABLE_SCHEMA,
|
|
167
183
|
"evals": EVAL_TABLE_SCHEMA,
|
|
@@ -169,7 +185,6 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
|
169
185
|
"memories": USER_MEMORY_TABLE_SCHEMA,
|
|
170
186
|
"knowledge": KNOWLEDGE_TABLE_SCHEMA,
|
|
171
187
|
"traces": TRACE_TABLE_SCHEMA,
|
|
172
|
-
"spans": SPAN_TABLE_SCHEMA,
|
|
173
188
|
"culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
|
|
174
189
|
"versions": VERSIONS_TABLE_SCHEMA,
|
|
175
190
|
}
|
agno/db/sqlite/sqlite.py
CHANGED
|
@@ -173,7 +173,8 @@ class SqliteDb(BaseDb):
|
|
|
173
173
|
Table: SQLAlchemy Table object
|
|
174
174
|
"""
|
|
175
175
|
try:
|
|
176
|
-
|
|
176
|
+
# Pass traces_table_name for spans table foreign key resolution
|
|
177
|
+
table_schema = get_table_schema_definition(table_type, traces_table_name=self.trace_table_name).copy()
|
|
177
178
|
|
|
178
179
|
columns: List[Column] = []
|
|
179
180
|
indexes: List[str] = []
|
|
@@ -197,12 +198,7 @@ class SqliteDb(BaseDb):
|
|
|
197
198
|
|
|
198
199
|
# Handle foreign key constraint
|
|
199
200
|
if "foreign_key" in col_config:
|
|
200
|
-
|
|
201
|
-
# For spans table, dynamically replace the traces table reference
|
|
202
|
-
# with the actual trace table name configured for this db instance
|
|
203
|
-
if table_type == "spans" and "trace_id" in fk_ref:
|
|
204
|
-
fk_ref = f"{self.trace_table_name}.trace_id"
|
|
205
|
-
column_args.append(ForeignKey(fk_ref))
|
|
201
|
+
column_args.append(ForeignKey(col_config["foreign_key"]))
|
|
206
202
|
|
|
207
203
|
columns.append(Column(*column_args, **column_kwargs)) # type: ignore
|
|
208
204
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import tempfile
|
|
3
|
-
from typing import List
|
|
4
|
+
from typing import List, Union
|
|
4
5
|
|
|
5
6
|
try:
|
|
6
7
|
from unstructured.chunking.title import chunk_by_title # type: ignore
|
|
@@ -13,17 +14,83 @@ from agno.knowledge.document.base import Document
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class MarkdownChunking(ChunkingStrategy):
|
|
16
|
-
"""A chunking strategy that splits markdown based on structure like headers, paragraphs and sections
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
"""A chunking strategy that splits markdown based on structure like headers, paragraphs and sections
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
chunk_size: Maximum size of each chunk in characters
|
|
21
|
+
overlap: Number of characters to overlap between chunks
|
|
22
|
+
split_on_headings: Controls heading-based splitting behavior:
|
|
23
|
+
- False: Use size-based chunking (default)
|
|
24
|
+
- True: Split on all headings (H1-H6)
|
|
25
|
+
- int: Split on headings at or above this level (1-6)
|
|
26
|
+
e.g., 2 splits on H1 and H2, keeping H3-H6 content together
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, chunk_size: int = 5000, overlap: int = 0, split_on_headings: Union[bool, int] = False):
|
|
19
30
|
self.chunk_size = chunk_size
|
|
20
31
|
self.overlap = overlap
|
|
32
|
+
self.split_on_headings = split_on_headings
|
|
33
|
+
|
|
34
|
+
# Validate split_on_headings parameter
|
|
35
|
+
# Note: In Python, isinstance(False, int) is True, so we exclude booleans explicitly
|
|
36
|
+
if isinstance(split_on_headings, int) and not isinstance(split_on_headings, bool):
|
|
37
|
+
if not (1 <= split_on_headings <= 6):
|
|
38
|
+
raise ValueError("split_on_headings must be between 1 and 6 when using integer value")
|
|
39
|
+
|
|
40
|
+
def _split_by_headings(self, content: str) -> List[str]:
|
|
41
|
+
"""
|
|
42
|
+
Split markdown content by headings, keeping each heading with its content.
|
|
43
|
+
Returns a list of sections where each section starts with a heading.
|
|
44
|
+
|
|
45
|
+
When split_on_headings is an int, only splits on headings at or above that level.
|
|
46
|
+
For example, split_on_headings=2 splits on H1 and H2, keeping H3-H6 content together.
|
|
47
|
+
"""
|
|
48
|
+
# Determine which heading levels to split on
|
|
49
|
+
if isinstance(self.split_on_headings, int) and not isinstance(self.split_on_headings, bool):
|
|
50
|
+
# Split on headings at or above this level (1 to split_on_headings)
|
|
51
|
+
max_heading_level = self.split_on_headings
|
|
52
|
+
heading_pattern = rf"^#{{{1},{max_heading_level}}}\s+.+$"
|
|
53
|
+
else:
|
|
54
|
+
# split_on_headings is True: split on all headings (# to ######)
|
|
55
|
+
heading_pattern = r"^#{1,6}\s+.+$"
|
|
56
|
+
|
|
57
|
+
# Split content while keeping the delimiter (heading)
|
|
58
|
+
# Use non-capturing group for the pattern to avoid extra capture groups
|
|
59
|
+
parts = re.split(f"({heading_pattern})", content, flags=re.MULTILINE)
|
|
60
|
+
|
|
61
|
+
sections = []
|
|
62
|
+
current_section = ""
|
|
63
|
+
|
|
64
|
+
for part in parts:
|
|
65
|
+
if not part or not part.strip():
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Check if this part is a heading
|
|
69
|
+
if re.match(heading_pattern, part.strip(), re.MULTILINE):
|
|
70
|
+
# Save previous section if exists
|
|
71
|
+
if current_section.strip():
|
|
72
|
+
sections.append(current_section.strip())
|
|
73
|
+
# Start new section with this heading
|
|
74
|
+
current_section = part
|
|
75
|
+
else:
|
|
76
|
+
# Add content to current section
|
|
77
|
+
current_section += "\n\n" + part if current_section else part
|
|
78
|
+
|
|
79
|
+
# Don't forget the last section
|
|
80
|
+
if current_section.strip():
|
|
81
|
+
sections.append(current_section.strip())
|
|
82
|
+
|
|
83
|
+
return sections if sections else [content]
|
|
21
84
|
|
|
22
85
|
def _partition_markdown_content(self, content: str) -> List[str]:
|
|
23
86
|
"""
|
|
24
87
|
Partition markdown content and return a list of text chunks.
|
|
25
88
|
Falls back to paragraph splitting if the markdown chunking fails.
|
|
26
89
|
"""
|
|
90
|
+
# When split_on_headings is True or an int, use regex-based splitting to preserve headings
|
|
91
|
+
if self.split_on_headings:
|
|
92
|
+
return self._split_by_headings(content)
|
|
93
|
+
|
|
27
94
|
try:
|
|
28
95
|
# Create a temporary file with the markdown content.
|
|
29
96
|
# This is the recommended usage of the unstructured library.
|
|
@@ -38,7 +105,6 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
38
105
|
raw_paragraphs = content.split("\n\n")
|
|
39
106
|
return [self.clean_text(para) for para in raw_paragraphs]
|
|
40
107
|
|
|
41
|
-
# Chunk by title with some default values
|
|
42
108
|
chunked_elements = chunk_by_title(
|
|
43
109
|
elements=elements,
|
|
44
110
|
max_characters=self.chunk_size,
|
|
@@ -74,7 +140,13 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
74
140
|
|
|
75
141
|
def chunk(self, document: Document) -> List[Document]:
|
|
76
142
|
"""Split markdown document into chunks based on markdown structure"""
|
|
77
|
-
|
|
143
|
+
# If content is empty, return as-is
|
|
144
|
+
if not document.content:
|
|
145
|
+
return [document]
|
|
146
|
+
|
|
147
|
+
# When split_on_headings is enabled, always split by headings regardless of size
|
|
148
|
+
# Only skip chunking for small content when using size-based chunking
|
|
149
|
+
if not self.split_on_headings and len(document.content) <= self.chunk_size:
|
|
78
150
|
return [document]
|
|
79
151
|
|
|
80
152
|
# Split using markdown chunking logic, or fallback to paragraphs
|
|
@@ -90,7 +162,20 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
90
162
|
section = section.strip()
|
|
91
163
|
section_size = len(section)
|
|
92
164
|
|
|
93
|
-
|
|
165
|
+
# When split_on_headings is True or an int, each section becomes its own chunk
|
|
166
|
+
if self.split_on_headings:
|
|
167
|
+
meta_data = chunk_meta_data.copy()
|
|
168
|
+
meta_data["chunk"] = chunk_number
|
|
169
|
+
chunk_id = None
|
|
170
|
+
if document.id:
|
|
171
|
+
chunk_id = f"{document.id}_{chunk_number}"
|
|
172
|
+
elif document.name:
|
|
173
|
+
chunk_id = f"{document.name}_{chunk_number}"
|
|
174
|
+
meta_data["chunk_size"] = section_size
|
|
175
|
+
|
|
176
|
+
chunks.append(Document(id=chunk_id, name=document.name, meta_data=meta_data, content=section))
|
|
177
|
+
chunk_number += 1
|
|
178
|
+
elif current_size + section_size <= self.chunk_size:
|
|
94
179
|
current_chunk.append(section)
|
|
95
180
|
current_size += section_size
|
|
96
181
|
else:
|
|
@@ -114,7 +199,8 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
114
199
|
current_chunk = [section]
|
|
115
200
|
current_size = section_size
|
|
116
201
|
|
|
117
|
-
|
|
202
|
+
# Handle remaining content (only when not split_on_headings)
|
|
203
|
+
if current_chunk and not self.split_on_headings:
|
|
118
204
|
meta_data = chunk_meta_data.copy()
|
|
119
205
|
meta_data["chunk"] = chunk_number
|
|
120
206
|
chunk_id = None
|
|
@@ -17,7 +17,7 @@ except ImportError:
|
|
|
17
17
|
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
18
18
|
from agno.knowledge.document.base import Document
|
|
19
19
|
from agno.knowledge.embedder.base import Embedder
|
|
20
|
-
from agno.utils.log import
|
|
20
|
+
from agno.utils.log import log_debug
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _get_chonkie_embedder_wrapper(embedder: Embedder):
|
|
@@ -87,7 +87,7 @@ class SemanticChunking(ChunkingStrategy):
|
|
|
87
87
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
88
88
|
|
|
89
89
|
embedder = OpenAIEmbedder() # type: ignore
|
|
90
|
-
|
|
90
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
91
91
|
self.embedder = embedder
|
|
92
92
|
self.chunk_size = chunk_size
|
|
93
93
|
self.similarity_threshold = similarity_threshold
|