agno 2.2.8__py3-none-any.whl → 2.2.10__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 (50) hide show
  1. agno/agent/agent.py +37 -19
  2. agno/db/base.py +23 -0
  3. agno/db/dynamo/dynamo.py +20 -25
  4. agno/db/dynamo/schemas.py +1 -0
  5. agno/db/firestore/firestore.py +11 -0
  6. agno/db/gcs_json/gcs_json_db.py +4 -0
  7. agno/db/in_memory/in_memory_db.py +4 -0
  8. agno/db/json/json_db.py +4 -0
  9. agno/db/mongo/async_mongo.py +27 -0
  10. agno/db/mongo/mongo.py +25 -0
  11. agno/db/mysql/mysql.py +26 -1
  12. agno/db/postgres/async_postgres.py +26 -1
  13. agno/db/postgres/postgres.py +26 -1
  14. agno/db/redis/redis.py +4 -0
  15. agno/db/singlestore/singlestore.py +24 -0
  16. agno/db/sqlite/async_sqlite.py +25 -1
  17. agno/db/sqlite/sqlite.py +25 -1
  18. agno/db/surrealdb/surrealdb.py +13 -1
  19. agno/knowledge/reader/docx_reader.py +0 -1
  20. agno/models/azure/ai_foundry.py +2 -1
  21. agno/models/cerebras/cerebras.py +3 -2
  22. agno/models/openai/chat.py +2 -1
  23. agno/models/openai/responses.py +2 -1
  24. agno/os/app.py +127 -65
  25. agno/os/config.py +1 -0
  26. agno/os/interfaces/agui/router.py +9 -0
  27. agno/os/interfaces/agui/utils.py +49 -3
  28. agno/os/mcp.py +8 -8
  29. agno/os/router.py +27 -9
  30. agno/os/routers/evals/evals.py +12 -7
  31. agno/os/routers/memory/memory.py +18 -10
  32. agno/os/routers/metrics/metrics.py +6 -4
  33. agno/os/routers/session/session.py +21 -11
  34. agno/os/utils.py +57 -11
  35. agno/team/team.py +33 -23
  36. agno/vectordb/mongodb/__init__.py +7 -1
  37. agno/vectordb/redis/__init__.py +4 -0
  38. agno/workflow/agent.py +2 -2
  39. agno/workflow/condition.py +26 -4
  40. agno/workflow/loop.py +9 -0
  41. agno/workflow/parallel.py +39 -16
  42. agno/workflow/router.py +25 -4
  43. agno/workflow/step.py +162 -91
  44. agno/workflow/steps.py +9 -0
  45. agno/workflow/workflow.py +26 -22
  46. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/METADATA +11 -13
  47. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/RECORD +50 -50
  48. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/WHEEL +0 -0
  49. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/licenses/LICENSE +0 -0
  50. {agno-2.2.8.dist-info → agno-2.2.10.dist-info}/top_level.txt +0 -0
agno/agent/agent.py CHANGED
@@ -1140,7 +1140,7 @@ class Agent:
1140
1140
  add_session_state_to_context: Optional[bool] = None,
1141
1141
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
1142
1142
  stream_events: bool = False,
1143
- yield_run_response: bool = False,
1143
+ yield_run_output: Optional[bool] = None,
1144
1144
  debug_mode: Optional[bool] = None,
1145
1145
  **kwargs: Any,
1146
1146
  ) -> Iterator[Union[RunOutputEvent, RunOutput]]:
@@ -1410,7 +1410,7 @@ class Agent:
1410
1410
  if stream_events:
1411
1411
  yield completed_event # type: ignore
1412
1412
 
1413
- if yield_run_response:
1413
+ if yield_run_output:
1414
1414
  yield run_response
1415
1415
 
1416
1416
  # Log Agent Telemetry
@@ -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,
@@ -1491,7 +1493,8 @@ class Agent:
1491
1493
  add_session_state_to_context: Optional[bool] = None,
1492
1494
  dependencies: Optional[Dict[str, Any]] = None,
1493
1495
  metadata: Optional[Dict[str, Any]] = None,
