agno 2.2.9__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.
Files changed (43) hide show
  1. agno/agent/agent.py +27 -5
  2. agno/db/dynamo/utils.py +1 -1
  3. agno/db/firestore/utils.py +1 -1
  4. agno/db/gcs_json/utils.py +1 -1
  5. agno/db/in_memory/utils.py +1 -1
  6. agno/db/json/utils.py +1 -1
  7. agno/db/mongo/utils.py +3 -3
  8. agno/db/mysql/utils.py +1 -1
  9. agno/db/postgres/utils.py +1 -1
  10. agno/db/redis/utils.py +1 -1
  11. agno/db/singlestore/utils.py +1 -1
  12. agno/db/sqlite/utils.py +1 -1
  13. agno/knowledge/chunking/agentic.py +8 -9
  14. agno/knowledge/chunking/strategy.py +59 -15
  15. agno/knowledge/embedder/sentence_transformer.py +6 -2
  16. agno/knowledge/reader/base.py +6 -2
  17. agno/knowledge/utils.py +20 -0
  18. agno/models/anthropic/claude.py +45 -9
  19. agno/models/base.py +4 -0
  20. agno/os/app.py +35 -19
  21. agno/os/routers/health.py +5 -3
  22. agno/os/routers/knowledge/knowledge.py +43 -17
  23. agno/os/routers/knowledge/schemas.py +4 -3
  24. agno/run/agent.py +11 -1
  25. agno/team/team.py +20 -3
  26. agno/tools/file_generation.py +4 -4
  27. agno/tools/gmail.py +179 -0
  28. agno/tools/parallel.py +314 -0
  29. agno/utils/models/claude.py +2 -1
  30. agno/workflow/agent.py +2 -2
  31. agno/workflow/condition.py +26 -4
  32. agno/workflow/loop.py +9 -0
  33. agno/workflow/parallel.py +39 -16
  34. agno/workflow/router.py +25 -4
  35. agno/workflow/step.py +163 -91
  36. agno/workflow/steps.py +9 -0
  37. agno/workflow/types.py +20 -1
  38. agno/workflow/workflow.py +117 -30
  39. {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/METADATA +4 -1
  40. {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/RECORD +43 -42
  41. {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/WHEEL +0 -0
  42. {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/licenses/LICENSE +0 -0
  43. {agno-2.2.9.dist-info → agno-2.2.11.dist-info}/top_level.txt +0 -0
agno/agent/agent.py CHANGED
@@ -1454,6 +1454,7 @@ class Agent:
1454
1454
  user_id: Optional[str] = None,
1455
1455
  session_id: Optional[str] = None,
1456
1456
  session_state: Optional[Dict[str, Any]] = None,
1457
+ run_context: Optional[RunContext] = None,
1457
1458
  audio: Optional[Sequence[Audio]] = None,
1458
1459
  images: Optional[Sequence[Image]] = None,
1459
1460
  videos: Optional[Sequence[Video]] = None,
@@ -1480,6 +1481,7 @@ class Agent:
1480
1481
  user_id: Optional[str] = None,
1481
1482
  session_id: Optional[str] = None,
1482
1483
  session_state: Optional[Dict[str, Any]] = None,
1484
+ run_context: Optional[RunContext] = None,
1483
1485
  audio: Optional[Sequence[Audio]] = None,
1484
1486
  images: Optional[Sequence[Image]] = None,
1485
1487
  videos: Optional[Sequence[Video]] = None,
@@ -1507,6 +1509,7 @@ class Agent:
1507
1509
  user_id: Optional[str] = None,
1508
1510
  session_id: Optional[str] = None,
1509
1511
  session_state: Optional[Dict[str, Any]] = None,
1512
+ run_context: Optional[RunContext] = None,
1510
1513
  audio: Optional[Sequence[Audio]] = None,
1511
1514
  images: Optional[Sequence[Image]] = None,
1512
1515
  videos: Optional[Sequence[Video]] = None,
@@ -1581,7 +1584,7 @@ class Agent:
1581
1584
  dependencies = dependencies if dependencies is not None else self.dependencies
1582
1585
 
1583
1586
  # Initialize run context
1584
- run_context = RunContext(
1587
+ run_context = run_context or RunContext(
1585
1588
  run_id=run_id,
1586
1589
  session_id=session_id,
1587
1590
  user_id=user_id,
@@ -2374,6 +2377,7 @@ class Agent:
2374
2377
  user_id: Optional[str] = None,
2375
2378
  session_id: Optional[str] = None,
2376
2379
  session_state: Optional[Dict[str, Any]] = None,
2380
+ run_context: Optional[RunContext] = None,
2377
2381
  audio: Optional[Sequence[Audio]] = None,
2378
2382
  images: Optional[Sequence[Image]] = None,
2379
2383
  videos: Optional[Sequence[Video]] = None,
@@ -2399,6 +2403,7 @@ class Agent:
2399
2403
  stream: Literal[True] = True,
2400
2404
  user_id: Optional[str] = None,
2401
2405
  session_id: Optional[str] = None,
2406
+ run_context: Optional[RunContext] = None,
2402
2407
  audio: Optional[Sequence[Audio]] = None,
2403
2408
  images: Optional[Sequence[Image]] = None,
2404
2409
  videos: Optional[Sequence[Video]] = None,
@@ -2426,6 +2431,7 @@ class Agent:
2426
2431
  user_id: Optional[str] = None,
2427
2432
  session_id: Optional[str] = None,
2428
2433
  session_state: Optional[Dict[str, Any]] = None,
2434
+ run_context: Optional[RunContext] = None,
2429
2435
  audio: Optional[Sequence[Audio]] = None,
2430
2436
  images: Optional[Sequence[Image]] = None,
2431
2437
  videos: Optional[Sequence[Video]] = None,
@@ -2530,7 +2536,7 @@ class Agent:
2530
2536
  merge_dictionaries(metadata, self.metadata)
2531
2537
 
2532
2538
  # Initialize run context
2533
- run_context = RunContext(
2539
+ run_context = run_context or RunContext(
2534
2540
  run_id=run_id,
2535
2541
  session_id=session_id,
2536
2542
  user_id=user_id,
@@ -2692,6 +2698,7 @@ class Agent:
2692
2698
  stream_intermediate_steps: Optional[bool] = None,
2693
2699
  user_id: Optional[str] = None,
2694
2700
  session_id: Optional[str] = None,
2701
+ run_context: Optional[RunContext] = None,
2695
2702
  retries: Optional[int] = None,
2696
2703
  knowledge_filters: Optional[Dict[str, Any]] = None,
2697
2704
  dependencies: Optional[Dict[str, Any]] = None,
@@ -2709,6 +2716,7 @@ class Agent:
2709
2716
  stream_events: Whether to stream all events.
2710
2717
  user_id: The user id to continue the run for.
2711
2718
  session_id: The session id to continue the run for.
2719
+ run_context: The run context to use for the run.
2712
2720
  retries: The number of retries to continue the run for.
2713
2721
  knowledge_filters: The knowledge filters to use for the run.
2714
2722
  dependencies: The dependencies to use for the run.
@@ -2749,7 +2757,7 @@ class Agent:
2749
2757
  dependencies = dependencies if dependencies is not None else self.dependencies
2750
2758
 
2751
2759
  # Initialize run context
2752
- run_context = RunContext(
2760
+ run_context = run_context or RunContext(
2753
2761
  run_id=run_id, # type: ignore
2754
2762
  session_id=session_id,
2755
2763
  user_id=user_id,
@@ -3251,6 +3259,7 @@ class Agent:
3251
3259
  stream_intermediate_steps: Optional[bool] = None,
3252
3260
  user_id: Optional[str] = None,
3253
3261
  session_id: Optional[str] = None,
3262
+ run_context: Optional[RunContext] = None,
3254
3263
  retries: Optional[int] = None,
3255
3264
  knowledge_filters: Optional[Dict[str, Any]] = None,
3256
3265
  dependencies: Optional[Dict[str, Any]] = None,
@@ -3269,6 +3278,7 @@ class Agent:
3269
3278
  stream_events: Whether to stream all events.
3270
3279
  user_id: The user id to continue the run for.
3271
3280
  session_id: The session id to continue the run for.
3281
+ run_context: The run context to use for the run.
3272
3282
  retries: The number of retries to continue the run for.
3273
3283
  knowledge_filters: The knowledge filters to use for the run.
3274
3284
  dependencies: The dependencies to use for continuing the run.
@@ -3335,7 +3345,7 @@ class Agent:
3335
3345
  self.model = cast(Model, self.model)
3336
3346
 
3337
3347
  # Initialize run context
3338
- run_context = RunContext(
3348
+ run_context = run_context or RunContext(
3339
3349
  run_id=run_id, # type: ignore
3340
3350
  session_id=session_id,
3341
3351
  user_id=user_id,
@@ -5753,6 +5763,9 @@ class Agent:
5753
5763
  raise ValueError("Db not initialized")
5754
5764
  return self.db.get_session(session_id=session_id, session_type=session_type) # type: ignore
5755
5765
  except Exception as e:
5766
+ import traceback
5767
+
5768
+ traceback.print_exc(limit=3)
5756
5769
  log_warning(f"Error getting session from db: {e}")
5757
5770
  return None
5758
5771
 
@@ -5763,8 +5776,11 @@ class Agent:
5763
5776
  try:
5764
5777
  if not self.db:
5765
5778
  raise ValueError("Db not initialized")
5766
- return await self.db.get_session(session_id=session_id, session_type=SessionType.AGENT) # type: ignore
5779
+ return await self.db.get_session(session_id=session_id, session_type=session_type) # type: ignore
5767
5780
  except Exception as e:
5781
+ import traceback
5782
+
5783
+ traceback.print_exc(limit=3)
5768
5784
  log_warning(f"Error getting session from db: {e}")
5769
5785
  return None
5770
5786
 
@@ -5776,6 +5792,9 @@ class Agent:
5776
5792
  raise ValueError("Db not initialized")
5777
5793
  return self.db.upsert_session(session=session) # type: ignore
5778
5794
  except Exception as e:
5795
+ import traceback
5796
+
5797
+ traceback.print_exc(limit=3)
5779
5798
  log_warning(f"Error upserting session into db: {e}")
5780
5799
  return None
5781
5800
 
@@ -5786,6 +5805,9 @@ class Agent:
5786
5805
  raise ValueError("Db not initialized")
5787
5806
  return await self.db.upsert_session(session=session) # type: ignore
5788
5807
  except Exception as e:
5808
+ import traceback
5809
+
5810
+ traceback.print_exc(limit=3)
5789
5811
  log_warning(f"Error upserting session into db: {e}")
5790
5812
  return None
5791
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"):
@@ -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:
@@ -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", []) or []
99
- metrics[runs_count_key] += len(sessions)
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:
@@ -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.max_chunk_size:
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 max_chunk_size
39
- prompt = f"""Analyze this text and determine a natural breakpoint within the first {self.max_chunk_size} characters.
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.max_chunk_size]}"""
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.max_chunk_size)
47
+ break_point = min(int(response.content.strip()), self.chunk_size)
49
48
  else:
50
- break_point = self.max_chunk_size
49
+ break_point = self.chunk_size
51
50
  except Exception:
52
51
  # Fallback to max size if model fails
53
- break_point = self.max_chunk_size
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(cls, strategy_type: ChunkingStrategyType, **kwargs) -> ChunkingStrategy:
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(cls, **kwargs) -> ChunkingStrategy:
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
- # Map chunk_size to max_chunk_size for AgenticChunking
81
- if "chunk_size" in kwargs and "max_chunk_size" not in kwargs:
82
- kwargs["max_chunk_size"] = kwargs.pop("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(cls, **kwargs) -> ChunkingStrategy:
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(cls, **kwargs) -> ChunkingStrategy:
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(cls, **kwargs) -> ChunkingStrategy:
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(cls, **kwargs) -> ChunkingStrategy:
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(cls, **kwargs) -> ChunkingStrategy:
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
- # Remove chunk_size if present since RowChunking doesn't use it
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(cls, **kwargs) -> ChunkingStrategy:
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 get_embedding(self, text: Union[str, List[str]]) -> List[float]:
29
- if not self.sentence_transformer_client:
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:
@@ -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(self, strategy_name: str, **kwargs) -> None:
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(strategy_type, **kwargs)
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}")
@@ -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
- # Skills configuration
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.mcp_servers is not None or self.skills is not None:
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
- if self.mcp_servers is not None or self.skills is not None:
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
- if self.mcp_servers is not None or self.skills is not None:
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.mcp_servers is not None or self.skills is not None:
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