agno 2.1.3__py3-none-any.whl → 2.1.5__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 (94) hide show
  1. agno/agent/agent.py +1779 -577
  2. agno/db/async_postgres/__init__.py +3 -0
  3. agno/db/async_postgres/async_postgres.py +1668 -0
  4. agno/db/async_postgres/schemas.py +124 -0
  5. agno/db/async_postgres/utils.py +289 -0
  6. agno/db/base.py +237 -2
  7. agno/db/dynamo/dynamo.py +10 -8
  8. agno/db/dynamo/schemas.py +1 -10
  9. agno/db/dynamo/utils.py +2 -2
  10. agno/db/firestore/firestore.py +2 -2
  11. agno/db/firestore/utils.py +4 -2
  12. agno/db/gcs_json/gcs_json_db.py +2 -2
  13. agno/db/in_memory/in_memory_db.py +2 -2
  14. agno/db/json/json_db.py +2 -2
  15. agno/db/migrations/v1_to_v2.py +30 -13
  16. agno/db/mongo/mongo.py +18 -6
  17. agno/db/mysql/mysql.py +35 -13
  18. agno/db/postgres/postgres.py +29 -6
  19. agno/db/redis/redis.py +2 -2
  20. agno/db/singlestore/singlestore.py +2 -2
  21. agno/db/sqlite/sqlite.py +34 -12
  22. agno/db/sqlite/utils.py +8 -3
  23. agno/eval/accuracy.py +50 -43
  24. agno/eval/performance.py +6 -3
  25. agno/eval/reliability.py +6 -3
  26. agno/eval/utils.py +33 -16
  27. agno/exceptions.py +8 -2
  28. agno/knowledge/embedder/fastembed.py +1 -1
  29. agno/knowledge/knowledge.py +260 -46
  30. agno/knowledge/reader/pdf_reader.py +4 -6
  31. agno/knowledge/reader/reader_factory.py +2 -3
  32. agno/memory/manager.py +241 -33
  33. agno/models/anthropic/claude.py +37 -0
  34. agno/os/app.py +15 -10
  35. agno/os/interfaces/a2a/router.py +3 -5
  36. agno/os/interfaces/agui/router.py +4 -1
  37. agno/os/interfaces/agui/utils.py +33 -6
  38. agno/os/interfaces/slack/router.py +2 -4
  39. agno/os/mcp.py +98 -41
  40. agno/os/router.py +23 -0
  41. agno/os/routers/evals/evals.py +52 -20
  42. agno/os/routers/evals/utils.py +14 -14
  43. agno/os/routers/knowledge/knowledge.py +130 -9
  44. agno/os/routers/knowledge/schemas.py +57 -0
  45. agno/os/routers/memory/memory.py +116 -44
  46. agno/os/routers/metrics/metrics.py +16 -6
  47. agno/os/routers/session/session.py +65 -22
  48. agno/os/schema.py +38 -0
  49. agno/os/utils.py +69 -13
  50. agno/reasoning/anthropic.py +80 -0
  51. agno/reasoning/gemini.py +73 -0
  52. agno/reasoning/openai.py +5 -0
  53. agno/reasoning/vertexai.py +76 -0
  54. agno/session/workflow.py +69 -1
  55. agno/team/team.py +934 -241
  56. agno/tools/function.py +36 -18
  57. agno/tools/google_drive.py +270 -0
  58. agno/tools/googlesheets.py +20 -5
  59. agno/tools/mcp_toolbox.py +3 -3
  60. agno/tools/scrapegraph.py +1 -1
  61. agno/utils/models/claude.py +3 -1
  62. agno/utils/print_response/workflow.py +112 -12
  63. agno/utils/streamlit.py +1 -1
  64. agno/vectordb/base.py +22 -1
  65. agno/vectordb/cassandra/cassandra.py +9 -0
  66. agno/vectordb/chroma/chromadb.py +26 -6
  67. agno/vectordb/clickhouse/clickhousedb.py +9 -1
  68. agno/vectordb/couchbase/couchbase.py +11 -0
  69. agno/vectordb/lancedb/lance_db.py +20 -0
  70. agno/vectordb/langchaindb/langchaindb.py +11 -0
  71. agno/vectordb/lightrag/lightrag.py +9 -0
  72. agno/vectordb/llamaindex/llamaindexdb.py +15 -1
  73. agno/vectordb/milvus/milvus.py +23 -0
  74. agno/vectordb/mongodb/mongodb.py +22 -0
  75. agno/vectordb/pgvector/pgvector.py +19 -0
  76. agno/vectordb/pineconedb/pineconedb.py +35 -4
  77. agno/vectordb/qdrant/qdrant.py +24 -0
  78. agno/vectordb/singlestore/singlestore.py +25 -17
  79. agno/vectordb/surrealdb/surrealdb.py +18 -1
  80. agno/vectordb/upstashdb/upstashdb.py +26 -1
  81. agno/vectordb/weaviate/weaviate.py +18 -0
  82. agno/workflow/condition.py +29 -0
  83. agno/workflow/loop.py +29 -0
  84. agno/workflow/parallel.py +141 -113
  85. agno/workflow/router.py +29 -0
  86. agno/workflow/step.py +146 -25
  87. agno/workflow/steps.py +29 -0
  88. agno/workflow/types.py +26 -1
  89. agno/workflow/workflow.py +507 -22
  90. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/METADATA +100 -41
  91. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/RECORD +94 -86
  92. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/WHEEL +0 -0
  93. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/licenses/LICENSE +0 -0
  94. {agno-2.1.3.dist-info → agno-2.1.5.dist-info}/top_level.txt +0 -0