1494
- yield_run_response: bool = False,
1496
+ yield_run_response: bool = False, # To be deprecated: use yield_run_output instead
1497
+ yield_run_output: bool = False,
1495
1498
  debug_mode: Optional[bool] = None,
1496
1499
  **kwargs: Any,
1497
1500
  ) -> Iterator[Union[RunOutputEvent, RunOutput]]: ...
@@ -1506,6 +1509,7 @@ class Agent:
1506
1509
  user_id: Optional[str] = None,
1507
1510
  session_id: Optional[str] = None,
1508
1511
  session_state: Optional[Dict[str, Any]] = None,
1512
+ run_context: Optional[RunContext] = None,
1509
1513
  audio: Optional[Sequence[Audio]] = None,
1510
1514
  images: Optional[Sequence[Image]] = None,
1511
1515
  videos: Optional[Sequence[Video]] = None,
@@ -1517,7 +1521,8 @@ class Agent:
1517
1521
  add_session_state_to_context: Optional[bool] = None,
1518
1522
  dependencies: Optional[Dict[str, Any]] = None,
1519
1523
  metadata: Optional[Dict[str, Any]] = None,
1520
- yield_run_response: bool = False,
1524
+ yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
1525
+ yield_run_output: Optional[bool] = None,
1521
1526
  debug_mode: Optional[bool] = None,
1522
1527
  **kwargs: Any,
1523
1528
  ) -> Union[RunOutput, Iterator[Union[RunOutputEvent, RunOutput]]]:
@@ -1579,7 +1584,7 @@ class Agent:
1579
1584
  dependencies = dependencies if dependencies is not None else self.dependencies
1580
1585
 
1581
1586
  # Initialize run context
1582
- run_context = RunContext(
1587
+ run_context = run_context or RunContext(
1583
1588
  run_id=run_id,
1584
1589
  session_id=session_id,
1585
1590
  user_id=user_id,
@@ -1655,6 +1660,8 @@ class Agent:
1655
1660
  last_exception = None
1656
1661
  num_attempts = retries + 1
1657
1662
 
1663
+ yield_run_output = yield_run_output or yield_run_response # For backwards compatibility
1664
+
1658
1665
  for attempt in range(num_attempts):
1659
1666
  try:
1660
1667
  if stream:
@@ -1668,7 +1675,7 @@ class Agent:
1668
1675
  add_session_state_to_context=add_session_state,
1669
1676
  response_format=response_format,
1670
1677
  stream_events=stream_events,
1671
- yield_run_response=yield_run_response,
1678
+ yield_run_output=yield_run_output,
1672
1679
  debug_mode=debug_mode,
1673
1680
  **kwargs,
1674
1681
  )
@@ -2009,7 +2016,7 @@ class Agent:
2009
2016
  add_session_state_to_context: Optional[bool] = None,
2010
2017
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
2011
2018
  stream_events: bool = False,
2012
- yield_run_response: Optional[bool] = None,
2019
+ yield_run_output: Optional[bool] = None,
2013
2020
  debug_mode: Optional[bool] = None,
2014
2021
  **kwargs: Any,
2015
2022
  ) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]:
@@ -2307,7 +2314,7 @@ class Agent:
2307
2314
  if stream_events:
2308
2315
  yield completed_event # type: ignore
2309
2316
 
2310
- if yield_run_response:
2317
+ if yield_run_output:
2311
2318
  yield run_response
2312
2319
 
2313
2320
  # Log Agent Telemetry
@@ -2370,6 +2377,7 @@ class Agent:
2370
2377
  user_id: Optional[str] = None,
2371
2378
  session_id: Optional[str] = None,
2372
2379
  session_state: Optional[Dict[str, Any]] = None,
2380
+ run_context: Optional[RunContext] = None,
2373
2381
  audio: Optional[Sequence[Audio]] = None,
2374
2382
  images: Optional[Sequence[Image]] = None,
2375
2383
  videos: Optional[Sequence[Video]] = None,
@@ -2395,6 +2403,7 @@ class Agent:
2395
2403
  stream: Literal[True] = True,
2396
2404
  user_id: Optional[str] = None,
2397
2405
  session_id: Optional[str] = None,
