agno 2.1.4__py3-none-any.whl → 2.1.6__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 +1775 -538
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/async_postgres/async_postgres.py +1668 -0
- agno/db/async_postgres/schemas.py +124 -0
- agno/db/async_postgres/utils.py +289 -0
- agno/db/base.py +237 -2
- agno/db/dynamo/dynamo.py +2 -2
- agno/db/firestore/firestore.py +2 -2
- agno/db/firestore/utils.py +4 -2
- agno/db/gcs_json/gcs_json_db.py +2 -2
- agno/db/in_memory/in_memory_db.py +2 -2
- agno/db/json/json_db.py +2 -2
- agno/db/migrations/v1_to_v2.py +43 -13
- agno/db/mongo/mongo.py +14 -6
- agno/db/mongo/utils.py +0 -4
- agno/db/mysql/mysql.py +23 -13
- agno/db/postgres/postgres.py +17 -6
- agno/db/redis/redis.py +2 -2
- agno/db/singlestore/singlestore.py +19 -10
- agno/db/sqlite/sqlite.py +22 -12
- agno/db/sqlite/utils.py +8 -3
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +259 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1193 -0
- agno/db/surrealdb/utils.py +87 -0
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +8 -2
- agno/knowledge/knowledge.py +260 -46
- agno/knowledge/reader/pdf_reader.py +4 -6
- agno/knowledge/reader/reader_factory.py +2 -3
- agno/memory/manager.py +254 -46
- agno/models/anthropic/claude.py +37 -0
- agno/os/app.py +8 -7
- agno/os/interfaces/a2a/router.py +3 -5
- agno/os/interfaces/agui/router.py +4 -1
- agno/os/interfaces/agui/utils.py +27 -6
- agno/os/interfaces/slack/router.py +2 -4
- agno/os/mcp.py +98 -41
- agno/os/router.py +23 -0
- agno/os/routers/evals/evals.py +52 -20
- agno/os/routers/evals/utils.py +14 -14
- agno/os/routers/knowledge/knowledge.py +130 -9
- agno/os/routers/knowledge/schemas.py +57 -0
- agno/os/routers/memory/memory.py +116 -44
- agno/os/routers/metrics/metrics.py +16 -6
- agno/os/routers/session/session.py +65 -22
- agno/os/schema.py +36 -0
- agno/os/utils.py +64 -11
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/session/workflow.py +3 -3
- agno/team/team.py +968 -179
- agno/tools/googlesheets.py +20 -5
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/scrapegraph.py +1 -1
- agno/utils/models/claude.py +3 -1
- agno/utils/streamlit.py +1 -1
- agno/vectordb/base.py +22 -1
- agno/vectordb/cassandra/cassandra.py +9 -0
- agno/vectordb/chroma/chromadb.py +26 -6
- agno/vectordb/clickhouse/clickhousedb.py +9 -1
- agno/vectordb/couchbase/couchbase.py +11 -0
- agno/vectordb/lancedb/lance_db.py +20 -0
- agno/vectordb/langchaindb/langchaindb.py +11 -0
- agno/vectordb/lightrag/lightrag.py +9 -0
- agno/vectordb/llamaindex/llamaindexdb.py +15 -1
- agno/vectordb/milvus/milvus.py +23 -0
- agno/vectordb/mongodb/mongodb.py +22 -0
- agno/vectordb/pgvector/pgvector.py +19 -0
- agno/vectordb/pineconedb/pineconedb.py +35 -4
- agno/vectordb/qdrant/qdrant.py +24 -0
- agno/vectordb/singlestore/singlestore.py +25 -17
- agno/vectordb/surrealdb/surrealdb.py +18 -2
- agno/vectordb/upstashdb/upstashdb.py +26 -1
- agno/vectordb/weaviate/weaviate.py +18 -0
- agno/workflow/condition.py +4 -0
- agno/workflow/loop.py +4 -0
- agno/workflow/parallel.py +4 -0
- agno/workflow/router.py +4 -0
- agno/workflow/step.py +30 -14
- agno/workflow/steps.py +4 -0
- agno/workflow/types.py +2 -2
- agno/workflow/workflow.py +328 -61
- {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/METADATA +100 -41
- {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/RECORD +95 -82
- {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/WHEEL +0 -0
- {agno-2.1.4.dist-info → agno-2.1.6.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.4.dist-info → agno-2.1.6.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
|
|
@@ -36,7 +36,7 @@ class MemoryManager:
|
|
|
36
36
|
system_message: Optional[str] = None
|
|
37
37
|
# Provide the memory capture instructions for the manager as a string. If not provided, a default prompt will be used.
|
|
38
38
|
memory_capture_instructions: Optional[str] = None
|
|
39
|
-
# Additional instructions for the manager
|
|
39
|
+
# Additional instructions for the manager. These instructions are appended to the default system prompt.
|
|
40
40
|
additional_instructions: Optional[str] = None
|
|
41
41
|
|
|
42
42
|
# Whether memories were created in the last run
|
|
@@ -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 =
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
|
|
@@ -673,17 +719,17 @@ class MemoryManager:
|
|
|
673
719
|
return Message(role="system", content=self.system_message)
|
|
674
720
|
|
|
675
721
|
memory_capture_instructions = self.memory_capture_instructions or dedent("""\
|
|
676
|
-
Memories should
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
722
|
+
Memories should capture personal information about the user that is relevant to the current conversation, such as:
|
|
723
|
+
- Personal facts: name, age, occupation, location, interests, and preferences
|
|
724
|
+
- Opinions and preferences: what the user likes, dislikes, enjoys, or finds frustrating
|
|
725
|
+
- Significant life events or experiences shared by the user
|
|
726
|
+
- Important context about the user's current situation, challenges, or goals
|
|
727
|
+
- Any other details that offer meaningful insight into the user's personality, perspective, or needs
|
|
682
728
|
""")
|
|
683
729
|
|
|
684
730
|
# -*- Return a system message for the memory manager
|
|
685
731
|
system_prompt_lines = [
|
|
686
|
-
"You are a
|
|
732
|
+
"You are a Memory Manager that is responsible for managing information and preferences about the user. "
|
|
687
733
|
"You will be provided with a criteria for memories to capture in the <memories_to_capture> section and a list of existing memories in the <existing_memories> section.",
|
|
688
734
|
"",
|
|
689
735
|
"## When to add or update memories",
|
|
@@ -712,16 +758,16 @@ class MemoryManager:
|
|
|
712
758
|
"",
|
|
713
759
|
"## Updating memories",
|
|
714
760
|
"You will also be provided with a list of existing memories in the <existing_memories> section. You can:",
|
|
715
|
-
"
|
|
761
|
+
" - Decide to make no changes.",
|
|
716
762
|
]
|
|
717
763
|
if enable_add_memory:
|
|
718
|
-
system_prompt_lines.append("
|
|
764
|
+
system_prompt_lines.append(" - Decide to add a new memory, using the `add_memory` tool.")
|
|
719
765
|
if enable_update_memory:
|
|
720
|
-
system_prompt_lines.append("
|
|
766
|
+
system_prompt_lines.append(" - Decide to update an existing memory, using the `update_memory` tool.")
|
|
721
767
|
if enable_delete_memory:
|
|
722
|
-
system_prompt_lines.append("
|
|
768
|
+
system_prompt_lines.append(" - Decide to delete an existing memory, using the `delete_memory` tool.")
|
|
723
769
|
if enable_clear_memory:
|
|
724
|
-
system_prompt_lines.append("
|
|
770
|
+
system_prompt_lines.append(" - Decide to clear all memories, using the `clear_memory` tool.")
|
|
725
771
|
|
|
726
772
|
system_prompt_lines += [
|
|
727
773
|
"You can call multiple tools in a single response if needed. ",
|
|
@@ -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
|
-
|
|
830
|
-
self.
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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
|
-
|
|
941
|
-
self.
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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
|
agno/models/anthropic/claude.py
CHANGED
|
@@ -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,
|
|
@@ -459,10 +459,10 @@ class AgentOS:
|
|
|
459
459
|
|
|
460
460
|
def _auto_discover_databases(self) -> None:
|
|
461
461
|
"""Auto-discover the databases used by all contextual agents, teams and workflows."""
|
|
462
|
-
from agno.db.base import BaseDb
|
|
462
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
463
463
|
|
|
464
|
-
dbs: Dict[str, BaseDb] = {}
|
|
465
|
-
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
|
|
466
466
|
|
|
467
467
|
for agent in self.agents or []:
|
|
468
468
|
if agent.db:
|
|
@@ -489,7 +489,7 @@ class AgentOS:
|
|
|
489
489
|
self.dbs = dbs
|
|
490
490
|
self.knowledge_dbs = knowledge_dbs
|
|
491
491
|
|
|
492
|
-
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:
|
|
493
493
|
"""Register a database in the contextual OS after validating it is not conflicting with registered databases"""
|
|
494
494
|
if db.id in registered_dbs:
|
|
495
495
|
existing_db = registered_dbs[db.id]
|
|
@@ -500,7 +500,7 @@ class AgentOS:
|
|
|
500
500
|
)
|
|
501
501
|
registered_dbs[db.id] = db
|
|
502
502
|
|
|
503
|
-
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:
|
|
504
504
|
"""
|
|
505
505
|
Return True if the two given database objects are compatible
|
|
506
506
|
Two database objects are compatible if they point to the same database with identical configuration.
|
|
@@ -649,6 +649,7 @@ class AgentOS:
|
|
|
649
649
|
port: int = 7777,
|
|
650
650
|
reload: bool = False,
|
|
651
651
|
workers: Optional[int] = None,
|
|
652
|
+
access_log: bool = False,
|
|
652
653
|
**kwargs,
|
|
653
654
|
):
|
|
654
655
|
import uvicorn
|
|
@@ -681,4 +682,4 @@ class AgentOS:
|
|
|
681
682
|
)
|
|
682
683
|
)
|
|
683
684
|
|
|
684
|
-
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)
|
agno/os/interfaces/a2a/router.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
agno/os/interfaces/agui/utils.py
CHANGED
|
@@ -297,20 +297,41 @@ def _create_completion_events(
|
|
|
297
297
|
|
|
298
298
|
# emit frontend tool calls, i.e. external_execution=True
|
|
299
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
|
|
300
326
|
for tool in chunk.tools:
|
|
301
327
|
if tool.tool_call_id is None or tool.tool_name is None:
|
|
302
328
|
continue
|
|
303
329
|
|
|
304
|
-
# Use the current text message ID from event buffer as parent
|
|
305
|
-
parent_message_id = event_buffer.get_parent_message_id_for_tool_call()
|
|
306
|
-
if not parent_message_id:
|
|
307
|
-
parent_message_id = message_id # Fallback to the passed message_id
|
|
308
|
-
|
|
309
330
|
start_event = ToolCallStartEvent(
|
|
310
331
|
type=EventType.TOOL_CALL_START,
|
|
311
332
|
tool_call_id=tool.tool_call_id,
|
|
312
333
|
tool_call_name=tool.tool_name,
|
|
313
|
-
parent_message_id=
|
|
334
|
+
parent_message_id=assistant_message_id, # Use the assistant message as parent
|
|
314
335
|
)
|
|
315
336
|
events_to_emit.append(start_event)
|
|
316
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
|
-
|
|
37
|
-
description=
|
|
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={
|