agno/memory/manager.py CHANGED
@@ -7,7 +7,7 @@ from typing import Any, Callable, Dict, List, Literal, Optional, Type, Union
7
7
 
8
8
  from pydantic import BaseModel, Field
9
9
 
10
- from agno.db.base import BaseDb
10
+ from agno.db.base import AsyncBaseDb, BaseDb
11
11
  from agno.db.schemas import UserMemory
12
12
  from agno.models.base import Model
13
13
  from agno.models.message import Message
@@ -53,7 +53,7 @@ class MemoryManager:
53
53
  add_memories: bool = True
54
54
 
55
55
  # The database to store memories
56
- db: Optional[BaseDb] = None
56
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None
57
57
 
58
58
  debug_mode: bool = False
59
59
 
@@ -63,11 +63,11 @@ class MemoryManager:
63
63
  system_message: Optional[str] = None,
64
64
  memory_capture_instructions: Optional[str] = None,
65
65
  additional_instructions: Optional[str] = None,
66
- db: Optional[BaseDb] = None,
67
- delete_memories: bool = True,
66
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
67
+ delete_memories: bool = False,
68
68
  update_memories: bool = True,
69
69
  add_memories: bool = True,
70
- clear_memories: bool = True,
70
+ clear_memories: bool = False,
71
71
  debug_mode: bool = False,
72
72
  ):
73
73
  self.model = model
@@ -114,6 +114,22 @@ class MemoryManager:
114
114
  return memories
115
115
  return None
116
116
 
117
+ async def aread_from_db(self, user_id: Optional[str] = None):
118
+ if self.db:
119
+ # If no user_id is provided, read all memories
120
+ if user_id is None:
121
+ all_memories: List[UserMemory] = await self.db.get_user_memories() # type: ignore
122
+ else:
123
+ all_memories = await self.db.get_user_memories(user_id=user_id) # type: ignore
124
+
125
+ memories: Dict[str, List[UserMemory]] = {}
126
+ for memory in all_memories:
127
+ if memory.user_id is not None and memory.memory_id is not None:
128
+ memories.setdefault(memory.user_id, []).append(memory)
129
+
130
+ return memories
131
+ return None
132
+
117
133
  def set_log_level(self):
118
134
  if self.debug_mode or getenv("AGNO_DEBUG", "false").lower() == "true":
119
135
  self.debug_mode = True
@@ -139,6 +155,20 @@ class MemoryManager:
139
155
  log_warning("Memory Db not provided.")
140
156
  return []
141
157
 
