agno 2.1.9__py3-none-any.whl → 2.2.0__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 +2048 -1204
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +954 -0
- agno/db/async_postgres/async_postgres.py +232 -0
- agno/db/async_postgres/schemas.py +15 -0
- agno/db/async_postgres/utils.py +58 -0
- agno/db/base.py +83 -6
- agno/db/dynamo/dynamo.py +162 -0
- agno/db/dynamo/schemas.py +44 -0
- agno/db/dynamo/utils.py +59 -0
- agno/db/firestore/firestore.py +231 -0
- agno/db/firestore/schemas.py +10 -0
- agno/db/firestore/utils.py +96 -0
- agno/db/gcs_json/gcs_json_db.py +190 -0
- agno/db/gcs_json/utils.py +58 -0
- agno/db/in_memory/in_memory_db.py +118 -0
- agno/db/in_memory/utils.py +58 -0
- agno/db/json/json_db.py +129 -0
- agno/db/json/utils.py +58 -0
- agno/db/mongo/mongo.py +222 -0
- agno/db/mongo/schemas.py +10 -0
- agno/db/mongo/utils.py +59 -0
- agno/db/mysql/mysql.py +232 -1
- agno/db/mysql/schemas.py +14 -0
- agno/db/mysql/utils.py +58 -0
- agno/db/postgres/postgres.py +242 -0
- agno/db/postgres/schemas.py +15 -0
- agno/db/postgres/utils.py +58 -0
- agno/db/redis/redis.py +181 -0
- agno/db/redis/schemas.py +14 -0
- agno/db/redis/utils.py +58 -0
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/singlestore/schemas.py +14 -0
- agno/db/singlestore/singlestore.py +231 -0
- agno/db/singlestore/utils.py +58 -0
- agno/db/sqlite/schemas.py +14 -0
- agno/db/sqlite/sqlite.py +274 -7
- agno/db/sqlite/utils.py +62 -0
- agno/db/surrealdb/models.py +51 -1
- agno/db/surrealdb/surrealdb.py +154 -0
- agno/db/surrealdb/utils.py +61 -1
- agno/knowledge/reader/field_labeled_csv_reader.py +0 -2
- agno/memory/manager.py +28 -11
- agno/models/anthropic/claude.py +2 -2
- agno/models/message.py +0 -1
- agno/models/ollama/chat.py +7 -2
- agno/os/app.py +29 -7
- agno/os/interfaces/a2a/router.py +2 -2
- agno/os/interfaces/agui/router.py +2 -2
- agno/os/router.py +7 -7
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/health.py +6 -2
- agno/os/routers/knowledge/schemas.py +49 -47
- agno/os/routers/memory/schemas.py +16 -16
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +382 -7
- agno/os/schema.py +254 -231
- agno/os/utils.py +1 -1
- agno/run/agent.py +49 -1
- agno/run/team.py +43 -0
- agno/session/summary.py +45 -13
- agno/session/team.py +90 -5
- agno/team/team.py +1118 -857
- agno/tools/gmail.py +59 -14
- agno/utils/agent.py +372 -0
- agno/utils/events.py +144 -2
- agno/utils/print_response/agent.py +10 -6
- agno/utils/print_response/team.py +6 -4
- agno/utils/print_response/workflow.py +7 -5
- agno/utils/team.py +9 -8
- agno/workflow/condition.py +17 -9
- agno/workflow/loop.py +18 -10
- agno/workflow/parallel.py +14 -6
- agno/workflow/router.py +17 -9
- agno/workflow/step.py +14 -6
- agno/workflow/steps.py +14 -6
- agno/workflow/workflow.py +245 -122
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/METADATA +60 -23
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/RECORD +83 -79
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/WHEEL +0 -0
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/top_level.txt +0 -0
agno/db/surrealdb/surrealdb.py
CHANGED
|
@@ -7,6 +7,7 @@ from agno.db.postgres.utils import (
|
|
|
7
7
|
get_dates_to_calculate_metrics_for,
|
|
8
8
|
)
|
|
9
9
|
from agno.db.schemas import UserMemory
|
|
10
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
10
11
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
11
12
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
12
13
|
from agno.db.surrealdb import utils
|
|
@@ -19,6 +20,7 @@ from agno.db.surrealdb.metrics import (
|
|
|
19
20
|
)
|
|
20
21
|
from agno.db.surrealdb.models import (
|
|
21
22
|
TableType,
|
|
23
|
+
deserialize_cultural_knowledge,
|
|
22
24
|
deserialize_eval_run_record,
|
|
23
25
|
deserialize_knowledge_row,
|
|
24
26
|
deserialize_session,
|
|
@@ -30,6 +32,7 @@ from agno.db.surrealdb.models import (
|
|
|
30
32
|
desurrealize_user_memory,
|
|
31
33
|
get_schema,
|
|
32
34
|
get_session_type,
|
|
35
|
+
serialize_cultural_knowledge,
|
|
33
36
|
serialize_eval_run_record,
|
|
34
37
|
serialize_knowledge_row,
|
|
35
38
|
serialize_session,
|
|
@@ -60,6 +63,7 @@ class SurrealDb(BaseDb):
|
|
|
60
63
|
metrics_table: Optional[str] = None,
|
|
61
64
|
eval_table: Optional[str] = None,
|
|
62
65
|
knowledge_table: Optional[str] = None,
|
|
66
|
+
culture_table: Optional[str] = None,
|
|
63
67
|
id: Optional[str] = None,
|
|
64
68
|
):
|
|
65
69
|
"""
|
|
@@ -80,6 +84,7 @@ class SurrealDb(BaseDb):
|
|
|
80
84
|
metrics_table=metrics_table,
|
|
81
85
|
eval_table=eval_table,
|
|
82
86
|
knowledge_table=knowledge_table,
|
|
87
|
+
culture_table=culture_table,
|
|
83
88
|
)
|
|
84
89
|
self._client = client
|
|
85
90
|
self._db_url = db_url
|
|
@@ -101,6 +106,7 @@ class SurrealDb(BaseDb):
|
|
|
101
106
|
def table_names(self) -> dict[TableType, str]:
|
|
102
107
|
return {
|
|
103
108
|
"agents": self._agents_table_name,
|
|
109
|
+
"culture": self.culture_table_name,
|
|
104
110
|
"evals": self.eval_table_name,
|
|
105
111
|
"knowledge": self.knowledge_table_name,
|
|
106
112
|
"memories": self.memory_table_name,
|
|
@@ -127,6 +133,8 @@ class SurrealDb(BaseDb):
|
|
|
127
133
|
table_name = self.memory_table_name
|
|
128
134
|
elif table_type == "knowledge":
|
|
129
135
|
table_name = self.knowledge_table_name
|
|
136
|
+
elif table_type == "culture":
|
|
137
|
+
table_name = self.culture_table_name
|
|
130
138
|
elif table_type == "users":
|
|
131
139
|
table_name = self._users_table_name
|
|
132
140
|
elif table_type == "agents":
|
|
@@ -463,6 +471,152 @@ class SurrealDb(BaseDb):
|
|
|
463
471
|
table = self._get_table("memories")
|
|
464
472
|
_ = self.client.delete(table)
|
|
465
473
|
|
|
474
|
+
# -- Cultural Knowledge methods --
|
|
475
|
+
def clear_cultural_knowledge(self) -> None:
|
|
476
|
+
"""Delete all cultural knowledge from the database.
|
|
477
|
+
|
|
478
|
+
Raises:
|
|
479
|
+
Exception: If an error occurs during deletion.
|
|
480
|
+
"""
|
|
481
|
+
table = self._get_table("culture")
|
|
482
|
+
_ = self.client.delete(table)
|
|
483
|
+
|
|
484
|
+
def delete_cultural_knowledge(self, id: str) -> None:
|
|
485
|
+
"""Delete cultural knowledge by ID.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
id (str): The ID of the cultural knowledge to delete.
|
|
489
|
+
|
|
490
|
+
Raises:
|
|
491
|
+
Exception: If an error occurs during deletion.
|
|
492
|
+
"""
|
|
493
|
+
table = self._get_table("culture")
|
|
494
|
+
rec_id = RecordID(table, id)
|
|
495
|
+
self.client.delete(rec_id)
|
|
496
|
+
|
|
497
|
+
def get_cultural_knowledge(
|
|
498
|
+
self, id: str, deserialize: Optional[bool] = True
|
|
499
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
500
|
+
"""Get cultural knowledge by ID.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
id (str): The ID of the cultural knowledge to retrieve.
|
|
504
|
+
deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge object. Defaults to True.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge if found, None otherwise.
|
|
508
|
+
|
|
509
|
+
Raises:
|
|
510
|
+
Exception: If an error occurs during retrieval.
|
|
511
|
+
"""
|
|
512
|
+
table = self._get_table("culture")
|
|
513
|
+
rec_id = RecordID(table, id)
|
|
514
|
+
result = self.client.select(rec_id)
|
|
515
|
+
|
|
516
|
+
if result is None:
|
|
517
|
+
return None
|
|
518
|
+
|
|
519
|
+
if not deserialize:
|
|
520
|
+
return result # type: ignore
|
|
521
|
+
|
|
522
|
+
return deserialize_cultural_knowledge(result) # type: ignore
|
|
523
|
+
|
|
524
|
+
def get_all_cultural_knowledge(
|
|
525
|
+
self,
|
|
526
|
+
agent_id: Optional[str] = None,
|
|
527
|
+
team_id: Optional[str] = None,
|
|
528
|
+
name: Optional[str] = None,
|
|
529
|
+
limit: Optional[int] = None,
|
|
530
|
+
page: Optional[int] = None,
|
|
531
|
+
sort_by: Optional[str] = None,
|
|
532
|
+
sort_order: Optional[str] = None,
|
|
533
|
+
deserialize: Optional[bool] = True,
|
|
534
|
+
) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
535
|
+
"""Get all cultural knowledge with filtering and pagination.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
agent_id (Optional[str]): Filter by agent ID.
|
|
539
|
+
team_id (Optional[str]): Filter by team ID.
|
|
540
|
+
name (Optional[str]): Filter by name (case-insensitive partial match).
|
|
541
|
+
limit (Optional[int]): Maximum number of results to return.
|
|
542
|
+
page (Optional[int]): Page number for pagination.
|
|
543
|
+
sort_by (Optional[str]): Field to sort by.
|
|
544
|
+
sort_order (Optional[str]): Sort order ('asc' or 'desc').
|
|
545
|
+
deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge objects. Defaults to True.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
549
|
+
- When deserialize=True: List of CulturalKnowledge objects
|
|
550
|
+
- When deserialize=False: Tuple with list of dictionaries and total count
|
|
551
|
+
|
|
552
|
+
Raises:
|
|
553
|
+
Exception: If an error occurs during retrieval.
|
|
554
|
+
"""
|
|
555
|
+
table = self._get_table("culture")
|
|
556
|
+
|
|
557
|
+
# Build where clauses
|
|
558
|
+
where_clauses: List[WhereClause] = []
|
|
559
|
+
if agent_id is not None:
|
|
560
|
+
agent_rec_id = RecordID(self._get_table("agents"), agent_id)
|
|
561
|
+
where_clauses.append(("agent", "=", agent_rec_id)) # type: ignore
|
|
562
|
+
if team_id is not None:
|
|
563
|
+
team_rec_id = RecordID(self._get_table("teams"), team_id)
|
|
564
|
+
where_clauses.append(("team", "=", team_rec_id)) # type: ignore
|
|
565
|
+
if name is not None:
|
|
566
|
+
where_clauses.append(("string::lowercase(name)", "CONTAINS", name.lower())) # type: ignore
|
|
567
|
+
|
|
568
|
+
# Build query for total count
|
|
569
|
+
count_query = COUNT_QUERY.format(
|
|
570
|
+
table=table,
|
|
571
|
+
where=""
|
|
572
|
+
if not where_clauses
|
|
573
|
+
else f"WHERE {' AND '.join(f'{w[0]} {w[1]} ${chr(97 + i)}' for i, w in enumerate(where_clauses))}", # type: ignore
|
|
574
|
+
)
|
|
575
|
+
params = {chr(97 + i): w[2] for i, w in enumerate(where_clauses)} # type: ignore
|
|
576
|
+
total_count = self._query_one(count_query, params, int) or 0
|
|
577
|
+
|
|
578
|
+
# Build main query
|
|
579
|
+
order_limit = order_limit_start(sort_by, sort_order, limit, page)
|
|
580
|
+
query = f"SELECT * FROM {table}"
|
|
581
|
+
if where_clauses:
|
|
582
|
+
query += f" WHERE {' AND '.join(f'{w[0]} {w[1]} ${chr(97 + i)}' for i, w in enumerate(where_clauses))}" # type: ignore
|
|
583
|
+
query += order_limit
|
|
584
|
+
|
|
585
|
+
results = self._query(query, params, list) or []
|
|
586
|
+
|
|
587
|
+
if not deserialize:
|
|
588
|
+
return results, total_count # type: ignore
|
|
589
|
+
|
|
590
|
+
return [deserialize_cultural_knowledge(r) for r in results] # type: ignore
|
|
591
|
+
|
|
592
|
+
def upsert_cultural_knowledge(
|
|
593
|
+
self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
|
|
594
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
595
|
+
"""Upsert cultural knowledge in SurrealDB.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
|
|
599
|
+
deserialize (Optional[bool]): Whether to deserialize the result. Defaults to True.
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The upserted cultural knowledge.
|
|
603
|
+
|
|
604
|
+
Raises:
|
|
605
|
+
Exception: If an error occurs during upsert.
|
|
606
|
+
"""
|
|
607
|
+
table = self._get_table("culture", create_table_if_not_found=True)
|
|
608
|
+
serialized = serialize_cultural_knowledge(cultural_knowledge, table)
|
|
609
|
+
|
|
610
|
+
result = self.client.upsert(serialized["id"], serialized)
|
|
611
|
+
|
|
612
|
+
if result is None:
|
|
613
|
+
return None
|
|
614
|
+
|
|
615
|
+
if not deserialize:
|
|
616
|
+
return result # type: ignore
|
|
617
|
+
|
|
618
|
+
return deserialize_cultural_knowledge(result) # type: ignore
|
|
619
|
+
|
|
466
620
|
def delete_user_memory(self, memory_id: str, user_id: Optional[str] = None) -> None:
|
|
467
621
|
"""Delete a user memory from the database.
|
|
468
622
|
|
agno/db/surrealdb/utils.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
-
from typing import Any, Optional, Sequence, TypeVar, Union, cast
|
|
2
|
+
from typing import Any, Dict, Optional, Sequence, TypeVar, Union, cast
|
|
3
3
|
|
|
4
4
|
from surrealdb import BlockingHttpSurrealConnection, BlockingWsSurrealConnection, Surreal
|
|
5
5
|
|
|
6
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
6
7
|
from agno.utils.log import logger
|
|
7
8
|
|
|
8
9
|
RecordType = TypeVar("RecordType")
|
|
@@ -85,3 +86,62 @@ def query_one(
|
|
|
85
86
|
raise ValueError(f"Expected single record, got {len(response)} records: {response}")
|
|
86
87
|
else:
|
|
87
88
|
raise ValueError(f"Unexpected response type: {type(response)}")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# -- Cultural Knowledge util methods --
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
|
|
95
|
+
"""Serialize a CulturalKnowledge object for database storage.
|
|
96
|
+
|
|
97
|
+
Converts the model's separate content, categories, and notes fields
|
|
98
|
+
into a single dict for the database content field.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Dict[str, Any]: A dictionary with content, categories, and notes.
|
|
105
|
+
"""
|
|
106
|
+
content_dict: Dict[str, Any] = {}
|
|
107
|
+
if cultural_knowledge.content is not None:
|
|
108
|
+
content_dict["content"] = cultural_knowledge.content
|
|
109
|
+
if cultural_knowledge.categories is not None:
|
|
110
|
+
content_dict["categories"] = cultural_knowledge.categories
|
|
111
|
+
if cultural_knowledge.notes is not None:
|
|
112
|
+
content_dict["notes"] = cultural_knowledge.notes
|
|
113
|
+
|
|
114
|
+
return content_dict if content_dict else {}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
|
|
118
|
+
"""Deserialize a database row to a CulturalKnowledge object.
|
|
119
|
+
|
|
120
|
+
The database stores content as a dict containing content, categories, and notes.
|
|
121
|
+
This method extracts those fields and converts them back to the model format.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
db_row (Dict[str, Any]): The database row as a dictionary.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
CulturalKnowledge: The cultural knowledge object.
|
|
128
|
+
"""
|
|
129
|
+
# Extract content, categories, and notes from the content field
|
|
130
|
+
content_json = db_row.get("content", {}) or {}
|
|
131
|
+
|
|
132
|
+
return CulturalKnowledge.from_dict(
|
|
133
|
+
{
|
|
134
|
+
"id": db_row.get("id"),
|
|
135
|
+
"name": db_row.get("name"),
|
|
136
|
+
"summary": db_row.get("summary"),
|
|
137
|
+
"content": content_json.get("content"),
|
|
138
|
+
"categories": content_json.get("categories"),
|
|
139
|
+
"notes": content_json.get("notes"),
|
|
140
|
+
"metadata": db_row.get("metadata"),
|
|
141
|
+
"input": db_row.get("input"),
|
|
142
|
+
"created_at": db_row.get("created_at"),
|
|
143
|
+
"updated_at": db_row.get("updated_at"),
|
|
144
|
+
"agent_id": db_row.get("agent_id"),
|
|
145
|
+
"team_id": db_row.get("team_id"),
|
|
146
|
+
}
|
|
147
|
+
)
|
|
@@ -33,8 +33,6 @@ class FieldLabeledCSVReader(Reader):
|
|
|
33
33
|
self.format_headers = format_headers
|
|
34
34
|
self.skip_empty_fields = skip_empty_fields
|
|
35
35
|
|
|
36
|
-
logger.info(f"FieldLabeledCSVReader initialized - chunk_title: {chunk_title}, field_names: {self.field_names}")
|
|
37
|
-
|
|
38
36
|
@classmethod
|
|
39
37
|
def get_supported_chunking_strategies(cls) -> List[ChunkingStrategyType]:
|
|
40
38
|
"""Chunking is not supported - each row is already a logical document unit."""
|
agno/memory/manager.py
CHANGED
|
@@ -12,7 +12,13 @@ from agno.db.schemas import UserMemory
|
|
|
12
12
|
from agno.models.base import Model
|
|
13
13
|
from agno.models.message import Message
|
|
14
14
|
from agno.tools.function import Function
|
|
15
|
-
from agno.utils.log import
|
|
15
|
+
from agno.utils.log import (
|
|
16
|
+
log_debug,
|
|
17
|
+
log_error,
|
|
18
|
+
log_warning,
|
|
19
|
+
set_log_level_to_debug,
|
|
20
|
+
set_log_level_to_info,
|
|
21
|
+
)
|
|
16
22
|
from agno.utils.prompts import get_json_output_prompt
|
|
17
23
|
from agno.utils.string import parse_response_model_str
|
|
18
24
|
|
|
@@ -21,7 +27,8 @@ class MemorySearchResponse(BaseModel):
|
|
|
21
27
|
"""Model for Memory Search Response."""
|
|
22
28
|
|
|
23
29
|
memory_ids: List[str] = Field(
|
|
24
|
-
...,
|
|
30
|
+
...,
|
|
31
|
+
description="The IDs of the memories that are most semantically similar to the query.",
|
|
25
32
|
)
|
|
26
33
|
|
|
27
34
|
|
|
@@ -32,11 +39,11 @@ class MemoryManager:
|
|
|
32
39
|
# Model used for memory management
|
|
33
40
|
model: Optional[Model] = None
|
|
34
41
|
|
|
35
|
-
# Provide the system message for the manager as a string. If not provided,
|
|
42
|
+
# Provide the system message for the manager as a string. If not provided, the default system message will be used.
|
|
36
43
|
system_message: Optional[str] = None
|
|
37
|
-
# Provide the memory capture instructions for the manager as a string. If not provided,
|
|
44
|
+
# Provide the memory capture instructions for the manager as a string. If not provided, the default memory capture instructions will be used.
|
|
38
45
|
memory_capture_instructions: Optional[str] = None
|
|
39
|
-
# Additional instructions for the manager. These instructions are appended to the default system
|
|
46
|
+
# Additional instructions for the manager. These instructions are appended to the default system message.
|
|
40
47
|
additional_instructions: Optional[str] = None
|
|
41
48
|
|
|
42
49
|
# Whether memories were created in the last run
|
|
@@ -731,14 +738,16 @@ class MemoryManager:
|
|
|
731
738
|
if self.system_message is not None:
|
|
732
739
|
return Message(role="system", content=self.system_message)
|
|
733
740
|
|
|
734
|
-
memory_capture_instructions = self.memory_capture_instructions or dedent(
|
|
741
|
+
memory_capture_instructions = self.memory_capture_instructions or dedent(
|
|
742
|
+
"""\
|
|
735
743
|
Memories should capture personal information about the user that is relevant to the current conversation, such as:
|
|
736
744
|
- Personal facts: name, age, occupation, location, interests, and preferences
|
|
737
745
|
- Opinions and preferences: what the user likes, dislikes, enjoys, or finds frustrating
|
|
738
746
|
- Significant life events or experiences shared by the user
|
|
739
747
|
- Important context about the user's current situation, challenges, or goals
|
|
740
748
|
- Any other details that offer meaningful insight into the user's personality, perspective, or needs
|
|
741
|
-
"""
|
|
749
|
+
"""
|
|
750
|
+
)
|
|
742
751
|
|
|
743
752
|
# -*- Return a system message for the memory manager
|
|
744
753
|
system_prompt_lines = [
|
|
@@ -852,7 +861,9 @@ class MemoryManager:
|
|
|
852
861
|
|
|
853
862
|
# Generate a response from the Model (includes running function calls)
|
|
854
863
|
response = model_copy.response(
|
|
855
|
-
messages=messages_for_model,
|
|
864
|
+
messages=messages_for_model,
|
|
865
|
+
tools=self._tools_for_model,
|
|
866
|
+
functions=self._functions_for_model,
|
|
856
867
|
)
|
|
857
868
|
|
|
858
869
|
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
@@ -928,7 +939,9 @@ class MemoryManager:
|
|
|
928
939
|
|
|
929
940
|
# Generate a response from the Model (includes running function calls)
|
|
930
941
|
response = await model_copy.aresponse(
|
|
931
|
-
messages=messages_for_model,
|
|
942
|
+
messages=messages_for_model,
|
|
943
|
+
tools=self._tools_for_model,
|
|
944
|
+
functions=self._functions_for_model,
|
|
932
945
|
)
|
|
933
946
|
|
|
934
947
|
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
@@ -983,7 +996,9 @@ class MemoryManager:
|
|
|
983
996
|
|
|
984
997
|
# Generate a response from the Model (includes running function calls)
|
|
985
998
|
response = model_copy.response(
|
|
986
|
-
messages=messages_for_model,
|
|
999
|
+
messages=messages_for_model,
|
|
1000
|
+
tools=self._tools_for_model,
|
|
1001
|
+
functions=self._functions_for_model,
|
|
987
1002
|
)
|
|
988
1003
|
|
|
989
1004
|
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
@@ -1051,7 +1066,9 @@ class MemoryManager:
|
|
|
1051
1066
|
|
|
1052
1067
|
# Generate a response from the Model (includes running function calls)
|
|
1053
1068
|
response = await model_copy.aresponse(
|
|
1054
|
-
messages=messages_for_model,
|
|
1069
|
+
messages=messages_for_model,
|
|
1070
|
+
tools=self._tools_for_model,
|
|
1071
|
+
functions=self._functions_for_model,
|
|
1055
1072
|
)
|
|
1056
1073
|
|
|
1057
1074
|
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
agno/models/anthropic/claude.py
CHANGED
|
@@ -75,7 +75,7 @@ class Claude(Model):
|
|
|
75
75
|
provider: str = "Anthropic"
|
|
76
76
|
|
|
77
77
|
# Request parameters
|
|
78
|
-
max_tokens: Optional[int] =
|
|
78
|
+
max_tokens: Optional[int] = 8192
|
|
79
79
|
thinking: Optional[Dict[str, Any]] = None
|
|
80
80
|
temperature: Optional[float] = None
|
|
81
81
|
stop_sequences: Optional[List[str]] = None
|
|
@@ -656,7 +656,7 @@ class Claude(Model):
|
|
|
656
656
|
|
|
657
657
|
# Anthropic-specific additional fields
|
|
658
658
|
if response_usage.server_tool_use:
|
|
659
|
-
metrics.provider_metrics = {"server_tool_use": response_usage.server_tool_use}
|
|
659
|
+
metrics.provider_metrics = {"server_tool_use": response_usage.server_tool_use.model_dump()}
|
|
660
660
|
if isinstance(response_usage, Usage):
|
|
661
661
|
if response_usage.service_tier:
|
|
662
662
|
metrics.provider_metrics = metrics.provider_metrics or {}
|
agno/models/message.py
CHANGED
agno/models/ollama/chat.py
CHANGED
|
@@ -429,8 +429,13 @@ class Ollama(Model):
|
|
|
429
429
|
"""
|
|
430
430
|
metrics = Metrics()
|
|
431
431
|
|
|
432
|
-
|
|
433
|
-
|
|
432
|
+
# Safely handle None values from Ollama Cloud responses
|
|
433
|
+
input_tokens = response.get("prompt_eval_count")
|
|
434
|
+
output_tokens = response.get("eval_count")
|
|
435
|
+
|
|
436
|
+
# Default to 0 if None
|
|
437
|
+
metrics.input_tokens = input_tokens if input_tokens is not None else 0
|
|
438
|
+
metrics.output_tokens = output_tokens if output_tokens is not None else 0
|
|
434
439
|
metrics.total_tokens = metrics.input_tokens + metrics.output_tokens
|
|
435
440
|
|
|
436
441
|
return metrics
|
agno/os/app.py
CHANGED
|
@@ -13,6 +13,7 @@ from starlette.requests import Request
|
|
|
13
13
|
|
|
14
14
|
from agno.agent.agent import Agent
|
|
15
15
|
from agno.db.base import AsyncBaseDb, BaseDb
|
|
16
|
+
from agno.knowledge.knowledge import Knowledge
|
|
16
17
|
from agno.os.config import (
|
|
17
18
|
AgentOSConfig,
|
|
18
19
|
DatabaseConfig,
|
|
@@ -98,6 +99,7 @@ class AgentOS:
|
|
|
98
99
|
agents: Optional[List[Agent]] = None,
|
|
99
100
|
teams: Optional[List[Team]] = None,
|
|
100
101
|
workflows: Optional[List[Workflow]] = None,
|
|
102
|
+
knowledge: Optional[List[Knowledge]] = None,
|
|
101
103
|
interfaces: Optional[List[BaseInterface]] = None,
|
|
102
104
|
a2a_interface: bool = False,
|
|
103
105
|
config: Optional[Union[str, AgentOSConfig]] = None,
|
|
@@ -122,6 +124,7 @@ class AgentOS:
|
|
|
122
124
|
agents: List of agents to include in the OS
|
|
123
125
|
teams: List of teams to include in the OS
|
|
124
126
|
workflows: List of workflows to include in the OS
|
|
127
|
+
knowledge: List of knowledge bases to include in the OS
|
|
125
128
|
interfaces: List of interfaces to include in the OS
|
|
126
129
|
a2a_interface: Whether to expose the OS agents and teams in an A2A server
|
|
127
130
|
config: Configuration file path or AgentOSConfig instance
|
|
@@ -133,8 +136,8 @@ class AgentOS:
|
|
|
133
136
|
telemetry: Whether to enable telemetry
|
|
134
137
|
|
|
135
138
|
"""
|
|
136
|
-
if not agents and not workflows and not teams:
|
|
137
|
-
raise ValueError("Either agents, teams or
|
|
139
|
+
if not agents and not workflows and not teams and not knowledge:
|
|
140
|
+
raise ValueError("Either agents, teams, workflows or knowledge bases must be provided.")
|
|
138
141
|
|
|
139
142
|
self.config = load_yaml_config(config) if isinstance(config, str) else config
|
|
140
143
|
|
|
@@ -143,7 +146,7 @@ class AgentOS:
|
|
|
143
146
|
self.teams: Optional[List[Team]] = teams
|
|
144
147
|
self.interfaces = interfaces or []
|
|
145
148
|
self.a2a_interface = a2a_interface
|
|
146
|
-
|
|
149
|
+
self.knowledge = knowledge
|
|
147
150
|
self.settings: AgnoAPISettings = settings or AgnoAPISettings()
|
|
148
151
|
|
|
149
152
|
self._app_set = False
|
|
@@ -309,7 +312,7 @@ class AgentOS:
|
|
|
309
312
|
async with self._mcp_app.lifespan(app): # type: ignore
|
|
310
313
|
yield
|
|
311
314
|
|
|
312
|
-
final_lifespan = combined_lifespan
|
|
315
|
+
final_lifespan = combined_lifespan # type: ignore
|
|
313
316
|
|
|
314
317
|
fastapi_app = self._make_app(lifespan=final_lifespan)
|
|
315
318
|
else:
|
|
@@ -480,6 +483,10 @@ class AgentOS:
|
|
|
480
483
|
if workflow.db:
|
|
481
484
|
self._register_db_with_validation(dbs, workflow.db)
|
|
482
485
|
|
|
486
|
+
for knowledge_base in self.knowledge or []:
|
|
487
|
+
if knowledge_base.contents_db:
|
|
488
|
+
self._register_db_with_validation(knowledge_dbs, knowledge_base.contents_db)
|
|
489
|
+
|
|
483
490
|
for interface in self.interfaces or []:
|
|
484
491
|
if interface.agent and interface.agent.db:
|
|
485
492
|
self._register_db_with_validation(dbs, interface.agent.db)
|
|
@@ -534,14 +541,29 @@ class AgentOS:
|
|
|
534
541
|
|
|
535
542
|
def _auto_discover_knowledge_instances(self) -> None:
|
|
536
543
|
"""Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
|
|
537
|
-
|
|
544
|
+
seen_ids = set()
|
|
545
|
+
knowledge_instances: List[Knowledge] = []
|
|
546
|
+
|
|
547
|
+
def _add_knowledge_if_not_duplicate(knowledge: "Knowledge") -> None:
|
|
548
|
+
"""Add knowledge instance if it's not already in the list (by object identity or db_id)."""
|
|
549
|
+
# Use database ID if available, otherwise use object ID as fallback
|
|
550
|
+
if not knowledge.contents_db:
|
|
551
|
+
return
|
|
552
|
+
if knowledge.contents_db.id in seen_ids:
|
|
553
|
+
return
|
|
554
|
+
seen_ids.add(knowledge.contents_db.id)
|
|
555
|
+
knowledge_instances.append(knowledge)
|
|
556
|
+
|
|
538
557
|
for agent in self.agents or []:
|
|
539
558
|
if agent.knowledge:
|
|
540
|
-
|
|
559
|
+
_add_knowledge_if_not_duplicate(agent.knowledge)
|
|
541
560
|
|
|
542
561
|
for team in self.teams or []:
|
|
543
562
|
if team.knowledge:
|
|
544
|
-
|
|
563
|
+
_add_knowledge_if_not_duplicate(team.knowledge)
|
|
564
|
+
|
|
565
|
+
for knowledge_base in self.knowledge or []:
|
|
566
|
+
_add_knowledge_if_not_duplicate(knowledge_base)
|
|
545
567
|
|
|
546
568
|
self.knowledge_instances = knowledge_instances
|
|
547
569
|
|
agno/os/interfaces/a2a/router.py
CHANGED
|
@@ -221,7 +221,7 @@ def attach_routes(
|
|
|
221
221
|
session_id=context_id,
|
|
222
222
|
user_id=user_id,
|
|
223
223
|
stream=True,
|
|
224
|
-
|
|
224
|
+
stream_events=True,
|
|
225
225
|
**kwargs,
|
|
226
226
|
)
|
|
227
227
|
else:
|
|
@@ -234,7 +234,7 @@ def attach_routes(
|
|
|
234
234
|
session_id=context_id,
|
|
235
235
|
user_id=user_id,
|
|
236
236
|
stream=True,
|
|
237
|
-
|
|
237
|
+
stream_events=True,
|
|
238
238
|
**kwargs,
|
|
239
239
|
)
|
|
240
240
|
|
|
@@ -44,7 +44,7 @@ async def run_agent(agent: Agent, run_input: RunAgentInput) -> AsyncIterator[Bas
|
|
|
44
44
|
input=messages,
|
|
45
45
|
session_id=run_input.thread_id,
|
|
46
46
|
stream=True,
|
|
47
|
-
|
|
47
|
+
stream_events=True,
|
|
48
48
|
user_id=user_id,
|
|
49
49
|
)
|
|
50
50
|
|
|
@@ -80,7 +80,7 @@ async def run_team(team: Team, input: RunAgentInput) -> AsyncIterator[BaseEvent]
|
|
|
80
80
|
input=messages,
|
|
81
81
|
session_id=input.thread_id,
|
|
82
82
|
stream=True,
|
|
83
|
-
|
|
83
|
+
stream_steps=True,
|
|
84
84
|
user_id=user_id,
|
|
85
85
|
)
|
|
86
86
|
|
agno/os/router.py
CHANGED
|
@@ -250,7 +250,7 @@ async def agent_response_streamer(
|
|
|
250
250
|
videos=videos,
|
|
251
251
|
files=files,
|
|
252
252
|
stream=True,
|
|
253
|
-
|
|
253
|
+
stream_events=True,
|
|
254
254
|
**kwargs,
|
|
255
255
|
)
|
|
256
256
|
async for run_response_chunk in run_response:
|
|
@@ -287,7 +287,7 @@ async def agent_continue_response_streamer(
|
|
|
287
287
|
session_id=session_id,
|
|
288
288
|
user_id=user_id,
|
|
289
289
|
stream=True,
|
|
290
|
-
|
|
290
|
+
stream_events=True,
|
|
291
291
|
)
|
|
292
292
|
async for run_response_chunk in continue_response:
|
|
293
293
|
yield format_sse_event(run_response_chunk) # type: ignore
|
|
@@ -335,7 +335,7 @@ async def team_response_streamer(
|
|
|
335
335
|
videos=videos,
|
|
336
336
|
files=files,
|
|
337
337
|
stream=True,
|
|
338
|
-
|
|
338
|
+
stream_events=True,
|
|
339
339
|
**kwargs,
|
|
340
340
|
)
|
|
341
341
|
async for run_response_chunk in run_response:
|
|
@@ -389,12 +389,12 @@ async def handle_workflow_via_websocket(websocket: WebSocket, message: dict, os:
|
|
|
389
389
|
session_id = str(uuid4())
|
|
390
390
|
|
|
391
391
|
# Execute workflow in background with streaming
|
|
392
|
-
workflow_result = await workflow.arun(
|
|
392
|
+
workflow_result = await workflow.arun( # type: ignore
|
|
393
393
|
input=user_message,
|
|
394
394
|
session_id=session_id,
|
|
395
395
|
user_id=user_id,
|
|
396
396
|
stream=True,
|
|
397
|
-
|
|
397
|
+
stream_events=True,
|
|
398
398
|
background=True,
|
|
399
399
|
websocket=websocket,
|
|
400
400
|
)
|
|
@@ -435,12 +435,12 @@ async def workflow_response_streamer(
|
|
|
435
435
|
**kwargs: Any,
|
|
436
436
|
) -> AsyncGenerator:
|
|
437
437
|
try:
|
|
438
|
-
run_response =
|
|
438
|
+
run_response = workflow.arun(
|
|
439
439
|
input=input,
|
|
440
440
|
session_id=session_id,
|
|
441
441
|
user_id=user_id,
|
|
442
442
|
stream=True,
|
|
443
|
-
|
|
443
|
+
stream_events=True,
|
|
444
444
|
**kwargs,
|
|
445
445
|
)
|
|
446
446
|
|