2406
+ run_context: Optional[RunContext] = None,
2398
2407
  audio: Optional[Sequence[Audio]] = None,
2399
2408
  images: Optional[Sequence[Image]] = None,
2400
2409
  videos: Optional[Sequence[Video]] = None,
@@ -2408,7 +2417,8 @@ class Agent:
2408
2417
  add_session_state_to_context: Optional[bool] = None,
2409
2418
  dependencies: Optional[Dict[str, Any]] = None,
2410
2419
  metadata: Optional[Dict[str, Any]] = None,
2411
- yield_run_response: Optional[bool] = None,
2420
+ yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2421
+ yield_run_output: Optional[bool] = None,
2412
2422
  debug_mode: Optional[bool] = None,
2413
2423
  **kwargs: Any,
2414
2424
  ) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]: ...
@@ -2421,6 +2431,7 @@ class Agent:
2421
2431
  user_id: Optional[str] = None,
2422
2432
  session_id: Optional[str] = None,
2423
2433
  session_state: Optional[Dict[str, Any]] = None,
2434
+ run_context: Optional[RunContext] = None,
2424
2435
  audio: Optional[Sequence[Audio]] = None,
2425
2436
  images: Optional[Sequence[Image]] = None,
2426
2437
  videos: Optional[Sequence[Video]] = None,
@@ -2434,7 +2445,8 @@ class Agent:
2434
2445
  add_session_state_to_context: Optional[bool] = None,
2435
2446
  dependencies: Optional[Dict[str, Any]] = None,
2436
2447
  metadata: Optional[Dict[str, Any]] = None,
2437
- yield_run_response: Optional[bool] = None,
2448
+ yield_run_response: Optional[bool] = None, # To be deprecated: use yield_run_output instead
2449
+ yield_run_output: Optional[bool] = None,
2438
2450
  debug_mode: Optional[bool] = None,
2439
2451
  **kwargs: Any,
2440
2452
  ) -> Union[RunOutput, AsyncIterator[RunOutputEvent]]:
@@ -2524,7 +2536,7 @@ class Agent:
2524
2536
  merge_dictionaries(metadata, self.metadata)
2525
2537
 
2526
2538
  # Initialize run context