158
+ async def aget_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
159
+ """Get the user memories for a given user id"""
160
+ if self.db:
161
+ if user_id is None:
162
+ user_id = "default"
163
+ # Refresh from the Db
164
+ memories = await self.aread_from_db(user_id=user_id)
165
+ if memories is None:
166
+ return []
167
+ return memories.get(user_id, [])
168
+ else:
169
+ log_warning("Memory Db not provided.")
170
+ return []
171
+
142
172
  def get_user_memory(self, memory_id: str, user_id: Optional[str] = None) -> Optional[UserMemory]:
143
173
  """Get the user memory for a given user id"""
144
174
  if self.db:
@@ -261,6 +291,11 @@ class MemoryManager:
261
291
  log_warning("MemoryDb not provided.")
262
292
  return "Please provide a db to store memories"
263
293
 
294
+ if isinstance(self.db, AsyncBaseDb):
295
+ raise ValueError(
296
+ "create_user_memories() is not supported with an async DB. Please use acreate_user_memories() instead."
297
+ )
298
+
264
299
  if not messages and not message:
265
300
  raise ValueError("You must provide either a message or a list of messages")
266
301
 
@@ -321,7 +356,10 @@ class MemoryManager:
321
356
  if user_id is None:
322
357
  user_id = "default"
323
358
 
324
- memories = self.read_from_db(user_id=user_id)
359
+ if isinstance(self.db, AsyncBaseDb):
360
+ memories = await self.aread_from_db(user_id=user_id)
361
+ else:
362
+ memories = self.read_from_db(user_id=user_id)
325
363
  if memories is None:
326
364
  memories = {}
327
365
 
@@ -340,7 +378,10 @@ class MemoryManager:
340
378
  )
341
379
 
342
380
  # We refresh from the DB
343
- self.read_from_db(user_id=user_id)
381
+ if isinstance(self.db, AsyncBaseDb):
382
+ memories = await self.aread_from_db(user_id=user_id)
383
+ else:
384
+ memories = self.read_from_db(user_id=user_id)
344
385
 
345
386
  return response
346
387
 
@@ -351,6 +392,11 @@ class MemoryManager:
351
392
  log_warning("MemoryDb not provided.")
352
393
  return "Please provide a db to store memories"
353
394
 
395
+ if not isinstance(self.db, BaseDb):
396
+ raise ValueError(
397
+ "update_memory_task() is not supported with an async DB. Please use aupdate_memory_task() instead."
398
+ )
399
+
354
400
  if user_id is None:
355
401
  user_id = "default"
356
402
 
@@ -807,7 +853,7 @@ class MemoryManager:
807
853
  messages: List[Message],
808
854
  existing_memories: List[Dict[str, Any]],
809
855
  user_id: str,
810
- db: BaseDb,
856
+ db: Union[BaseDb, AsyncBaseDb],
811
857
  agent_id: Optional[str] = None,
812
858
  team_id: Optional[str] = None,
813
859
  update_memories: bool = True,
@@ -826,19 +872,34 @@ class MemoryManager:
826
872
 
827
873
  model_copy = deepcopy(self.model)
828
874
  # Update the Model (set defaults, add logit etc.)
829
- self.determine_tools_for_model(
830
- self._get_db_tools(
831
- user_id,
832
- db,
833
- input_string,
834
- agent_id=agent_id,
835
- team_id=team_id,
836
- enable_add_memory=add_memories,
837
- enable_update_memory=update_memories,
838
- enable_delete_memory=False,
839
- enable_clear_memory=False,
840
- ),
841
- )
875
+ if isinstance(db, AsyncBaseDb):
876
+ self.determine_tools_for_model(
877
+ await self._aget_db_tools(
878
+ user_id,
879
+ db,
880
+ input_string,
881
+ agent_id=agent_id,
882
+ team_id=team_id,
883
+ enable_add_memory=add_memories,
884
+ enable_update_memory=update_memories,
885
+ enable_delete_memory=False,
886
+ enable_clear_memory=False,
887
+ ),
888
+ )
889
+ else:
890
+ self.determine_tools_for_model(
891
+ self._get_db_tools(
892
+ user_id,
893
+ db,
894
+ input_string,
895
+ agent_id=agent_id,
896
+ team_id=team_id,
897
+ enable_add_memory=add_memories,
898
+ enable_update_memory=update_memories,
899
+ enable_delete_memory=False,
900
+ enable_clear_memory=False,
901
+ ),
902
+ )
842
903
 
