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.
Files changed (83) hide show
  1. agno/agent/agent.py +2048 -1204
  2. agno/culture/__init__.py +3 -0
  3. agno/culture/manager.py +954 -0
  4. agno/db/async_postgres/async_postgres.py +232 -0
  5. agno/db/async_postgres/schemas.py +15 -0
  6. agno/db/async_postgres/utils.py +58 -0
  7. agno/db/base.py +83 -6
  8. agno/db/dynamo/dynamo.py +162 -0
  9. agno/db/dynamo/schemas.py +44 -0
  10. agno/db/dynamo/utils.py +59 -0
  11. agno/db/firestore/firestore.py +231 -0
  12. agno/db/firestore/schemas.py +10 -0
  13. agno/db/firestore/utils.py +96 -0
  14. agno/db/gcs_json/gcs_json_db.py +190 -0
  15. agno/db/gcs_json/utils.py +58 -0
  16. agno/db/in_memory/in_memory_db.py +118 -0
  17. agno/db/in_memory/utils.py +58 -0
  18. agno/db/json/json_db.py +129 -0
  19. agno/db/json/utils.py +58 -0
  20. agno/db/mongo/mongo.py +222 -0
  21. agno/db/mongo/schemas.py +10 -0
  22. agno/db/mongo/utils.py +59 -0
  23. agno/db/mysql/mysql.py +232 -1
  24. agno/db/mysql/schemas.py +14 -0
  25. agno/db/mysql/utils.py +58 -0
  26. agno/db/postgres/postgres.py +242 -0
  27. agno/db/postgres/schemas.py +15 -0
  28. agno/db/postgres/utils.py +58 -0
  29. agno/db/redis/redis.py +181 -0
  30. agno/db/redis/schemas.py +14 -0
  31. agno/db/redis/utils.py +58 -0
  32. agno/db/schemas/__init__.py +2 -1
  33. agno/db/schemas/culture.py +120 -0
  34. agno/db/singlestore/schemas.py +14 -0
  35. agno/db/singlestore/singlestore.py +231 -0
  36. agno/db/singlestore/utils.py +58 -0
  37. agno/db/sqlite/schemas.py +14 -0
  38. agno/db/sqlite/sqlite.py +274 -7
  39. agno/db/sqlite/utils.py +62 -0
  40. agno/db/surrealdb/models.py +51 -1
  41. agno/db/surrealdb/surrealdb.py +154 -0
  42. agno/db/surrealdb/utils.py +61 -1
  43. agno/knowledge/reader/field_labeled_csv_reader.py +0 -2
  44. agno/memory/manager.py +28 -11
  45. agno/models/anthropic/claude.py +2 -2
  46. agno/models/message.py +0 -1
  47. agno/models/ollama/chat.py +7 -2
  48. agno/os/app.py +29 -7
  49. agno/os/interfaces/a2a/router.py +2 -2
  50. agno/os/interfaces/agui/router.py +2 -2
  51. agno/os/router.py +7 -7
  52. agno/os/routers/evals/schemas.py +31 -31
  53. agno/os/routers/health.py +6 -2
  54. agno/os/routers/knowledge/schemas.py +49 -47
  55. agno/os/routers/memory/schemas.py +16 -16
  56. agno/os/routers/metrics/schemas.py +16 -16
  57. agno/os/routers/session/session.py +382 -7
  58. agno/os/schema.py +254 -231
  59. agno/os/utils.py +1 -1
  60. agno/run/agent.py +49 -1
  61. agno/run/team.py +43 -0
  62. agno/session/summary.py +45 -13
  63. agno/session/team.py +90 -5
  64. agno/team/team.py +1118 -857
  65. agno/tools/gmail.py +59 -14
  66. agno/utils/agent.py +372 -0
  67. agno/utils/events.py +144 -2
  68. agno/utils/print_response/agent.py +10 -6
  69. agno/utils/print_response/team.py +6 -4
  70. agno/utils/print_response/workflow.py +7 -5
  71. agno/utils/team.py +9 -8
  72. agno/workflow/condition.py +17 -9
  73. agno/workflow/loop.py +18 -10
  74. agno/workflow/parallel.py +14 -6
  75. agno/workflow/router.py +17 -9
  76. agno/workflow/step.py +14 -6
  77. agno/workflow/steps.py +14 -6
  78. agno/workflow/workflow.py +245 -122
  79. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/METADATA +60 -23
  80. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/RECORD +83 -79
  81. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/WHEEL +0 -0
  82. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/licenses/LICENSE +0 -0
  83. {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,954 @@
1
+ from copy import deepcopy
2
+ from dataclasses import dataclass
3
+ from os import getenv
4
+ from textwrap import dedent
5
+ from typing import Any, Callable, Dict, List, Optional, Union, cast
6
+
7
+ from agno.db.base import AsyncBaseDb, BaseDb
8
+ from agno.db.schemas.culture import CulturalKnowledge
9
+ from agno.models.base import Model
10
+ from agno.models.message import Message
11
+ from agno.tools.function import Function
12
+ from agno.utils.log import (
13
+ log_debug,
14
+ log_error,
15
+ log_warning,
16
+ set_log_level_to_debug,
17
+ set_log_level_to_info,
18
+ )
19
+
20
+
21
+ @dataclass
22
+ class CultureManager:
23
+ """Culture Manager
24
+
25
+ Notice: Culture is an experimental feature and is subject to change.
26
+ """
27
+
28
+ # Model used for culture management
29
+ model: Optional[Model] = None
30
+
31
+ # Provide the system message for the manager as a string. If not provided, the default system message will be used.
32
+ system_message: Optional[str] = None
33
+ # Provide the cultural knowledge capture instructions for the manager as a string. If not provided, the default cultural knowledge capture instructions will be used.
34
+ culture_capture_instructions: Optional[str] = None
35
+ # Additional instructions for the manager. These instructions are appended to the default system message.
36
+ additional_instructions: Optional[str] = None
37
+
38
+ # The database to store cultural knowledge
39
+ db: Optional[Union[AsyncBaseDb, BaseDb]] = None
40
+
41
+ # ----- Db tools ---------
42
+ # If the Culture Manager can add cultural knowledge
43
+ add_knowledge: bool = True
44
+ # If the Culture Manager can update cultural knowledge
45
+ update_knowledge: bool = True
46
+ # If the Culture Manager can delete cultural knowledge
47
+ delete_knowledge: bool = True
48
+ # If the Culture Manager can clear cultural knowledge
49
+ clear_knowledge: bool = True
50
+
51
+ # ----- Internal settings ---------
52
+ # Whether cultural knowledge were updated in the last run of the CultureManager
53
+ knowledge_updated: bool = False
54
+ debug_mode: bool = False
55
+
56
+ def __init__(
57
+ self,
58
+ model: Optional[Model] = None,
59
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
60
+ system_message: Optional[str] = None,
61
+ culture_capture_instructions: Optional[str] = None,
62
+ additional_instructions: Optional[str] = None,
63
+ add_knowledge: bool = True,
64
+ update_knowledge: bool = True,
65
+ delete_knowledge: bool = False,
66
+ clear_knowledge: bool = True,
67
+ debug_mode: bool = False,
68
+ ):
69
+ self.model = model
70
+ if self.model is not None and isinstance(self.model, str):
71
+ raise ValueError("Model must be a Model object, not a string")
72
+ self.db = db
73
+ self.system_message = system_message
74
+ self.culture_capture_instructions = culture_capture_instructions
75
+ self.additional_instructions = additional_instructions
76
+ self.add_knowledge = add_knowledge
77
+ self.update_knowledge = update_knowledge
78
+ self.delete_knowledge = delete_knowledge
79
+ self.clear_knowledge = clear_knowledge
80
+ self.debug_mode = debug_mode
81
+ self._tools_for_model: Optional[List[Dict[str, Any]]] = None
82
+ self._functions_for_model: Optional[Dict[str, Function]] = None
83
+
84
+ def get_model(self) -> Model:
85
+ if self.model is None:
86
+ try:
87
+ from agno.models.openai import OpenAIChat
88
+ except ModuleNotFoundError as e:
89
+ log_error(e)
90
+ log_error(
91
+ "Agno uses `openai` as the default model provider. Please provide a `model` or install `openai`."
92
+ )
93
+ exit(1)
94
+ self.model = OpenAIChat(id="gpt-4o")
95
+ return self.model
96
+
97
+ def set_log_level(self):
98
+ if self.debug_mode or getenv("AGNO_DEBUG", "false").lower() == "true":
99
+ self.debug_mode = True
100
+ set_log_level_to_debug()
101
+ else:
102
+ set_log_level_to_info()
103
+
104
+ def initialize(self):
105
+ self.set_log_level()
106
+
107
+ # -*- Public functions
108
+ def get_knowledge(self, id: str) -> Optional[CulturalKnowledge]:
109
+ """Get the cultural knowledge by id"""
110
+ if not self.db:
111
+ return None
112
+
113
+ self.db = cast(BaseDb, self.db)
114
+
115
+ return self.db.get_cultural_knowledge(id=id)
116
+
117
+ async def aget_knowledge(self, id: str) -> Optional[CulturalKnowledge]:
118
+ """Get the cultural knowledge by id"""
119
+ if not self.db:
120
+ return None
121
+
122
+ self.db = cast(AsyncBaseDb, self.db)
123
+
124
+ return await self.db.get_cultural_knowledge(id=id)
125
+
126
+ def get_all_knowledge(self, name: Optional[str] = None) -> Optional[List[CulturalKnowledge]]:
127
+ """Get all cultural knowledge in the database"""
128
+ if not self.db:
129
+ return None
130
+
131
+ self.db = cast(BaseDb, self.db)
132
+
133
+ return self.db.get_all_cultural_knowledge(name=name)
134
+
135
+ async def aget_all_knowledge(self, name: Optional[str] = None) -> Optional[List[CulturalKnowledge]]:
136
+ """Get all cultural knowledge in the database"""
137
+ if not self.db:
138
+ return None
139
+
140
+ self.db = cast(AsyncBaseDb, self.db)
141
+
142
+ return await self.db.get_all_cultural_knowledge(name=name)
143
+
144
+ def add_cultural_knowledge(
145
+ self,
146
+ knowledge: CulturalKnowledge,
147
+ ) -> Optional[str]:
148
+ """Add a cultural knowledge
149
+ Args:
150
+ knowledge (CulturalKnowledge): The knowledge to add
151
+ Returns:
152
+ str: The id of the knowledge
153
+ """
154
+ if self.db:
155
+ if knowledge.id is None:
156
+ from uuid import uuid4
157
+
158
+ knowledge_id = knowledge.id or str(uuid4())
159
+ knowledge.id = knowledge_id
160
+
161
+ if not knowledge.updated_at:
162
+ knowledge.bump_updated_at()
163
+
164
+ self._upsert_db_knowledge(knowledge=knowledge)
165
+ return knowledge.id
166
+
167
+ else:
168
+ log_warning("Cultural knowledge database not provided.")
169
+ return None
170
+
171
+ def clear_all_knowledge(self) -> None:
172
+ """Clears all cultural knowledge."""
173
+ if self.db:
174
+ self.db.clear_cultural_knowledge()
175
+
176
+ # -*- Agent Functions -*-
177
+ def create_cultural_knowledge(
178
+ self,
179
+ message: Optional[str] = None,
180
+ messages: Optional[List[Message]] = None,
181
+ ) -> str:
182
+ """Creates a cultural knowledge from a message or a list of messages"""
183
+ self.set_log_level()
184
+
185
+ if self.db is None:
186
+ log_warning("CultureDb not provided.")
187
+ return "Please provide a db to store cultural knowledge"
188
+
189
+ if not messages and not message:
190
+ raise ValueError("You must provide either a message or a list of messages")
191
+
192
+ if message:
193
+ messages = [Message(role="user", content=message)]
194
+
195
+ if not messages or not isinstance(messages, list):
196
+ raise ValueError("Invalid messages list")
197
+
198
+ cultural_knowledge = self.get_all_knowledge()
199
+ if cultural_knowledge is None:
200
+ cultural_knowledge = []
201
+
202
+ existing_knowledge = [cultural_knowledge.to_dict() for cultural_knowledge in cultural_knowledge]
203
+
204
+ self.db = cast(BaseDb, self.db)
205
+ response = self.create_or_update_cultural_knowledge(
206
+ messages=messages,
207
+ existing_knowledge=existing_knowledge,
208
+ db=self.db,
209
+ update_knowledge=self.update_knowledge,
210
+ add_knowledge=self.add_knowledge,
211
+ )
212
+
213
+ return response
214
+
215
+ async def acreate_cultural_knowledge(
216
+ self,
217
+ message: Optional[str] = None,
218
+ messages: Optional[List[Message]] = None,
219
+ ) -> str:
220
+ """Creates a cultural knowledge from a message or a list of messages"""
221
+ self.set_log_level()
222
+
223
+ if self.db is None:
224
+ log_warning("CultureDb not provided.")
225
+ return "Please provide a db to store cultural knowledge"
226
+
227
+ if not messages and not message:
228
+ raise ValueError("You must provide either a message or a list of messages")
229
+
230
+ if message:
231
+ messages = [Message(role="user", content=message)]
232
+
233
+ if not messages or not isinstance(messages, list):
234
+ raise ValueError("Invalid messages list")
235
+
236
+ knowledge = self.get_all_knowledge()
237
+ if knowledge is None:
238
+ knowledge = []
239
+
240
+ existing_knowledge = [knowledge.preview() for knowledge in knowledge]
241
+
242
+ self.db = cast(AsyncBaseDb, self.db)
243
+ response = await self.acreate_or_update_cultural_knowledge(
244
+ messages=messages,
245
+ existing_knowledge=existing_knowledge,
246
+ db=self.db,
247
+ update_knowledge=self.update_knowledge,
248
+ add_knowledge=self.add_knowledge,
249
+ )
250
+
251
+ return response
252
+
253
+ def update_culture_task(self, task: str) -> str:
254
+ """Updates the culture with a task"""
255
+
256
+ if not self.db:
257
+ log_warning("CultureDb not provided.")
258
+ return "Please provide a db to store cultural knowledge"
259
+
260
+ if not isinstance(self.db, BaseDb):
261
+ raise ValueError(
262
+ "update_culture_task() is not supported with an async DB. Please use aupdate_culture_task() instead."
263
+ )
264
+
265
+ knowledge = self.get_all_knowledge()
266
+ if knowledge is None:
267
+ knowledge = []
268
+
269
+ existing_knowledge = [knowledge.preview() for knowledge in knowledge]
270
+
271
+ self.db = cast(BaseDb, self.db)
272
+ response = self.run_cultural_knowledge_task(
273
+ task=task,
274
+ existing_knowledge=existing_knowledge,
275
+ db=self.db,
276
+ delete_knowledge=self.delete_knowledge,
277
+ update_knowledge=self.update_knowledge,
278
+ add_knowledge=self.add_knowledge,
279
+ clear_knowledge=self.clear_knowledge,
280
+ )
281
+
282
+ return response
283
+
284
+ async def aupdate_culture_task(
285
+ self,
286
+ task: str,
287
+ ) -> str:
288
+ """Updates the culture with a task asynchronously"""
289
+
290
+ if not self.db:
291
+ log_warning("CultureDb not provided.")
292
+ return "Please provide a db to store cultural knowledge"
293
+
294
+ if not isinstance(self.db, AsyncBaseDb):
295
+ raise ValueError(
296
+ "aupdate_culture_task() is not supported with a sync DB. Please use update_culture_task() instead."
297
+ )
298
+
299
+ knowledge = await self.aget_all_knowledge()
300
+ if knowledge is None:
301
+ knowledge = []
302
+
303
+ existing_knowledge = [_knowledge.preview() for _knowledge in knowledge]
304
+
305
+ self.db = cast(AsyncBaseDb, self.db)
306
+ response = await self.arun_cultural_knowledge_task(
307
+ task=task,
308
+ existing_knowledge=existing_knowledge,
309
+ db=self.db,
310
+ delete_knowledge=self.delete_knowledge,
311
+ update_knowledge=self.update_knowledge,
312
+ add_knowledge=self.add_knowledge,
313
+ clear_knowledge=self.clear_knowledge,
314
+ )
315
+
316
+ return response
317
+
318
+ # -*- Utility Functions -*-
319
+ def _determine_tools_for_model(self, tools: List[Callable]) -> None:
320
+ # Have to reset each time, because of different user IDs
321
+ self._tools_for_model = []
322
+ self._functions_for_model = {}
323
+
324
+ for tool in tools:
325
+ try:
326
+ function_name = tool.__name__
327
+ if function_name not in self._functions_for_model:
328
+ func = Function.from_callable(tool, strict=True) # type: ignore
329
+ func.strict = True
330
+ self._functions_for_model[func.name] = func
331
+ self._tools_for_model.append({"type": "function", "function": func.to_dict()})
332
+ log_debug(f"Added function {func.name}")
333
+ except Exception as e:
334
+ log_warning(f"Could not add function {tool}: {e}")
335
+
336
+ def get_system_message(
337
+ self,
338
+ existing_knowledge: Optional[List[Dict[str, Any]]] = None,
339
+ enable_delete_knowledge: bool = True,
340
+ enable_clear_knowledge: bool = True,
341
+ enable_update_knowledge: bool = True,
342
+ enable_add_knowledge: bool = True,
343
+ ) -> Message:
344
+ """Build the system prompt that instructs the model how to maintain cultural knowledge."""
345
+
346
+ if self.system_message is not None:
347
+ return Message(role="system", content=self.system_message)
348
+
349
+ # Default capture instructions
350
+ culture_capture_instructions = self.culture_capture_instructions or dedent(
351
+ """
352
+ Cultural knowledge should capture shared knowledge, insights, and practices that can improve performance across agents:
353
+ - Best practices and successful approaches discovered in previous interactions
354
+ - Common patterns in user behavior, team workflows, or recurring issues
355
+ - Processes, design principles, or rules of operation
356
+ - Guardrails, decision rationales, or ethical guidelines
357
+ - Domain-specific lessons that generalize beyond one case
358
+ - Communication styles or collaboration methods that lead to better outcomes
359
+ - Any other valuable insight that should persist across agents and time
360
+ """
361
+ )
362
+
363
+ system_prompt_lines: List[str] = [
364
+ "You are the **Cultural Knowledge Manager**, responsible for maintaining, evolving, and safeguarding "
365
+ "the shared cultural knowledge for Agents and Multi-Agent Teams. ",
366
+ "",
367
+ "Given a user message, your task is to distill, organize, and extract collective intelligence from it, including insights, lessons, "
368
+ "rules, principles, and narratives that guide future behavior across agents and teams.",
369
+ "",
370
+ "You will be provided with criteria for cultural knowledge to capture in the <knowledge_to_capture> section, "
371
+ "and the existing cultural knowledge in the <existing_knowledge> section.",
372
+ "",
373
+ "## When to add or update cultural knowledge",
374
+ "- Decide if knowledge should be **added, updated, deleted**, or if **no changes are needed**.",
375
+ "- If new insights meet the criteria in <knowledge_to_capture> and are not already captured in the <existing_knowledge> section, add them.",
376
+ "- If existing practices evolve, update relevant entries (while preserving historical context if useful).",
377
+ "- If nothing new or valuable emerged, respond with exactly: `No changes needed`.",
378
+ "",
379
+ "## How to add or update cultural knowledge",
380
+ "- Write entries that are **clear, specific, and actionable** (avoid vague abstractions).",
381
+ "- Each entry should capture one coherent idea or rule — use multiple entries if necessary.",
382
+ "- Do **not** duplicate information; update similar entries instead.",
383
+ "- When updating, append new insights rather than overwriting useful context.",
384
+ "- Use short Markdown lists, examples, or code blocks to increase clarity.",
385
+ "",
386
+ "## Criteria for creating cultural knowledge",
387
+ "<knowledge_to_capture>" + culture_capture_instructions + "</knowledge_to_capture>",
388
+ "",
389
+ "## Metadata & structure (use these fields when creating/updating)",
390
+ "- `name`: short, specific title (required).",
391
+ "- `summary`: one-line purpose or takeaway.",
392
+ "- `content`: reusable insight, rule, or guideline (required).",
393
+ "- `categories`: list of tags (e.g., ['guardrails', 'rules', 'principles', 'practices', 'patterns', 'behaviors', 'stories']).",
394
+ "- `notes`: list of contextual notes, rationale, or examples.",
395
+ "- `metadata`: optional structured info (e.g., source, author, version).",
396
+ "",
397
+ "## De-duplication, lineage, and precedence",
398
+ "- Search <existing_knowledge> by name/category before adding new entries.",
399
+ "- If a similar entry exists, **update** it instead of creating a duplicate.",
400
+ "- Preserve lineage via `notes` when revising entries.",
401
+ "- When entries conflict, prefer the entry with higher `confidence`.",
402
+ "",
403
+ "## Safety & privacy",
404
+ "- Never include secrets, credentials, personal data, or proprietary information.",
405
+ "",
406
+ "## Tool usage",
407
+ "You can call multiple tools in a single response. Use them only when valuable cultural knowledge emerges.",
408
+ ]
409
+
410
+ # Tool permissions (based on flags)
411
+ tool_lines: List[str] = []
412
+ if enable_add_knowledge:
413
+ tool_lines.append("- Add new entries using the `add_knowledge` tool.")
414
+ if enable_update_knowledge:
415
+ tool_lines.append("- Update existing entries using the `update_knowledge` tool.")
416
+ if enable_delete_knowledge:
417
+ tool_lines.append("- Delete entries using the `delete_knowledge` tool (use sparingly; prefer deprecate).")
418
+ if enable_clear_knowledge:
419
+ tool_lines.append("- Clear all entries using the `clear_knowledge` tool (only when explicitly instructed).")
420
+ if tool_lines:
421
+ system_prompt_lines += [""] + tool_lines
422
+
423
+ if existing_knowledge and len(existing_knowledge) > 0:
424
+ system_prompt_lines.append("\n<existing_knowledge>")
425
+ for _existing_knowledge in existing_knowledge: # type: ignore
426
+ system_prompt_lines.append("--------------------------------")
427
+ system_prompt_lines.append(f"Knowledge ID: {_existing_knowledge.get('id')}")
428
+ system_prompt_lines.append(f"Name: {_existing_knowledge.get('name')}")
429
+ system_prompt_lines.append(f"Summary: {_existing_knowledge.get('summary')}")
430
+ system_prompt_lines.append(f"Categories: {_existing_knowledge.get('categories')}")
431
+ system_prompt_lines.append(f"Content: {_existing_knowledge.get('content')}")
432
+ system_prompt_lines.append("</existing_knowledge>")
433
+
434
+ # Final guardrail for no-op
435
+ system_prompt_lines += [
436
+ "",
437
+ "## When no changes are needed",
438
+ "If no valuable cultural knowledge emerges, or everything is already captured, respond with exactly:",
439
+ "`No changes needed`",
440
+ ]
441
+
442
+ if self.additional_instructions:
443
+ system_prompt_lines.append(self.additional_instructions)
444
+
445
+ return Message(role="system", content="\n".join(system_prompt_lines))
446
+
447
+ def create_or_update_cultural_knowledge(
448
+ self,
449
+ messages: List[Message],
450
+ existing_knowledge: List[Dict[str, Any]],
451
+ db: BaseDb,
452
+ update_knowledge: bool = True,
453
+ add_knowledge: bool = True,
454
+ ) -> str:
455
+ if self.model is None:
456
+ log_error("No model provided for culture manager")
457
+ return "No model provided for culture manager"
458
+
459
+ log_debug("CultureManager Start", center=True)
460
+
461
+ model_copy = deepcopy(self.model)
462
+ # Update the Model (set defaults, add logit etc.)
463
+ self._determine_tools_for_model(
464
+ self._get_db_tools(
465
+ db,
466
+ enable_add_knowledge=add_knowledge,
467
+ enable_update_knowledge=update_knowledge,
468
+ enable_delete_knowledge=False,
469
+ enable_clear_knowledge=False,
470
+ ),
471
+ )
472
+
473
+ # Prepare the List of messages to send to the Model
474
+ messages_for_model: List[Message] = [
475
+ self.get_system_message(
476
+ existing_knowledge=existing_knowledge,
477
+ enable_update_knowledge=update_knowledge,
478
+ enable_add_knowledge=add_knowledge,
479
+ enable_delete_knowledge=False,
480
+ enable_clear_knowledge=False,
481
+ ),
482
+ *messages,
483
+ ]
484
+
485
+ # Generate a response from the Model (includes running function calls)
486
+ response = model_copy.response(
487
+ messages=messages_for_model,
488
+ tools=self._tools_for_model,
489
+ functions=self._functions_for_model,
490
+ )
491
+
492
+ if response.tool_calls is not None and len(response.tool_calls) > 0:
493
+ self.knowledge_updated = True
494
+
495
+ log_debug("Culture Manager End", center=True)
496
+
497
+ return response.content or "No response from model"
498
+
499
+ async def acreate_or_update_cultural_knowledge(
500
+ self,
501
+ messages: List[Message],
502
+ existing_knowledge: List[Dict[str, Any]],
503
+ db: AsyncBaseDb,
504
+ update_knowledge: bool = True,
505
+ add_knowledge: bool = True,
506
+ ) -> str:
507
+ if self.model is None:
508
+ log_error("No model provided for cultural manager")
509
+ return "No model provided for cultural manager"
510
+
511
+ log_debug("Cultural Manager Start", center=True)
512
+
513
+ model_copy = deepcopy(self.model)
514
+ db = cast(AsyncBaseDb, db)
515
+
516
+ self._determine_tools_for_model(
517
+ await self._aget_db_tools(
518
+ db,
519
+ enable_update_knowledge=update_knowledge,
520
+ enable_add_knowledge=add_knowledge,
521
+ ),
522
+ )
523
+
524
+ # Prepare the List of messages to send to the Model
525
+ messages_for_model: List[Message] = [
526
+ self.get_system_message(
527
+ existing_knowledge=existing_knowledge,
528
+ enable_update_knowledge=update_knowledge,
529
+ enable_add_knowledge=add_knowledge,
530
+ ),
531
+ # For models that require a non-system message
532
+ *messages,
533
+ ]
534
+
535
+ # Generate a response from the Model (includes running function calls)
536
+ response = await model_copy.aresponse(
537
+ messages=messages_for_model,
538
+ tools=self._tools_for_model,
539
+ functions=self._functions_for_model,
540
+ )
541
+
542
+ if response.tool_calls is not None and len(response.tool_calls) > 0:
543
+ self.knowledge_updated = True
544
+
545
+ log_debug("Cultural Knowledge Manager End", center=True)
546
+
547
+ return response.content or "No response from model"
548
+
549
+ def run_cultural_knowledge_task(
550
+ self,
551
+ task: str,
552
+ existing_knowledge: List[Dict[str, Any]],
553
+ db: BaseDb,
554
+ delete_knowledge: bool = True,
555
+ update_knowledge: bool = True,
556
+ add_knowledge: bool = True,
557
+ clear_knowledge: bool = True,
558
+ ) -> str:
559
+ if self.model is None:
560
+ log_error("No model provided for cultural manager")
561
+ return "No model provided for cultural manager"
562
+
563
+ log_debug("Cultural Knowledge Manager Start", center=True)
564
+
565
+ model_copy = deepcopy(self.model)
566
+ # Update the Model (set defaults, add logit etc.)
567
+ self._determine_tools_for_model(
568
+ self._get_db_tools(
569
+ db,
570
+ enable_delete_knowledge=delete_knowledge,
571
+ enable_clear_knowledge=clear_knowledge,
572
+ enable_update_knowledge=update_knowledge,
573
+ enable_add_knowledge=add_knowledge,
574
+ ),
575
+ )
576
+
577
+ # Prepare the List of messages to send to the Model
578
+ messages_for_model: List[Message] = [
579
+ self.get_system_message(
580
+ existing_knowledge,
581
+ enable_delete_knowledge=delete_knowledge,
582
+ enable_clear_knowledge=clear_knowledge,
583
+ enable_update_knowledge=update_knowledge,
584
+ enable_add_knowledge=add_knowledge,
585
+ ),
586
+ # For models that require a non-system message
587
+ Message(role="user", content=task),
588
+ ]
589
+
590
+ # Generate a response from the Model (includes running function calls)
591
+ response = model_copy.response(
592
+ messages=messages_for_model,
593
+ tools=self._tools_for_model,
594
+ functions=self._functions_for_model,
595
+ )
596
+
597
+ if response.tool_calls is not None and len(response.tool_calls) > 0:
598
+ self.knowledge_updated = True
599
+
600
+ log_debug("Cultural Knowledge Manager End", center=True)
601
+
602
+ return response.content or "No response from model"
603
+
604
+ async def arun_cultural_knowledge_task(
605
+ self,
606
+ task: str,
607
+ existing_knowledge: List[Dict[str, Any]],
608
+ db: Union[BaseDb, AsyncBaseDb],
609
+ delete_knowledge: bool = True,
610
+ clear_knowledge: bool = True,
611
+ update_knowledge: bool = True,
612
+ add_knowledge: bool = True,
613
+ ) -> str:
614
+ if self.model is None:
615
+ log_error("No model provided for cultural manager")
616
+ return "No model provided for cultural manager"
617
+
618
+ log_debug("Cultural Manager Start", center=True)
619
+
620
+ model_copy = deepcopy(self.model)
621
+ # Update the Model (set defaults, add logit etc.)
622
+ if isinstance(db, AsyncBaseDb):
623
+ self._determine_tools_for_model(
624
+ await self._aget_db_tools(
625
+ db,
626
+ enable_delete_knowledge=delete_knowledge,
627
+ enable_clear_knowledge=clear_knowledge,
628
+ enable_update_knowledge=update_knowledge,
629
+ enable_add_knowledge=add_knowledge,
630
+ ),
631
+ )
632
+ else:
633
+ self._determine_tools_for_model(
634
+ self._get_db_tools(
635
+ db,
636
+ enable_delete_knowledge=delete_knowledge,
637
+ enable_clear_knowledge=clear_knowledge,
638
+ enable_update_knowledge=update_knowledge,
639
+ enable_add_knowledge=add_knowledge,
640
+ ),
641
+ )
642
+
643
+ # Prepare the List of messages to send to the Model
644
+ messages_for_model: List[Message] = [
645
+ self.get_system_message(
646
+ existing_knowledge,
647
+ enable_delete_knowledge=delete_knowledge,
648
+ enable_clear_knowledge=clear_knowledge,
649
+ enable_update_knowledge=update_knowledge,
650
+ enable_add_knowledge=add_knowledge,
651
+ ),
652
+ # For models that require a non-system message
653
+ Message(role="user", content=task),
654
+ ]
655
+
656
+ # Generate a response from the Model (includes running function calls)
657
+ response = await model_copy.aresponse(
658
+ messages=messages_for_model,
659
+ tools=self._tools_for_model,
660
+ functions=self._functions_for_model,
661
+ )
662
+
663
+ if response.tool_calls is not None and len(response.tool_calls) > 0:
664
+ self.knowledge_updated = True
665
+
666
+ log_debug("Cultural Manager End", center=True)
667
+
668
+ return response.content or "No response from model"
669
+
670
+ # -*- DB Functions -*-
671
+ def _clear_db_knowledge(self) -> str:
672
+ """Use this function to clear all cultural knowledge from the database."""
673
+ try:
674
+ if not self.db:
675
+ raise ValueError("Culture db not initialized")
676
+ self.db = cast(BaseDb, self.db)
677
+ self.db.clear_cultural_knowledge()
678
+ return "Cultural knowledge cleared successfully"
679
+ except Exception as e:
680
+ log_warning(f"Error clearing cultural knowledge in db: {e}")
681
+ return f"Error clearing cultural knowledge: {e}"
682
+
683
+ async def _aclear_db_knowledge(self) -> str:
684
+ """Use this function to clear all cultural knowledge from the database."""
685
+ try:
686
+ if not self.db:
687
+ raise ValueError("Culture db not initialized")
688
+ self.db = cast(AsyncBaseDb, self.db)
689
+ await self.db.clear_cultural_knowledge()
690
+ return "Cultural knowledge cleared successfully"
691
+ except Exception as e:
692
+ log_warning(f"Error clearing cultural knowledge in db: {e}")
693
+ return f"Error clearing cultural knowledge: {e}"
694
+
695
+ def _delete_db_knowledge(self, knowledge_id: str) -> str:
696
+ """Use this function to delete a cultural knowledge from the database."""
697
+ try:
698
+ if not self.db:
699
+ raise ValueError("Culture db not initialized")
700
+ self.db = cast(BaseDb, self.db)
701
+ self.db.delete_cultural_knowledge(id=knowledge_id)
702
+ return "Cultural knowledge deleted successfully"
703
+ except Exception as e:
704
+ log_warning(f"Error deleting cultural knowledge in db: {e}")
705
+ return f"Error deleting cultural knowledge: {e}"
706
+
707
+ async def _adelete_db_knowledge(self, knowledge_id: str) -> str:
708
+ """Use this function to delete a cultural knowledge from the database."""
709
+ try:
710
+ if not self.db:
711
+ raise ValueError("Culture db not initialized")
712
+ self.db = cast(AsyncBaseDb, self.db)
713
+ await self.db.delete_cultural_knowledge(id=knowledge_id)
714
+ return "Cultural knowledge deleted successfully"
715
+ except Exception as e:
716
+ log_warning(f"Error deleting cultural knowledge in db: {e}")
717
+ return f"Error deleting cultural knowledge: {e}"
718
+
719
+ def _upsert_db_knowledge(self, knowledge: CulturalKnowledge) -> str:
720
+ """Use this function to add a cultural knowledge to the database."""
721
+ try:
722
+ if not self.db:
723
+ raise ValueError("Culture db not initialized")
724
+ self.db = cast(BaseDb, self.db)
725
+ self.db.upsert_cultural_knowledge(cultural_knowledge=knowledge)
726
+ return "Cultural knowledge added successfully"
727
+ except Exception as e:
728
+ log_warning(f"Error storing cultural knowledge in db: {e}")
729
+ return f"Error adding cultural knowledge: {e}"
730
+
731
+ # -* Get DB Tools -*-
732
+ def _get_db_tools(
733
+ self,
734
+ db: Union[BaseDb, AsyncBaseDb],
735
+ enable_add_knowledge: bool = True,
736
+ enable_update_knowledge: bool = True,
737
+ enable_delete_knowledge: bool = True,
738
+ enable_clear_knowledge: bool = True,
739
+ ) -> List[Callable]:
740
+ def add_cultural_knowledge(
741
+ name: str,
742
+ summary: Optional[str] = None,
743
+ content: Optional[str] = None,
744
+ categories: Optional[List[str]] = None,
745
+ ) -> str:
746
+ """Use this function to add a cultural knowledge to the database.
747
+ Args:
748
+ name (str): The name of the cultural knowledge. Short, specific title.
749
+ summary (Optional[str]): The summary of the cultural knowledge. One-line purpose or takeaway.
750
+ content (Optional[str]): The content of the cultural knowledge. Reusable insight, rule, or guideline.
751
+ categories (Optional[List[str]]): The categories of the cultural knowledge. List of tags (e.g. ["guardrails", "rules", "principles", "practices", "patterns", "behaviors", "stories"]).
752
+ Returns:
753
+ str: A message indicating if the cultural knowledge was added successfully or not.
754
+ """
755
+ from uuid import uuid4
756
+
757
+ try:
758
+ knowledge_id = str(uuid4())
759
+ db.upsert_cultural_knowledge(
760
+ CulturalKnowledge(
761
+ id=knowledge_id,
762
+ name=name,
763
+ summary=summary,
764
+ content=content,
765
+ categories=categories,
766
+ )
767
+ )
768
+ log_debug(f"Cultural knowledge added: {knowledge_id}")
769
+ return "Cultural knowledge added successfully"
770
+ except Exception as e:
771
+ log_warning(f"Error storing cultural knowledge in db: {e}")
772
+ return f"Error adding cultural knowledge: {e}"
773
+
774
+ def update_cultural_knowledge(
775
+ knowledge_id: str,
776
+ name: str,
777
+ summary: Optional[str] = None,
778
+ content: Optional[str] = None,
779
+ categories: Optional[List[str]] = None,
780
+ ) -> str:
781
+ """Use this function to update an existing cultural knowledge in the database.
782
+ Args:
783
+ knowledge_id (str): The id of the cultural knowledge to be updated.
784
+ name (str): The name of the cultural knowledge. Short, specific title.
785
+ summary (Optional[str]): The summary of the cultural knowledge. One-line purpose or takeaway.
786
+ content (Optional[str]): The content of the cultural knowledge. Reusable insight, rule, or guideline.
787
+ categories (Optional[List[str]]): The categories of the cultural knowledge. List of tags (e.g. ["guardrails", "rules", "principles", "practices", "patterns", "behaviors", "stories"]).
788
+ Returns:
789
+ str: A message indicating if the cultural knowledge was updated successfully or not.
790
+ """
791
+ from agno.db.base import CulturalKnowledge
792
+
793
+ try:
794
+ db.upsert_cultural_knowledge(
795
+ CulturalKnowledge(
796
+ id=knowledge_id,
797
+ name=name,
798
+ summary=summary,
799
+ content=content,
800
+ categories=categories,
801
+ )
802
+ )
803
+ log_debug("Cultural knowledge updated")
804
+ return "Cultural knowledge updated successfully"
805
+ except Exception as e:
806
+ log_warning(f"Error storing cultural knowledge in db: {e}")
807
+ return f"Error adding cultural knowledge: {e}"
808
+
809
+ def delete_cultural_knowledge(knowledge_id: str) -> str:
810
+ """Use this function to delete a single cultural knowledge from the database.
811
+ Args:
812
+ knowledge_id (str): The id of the cultural knowledge to be deleted.
813
+ Returns:
814
+ str: A message indicating if the cultural knowledge was deleted successfully or not.
815
+ """
816
+ try:
817
+ db.delete_cultural_knowledge(id=knowledge_id)
818
+ log_debug("Cultural knowledge deleted")
819
+ return "Cultural knowledge deleted successfully"
820
+ except Exception as e:
821
+ log_warning(f"Error deleting cultural knowledge in db: {e}")
822
+ return f"Error deleting cultural knowledge: {e}"
823
+
824
+ def clear_cultural_knowledge() -> str:
825
+ """Use this function to remove all (or clear all) cultural knowledge from the database.
826
+ Returns:
827
+ str: A message indicating if the cultural knowledge was cleared successfully or not.
828
+ """
829
+ db.clear_cultural_knowledge()
830
+ log_debug("Cultural knowledge cleared")
831
+ return "Cultural knowledge cleared successfully"
832
+
833
+ functions: List[Callable] = []
834
+ if enable_add_knowledge:
835
+ functions.append(add_cultural_knowledge)
836
+ if enable_update_knowledge:
837
+ functions.append(update_cultural_knowledge)
838
+ if enable_delete_knowledge:
839
+ functions.append(delete_cultural_knowledge)
840
+ if enable_clear_knowledge:
841
+ functions.append(clear_cultural_knowledge)
842
+ return functions
843
+
844
+ async def _aget_db_tools(
845
+ self,
846
+ db: AsyncBaseDb,
847
+ enable_add_knowledge: bool = True,
848
+ enable_update_knowledge: bool = True,
849
+ enable_delete_knowledge: bool = True,
850
+ enable_clear_knowledge: bool = True,
851
+ ) -> List[Callable]:
852
+ async def add_cultural_knowledge(
853
+ name: str,
854
+ summary: Optional[str] = None,
855
+ content: Optional[str] = None,
856
+ categories: Optional[List[str]] = None,
857
+ ) -> str:
858
+ """Use this function to add a cultural knowledge to the database.
859
+ Args:
860
+ name (str): The name of the cultural knowledge.
861
+ summary (Optional[str]): The summary of the cultural knowledge.
862
+ content (Optional[str]): The content of the cultural knowledge.
863
+ categories (Optional[List[str]]): The categories of the cultural knowledge (e.g. ["name", "hobbies", "location"]).
864
+ Returns:
865
+ str: A message indicating if the cultural knowledge was added successfully or not.
866
+ """
867
+ from uuid import uuid4
868
+
869
+ try:
870
+ knowledge_id = str(uuid4())
871
+ await db.upsert_cultural_knowledge(
872
+ CulturalKnowledge(
873
+ id=knowledge_id,
874
+ name=name,
875
+ summary=summary,
876
+ content=content,
877
+ categories=categories,
878
+ )
879
+ )
880
+ log_debug(f"Cultural knowledge added: {knowledge_id}")
881
+ return "Cultural knowledge added successfully"
882
+ except Exception as e:
883
+ log_warning(f"Error storing cultural knowledge in db: {e}")
884
+ return f"Error adding cultural knowledge: {e}"
885
+
886
+ async def update_cultural_knowledge(
887
+ knowledge_id: str,
888
+ name: str,
889
+ summary: Optional[str] = None,
890
+ content: Optional[str] = None,
891
+ categories: Optional[List[str]] = None,
892
+ ) -> str:
893
+ """Use this function to update an existing cultural knowledge in the database.
894
+ Args:
895
+ knowledge_id (str): The id of the cultural knowledge to be updated.
896
+ name (str): The name of the cultural knowledge.
897
+ summary (Optional[str]): The summary of the cultural knowledge.
898
+ content (Optional[str]): The content of the cultural knowledge.
899
+ categories (Optional[List[str]]): The categories of the cultural knowledge (e.g. ["name", "hobbies", "location"]).
900
+ Returns:
901
+ str: A message indicating if the cultural knowledge was updated successfully or not.
902
+ """
903
+ from agno.db.base import CulturalKnowledge
904
+
905
+ try:
906
+ await db.upsert_cultural_knowledge(
907
+ CulturalKnowledge(
908
+ id=knowledge_id,
909
+ name=name,
910
+ summary=summary,
911
+ content=content,
912
+ categories=categories,
913
+ )
914
+ )
915
+ log_debug("Cultural knowledge updated")
916
+ return "Cultural knowledge updated successfully"
917
+ except Exception as e:
918
+ log_warning(f"Error storing cultural knowledge in db: {e}")
919
+ return f"Error updating cultural knowledge: {e}"
920
+
921
+ async def delete_cultural_knowledge(knowledge_id: str) -> str:
922
+ """Use this function to delete a single cultural knowledge from the database.
923
+ Args:
924
+ knowledge_id (str): The id of the cultural knowledge to be deleted.
925
+ Returns:
926
+ str: A message indicating if the cultural knowledge was deleted successfully or not.
927
+ """
928
+ try:
929
+ await db.delete_cultural_knowledge(id=knowledge_id)
930
+ log_debug("Cultural knowledge deleted")
931
+ return "Cultural knowledge deleted successfully"
932
+ except Exception as e:
933
+ log_warning(f"Error deleting cultural knowledge in db: {e}")
934
+ return f"Error deleting cultural knowledge: {e}"
935
+
936
+ async def clear_cultural_knowledge() -> str:
937
+ """Use this function to remove all (or clear all) cultural knowledge from the database.
938
+ Returns:
939
+ str: A message indicating if the cultural knowledge was cleared successfully or not.
940
+ """
941
+ await db.clear_cultural_knowledge()
942
+ log_debug("Cultural knowledge cleared")
943
+ return "Cultural knowledge cleared successfully"
944
+
945
+ functions: List[Callable] = []
946
+ if enable_add_knowledge:
947
+ functions.append(add_cultural_knowledge)
948
+ if enable_update_knowledge:
949
+ functions.append(update_cultural_knowledge)
950
+ if enable_delete_knowledge:
951
+ functions.append(delete_cultural_knowledge)
952
+ if enable_clear_knowledge:
953
+ functions.append(clear_cultural_knowledge)
954
+ return functions