agno 2.2.10__py3-none-any.whl → 2.2.11__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 +13 -1
- agno/db/dynamo/utils.py +1 -1
- agno/db/firestore/utils.py +1 -1
- agno/db/gcs_json/utils.py +1 -1
- agno/db/in_memory/utils.py +1 -1
- agno/db/json/utils.py +1 -1
- agno/db/mongo/utils.py +3 -3
- agno/db/mysql/utils.py +1 -1
- agno/db/postgres/utils.py +1 -1
- agno/db/redis/utils.py +1 -1
- agno/db/singlestore/utils.py +1 -1
- agno/db/sqlite/utils.py +1 -1
- agno/knowledge/chunking/agentic.py +8 -9
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/utils.py +20 -0
- agno/models/anthropic/claude.py +45 -9
- agno/models/base.py +4 -0
- agno/os/app.py +23 -7
- agno/os/routers/health.py +5 -3
- agno/os/routers/knowledge/knowledge.py +43 -17
- agno/os/routers/knowledge/schemas.py +4 -3
- agno/run/agent.py +11 -1
- agno/team/team.py +13 -1
- agno/tools/file_generation.py +4 -4
- agno/tools/gmail.py +179 -0
- agno/tools/parallel.py +314 -0
- agno/utils/models/claude.py +2 -1
- agno/workflow/step.py +3 -2
- agno/workflow/types.py +20 -1
- agno/workflow/workflow.py +92 -9
- {agno-2.2.10.dist-info → agno-2.2.11.dist-info}/METADATA +4 -1
- {agno-2.2.10.dist-info → agno-2.2.11.dist-info}/RECORD +37 -36
- {agno-2.2.10.dist-info → agno-2.2.11.dist-info}/WHEEL +0 -0
- {agno-2.2.10.dist-info → agno-2.2.11.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.10.dist-info → agno-2.2.11.dist-info}/top_level.txt +0 -0
agno/agent/agent.py
CHANGED
|
@@ -5763,6 +5763,9 @@ class Agent:
|
|
|
5763
5763
|
raise ValueError("Db not initialized")
|
|
5764
5764
|
return self.db.get_session(session_id=session_id, session_type=session_type) # type: ignore
|
|
5765
5765
|
except Exception as e:
|
|
5766
|
+
import traceback
|
|
5767
|
+
|
|
5768
|
+
traceback.print_exc(limit=3)
|
|
5766
5769
|
log_warning(f"Error getting session from db: {e}")
|
|
5767
5770
|
return None
|
|
5768
5771
|
|
|
@@ -5773,8 +5776,11 @@ class Agent:
|
|
|
5773
5776
|
try:
|
|
5774
5777
|
if not self.db:
|
|
5775
5778
|
raise ValueError("Db not initialized")
|
|
5776
|
-
return await self.db.get_session(session_id=session_id, session_type=
|
|
5779
|
+
return await self.db.get_session(session_id=session_id, session_type=session_type) # type: ignore
|
|
5777
5780
|
except Exception as e:
|
|
5781
|
+
import traceback
|
|
5782
|
+
|
|
5783
|
+
traceback.print_exc(limit=3)
|
|
5778
5784
|
log_warning(f"Error getting session from db: {e}")
|
|
5779
5785
|
return None
|
|
5780
5786
|
|
|
@@ -5786,6 +5792,9 @@ class Agent:
|
|
|
5786
5792
|
raise ValueError("Db not initialized")
|
|
5787
5793
|
return self.db.upsert_session(session=session) # type: ignore
|
|
5788
5794
|
except Exception as e:
|
|
5795
|
+
import traceback
|
|
5796
|
+
|
|
5797
|
+
traceback.print_exc(limit=3)
|
|
5789
5798
|
log_warning(f"Error upserting session into db: {e}")
|
|
5790
5799
|
return None
|
|
5791
5800
|
|
|
@@ -5796,6 +5805,9 @@ class Agent:
|
|
|
5796
5805
|
raise ValueError("Db not initialized")
|
|
5797
5806
|
return await self.db.upsert_session(session=session) # type: ignore
|
|
5798
5807
|
except Exception as e:
|
|
5808
|
+
import traceback
|
|
5809
|
+
|
|
5810
|
+
traceback.print_exc(limit=3)
|
|
5799
5811
|
log_warning(f"Error upserting session into db: {e}")
|
|
5800
5812
|
return None
|
|
5801
5813
|
|
agno/db/dynamo/utils.py
CHANGED
|
@@ -343,7 +343,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
343
343
|
]
|
|
344
344
|
all_user_ids = set()
|
|
345
345
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
346
|
-
sessions = sessions_data.get(session_type, [])
|
|
346
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
347
347
|
metrics[sessions_count_key] = len(sessions)
|
|
348
348
|
for session in sessions:
|
|
349
349
|
if session.get("user_id"):
|
agno/db/firestore/utils.py
CHANGED
|
@@ -194,7 +194,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
194
194
|
all_user_ids = set()
|
|
195
195
|
|
|
196
196
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
197
|
-
sessions = sessions_data.get(session_type, [])
|
|
197
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
198
198
|
metrics[sessions_count_key] = len(sessions)
|
|
199
199
|
|
|
200
200
|
for session in sessions:
|
agno/db/gcs_json/utils.py
CHANGED
|
@@ -78,7 +78,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
78
78
|
all_user_ids = set()
|
|
79
79
|
|
|
80
80
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
81
|
-
sessions = sessions_data.get(session_type, [])
|
|
81
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
82
82
|
metrics[sessions_count_key] = len(sessions)
|
|
83
83
|
|
|
84
84
|
for session in sessions:
|
agno/db/in_memory/utils.py
CHANGED
|
@@ -78,7 +78,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
78
78
|
all_user_ids = set()
|
|
79
79
|
|
|
80
80
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
81
|
-
sessions = sessions_data.get(session_type, [])
|
|
81
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
82
82
|
metrics[sessions_count_key] = len(sessions)
|
|
83
83
|
|
|
84
84
|
for session in sessions:
|
agno/db/json/utils.py
CHANGED
|
@@ -78,7 +78,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
78
78
|
all_user_ids = set()
|
|
79
79
|
|
|
80
80
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
81
|
-
sessions = sessions_data.get(session_type, [])
|
|
81
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
82
82
|
metrics[sessions_count_key] = len(sessions)
|
|
83
83
|
|
|
84
84
|
for session in sessions:
|
agno/db/mongo/utils.py
CHANGED
|
@@ -89,14 +89,14 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
89
89
|
all_user_ids = set()
|
|
90
90
|
|
|
91
91
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
92
|
-
sessions = sessions_data.get(session_type, [])
|
|
92
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
93
93
|
metrics[sessions_count_key] = len(sessions)
|
|
94
94
|
|
|
95
95
|
for session in sessions:
|
|
96
96
|
if session.get("user_id"):
|
|
97
97
|
all_user_ids.add(session["user_id"])
|
|
98
|
-
runs = session.get("runs", [])
|
|
99
|
-
metrics[runs_count_key] += len(
|
|
98
|
+
runs = session.get("runs", [])
|
|
99
|
+
metrics[runs_count_key] += len(runs)
|
|
100
100
|
|
|
101
101
|
if runs := session.get("runs", []):
|
|
102
102
|
if isinstance(runs, str):
|
agno/db/mysql/utils.py
CHANGED
|
@@ -205,7 +205,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
205
205
|
all_user_ids = set()
|
|
206
206
|
|
|
207
207
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
208
|
-
sessions = sessions_data.get(session_type, [])
|
|
208
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
209
209
|
metrics[sessions_count_key] = len(sessions)
|
|
210
210
|
|
|
211
211
|
for session in sessions:
|
agno/db/postgres/utils.py
CHANGED
|
@@ -292,7 +292,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
292
292
|
all_user_ids = set()
|
|
293
293
|
|
|
294
294
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
295
|
-
sessions = sessions_data.get(session_type, [])
|
|
295
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
296
296
|
metrics[sessions_count_key] = len(sessions)
|
|
297
297
|
|
|
298
298
|
for session in sessions:
|
agno/db/redis/utils.py
CHANGED
|
@@ -200,7 +200,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
200
200
|
all_user_ids = set()
|
|
201
201
|
|
|
202
202
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
203
|
-
sessions = sessions_data.get(session_type, [])
|
|
203
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
204
204
|
metrics[sessions_count_key] = len(sessions)
|
|
205
205
|
|
|
206
206
|
for session in sessions:
|
agno/db/singlestore/utils.py
CHANGED
|
@@ -234,7 +234,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
234
234
|
all_user_ids = set()
|
|
235
235
|
|
|
236
236
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
237
|
-
sessions = sessions_data.get(session_type, [])
|
|
237
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
238
238
|
metrics[sessions_count_key] = len(sessions)
|
|
239
239
|
|
|
240
240
|
for session in sessions:
|
agno/db/sqlite/utils.py
CHANGED
|
@@ -270,7 +270,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
270
270
|
all_user_ids = set()
|
|
271
271
|
|
|
272
272
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
273
|
-
sessions = sessions_data.get(session_type, [])
|
|
273
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
274
274
|
metrics[sessions_count_key] = len(sessions)
|
|
275
275
|
|
|
276
276
|
for session in sessions:
|
|
@@ -20,13 +20,12 @@ class AgenticChunking(ChunkingStrategy):
|
|
|
20
20
|
except Exception:
|
|
21
21
|
raise ValueError("`openai` isn't installed. Please install it with `pip install openai`")
|
|
22
22
|
model = OpenAIChat(DEFAULT_OPENAI_MODEL_ID)
|
|
23
|
-
|
|
24
|
-
self.max_chunk_size = max_chunk_size
|
|
23
|
+
self.chunk_size = max_chunk_size
|
|
25
24
|
self.model = model
|
|
26
25
|
|
|
27
26
|
def chunk(self, document: Document) -> List[Document]:
|
|
28
27
|
"""Split text into chunks using LLM to determine natural breakpoints based on context"""
|
|
29
|
-
if len(document.content) <= self.
|
|
28
|
+
if len(document.content) <= self.chunk_size:
|
|
30
29
|
return [document]
|
|
31
30
|
|
|
32
31
|
chunks: List[Document] = []
|
|
@@ -35,22 +34,22 @@ class AgenticChunking(ChunkingStrategy):
|
|
|
35
34
|
chunk_number = 1
|
|
36
35
|
|
|
37
36
|
while remaining_text:
|
|
38
|
-
# Ask model to find a good breakpoint within
|
|
39
|
-
prompt = f"""Analyze this text and determine a natural breakpoint within the first {self.
|
|
37
|
+
# Ask model to find a good breakpoint within chunk_size
|
|
38
|
+
prompt = f"""Analyze this text and determine a natural breakpoint within the first {self.chunk_size} characters.
|
|
40
39
|
Consider semantic completeness, paragraph boundaries, and topic transitions.
|
|
41
40
|
Return only the character position number of where to break the text:
|
|
42
41
|
|
|
43
|
-
{remaining_text[: self.
|
|
42
|
+
{remaining_text[: self.chunk_size]}"""
|
|
44
43
|
|
|
45
44
|
try:
|
|
46
45
|
response = self.model.response([Message(role="user", content=prompt)])
|
|
47
46
|
if response and response.content:
|
|
48
|
-
break_point = min(int(response.content.strip()), self.
|
|
47
|
+
break_point = min(int(response.content.strip()), self.chunk_size)
|
|
49
48
|
else:
|
|
50
|
-
break_point = self.
|
|
49
|
+
break_point = self.chunk_size
|
|
51
50
|
except Exception:
|
|
52
51
|
# Fallback to max size if model fails
|
|
53
|
-
break_point = self.
|
|
52
|
+
break_point = self.chunk_size
|
|
54
53
|
|
|
55
54
|
# Extract chunk and update remaining text
|
|
56
55
|
chunk = remaining_text[:break_point].strip()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import List
|
|
3
|
+
from typing import List, Optional
|
|
4
4
|
|
|
5
5
|
from agno.knowledge.document.base import Document
|
|
6
6
|
|
|
@@ -60,7 +60,13 @@ class ChunkingStrategyFactory:
|
|
|
60
60
|
"""Factory for creating chunking strategy instances."""
|
|
61
61
|
|
|
62
62
|
@classmethod
|
|
63
|
-
def create_strategy(
|
|
63
|
+
def create_strategy(
|
|
64
|
+
cls,
|
|
65
|
+
strategy_type: ChunkingStrategyType,
|
|
66
|
+
chunk_size: Optional[int] = None,
|
|
67
|
+
overlap: Optional[int] = None,
|
|
68
|
+
**kwargs,
|
|
69
|
+
) -> ChunkingStrategy:
|
|
64
70
|
"""Create an instance of the chunking strategy with the given parameters."""
|
|
65
71
|
strategy_map = {
|
|
66
72
|
ChunkingStrategyType.AGENTIC_CHUNKER: cls._create_agentic_chunking,
|
|
@@ -71,51 +77,89 @@ class ChunkingStrategyFactory:
|
|
|
71
77
|
ChunkingStrategyType.ROW_CHUNKER: cls._create_row_chunking,
|
|
72
78
|
ChunkingStrategyType.MARKDOWN_CHUNKER: cls._create_markdown_chunking,
|
|
73
79
|
}
|
|
74
|
-
return strategy_map[strategy_type](**kwargs)
|
|
80
|
+
return strategy_map[strategy_type](chunk_size=chunk_size, overlap=overlap, **kwargs)
|
|
75
81
|
|
|
76
82
|
@classmethod
|
|
77
|
-
def _create_agentic_chunking(
|
|
83
|
+
def _create_agentic_chunking(
|
|
84
|
+
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
85
|
+
) -> ChunkingStrategy:
|
|
78
86
|
from agno.knowledge.chunking.agentic import AgenticChunking
|
|
79
87
|
|
|
80
|
-
#
|
|
81
|
-
if
|
|
82
|
-
kwargs["max_chunk_size"] =
|
|
88
|
+
# AgenticChunking accepts max_chunk_size (not chunk_size) and no overlap
|
|
89
|
+
if chunk_size is not None:
|
|
90
|
+
kwargs["max_chunk_size"] = chunk_size
|
|
91
|
+
# Remove overlap since AgenticChunking doesn't support it
|
|
83
92
|
return AgenticChunking(**kwargs)
|
|
84
93
|
|
|
85
94
|
@classmethod
|
|
86
|
-
def _create_document_chunking(
|
|
95
|
+
def _create_document_chunking(
|
|
96
|
+
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
97
|
+
) -> ChunkingStrategy:
|
|
87
98
|
from agno.knowledge.chunking.document import DocumentChunking
|
|
88
99
|
|
|
100
|
+
# DocumentChunking accepts both chunk_size and overlap
|
|
101
|
+
if chunk_size is not None:
|
|
102
|
+
kwargs["chunk_size"] = chunk_size
|
|
103
|
+
if overlap is not None:
|
|
104
|
+
kwargs["overlap"] = overlap
|
|
89
105
|
return DocumentChunking(**kwargs)
|
|
90
106
|
|
|
91
107
|
@classmethod
|
|
92
|
-
def _create_recursive_chunking(
|
|
108
|
+
def _create_recursive_chunking(
|
|
109
|
+
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
110
|
+
) -> ChunkingStrategy:
|
|
93
111
|
from agno.knowledge.chunking.recursive import RecursiveChunking
|
|
94
112
|
|
|
113
|
+
# RecursiveChunking accepts both chunk_size and overlap
|
|
114
|
+
if chunk_size is not None:
|
|
115
|
+
kwargs["chunk_size"] = chunk_size
|
|
116
|
+
if overlap is not None:
|
|
117
|
+
kwargs["overlap"] = overlap
|
|
95
118
|
return RecursiveChunking(**kwargs)
|
|
96
119
|
|
|
97
120
|
@classmethod
|
|
98
|
-
def _create_semantic_chunking(
|
|
121
|
+
def _create_semantic_chunking(
|
|
122
|
+
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
123
|
+
) -> ChunkingStrategy:
|
|
99
124
|
from agno.knowledge.chunking.semantic import SemanticChunking
|
|
100
125
|
|
|
126
|
+
# SemanticChunking accepts chunk_size but not overlap
|
|
127
|
+
if chunk_size is not None:
|
|
128
|
+
kwargs["chunk_size"] = chunk_size
|
|
129
|
+
# Remove overlap since SemanticChunking doesn't support it
|
|
101
130
|
return SemanticChunking(**kwargs)
|
|
102
131
|
|
|
103
132
|
@classmethod
|
|
104
|
-
def _create_fixed_chunking(
|
|
133
|
+
def _create_fixed_chunking(
|
|
134
|
+
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
135
|
+
) -> ChunkingStrategy:
|
|
105
136
|
from agno.knowledge.chunking.fixed import FixedSizeChunking
|
|
106
137
|
|
|
138
|
+
# FixedSizeChunking accepts both chunk_size and overlap
|
|
139
|
+
if chunk_size is not None:
|
|
140
|
+
kwargs["chunk_size"] = chunk_size
|
|
141
|
+
if overlap is not None:
|
|
142
|
+
kwargs["overlap"] = overlap
|
|
107
143
|
return FixedSizeChunking(**kwargs)
|
|
108
144
|
|
|
109
145
|
@classmethod
|
|
110
|
-
def _create_row_chunking(
|
|
146
|
+
def _create_row_chunking(
|
|
147
|
+
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
148
|
+
) -> ChunkingStrategy:
|
|
111
149
|
from agno.knowledge.chunking.row import RowChunking
|
|
112
150
|
|
|
113
|
-
#
|
|
114
|
-
kwargs.pop("chunk_size", None)
|
|
151
|
+
# RowChunking doesn't accept chunk_size or overlap, only skip_header and clean_rows
|
|
115
152
|
return RowChunking(**kwargs)
|
|
116
153
|
|
|
117
154
|
@classmethod
|
|
118
|
-
def _create_markdown_chunking(
|
|
155
|
+
def _create_markdown_chunking(
|
|
156
|
+
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
157
|
+
) -> ChunkingStrategy:
|
|
119
158
|
from agno.knowledge.chunking.markdown import MarkdownChunking
|
|
120
159
|
|
|
160
|
+
# MarkdownChunking accepts both chunk_size and overlap
|
|
161
|
+
if chunk_size is not None:
|
|
162
|
+
kwargs["chunk_size"] = chunk_size
|
|
163
|
+
if overlap is not None:
|
|
164
|
+
kwargs["overlap"] = overlap
|
|
121
165
|
return MarkdownChunking(**kwargs)
|
|
@@ -25,10 +25,14 @@ class SentenceTransformerEmbedder(Embedder):
|
|
|
25
25
|
prompt: Optional[str] = None
|
|
26
26
|
normalize_embeddings: bool = False
|
|
27
27
|
|
|
28
|
-
def
|
|
29
|
-
|
|
28
|
+
def __post_init__(self):
|
|
29
|
+
# Initialize the SentenceTransformer model eagerly to avoid race conditions in async contexts
|
|
30
|
+
if self.sentence_transformer_client is None:
|
|
30
31
|
self.sentence_transformer_client = SentenceTransformer(model_name_or_path=self.id)
|
|
31
32
|
|
|
33
|
+
def get_embedding(self, text: Union[str, List[str]]) -> List[float]:
|
|
34
|
+
if self.sentence_transformer_client is None:
|
|
35
|
+
raise RuntimeError("SentenceTransformer model not initialized")
|
|
32
36
|
model = self.sentence_transformer_client
|
|
33
37
|
embedding = model.encode(text, prompt=self.prompt, normalize_embeddings=self.normalize_embeddings)
|
|
34
38
|
try:
|
agno/knowledge/reader/base.py
CHANGED
|
@@ -44,11 +44,15 @@ class Reader:
|
|
|
44
44
|
self.max_results = max_results
|
|
45
45
|
self.encoding = encoding
|
|
46
46
|
|
|
47
|
-
def set_chunking_strategy_from_string(
|
|
47
|
+
def set_chunking_strategy_from_string(
|
|
48
|
+
self, strategy_name: str, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
49
|
+
) -> None:
|
|
48
50
|
"""Set the chunking strategy from a string name."""
|
|
49
51
|
try:
|
|
50
52
|
strategy_type = ChunkingStrategyType.from_string(strategy_name)
|
|
51
|
-
self.chunking_strategy = ChunkingStrategyFactory.create_strategy(
|
|
53
|
+
self.chunking_strategy = ChunkingStrategyFactory.create_strategy(
|
|
54
|
+
strategy_type, chunk_size=chunk_size, overlap=overlap, **kwargs
|
|
55
|
+
)
|
|
52
56
|
except ValueError as e:
|
|
53
57
|
raise ValueError(f"Failed to set chunking strategy: {e}")
|
|
54
58
|
|
agno/knowledge/utils.py
CHANGED
|
@@ -129,12 +129,32 @@ def get_chunker_info(chunker_key: str) -> Dict:
|
|
|
129
129
|
class_name = chunker_class.__name__
|
|
130
130
|
docstring = chunker_class.__doc__ or f"{class_name} chunking strategy"
|
|
131
131
|
|
|
132
|
+
# Check class __init__ signature for chunk_size and overlap parameters
|
|
133
|
+
metadata = {}
|
|
134
|
+
import inspect
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
sig = inspect.signature(chunker_class.__init__)
|
|
138
|
+
param_names = set(sig.parameters.keys())
|
|
139
|
+
|
|
140
|
+
# If class has chunk_size or max_chunk_size parameter, set default chunk_size
|
|
141
|
+
if "chunk_size" in param_names or "max_chunk_size" in param_names:
|
|
142
|
+
metadata["chunk_size"] = 5000
|
|
143
|
+
|
|
144
|
+
# If class has overlap parameter, set default overlap
|
|
145
|
+
if "overlap" in param_names:
|
|
146
|
+
metadata["chunk_overlap"] = 0
|
|
147
|
+
except Exception:
|
|
148
|
+
# If we can't inspect, skip metadata
|
|
149
|
+
pass
|
|
150
|
+
|
|
132
151
|
return {
|
|
133
152
|
"key": chunker_key,
|
|
134
153
|
"class_name": class_name,
|
|
135
154
|
"name": chunker_key,
|
|
136
155
|
"description": docstring.strip(),
|
|
137
156
|
"strategy_type": strategy_type.value,
|
|
157
|
+
"metadata": metadata,
|
|
138
158
|
}
|
|
139
159
|
except ValueError:
|
|
140
160
|
raise ValueError(f"Unknown chunker key: {chunker_key}")
|
agno/models/anthropic/claude.py
CHANGED
|
@@ -45,6 +45,8 @@ except ImportError as e:
|
|
|
45
45
|
# Import Beta types
|
|
46
46
|
try:
|
|
47
47
|
from anthropic.types.beta import BetaRawContentBlockDeltaEvent, BetaTextDelta
|
|
48
|
+
from anthropic.types.beta.beta_message import BetaMessage
|
|
49
|
+
from anthropic.types.beta.beta_usage import BetaUsage
|
|
48
50
|
except ImportError as e:
|
|
49
51
|
raise ImportError(
|
|
50
52
|
"`anthropic` not installed or missing beta components. Please install with `pip install anthropic`"
|
|
@@ -84,13 +86,14 @@ class Claude(Model):
|
|
|
84
86
|
cache_system_prompt: Optional[bool] = False
|
|
85
87
|
extended_cache_time: Optional[bool] = False
|
|
86
88
|
request_params: Optional[Dict[str, Any]] = None
|
|
87
|
-
mcp_servers: Optional[List[MCPServerConfiguration]] = None
|
|
88
89
|
|
|
89
|
-
#
|
|
90
|
+
# Anthropic beta and experimental features
|
|
91
|
+
betas: Optional[List[str]] = None # Enables specific experimental or newly released features.
|
|
92
|
+
context_management: Optional[Dict[str, Any]] = None
|
|
93
|
+
mcp_servers: Optional[List[MCPServerConfiguration]] = None
|
|
90
94
|
skills: Optional[List[Dict[str, str]]] = (
|
|
91
95
|
None # e.g., [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
|
|
92
96
|
)
|
|
93
|
-
betas: Optional[List[str]] = None # Enables specific experimental or newly released features.
|
|
94
97
|
|
|
95
98
|
# Client parameters
|
|
96
99
|
api_key: Optional[str] = None
|
|
@@ -129,6 +132,15 @@ class Claude(Model):
|
|
|
129
132
|
client_params["default_headers"] = self.default_headers
|
|
130
133
|
return client_params
|
|
131
134
|
|
|
135
|
+
def _has_beta_features(self) -> bool:
|
|
136
|
+
"""Check if the model has any Anthropic beta features enabled."""
|
|
137
|
+
return (
|
|
138
|
+
self.mcp_servers is not None
|
|
139
|
+
or self.context_management is not None
|
|
140
|
+
or self.skills is not None
|
|
141
|
+
or self.betas is not None
|
|
142
|
+
)
|
|
143
|
+
|
|
132
144
|
def get_client(self) -> AnthropicClient:
|
|
133
145
|
"""
|
|
134
146
|
Returns an instance of the Anthropic client.
|
|
@@ -208,6 +220,10 @@ class Claude(Model):
|
|
|
208
220
|
_request_params["top_p"] = self.top_p
|
|
209
221
|
if self.top_k:
|
|
210
222
|
_request_params["top_k"] = self.top_k
|
|
223
|
+
if self.betas:
|
|
224
|
+
_request_params["betas"] = self.betas
|
|
225
|
+
if self.context_management:
|
|
226
|
+
_request_params["context_management"] = self.context_management
|
|
211
227
|
if self.mcp_servers:
|
|
212
228
|
_request_params["mcp_servers"] = [
|
|
213
229
|
{k: v for k, v in asdict(server).items() if v is not None} for server in self.mcp_servers
|
|
@@ -279,7 +295,7 @@ class Claude(Model):
|
|
|
279
295
|
chat_messages, system_message = format_messages(messages)
|
|
280
296
|
request_kwargs = self._prepare_request_kwargs(system_message, tools)
|
|
281
297
|
|
|
282
|
-
if self.
|
|
298
|
+
if self._has_beta_features():
|
|
283
299
|
assistant_message.metrics.start_timer()
|
|
284
300
|
provider_response = self.get_client().beta.messages.create(
|
|
285
301
|
model=self.id,
|
|
@@ -346,7 +362,8 @@ class Claude(Model):
|
|
|
346
362
|
if run_response and run_response.metrics:
|
|
347
363
|
run_response.metrics.set_time_to_first_token()
|
|
348
364
|
|
|
349
|
-
|
|
365
|
+
# Beta features
|
|
366
|
+
if self._has_beta_features():
|
|
350
367
|
assistant_message.metrics.start_timer()
|
|
351
368
|
with self.get_client().beta.messages.stream(
|
|
352
369
|
model=self.id,
|
|
@@ -401,7 +418,8 @@ class Claude(Model):
|
|
|
401
418
|
chat_messages, system_message = format_messages(messages)
|
|
402
419
|
request_kwargs = self._prepare_request_kwargs(system_message, tools)
|
|
403
420
|
|
|
404
|
-
|
|
421
|
+
# Beta features
|
|
422
|
+
if self._has_beta_features():
|
|
405
423
|
assistant_message.metrics.start_timer()
|
|
406
424
|
provider_response = await self.get_async_client().beta.messages.create(
|
|
407
425
|
model=self.id,
|
|
@@ -465,7 +483,7 @@ class Claude(Model):
|
|
|
465
483
|
chat_messages, system_message = format_messages(messages)
|
|
466
484
|
request_kwargs = self._prepare_request_kwargs(system_message, tools)
|
|
467
485
|
|
|
468
|
-
if self.
|
|
486
|
+
if self._has_beta_features():
|
|
469
487
|
assistant_message.metrics.start_timer()
|
|
470
488
|
async with self.get_async_client().beta.messages.stream(
|
|
471
489
|
model=self.id,
|
|
@@ -507,7 +525,7 @@ class Claude(Model):
|
|
|
507
525
|
return tool_call_prompt
|
|
508
526
|
return None
|
|
509
527
|
|
|
510
|
-
def _parse_provider_response(self, response: AnthropicMessage, **kwargs) -> ModelResponse:
|
|
528
|
+
def _parse_provider_response(self, response: Union[AnthropicMessage, BetaMessage], **kwargs) -> ModelResponse:
|
|
511
529
|
"""
|
|
512
530
|
Parse the Claude response into a ModelResponse.
|
|
513
531
|
|
|
@@ -582,6 +600,14 @@ class Claude(Model):
|
|
|
582
600
|
if response.usage is not None:
|
|
583
601
|
model_response.response_usage = self._get_metrics(response.usage)
|
|
584
602
|
|
|
603
|
+
# Capture context management information if present
|
|
604
|
+
if self.context_management is not None and hasattr(response, "context_management"):
|
|
605
|
+
if response.context_management is not None: # type: ignore
|
|
606
|
+
model_response.provider_data = model_response.provider_data or {}
|
|
607
|
+
if hasattr(response.context_management, "model_dump"):
|
|
608
|
+
model_response.provider_data["context_management"] = response.context_management.model_dump() # type: ignore
|
|
609
|
+
else:
|
|
610
|
+
model_response.provider_data["context_management"] = response.context_management # type: ignore
|
|
585
611
|
# Extract file IDs if skills are enabled
|
|
586
612
|
if self.skills and response.content:
|
|
587
613
|
file_ids: List[str] = []
|
|
@@ -676,6 +702,16 @@ class Claude(Model):
|
|
|
676
702
|
DocumentCitation(document_title=citation.document_title, cited_text=citation.cited_text)
|
|
677
703
|
)
|
|
678
704
|
|
|
705
|
+
# Capture context management information if present
|
|
706
|
+
if self.context_management is not None and hasattr(response.message, "context_management"): # type: ignore
|
|
707
|
+
context_mgmt = response.message.context_management # type: ignore
|
|
708
|
+
if context_mgmt is not None:
|
|
709
|
+
model_response.provider_data = model_response.provider_data or {}
|
|
710
|
+
if hasattr(context_mgmt, "model_dump"):
|
|
711
|
+
model_response.provider_data["context_management"] = context_mgmt.model_dump()
|
|
712
|
+
else:
|
|
713
|
+
model_response.provider_data["context_management"] = context_mgmt
|
|
714
|
+
|
|
679
715
|
if hasattr(response, "message") and hasattr(response.message, "usage") and response.message.usage is not None: # type: ignore
|
|
680
716
|
model_response.response_usage = self._get_metrics(response.message.usage) # type: ignore
|
|
681
717
|
|
|
@@ -692,7 +728,7 @@ class Claude(Model):
|
|
|
692
728
|
|
|
693
729
|
return model_response
|
|
694
730
|
|
|
695
|
-
def _get_metrics(self, response_usage: Union[Usage, MessageDeltaUsage]) -> Metrics:
|
|
731
|
+
def _get_metrics(self, response_usage: Union[Usage, MessageDeltaUsage, BetaUsage]) -> Metrics:
|
|
696
732
|
"""
|
|
697
733
|
Parse the given Anthropic-specific usage into an Agno Metrics object.
|
|
698
734
|
|
agno/models/base.py
CHANGED
|
@@ -692,6 +692,8 @@ class Model(ABC):
|
|
|
692
692
|
if model_response.extra is None:
|
|
693
693
|
model_response.extra = {}
|
|
694
694
|
model_response.extra.update(provider_response.extra)
|
|
695
|
+
if provider_response.provider_data is not None:
|
|
696
|
+
model_response.provider_data = provider_response.provider_data
|
|
695
697
|
|
|
696
698
|
async def _aprocess_model_response(
|
|
697
699
|
self,
|
|
@@ -745,6 +747,8 @@ class Model(ABC):
|
|
|
745
747
|
if model_response.extra is None:
|
|
746
748
|
model_response.extra = {}
|
|
747
749
|
model_response.extra.update(provider_response.extra)
|
|
750
|
+
if provider_response.provider_data is not None:
|
|
751
|
+
model_response.provider_data = provider_response.provider_data
|
|
748
752
|
|
|
749
753
|
def _populate_assistant_message(
|
|
750
754
|
self,
|
agno/os/app.py
CHANGED
|
@@ -232,6 +232,12 @@ class AgentOS:
|
|
|
232
232
|
self._initialize_workflows()
|
|
233
233
|
self._auto_discover_databases()
|
|
234
234
|
self._auto_discover_knowledge_instances()
|
|
235
|
+
|
|
236
|
+
if self.enable_mcp_server:
|
|
237
|
+
from agno.os.mcp import get_mcp_server
|
|
238
|
+
|
|
239
|
+
self._mcp_app = get_mcp_server(self)
|
|
240
|
+
|
|
235
241
|
self._reprovision_routers(app=app)
|
|
236
242
|
|
|
237
243
|
def _reprovision_routers(self, app: FastAPI) -> None:
|
|
@@ -248,7 +254,9 @@ class AgentOS:
|
|
|
248
254
|
app.router.routes = [
|
|
249
255
|
route
|
|
250
256
|
for route in app.router.routes
|
|
251
|
-
if hasattr(route, "path")
|
|
257
|
+
if hasattr(route, "path")
|
|
258
|
+
and route.path in ["/docs", "/redoc", "/openapi.json", "/docs/oauth2-redirect"]
|
|
259
|
+
or route.path.startswith("/mcp") # type: ignore
|
|
252
260
|
]
|
|
253
261
|
|
|
254
262
|
# Add the built-in routes
|
|
@@ -258,13 +266,17 @@ class AgentOS:
|
|
|
258
266
|
for router in updated_routers:
|
|
259
267
|
self._add_router(app, router)
|
|
260
268
|
|
|
269
|
+
# Mount MCP if needed
|
|
270
|
+
if self.enable_mcp_server and self._mcp_app:
|
|
271
|
+
app.mount("/", self._mcp_app)
|
|
272
|
+
|
|
261
273
|
def _add_built_in_routes(self, app: FastAPI) -> None:
|
|
262
274
|
"""Add all AgentOSbuilt-in routes to the given app."""
|
|
263
275
|
# Add the home router if MCP server is not enabled
|
|
264
276
|
if not self.enable_mcp_server:
|
|
265
277
|
self._add_router(app, get_home_router(self))
|
|
266
278
|
|
|
267
|
-
self._add_router(app, get_health_router())
|
|
279
|
+
self._add_router(app, get_health_router(health_endpoint="/health"))
|
|
268
280
|
self._add_router(app, get_base_router(self, settings=self.settings))
|
|
269
281
|
self._add_router(app, get_websocket_router(self, settings=self.settings))
|
|
270
282
|
|
|
@@ -381,20 +393,24 @@ class AgentOS:
|
|
|
381
393
|
# Collect all lifespans that need to be combined
|
|
382
394
|
lifespans = []
|
|
383
395
|
|
|
396
|
+
# The user provided lifespan
|
|
397
|
+
if self.lifespan:
|
|
398
|
+
# Wrap the user lifespan with agent_os parameter
|
|
399
|
+
wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
|
|
400
|
+
lifespans.append(wrapped_lifespan)
|
|
401
|
+
|
|
402
|
+
# The provided app's existing lifespan
|
|
384
403
|
if fastapi_app.router.lifespan_context:
|
|
385
404
|
lifespans.append(fastapi_app.router.lifespan_context)
|
|
386
405
|
|
|
406
|
+
# The MCP tools lifespan
|
|
387
407
|
if self.mcp_tools:
|
|
388
408
|
lifespans.append(partial(mcp_lifespan, mcp_tools=self.mcp_tools))
|
|
389
409
|
|
|
410
|
+
# The /mcp server lifespan
|
|
390
411
|
if self.enable_mcp_server and self._mcp_app:
|
|
391
412
|
lifespans.append(self._mcp_app.lifespan)
|
|
392
413
|
|
|
393
|
-
if self.lifespan:
|
|
394
|
-
# Wrap the user lifespan with agent_os parameter
|
|
395
|
-
wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
|
|
396
|
-
lifespans.append(wrapped_lifespan)
|
|
397
|
-
|
|
398
414
|
# Combine lifespans and set them in the app
|
|
399
415
|
if lifespans:
|
|
400
416
|
fastapi_app.router.lifespan_context = _combine_app_lifespans(lifespans)
|