843
904
  # Prepare the List of messages to send to the Model
844
905
  messages_for_model: List[Message] = [
@@ -923,7 +984,7 @@ class MemoryManager:
923
984
  task: str,
924
985
  existing_memories: List[Dict[str, Any]],
925
986
  user_id: str,
926
- db: BaseDb,
987
+ db: Union[BaseDb, AsyncBaseDb],
927
988
  delete_memories: bool = True,
928
989
  clear_memories: bool = True,
929
990
  update_memories: bool = True,
@@ -937,17 +998,30 @@ class MemoryManager:
937
998
 
938
999
  model_copy = deepcopy(self.model)
939
1000
  # Update the Model (set defaults, add logit etc.)
940
- self.determine_tools_for_model(
941
- self._get_db_tools(
942
- user_id,
943
- db,
944
- task,
945
- enable_delete_memory=delete_memories,
946
- enable_clear_memory=clear_memories,
947
- enable_update_memory=update_memories,
948
- enable_add_memory=add_memories,
949
- ),
950
- )
1001
+ if isinstance(db, AsyncBaseDb):
1002
+ self.determine_tools_for_model(
1003
+ await self._aget_db_tools(
1004
+ user_id,
1005
+ db,
1006
+ task,
1007
+ enable_delete_memory=delete_memories,
1008
+ enable_clear_memory=clear_memories,
1009
+ enable_update_memory=update_memories,
1010
+ enable_add_memory=add_memories,
1011
+ ),
1012
+ )
1013
+ else:
1014
+ self.determine_tools_for_model(
1015
+ self._get_db_tools(
1016
+ user_id,
1017
+ db,
1018
+ task,
1019
+ enable_delete_memory=delete_memories,
1020
+ enable_clear_memory=clear_memories,
1021
+ enable_update_memory=update_memories,
1022
+ enable_add_memory=add_memories,
1023
+ ),
1024
+ )
951
1025
 
952
1026
  # Prepare the List of messages to send to the Model
953
1027
  messages_for_model: List[Message] = [
@@ -1079,3 +1153,137 @@ class MemoryManager:
1079
1153
  if enable_clear_memory:
1080
1154
  functions.append(clear_memory)
1081
1155
  return functions
1156
+
1157
+ async def _aget_db_tools(
1158
+ self,
1159
+ user_id: str,
1160
+ db: Union[BaseDb, AsyncBaseDb],
1161
+ input_string: str,
1162
+ enable_add_memory: bool = True,
1163
+ enable_update_memory: bool = True,
1164
+ enable_delete_memory: bool = True,
1165
+ enable_clear_memory: bool = True,
1166
+ agent_id: Optional[str] = None,
1167
+ team_id: Optional[str] = None,
1168
+ ) -> List[Callable]:
1169
+ async def add_memory(memory: str, topics: Optional[List[str]] = None) -> str:
1170
+ """Use this function to add a memory to the database.
1171
+ Args:
1172
+ memory (str): The memory to be added.
1173
+ topics (Optional[List[str]]): The topics of the memory (e.g. ["name", "hobbies", "location"]).
1174
+ Returns:
1175
+ str: A message indicating if the memory was added successfully or not.
1176
+ """
1177
+ from uuid import uuid4
1178
+
1179
+ from agno.db.base import UserMemory
1180
+
1181
+ try:
1182
+ memory_id = str(uuid4())
1183
+ if isinstance(db, AsyncBaseDb):
1184
+ await db.upsert_user_memory(
1185
+ UserMemory(
1186
+ memory_id=memory_id,
1187
+ user_id=user_id,
1188
+ agent_id=agent_id,
1189
+ team_id=team_id,
1190
+ memory=memory,
1191
+ topics=topics,
1192
+ input=input_string,
1193
+ )
1194
+ )
1195
+ else:
1196
+ db.upsert_user_memory(
1197
+ UserMemory(
1198
+ memory_id=memory_id,
1199
+ user_id=user_id,
1200
+ agent_id=agent_id,
1201
+ team_id=team_id,
1202
+ memory=memory,
1203
+ topics=topics,
1204
+ input=input_string,
1205
+ )
1206
+ )
1207
+ log_debug(f"Memory added: {memory_id}")
1208
+ return "Memory added successfully"
1209
+ except Exception as e:
1210
+ log_warning(f"Error storing memory in db: {e}")
1211
+ return f"Error adding memory: {e}"
1212
+
1213
+ async def update_memory(memory_id: str, memory: str, topics: Optional[List[str]] = None) -> str:
1214
+ """Use this function to update an existing memory in the database.
1215
+ Args:
1216
+ memory_id (str): The id of the memory to be updated.
1217
+ memory (str): The updated memory.
1218
+ topics (Optional[List[str]]): The topics of the memory (e.g. ["name", "hobbies", "location"]).
1219
+ Returns:
1220
+ str: A message indicating if the memory was updated successfully or not.
1221
+ """
1222
+ from agno.db.base import UserMemory
1223
+
1224
+ try:
1225
+ if isinstance(db, AsyncBaseDb):
1226
+ await db.upsert_user_memory(
1227
+ UserMemory(
1228
+ memory_id=memory_id,
1229
+ memory=memory,
1230
+ topics=topics,
1231
+ input=input_string,
1232
+ )
1233
+ )
1234
+ else:
1235
+ db.upsert_user_memory(
1236
+ UserMemory(
1237
+ memory_id=memory_id,
1238
+ memory=memory,
1239
+ topics=topics,
1240
+ input=input_string,
1241
+ )
1242
+ )
1243
+ log_debug("Memory updated")
1244
+ return "Memory updated successfully"
1245
+ except Exception as e:
1246
+ log_warning(f"Error storing memory in db: {e}")
1247
+ return f"Error adding memory: {e}"
1248
+
1249
+ async def delete_memory(memory_id: str) -> str:
1250
+ """Use this function to delete a single memory from the database.
1251
+ Args:
1252
+ memory_id (str): The id of the memory to be deleted.
1253
+ Returns:
1254
+ str: A message indicating if the memory was deleted successfully or not.
1255
+ """
1256
+ try:
1257
+ if isinstance(db, AsyncBaseDb):
1258
+ await db.delete_user_memory(memory_id=memory_id)
1259
+ else:
1260
+ db.delete_user_memory(memory_id=memory_id)
1261
+ log_debug("Memory deleted")
1262
+ return "Memory deleted successfully"
1263
+ except Exception as e:
1264
+ log_warning(f"Error deleting memory in db: {e}")
1265
+ return f"Error deleting memory: {e}"
1266
+
1267
+ async def clear_memory() -> str:
1268
+ """Use this function to remove all (or clear all) memories from the database.
1269
+
1270
+ Returns:
1271
+ str: A message indicating if the memory was cleared successfully or not.
1272
+ """
1273
+ if isinstance(db, AsyncBaseDb):
1274
+ await db.clear_memories()
1275
+ else:
1276
+ db.clear_memories()
1277
+ log_debug("Memory cleared")
1278
+ return "Memory cleared successfully"
1279
+
1280
+ functions: List[Callable] = []
1281
+ if enable_add_memory:
1282
+ functions.append(add_memory)
1283
+ if enable_update_memory:
1284
+ functions.append(update_memory)
1285
+ if enable_delete_memory:
1286
+ functions.append(delete_memory)
1287
+ if enable_clear_memory:
1288
+ functions.append(clear_memory)
1289
+ return functions
@@ -59,6 +59,17 @@ class Claude(Model):
59
59
  For more information, see: https://docs.anthropic.com/en/api/messages
60
60
  """
61
61
 
62
+ # Models that DO NOT support extended thinking
63
+ # All future models are assumed to support thinking
64
+ # Based on official Anthropic documentation: https://docs.claude.com/en/docs/about-claude/models/overview
65
+ NON_THINKING_MODELS = {
66
+ # Claude Haiku 3 family (does not support thinking)
67
+ "claude-3-haiku-20240307",
68
+ # Claude Haiku 3.5 family (does not support thinking)
69
+ "claude-3-5-haiku-20241022",
70
+ "claude-3-5-haiku-latest",
71
+ }
72
+
62
73
  id: str = "claude-sonnet-4-5-20250929"
63
74
  name: str = "Claude"
64
75
  provider: str = "Anthropic"
@@ -85,6 +96,12 @@ class Claude(Model):
85
96
  client: Optional[AnthropicClient] = None
86
97
  async_client: Optional[AsyncAnthropicClient] = None
87
98
 
99
+ def __post_init__(self):
100
+ """Validate model configuration after initialization"""
101
+ # Validate thinking support immediately at model creation
102
+ if self.thinking:
103
+ self._validate_thinking_support()
104
+
88
105
  def _get_client_params(self) -> Dict[str, Any]:
89
106
  client_params: Dict[str, Any] = {}
90
107
 
@@ -126,10 +143,30 @@ class Claude(Model):
126
143
  self.async_client = AsyncAnthropicClient(**_client_params)
127
144
  return self.async_client
128
145
 
146
+ def _validate_thinking_support(self) -> None:
147
+ """
148
+ Validate that the current model supports extended thinking.
149
+
150
+ Raises:
151
+ ValueError: If thinking is enabled but the model doesn't support it
152
+ """
153
+ if self.thinking and self.id in self.NON_THINKING_MODELS:
154
+ non_thinking_models = "\n - ".join(sorted(self.NON_THINKING_MODELS))
155
+ raise ValueError(
156
+ f"Model '{self.id}' does not support extended thinking.\n\n"
157
+ f"The following models do NOT support thinking:\n - {non_thinking_models}\n\n"
158
+ f"All other Claude models support extended thinking by default.\n"
159
+ f"For more information, see: https://docs.anthropic.com/en/docs/about-claude/models/overview"
160
+ )
161
+
129
162
  def get_request_params(self) -> Dict[str, Any]:
130
163
  """
131
164
  Generate keyword arguments for API requests.
132
165
  """
166
+ # Validate thinking support if thinking is enabled
167
+ if self.thinking:
168
+ self._validate_thinking_support()
169
+
133
170
  _request_params: Dict[str, Any] = {}
134
171
  if self.max_tokens:
135
172
  _request_params["max_tokens"] = self.max_tokens
agno/os/app.py CHANGED
@@ -12,7 +12,7 @@ from rich.panel import Panel
12
12
  from starlette.requests import Request
13
13
 
14
14
  from agno.agent.agent import Agent
15
- from agno.db.base import BaseDb
15
+ from agno.db.base import AsyncBaseDb, BaseDb
16
16
  from agno.os.config import (
17
17
  AgentOSConfig,
18
18
  DatabaseConfig,
@@ -207,9 +207,6 @@ class AgentOS:
207
207
 
208
208
  team.initialize_team()
209
209
 
210
- # Required for the built-in routes to work
211
- team.store_events = True
212
-
213
210
  for member in team.members:
214
211
  if isinstance(member, Agent):
215
212
  member.team_id = None
@@ -217,13 +214,20 @@ class AgentOS:
217
214
  elif isinstance(member, Team):
218
215
  member.initialize_team()
219
216
 
217
+ # Required for the built-in routes to work
218
+ team.store_events = True
219
+
220
220
  if self.workflows:
221
221
  for workflow in self.workflows:
222
222
  # Track MCP tools recursively in workflow members
223
223
  collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
224
+
224
225
  if not workflow.id:
225
226
  workflow.id = generate_id_from_name(workflow.name)
226
227
 
228
+ # Required for the built-in routes to work
229
+ workflow.store_events = True
230
+
227
231
  if self.telemetry:
228
232
  from agno.api.os import OSLaunch, log_os_telemetry
229
233
 
@@ -455,10 +459,10 @@ class AgentOS:
455
459
 
456
460
  def _auto_discover_databases(self) -> None:
457
461
  """Auto-discover the databases used by all contextual agents, teams and workflows."""
458
- from agno.db.base import BaseDb
462
+ from agno.db.base import AsyncBaseDb, BaseDb
459
463
 
460
- dbs: Dict[str, BaseDb] = {}
461
- knowledge_dbs: Dict[str, BaseDb] = {} # Track databases specifically used for knowledge
464
+ dbs: Dict[str, Union[BaseDb, AsyncBaseDb]] = {}
465
+ knowledge_dbs: Dict[str, Union[BaseDb, AsyncBaseDb]] = {} # Track databases specifically used for knowledge
462
466
 
463
467
  for agent in self.agents or []:
464
468
  if agent.db:
@@ -485,7 +489,7 @@ class AgentOS:
485
489
  self.dbs = dbs
486
490
  self.knowledge_dbs = knowledge_dbs
487
491
 
488
- def _register_db_with_validation(self, registered_dbs: Dict[str, Any], db: BaseDb) -> None:
492
+ def _register_db_with_validation(self, registered_dbs: Dict[str, Any], db: Union[BaseDb, AsyncBaseDb]) -> None:
489
493
  """Register a database in the contextual OS after validating it is not conflicting with registered databases"""
490
494
  if db.id in registered_dbs:
491
495
  existing_db = registered_dbs[db.id]
@@ -496,7 +500,7 @@ class AgentOS:
496
500
  )
497
501
  registered_dbs[db.id] = db
498
502
 
499
- def _are_db_instances_compatible(self, db1: BaseDb, db2: BaseDb) -> bool:
503
+ def _are_db_instances_compatible(self, db1: Union[BaseDb, AsyncBaseDb], db2: Union[BaseDb, AsyncBaseDb]) -> bool:
500
504
  """
501
505
  Return True if the two given database objects are compatible
502
506
  Two database objects are compatible if they point to the same database with identical configuration.
@@ -645,6 +649,7 @@ class AgentOS:
645
649
  port: int = 7777,
646
650
  reload: bool = False,
647
651
  workers: Optional[int] = None,
652
+ access_log: bool = False,
648
653
  **kwargs,
649
654
  ):
650
655
  import uvicorn
@@ -677,4 +682,4 @@ class AgentOS:
677
682
  )