2527
- run_context = RunContext(
2539
+ run_context = run_context or RunContext(
2528
2540
  run_id=run_id,
2529
2541
  session_id=session_id,
2530
2542
  user_id=user_id,
@@ -2559,6 +2571,8 @@ class Agent:
2559
2571
  last_exception = None
2560
2572
  num_attempts = retries + 1
2561
2573
 
2574
+ yield_run_output = yield_run_output or yield_run_response # For backwards compatibility
2575
+
2562
2576
  for attempt in range(num_attempts):
2563
2577
  try:
2564
2578
  # Pass the new run_response to _arun
@@ -2569,7 +2583,7 @@ class Agent:
2569
2583
  user_id=user_id,
2570
2584
  response_format=response_format,
2571
2585
  stream_events=stream_events,
2572
- yield_run_response=yield_run_response,
2586
+ yield_run_output=yield_run_output,
2573
2587
  session_id=session_id,
2574
2588
  add_history_to_context=add_history,
2575
2589
  add_dependencies_to_context=add_dependencies,
@@ -2684,6 +2698,7 @@ class Agent:
2684
2698
  stream_intermediate_steps: Optional[bool] = None,
2685
2699
  user_id: Optional[str] = None,
2686
2700
  session_id: Optional[str] = None,
2701
+ run_context: Optional[RunContext] = None,
2687
2702
  retries: Optional[int] = None,
2688
2703
  knowledge_filters: Optional[Dict[str, Any]] = None,
2689
2704
  dependencies: Optional[Dict[str, Any]] = None,
@@ -2701,6 +2716,7 @@ class Agent:
2701
2716
  stream_events: Whether to stream all events.
2702
2717
  user_id: The user id to continue the run for.
2703
2718
  session_id: The session id to continue the run for.
2719
+ run_context: The run context to use for the run.
2704
2720
  retries: The number of retries to continue the run for.
2705
2721
  knowledge_filters: The knowledge filters to use for the run.
2706
2722
  dependencies: The dependencies to use for the run.
@@ -2741,7 +2757,7 @@ class Agent:
2741
2757
  dependencies = dependencies if dependencies is not None else self.dependencies
2742
2758
 
2743
2759
  # Initialize run context
2744
- run_context = RunContext(
2760
+ run_context = run_context or RunContext(
2745
2761
  run_id=run_id, # type: ignore
2746
2762
  session_id=session_id,
2747
2763
  user_id=user_id,
@@ -3243,12 +3259,13 @@ class Agent:
3243
3259
  stream_intermediate_steps: Optional[bool] = None,
3244
3260
  user_id: Optional[str] = None,
3245
3261
  session_id: Optional[str] = None,
3262
+ run_context: Optional[RunContext] = None,
3246
3263
  retries: Optional[int] = None,
3247
3264
  knowledge_filters: Optional[Dict[str, Any]] = None,
3248
3265
  dependencies: Optional[Dict[str, Any]] = None,
3249
3266
  metadata: Optional[Dict[str, Any]] = None,
3250
3267
  debug_mode: Optional[bool] = None,
3251
- yield_run_response: bool = False,
3268
+ yield_run_output: bool = False,
3252
3269
  **kwargs,
3253
3270
  ) -> Union[RunOutput, AsyncIterator[Union[RunOutputEvent, RunOutput]]]:
3254
3271
  """Continue a previous run.
@@ -3261,12 +3278,13 @@ class Agent:
3261
3278
  stream_events: Whether to stream all events.
3262
3279
  user_id: The user id to continue the run for.
3263
3280
  session_id: The session id to continue the run for.
3281
+ run_context: The run context to use for the run.
3264
3282
  retries: The number of retries to continue the run for.
3265
3283
  knowledge_filters: The knowledge filters to use for the run.
3266
3284
  dependencies: The dependencies to use for continuing the run.
3267
3285
  metadata: The metadata to use for continuing the run.
3268
3286
  debug_mode: Whether to enable debug mode.
3269
- yield_run_response: Whether to yield the run response.
3287
+ yield_run_output: Whether to yield the run response.
3270
3288
  (deprecated) stream_intermediate_steps: Whether to stream all steps.
3271
3289
  """
3272
3290
  if run_response is None and run_id is None:
@@ -3327,7 +3345,7 @@ class Agent:
3327
3345
  self.model = cast(Model, self.model)
3328
3346
 
3329
3347
  # Initialize run context
3330
- run_context = RunContext(
3348
+ run_context = run_context or RunContext(
3331
3349
  run_id=run_id, # type: ignore
3332
3350
  session_id=session_id,
3333
3351
  user_id=user_id,
@@ -3351,7 +3369,7 @@ class Agent:
3351
3369
  session_id=session_id,
3352
3370
  response_format=response_format,
3353
3371
  stream_events=stream_events,
3354
- yield_run_response=yield_run_response,
3372
+ yield_run_output=yield_run_output,
3355
3373
  debug_mode=debug_mode,
3356
3374
  **kwargs,
3357
3375
  )
@@ -3620,7 +3638,7 @@ class Agent:
3620
3638
  user_id: Optional[str] = None,
3621
3639
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
3622
3640
  stream_events: bool = False,
3623
- yield_run_response: Optional[bool] = None,
3641
+ yield_run_output: bool = False,
3624
3642
  debug_mode: Optional[bool] = None,
3625
3643
  **kwargs,
3626
3644
  ) -> AsyncIterator[Union[RunOutputEvent, RunOutput]]:
@@ -3860,7 +3878,7 @@ class Agent:
3860
3878
  if stream_events:
3861
3879
  yield completed_event # type: ignore
3862
3880
 
3863
- if yield_run_response:
3881
+ if yield_run_output:
3864
3882
  yield run_response
3865
3883
 
3866
3884
  # Log Agent Telemetry
agno/db/base.py CHANGED
@@ -38,6 +38,14 @@ class BaseDb(ABC):
38
38
  self.eval_table_name = eval_table or "agno_eval_runs"
39
39
  self.knowledge_table_name = knowledge_table or "agno_knowledge"
40
40
 
41
+ @abstractmethod
42
+ def table_exists(self, table_name: str) -> bool:
43
+ raise NotImplementedError
44
+
45
+ def _create_all_tables(self) -> None:
46
+ """Create all tables for this database."""
47
+ pass
48
+
41
49
  # --- Sessions ---
42
50
  @abstractmethod
43
51
  def delete_session(self, session_id: str) -> bool:
@@ -328,6 +336,21 @@ class AsyncBaseDb(ABC):
328
336
  self.knowledge_table_name = knowledge_table or "agno_knowledge"
329
337
  self.culture_table_name = culture_table or "agno_culture"
330
338
 
339
+ @abstractmethod
340
+ async def table_exists(self, table_name: str) -> bool:
341
+ """Check if a table with the given name exists in this database.
342
+
343
+ Default implementation returns True if the table name is configured.
344
+ Subclasses should override this to perform actual existence checks.
345
+
346
+ Args:
347
+ table_name: Name of the table to check
348
+
349
+ Returns:
350
+ bool: True if the table exists, False otherwise
351
+ """
352
+ raise NotImplementedError
353
+
331
354
  # --- Sessions ---
332
355
  @abstractmethod
333
356
  async def delete_session(self, session_id: str) -> bool:
agno/db/dynamo/dynamo.py CHANGED
@@ -112,27 +112,8 @@ class DynamoDb(BaseDb):
112
112
  session = boto3.Session(**session_kwargs)
113
113
  self.client = session.client("dynamodb")
114
114
 
115
- def _create_tables(self):
116
- tables_to_create = [
117
- (self.session_table_name, "sessions"),
118
- (self.memory_table_name, "memories"),
119
- (self.metrics_table_name, "metrics"),
120
- (self.eval_table_name, "evals"),
121
- (self.knowledge_table_name, "knowledge_sources"),
122
- ]
123
-
124
- for table_name, table_type in tables_to_create:
125
- if table_name:
126
- try:
127
- schema = get_table_schema_definition(table_type)
128
- schema["TableName"] = table_name
129
- create_table_if_not_exists(self.client, table_name, schema)
130
-
131
- except Exception as e:
132
- log_error(f"Failed to create table {table_name}: {e}")
133
-
134
- def _table_exists(self, table_name: str) -> bool:
135
- """Check if a DynamoDB table with the given name exists.
115
+ def table_exists(self, table_name: str) -> bool:
116
+ """Check if a DynamoDB table exists.
136
117
 
137
118
  Args:
138
119
  table_name: The name of the table to check
@@ -145,9 +126,23 @@ class DynamoDb(BaseDb):
145
126
  return True
146
127
  except self.client.exceptions.ResourceNotFoundException:
147
128
  return False
148
- except Exception as e:
149
- log_error(f"Error checking if table {table_name} exists: {e}")
150
- return False
129
+
130
+ def _create_all_tables(self):
131
+ """Create all configured DynamoDB tables if they don't exist."""
132
+ tables_to_create = [
133
+ ("sessions", self.session_table_name),
134
+ ("memories", self.memory_table_name),
135
+ ("metrics", self.metrics_table_name),
136
+ ("evals", self.eval_table_name),
137
+ ("knowledge", self.knowledge_table_name),
138
+ ("culture", self.culture_table_name),
139
+ ]
140
+
141
+ for table_type, table_name in tables_to_create:
142
+ if not self.table_exists(table_name):
143
+ schema = get_table_schema_definition(table_type)
144
+ schema["TableName"] = table_name
145
+ create_table_if_not_exists(self.client, table_name, schema)
151
146
 
152
147
  def _get_table(self, table_type: str, create_table_if_not_found: Optional[bool] = True) -> Optional[str]:
153
148
  """
@@ -180,7 +175,7 @@ class DynamoDb(BaseDb):
180
175
  raise ValueError(f"Unknown table type: {table_type}")
181
176
 
182
177
  # Check if table exists, create if it doesn't
183
- if not self._table_exists(table_name) and create_table_if_not_found:
178
+ if not self.table_exists(table_name) and create_table_if_not_found:
184
179
  schema = get_table_schema_definition(table_type)
185
180
  schema["TableName"] = table_name
186
181
  create_table_if_not_exists(self.client, table_name, schema)
agno/db/dynamo/schemas.py CHANGED
@@ -176,6 +176,7 @@ KNOWLEDGE_TABLE_SCHEMA = {
176
176
  "KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
177
177
  "AttributeDefinitions": [
178
178
  {"AttributeName": "id", "AttributeType": "S"},
179
+ {"AttributeName": "user_id", "AttributeType": "S"},
179
180
  {"AttributeName": "type", "AttributeType": "S"},
180
181
  {"AttributeName": "status", "AttributeType": "S"},
181
182
  {"AttributeName": "created_at", "AttributeType": "N"},
@@ -89,6 +89,17 @@ class FirestoreDb(BaseDb):
89
89
 
90
90
  # -- DB methods --
91
91
 
92
+ def table_exists(self, table_name: str) -> bool:
93
+ """Check if a collection with the given name exists in the Firestore database.
94
+
95
+ Args:
96
+ table_name: Name of the collection to check
97
+
98
+ Returns:
99
+ bool: True if the collection exists in the database, False otherwise
100
+ """
101
+ return table_name in self.db_client.list_collections()
102
+
92
103
  def _get_collection(self, table_type: str, create_collection_if_not_found: Optional[bool] = True):
93
104
  """Get or create a collection based on table type.
94
105
 
@@ -83,6 +83,10 @@ class GcsJsonDb(BaseDb):
83
83
  self.client = gcs.Client(project=project, credentials=credentials)
84
84
  self.bucket = self.client.bucket(self.bucket_name)
85
85
 
86
+ def table_exists(self, table_name: str) -> bool:
87
+ """JSON implementation, always returns True."""
88
+ return True
89
+
86
90
  def _get_blob_name(self, filename: str) -> str:
87
91
  """Get the full blob name including prefix for a given filename."""
88
92
  return f"{self.prefix}{filename}.json"
@@ -34,6 +34,10 @@ class InMemoryDb(BaseDb):
34
34
  self._knowledge: List[Dict[str, Any]] = []
35
35
  self._cultural_knowledge: List[Dict[str, Any]] = []
36
36
 
37
+ def table_exists(self, table_name: str) -> bool:
38
+ """In-memory implementation, always returns True."""
39
+ return True
40
+
37
41
  # -- Session methods --
38
42
 
39
43
  def delete_session(self, session_id: str) -> bool:
agno/db/json/json_db.py CHANGED
@@ -66,6 +66,10 @@ class JsonDb(BaseDb):
66
66
  # Create the directory where the JSON files will be stored, if it doesn't exist
67
67
  self.db_path = Path(db_path or os.path.join(os.getcwd(), "agno_json_db"))
68
68
 
69
+ def table_exists(self, table_name: str) -> bool:
70
+ """JSON implementation, always returns True."""
71
+ return True
72
+
69
73
  def _read_json_file(self, filename: str, create_table_if_not_found: Optional[bool] = True) -> List[Dict[str, Any]]:
70
74
  """Read data from a JSON file, creating it if it doesn't exist.
71
75
 
@@ -99,6 +99,33 @@ class AsyncMongoDb(AsyncBaseDb):
99
99
  self._database: Optional[AsyncIOMotorDatabase] = None
100
100
  self._event_loop: Optional[asyncio.AbstractEventLoop] = None
101
101
 
102
+ async def table_exists(self, table_name: str) -> bool:
103
+ """Check if a collection with the given name exists in the MongoDB database.
104
+
105
+ Args:
106
+ table_name: Name of the collection to check
107
+
108
+ Returns:
109
+ bool: True if the collection exists in the database, False otherwise
110
+ """
111
+ collection_names = await self.database.list_collection_names()
112
+ return table_name in collection_names
113
+
114
+ async def _create_all_tables(self):
115
+ """Create all configured MongoDB collections if they don't exist."""
116
+ collections_to_create = [
117
+ ("sessions", self.session_table_name),
118
+ ("memories", self.memory_table_name),
119
+ ("metrics", self.metrics_table_name),
120
+ ("evals", self.eval_table_name),
121
+ ("knowledge", self.knowledge_table_name),
122
+ ("culture", self.culture_table_name),
123
+ ]
124
+
125
+ for collection_type, collection_name in collections_to_create:
126
+ if collection_name and not await self.table_exists(collection_name):
127
+ await self._get_collection(collection_type, create_collection_if_not_found=True)
128
+
102
129
  def _ensure_client(self) -> AsyncIOMotorClient:
103
130
  """
104
131
  Ensure the Motor client is valid for the current event loop.
agno/db/mongo/mongo.py CHANGED
@@ -100,6 +100,31 @@ class MongoDb(BaseDb):
100
100
  return self._database
101
101
 
102
102
  # -- DB methods --
103
+ def table_exists(self, table_name: str) -> bool:
104
+ """Check if a collection with the given name exists in the MongoDB database.
105
+
106
+ Args:
107
+ table_name: Name of the collection to check
108
+
109
+ Returns:
110
+ bool: True if the collection exists in the database, False otherwise
111
+ """
112
+ return table_name in self.database.list_collection_names()
113
+
114
+ def _create_all_tables(self):
115
+ """Create all configured MongoDB collections if they don't exist."""
116
+ collections_to_create = [
117
+ ("sessions", self.session_table_name),
118
+ ("memories", self.memory_table_name),
119
+ ("metrics", self.metrics_table_name),
120
+ ("evals", self.eval_table_name),
121
+ ("knowledge", self.knowledge_table_name),
122
+ ("culture", self.culture_table_name),
123
+ ]
124
+
125
+ for collection_type, collection_name in collections_to_create:
126
+ if collection_name and not self.table_exists(collection_name):
127
+ self._get_collection(collection_type, create_collection_if_not_found=True)
103
128
 
104
129
  def _get_collection(
105
130
  self, table_type: str, create_collection_if_not_found: Optional[bool] = True
agno/db/mysql/mysql.py CHANGED
@@ -107,6 +107,18 @@ class MySQLDb(BaseDb):
107
107
  self.Session: scoped_session = scoped_session(sessionmaker(bind=self.db_engine))
108
108
 
109
109
  # -- DB methods --
110
+ def table_exists(self, table_name: str) -> bool:
111
+ """Check if a table with the given name exists in the MySQL database.
112
+
113
+ Args:
114
+ table_name: Name of the table to check
115
+
116
+ Returns:
117
+ bool: True if the table exists in the database, False otherwise
118
+ """
119
+ with self.Session() as sess:
120
+ return is_table_available(session=sess, table_name=table_name, db_schema=self.db_schema)
121
+
110
122
  def _create_table(self, table_name: str, table_type: str, db_schema: str) -> Table:
111
123
  """
112
124
  Create a table with the appropriate schema based on the table type.
@@ -191,13 +203,26 @@ class MySQLDb(BaseDb):
191
203
  except Exception as e:
192
204
  log_error(f"Error creating index {idx.name}: {e}")
193
205
 
194
- log_info(f"Successfully created table {db_schema}.{table_name}")
206
+ log_debug(f"Successfully created table {db_schema}.{table_name}")
195
207
  return table
196
208
 
197
209
  except Exception as e:
198
210
  log_error(f"Could not create table {db_schema}.{table_name}: {e}")
199
211
  raise
200
212
 
213
+ def _create_all_tables(self):
214
+ """Create all tables for the database."""
215
+ tables_to_create = [
216
+ (self.session_table_name, "sessions"),
217
+ (self.memory_table_name, "memories"),
218
+ (self.metrics_table_name, "metrics"),
219
+ (self.eval_table_name, "evals"),
220
+ (self.knowledge_table_name, "knowledge"),
221
+ ]
222
+
223
+ for table_name, table_type in tables_to_create:
224
+ self._create_table(table_name=table_name, table_type=table_type, db_schema=self.db_schema)
225
+
201
226
  def _get_table(self, table_type: str, create_table_if_not_found: Optional[bool] = False) -> Optional[Table]:
202
227
  if table_type == "sessions":
203
228
  self.session_table = self._get_or_create_table(
@@ -102,6 +102,31 @@ class AsyncPostgresDb(AsyncBaseDb):
102
102
  self.async_session_factory = async_sessionmaker(bind=self.db_engine)
103
103
 
104
104
  # -- DB methods --
105
+ async def table_exists(self, table_name: str) -> bool:
106
+ """Check if a table with the given name exists in the Postgres database.
107
+
108
+ Args:
109
+ table_name: Name of the table to check
110
+
111
+ Returns:
112
+ bool: True if the table exists in the database, False otherwise
113
+ """
114
+ async with self.async_session_factory() as sess:
115
+ return await ais_table_available(session=sess, table_name=table_name, db_schema=self.db_schema)
116
+
117
+ async def _create_all_tables(self):
118
+ """Create all tables for the database."""
119
+ tables_to_create = [
120
+ (self.session_table_name, "sessions"),
121
+ (self.memory_table_name, "memories"),
122
+ (self.metrics_table_name, "metrics"),
123
+ (self.eval_table_name, "evals"),
124
+ (self.knowledge_table_name, "knowledge"),
125
+ ]
126
+
127
+ for table_name, table_type in tables_to_create:
128
+ await self._create_table(table_name=table_name, table_type=table_type, db_schema=self.db_schema)
129
+
105
130
  async def _create_table(self, table_name: str, table_type: str, db_schema: str) -> Table:
106
131
  """
107
132
  Create a table with the appropriate schema based on the table type.
@@ -180,7 +205,7 @@ class AsyncPostgresDb(AsyncBaseDb):
180
205
  except Exception as e:
181
206
  log_error(f"Error creating index {idx.name}: {e}")
182
207
 
183
- log_info(f"Successfully created table {table_name} in schema {db_schema}")
208
+ log_debug(f"Successfully created table {table_name} in schema {db_schema}")
184
209
  return table
185
210
 
186
211
  except Exception as e:
@@ -106,6 +106,31 @@ class PostgresDb(BaseDb):
106
106
  self.Session: scoped_session = scoped_session(sessionmaker(bind=self.db_engine))
107
107
 
108
108
  # -- DB methods --
109
+ def table_exists(self, table_name: str) -> bool:
110
+ """Check if a table with the given name exists in the Postgres database.
111
+
112
+ Args:
113
+ table_name: Name of the table to check
114
+
115
+ Returns:
116
+ bool: True if the table exists in the database, False otherwise
117
+ """
118
+ with self.Session() as sess:
119
+ return is_table_available(session=sess, table_name=table_name, db_schema=self.db_schema)
120
+
121
+ def _create_all_tables(self):
122
+ """Create all tables for the database."""
123
+ tables_to_create = [
124
+ (self.session_table_name, "sessions"),
125
+ (self.memory_table_name, "memories"),
126
+ (self.metrics_table_name, "metrics"),
127
+ (self.eval_table_name, "evals"),
128
+ (self.knowledge_table_name, "knowledge"),
129
+ ]
130
+
131
+ for table_name, table_type in tables_to_create:
132
+ self._create_table(table_name=table_name, table_type=table_type, db_schema=self.db_schema)
133
+
109
134
  def _create_table(self, table_name: str, table_type: str, db_schema: str) -> Table:
110
135
  """
111
136
  Create a table with the appropriate schema based on the table type.
@@ -184,7 +209,7 @@ class PostgresDb(BaseDb):
184
209
  except Exception as e:
185
210
  log_error(f"Error creating index {idx.name}: {e}")
186
211
 
187
- log_info(f"Successfully created table {table_name} in schema {db_schema}")
212
+ log_debug(f"Successfully created table {table_name} in schema {db_schema}")
188
213
  return table
189
214
 
190
215
  except Exception as e:
agno/db/redis/redis.py CHANGED
@@ -100,6 +100,10 @@ class RedisDb(BaseDb):
100
100
 
101
101
  # -- DB methods --
102
102
 
103
+ def table_exists(self, table_name: str) -> bool:
104
+ """Redis implementation, always returns True."""
105
+ return True
106
+
103
107
  def _get_table_name(self, table_type: str) -> str:
104
108
  """Get the active table name for the given table type."""
105
109
  if table_type == "sessions":