678
683
  )
679
684
 
680
- uvicorn.run(app=app, host=host, port=port, reload=reload, workers=workers, **kwargs)
685
+ uvicorn.run(app=app, host=host, port=port, reload=reload, workers=workers, access_log=access_log, **kwargs)
@@ -11,7 +11,7 @@ from typing_extensions import List
11
11
  try:
12
12
  from a2a.types import SendMessageSuccessResponse, Task, TaskState, TaskStatus
13
13
  except ImportError as e:
14
- raise ImportError("`a2a` not installed. Please install it with `pip install -U a2a`") from e
14
+ raise ImportError("`a2a` not installed. Please install it with `pip install -U a2a-sdk`") from e
15
15
 
16
16
  from agno.agent import Agent
17
17
  from agno.os.interfaces.a2a.utils import (
@@ -36,9 +36,8 @@ def attach_routes(
36
36
 
37
37
  @router.post(
38
38
  "/message/send",
39
- tags=["A2A"],
40
39
  operation_id="send_message",
41
- summary="Send message to Agent, Team, or Workflow (A2A Protocol)",
40
+ name="send_message",
42
41
  description="Send a message to an Agno Agent, Team, or Workflow. "
43
42
  "The Agent, Team or Workflow is identified via the 'agentId' field in params.message or X-Agent-ID header. "
44
43
  "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata.",
@@ -159,9 +158,8 @@ def attach_routes(
159
158
 
160
159
  @router.post(
161
160
  "/message/stream",
162
- tags=["A2A"],
163
161
  operation_id="stream_message",
164
- summary="Stream message to Agent, Team, or Workflow (A2A Protocol)",
162
+ name="stream_message",
165
163
  description="Stream a message to an Agno Agent, Team, or Workflow."
166
164
  "The Agent, Team or Workflow is identified via the 'agentId' field in params.message or X-Agent-ID header. "
167
165
  "Optional: Pass user ID via X-User-ID header (recommended) or 'userId' in params.message.metadata. "
@@ -101,7 +101,10 @@ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Option
101
101
 
102
102
  encoder = EventEncoder()
103
103
 
104
- @router.post("/agui")
104
+ @router.post(
105
+ "/agui",
106
+ name="run_agent",
107
+ )
105
108
  async def run_agent_agui(run_input: RunAgentInput):
106
109
  async def event_generator():
107
110
  if agent:
@@ -8,6 +8,7 @@ from typing import AsyncIterator, List, Set, Tuple, Union
8
8
 
9
9
  from ag_ui.core import (
10
10
  BaseEvent,
11
+ CustomEvent,
11
12
  EventType,
12
13
  RunFinishedEvent,
13
14
  StepFinishedEvent,
@@ -261,6 +262,11 @@ def _create_events_from_chunk(
261
262
  step_finished_event = StepFinishedEvent(type=EventType.STEP_FINISHED, step_name="reasoning")
262
263
  events_to_emit.append(step_finished_event)
263
264
 
265
+ # Handle custom events
266
+ elif chunk.event == RunEvent.custom_event:
267
+ custom_event = CustomEvent(name=chunk.event, value=chunk.content)
268
+ events_to_emit.append(custom_event)
269
+
264
270
  return events_to_emit, message_started, message_id
265
271
 
266
272
 
@@ -291,20 +297,41 @@ def _create_completion_events(
291
297
 
292
298
  # emit frontend tool calls, i.e. external_execution=True
293
299
  if isinstance(chunk, RunPausedEvent) and chunk.tools is not None:
300
+ # First, emit an assistant message for external tool calls
301
+ assistant_message_id = str(uuid.uuid4())
302
+ assistant_start_event = TextMessageStartEvent(
303
+ type=EventType.TEXT_MESSAGE_START,
304
+ message_id=assistant_message_id,
305
+ role="assistant",
306
+ )
307
+ events_to_emit.append(assistant_start_event)
308
+
309
+ # Add any text content if present for the assistant message
310
+ if chunk.content:
311
+ content_event = TextMessageContentEvent(
312
+ type=EventType.TEXT_MESSAGE_CONTENT,
313
+ message_id=assistant_message_id,
314
+ delta=str(chunk.content),
315
+ )
316
+ events_to_emit.append(content_event)
317
+
318
+ # End the assistant message
319
+ assistant_end_event = TextMessageEndEvent(
320
+ type=EventType.TEXT_MESSAGE_END,
321
+ message_id=assistant_message_id,
322
+ )
323
+ events_to_emit.append(assistant_end_event)
324
+
325
+ # Now emit the tool call events with the assistant message as parent
294
326
  for tool in chunk.tools:
295
327
  if tool.tool_call_id is None or tool.tool_name is None:
296
328
  continue
297
329
 
298
- # Use the current text message ID from event buffer as parent
299
- parent_message_id = event_buffer.get_parent_message_id_for_tool_call()
300
- if not parent_message_id:
301
- parent_message_id = message_id # Fallback to the passed message_id
302
-
303
330
  start_event = ToolCallStartEvent(
304
331
  type=EventType.TOOL_CALL_START,
305
332
  tool_call_id=tool.tool_call_id,
306
333
  tool_call_name=tool.tool_name,
307
- parent_message_id=parent_message_id,
334
+ parent_message_id=assistant_message_id, # Use the assistant message as parent
308
335
  )
309
336
  events_to_emit.append(start_event)
310
337
 
@@ -28,14 +28,12 @@ def attach_routes(
28
28
  ) -> APIRouter:
29
29
  # Determine entity type for documentation
30
30
  entity_type = "agent" if agent else "team" if team else "workflow" if workflow else "unknown"
31
- entity_name = getattr(agent or team or workflow, "name", f"Unnamed {entity_type}")
32
31
 
33
32
  @router.post(
34
33
  "/events",
35
34
  operation_id=f"slack_events_{entity_type}",
36
- summary=f"Process Slack Events for {entity_type.title()}",
37
- description=f"Process incoming Slack events and route them to the configured {entity_type}: {entity_name}",
38
- tags=["Slack", f"Slack-{entity_type.title()}"],
35
+ name="slack_events",
36
+ description="Process incoming Slack events",
39
37
  response_model=SlackEventResponse,
40
38
  response_model_exclude_none=True,
41
39
  responses={