agno 2.4.5__py3-none-any.whl → 2.4.7__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 +2 -1
- agno/db/singlestore/singlestore.py +4 -5
- agno/db/surrealdb/models.py +1 -1
- agno/knowledge/chunking/agentic.py +1 -5
- agno/knowledge/chunking/code.py +1 -1
- agno/knowledge/chunking/document.py +22 -42
- agno/knowledge/chunking/fixed.py +1 -5
- agno/knowledge/chunking/markdown.py +9 -25
- agno/knowledge/chunking/recursive.py +1 -3
- agno/knowledge/chunking/row.py +3 -2
- agno/knowledge/chunking/semantic.py +1 -1
- agno/knowledge/chunking/strategy.py +19 -0
- agno/knowledge/embedder/aws_bedrock.py +325 -106
- agno/knowledge/knowledge.py +173 -14
- agno/knowledge/reader/text_reader.py +1 -1
- agno/knowledge/reranker/aws_bedrock.py +299 -0
- agno/learn/machine.py +5 -6
- agno/learn/stores/learned_knowledge.py +108 -131
- agno/run/workflow.py +3 -0
- agno/tools/mcp/mcp.py +26 -1
- agno/utils/print_response/agent.py +8 -8
- agno/utils/print_response/team.py +8 -8
- agno/vectordb/lancedb/lance_db.py +9 -9
- agno/workflow/condition.py +135 -56
- {agno-2.4.5.dist-info → agno-2.4.7.dist-info}/METADATA +34 -59
- {agno-2.4.5.dist-info → agno-2.4.7.dist-info}/RECORD +29 -28
- {agno-2.4.5.dist-info → agno-2.4.7.dist-info}/WHEEL +0 -0
- {agno-2.4.5.dist-info → agno-2.4.7.dist-info}/licenses/LICENSE +0 -0
- {agno-2.4.5.dist-info → agno-2.4.7.dist-info}/top_level.txt +0 -0
|
@@ -75,9 +75,7 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
75
75
|
self._schema = self.config.schema or LearnedKnowledge
|
|
76
76
|
|
|
77
77
|
if self.config.mode == LearningMode.HITL:
|
|
78
|
-
log_warning(
|
|
79
|
-
"LearnedKnowledgeStore does not support HITL mode. Use PROPOSE mode for human-in-the-loop approval. "
|
|
80
|
-
)
|
|
78
|
+
log_warning("LearnedKnowledgeStore does not support HITL mode. Use PROPOSE mode for soft approval.")
|
|
81
79
|
|
|
82
80
|
# =========================================================================
|
|
83
81
|
# LearningStore Protocol Implementation
|
|
@@ -178,6 +176,9 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
178
176
|
namespace: Namespace to save learnings to (default: "global").
|
|
179
177
|
**kwargs: Additional context (ignored).
|
|
180
178
|
"""
|
|
179
|
+
# Reset state for this operation
|
|
180
|
+
self.learning_saved = False
|
|
181
|
+
|
|
181
182
|
# process only supported in ALWAYS mode
|
|
182
183
|
# for programmatic extraction, use extract_and_save directly
|
|
183
184
|
if self.config.mode != LearningMode.ALWAYS:
|
|
@@ -204,6 +205,9 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
204
205
|
**kwargs,
|
|
205
206
|
) -> None:
|
|
206
207
|
"""Async version of process."""
|
|
208
|
+
# Reset state for this operation
|
|
209
|
+
self.learning_saved = False
|
|
210
|
+
|
|
207
211
|
if self.config.mode != LearningMode.ALWAYS:
|
|
208
212
|
return
|
|
209
213
|
|
|
@@ -250,35 +254,45 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
250
254
|
→ Then incorporate any relevant findings into your response
|
|
251
255
|
|
|
252
256
|
**RULE 2: ALWAYS search before saving.**
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
Before saving anything, first call `search_learnings` to check if similar knowledge exists.
|
|
258
|
+
Only save if it's genuinely new (not a duplicate or minor variation).
|
|
259
|
+
|
|
260
|
+
**RULE 3: ALWAYS save when explicitly asked.**
|
|
261
|
+
When the user says "remember", "save", "note", "keep in mind", or similar:
|
|
262
|
+
→ These are explicit directives - save what they asked (after searching for duplicates)
|
|
263
|
+
|
|
264
|
+
**RULE 4: ALWAYS save team/org goals, constraints, and policies.**
|
|
265
|
+
When the user shares organizational context:
|
|
266
|
+
→ "We're trying to..." / "Our goal is..." (team goals)
|
|
267
|
+
→ "We can't use..." / "We need to avoid..." (constraints)
|
|
268
|
+
→ "Our policy is..." / "We always..." (policies)
|
|
269
|
+
→ "Our priority is..." / "We prefer..." at org level (priorities)
|
|
270
|
+
These are shared context - save them so other users benefit too.
|
|
256
271
|
|
|
257
272
|
## Tools
|
|
258
273
|
|
|
259
274
|
`search_learnings(query)` - Search for relevant prior insights. Use liberally.
|
|
260
|
-
`save_learning(title, learning, context, tags)` - Save
|
|
275
|
+
`save_learning(title, learning, context, tags)` - Save new insights or context.
|
|
261
276
|
|
|
262
277
|
## When to Search
|
|
263
278
|
|
|
264
|
-
|
|
279
|
+
Search when the user:
|
|
265
280
|
- Asks for recommendations or best practices
|
|
266
281
|
- Asks how to approach a problem
|
|
267
282
|
- Asks about trade-offs or considerations
|
|
268
283
|
- Mentions a technology, domain, or problem area
|
|
269
|
-
- Asks you to save something (
|
|
284
|
+
- Asks you to save something (check for duplicates first)
|
|
270
285
|
|
|
271
|
-
## When to Save
|
|
286
|
+
## When to Save (Self-Discovered Insights)
|
|
272
287
|
|
|
273
|
-
|
|
288
|
+
For insights you discover yourself (not explicit requests or org context), only save if:
|
|
274
289
|
- Non-obvious (required investigation to discover)
|
|
275
290
|
- Reusable (applies to a category of problems)
|
|
276
291
|
- Actionable (specific enough to apply directly)
|
|
277
|
-
- Not already in the knowledge base (you checked by searching first!)
|
|
278
292
|
|
|
279
293
|
Do NOT save:
|
|
280
294
|
- Raw facts or common knowledge
|
|
281
|
-
-
|
|
295
|
+
- Individual user preferences (use user memory instead)
|
|
282
296
|
- Duplicates of existing learnings
|
|
283
297
|
</learning_system>\
|
|
284
298
|
""")
|
|
@@ -308,7 +322,7 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
308
322
|
If you discover something worth preserving, propose it at the end of your response:
|
|
309
323
|
|
|
310
324
|
---
|
|
311
|
-
|
|
325
|
+
**Proposed Learning**
|
|
312
326
|
**Title:** [Concise title]
|
|
313
327
|
**Context:** [When this applies]
|
|
314
328
|
**Insight:** [The learning - specific and actionable]
|
|
@@ -484,15 +498,17 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
484
498
|
tools = []
|
|
485
499
|
|
|
486
500
|
if self.config.agent_can_search:
|
|
487
|
-
tools.append(
|
|
501
|
+
tools.append(
|
|
502
|
+
self._create_search_learnings_tool(namespace=namespace or self.config.namespace, user_id=user_id)
|
|
503
|
+
)
|
|
488
504
|
|
|
489
505
|
if self.config.agent_can_save:
|
|
490
506
|
tools.append(
|
|
491
507
|
self._create_save_learning_tool(
|
|
508
|
+
namespace=namespace or self.config.namespace,
|
|
492
509
|
user_id=user_id,
|
|
493
510
|
agent_id=agent_id,
|
|
494
511
|
team_id=team_id,
|
|
495
|
-
default_namespace=namespace,
|
|
496
512
|
)
|
|
497
513
|
)
|
|
498
514
|
|
|
@@ -509,15 +525,17 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
509
525
|
tools = []
|
|
510
526
|
|
|
511
527
|
if self.config.agent_can_search:
|
|
512
|
-
tools.append(
|
|
528
|
+
tools.append(
|
|
529
|
+
self._create_async_search_learnings_tool(namespace=namespace or self.config.namespace, user_id=user_id)
|
|
530
|
+
)
|
|
513
531
|
|
|
514
532
|
if self.config.agent_can_save:
|
|
515
533
|
tools.append(
|
|
516
534
|
self._create_async_save_learning_tool(
|
|
535
|
+
namespace=namespace or self.config.namespace,
|
|
517
536
|
user_id=user_id,
|
|
518
537
|
agent_id=agent_id,
|
|
519
538
|
team_id=team_id,
|
|
520
|
-
default_namespace=namespace,
|
|
521
539
|
)
|
|
522
540
|
)
|
|
523
541
|
|
|
@@ -529,10 +547,10 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
529
547
|
|
|
530
548
|
def _create_save_learning_tool(
|
|
531
549
|
self,
|
|
550
|
+
namespace: str,
|
|
532
551
|
user_id: Optional[str] = None,
|
|
533
552
|
agent_id: Optional[str] = None,
|
|
534
553
|
team_id: Optional[str] = None,
|
|
535
|
-
default_namespace: Optional[str] = None,
|
|
536
554
|
) -> Callable:
|
|
537
555
|
"""Create the save_learning tool for the agent."""
|
|
538
556
|
|
|
@@ -541,31 +559,20 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
541
559
|
learning: str,
|
|
542
560
|
context: Optional[str] = None,
|
|
543
561
|
tags: Optional[List[str]] = None,
|
|
544
|
-
namespace: Optional[str] = None,
|
|
545
562
|
) -> str:
|
|
546
|
-
"""Save a reusable insight to the knowledge base.
|
|
563
|
+
"""Save a reusable insight or organizational context to the knowledge base.
|
|
547
564
|
|
|
548
|
-
IMPORTANT:
|
|
549
|
-
if similar knowledge already exists. Do not save duplicates!
|
|
550
|
-
|
|
551
|
-
Only save insights that are:
|
|
552
|
-
- Non-obvious (not common knowledge)
|
|
553
|
-
- Reusable (applies beyond this specific case)
|
|
554
|
-
- Actionable (specific enough to apply directly)
|
|
555
|
-
- Not already saved (you searched first, right?)
|
|
565
|
+
IMPORTANT: You MUST call search_learnings first to check for duplicates.
|
|
556
566
|
|
|
557
567
|
Args:
|
|
558
|
-
title: Concise, searchable title
|
|
559
|
-
learning: The insight - specific and actionable.
|
|
560
|
-
context: When/where this applies
|
|
561
|
-
tags: Categories for organization
|
|
562
|
-
namespace: Access scope - "global" (shared) or "user" (private).
|
|
568
|
+
title: Concise, searchable title.
|
|
569
|
+
learning: The insight or context - specific and actionable.
|
|
570
|
+
context: When/where this applies.
|
|
571
|
+
tags: Categories for organization.
|
|
563
572
|
|
|
564
573
|
Returns:
|
|
565
574
|
Confirmation message.
|
|
566
575
|
"""
|
|
567
|
-
effective_namespace = namespace or default_namespace or "global"
|
|
568
|
-
|
|
569
576
|
success = self.save(
|
|
570
577
|
title=title,
|
|
571
578
|
learning=learning,
|
|
@@ -574,21 +581,21 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
574
581
|
user_id=user_id,
|
|
575
582
|
agent_id=agent_id,
|
|
576
583
|
team_id=team_id,
|
|
577
|
-
namespace=
|
|
584
|
+
namespace=namespace,
|
|
578
585
|
)
|
|
579
586
|
if success:
|
|
580
587
|
self.learning_saved = True
|
|
581
|
-
return f"Learning saved: {title} (namespace: {
|
|
588
|
+
return f"Learning saved: {title} (namespace: {namespace})"
|
|
582
589
|
return "Failed to save learning"
|
|
583
590
|
|
|
584
591
|
return save_learning
|
|
585
592
|
|
|
586
593
|
def _create_async_save_learning_tool(
|
|
587
594
|
self,
|
|
595
|
+
namespace: str,
|
|
588
596
|
user_id: Optional[str] = None,
|
|
589
597
|
agent_id: Optional[str] = None,
|
|
590
598
|
team_id: Optional[str] = None,
|
|
591
|
-
default_namespace: Optional[str] = None,
|
|
592
599
|
) -> Callable:
|
|
593
600
|
"""Create the async save_learning tool for the agent."""
|
|
594
601
|
|
|
@@ -597,31 +604,20 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
597
604
|
learning: str,
|
|
598
605
|
context: Optional[str] = None,
|
|
599
606
|
tags: Optional[List[str]] = None,
|
|
600
|
-
namespace: Optional[str] = None,
|
|
601
607
|
) -> str:
|
|
602
|
-
"""Save a reusable insight to the knowledge base.
|
|
608
|
+
"""Save a reusable insight or organizational context to the knowledge base.
|
|
603
609
|
|
|
604
|
-
IMPORTANT:
|
|
605
|
-
if similar knowledge already exists. Do not save duplicates!
|
|
606
|
-
|
|
607
|
-
Only save insights that are:
|
|
608
|
-
- Non-obvious (not common knowledge)
|
|
609
|
-
- Reusable (applies beyond this specific case)
|
|
610
|
-
- Actionable (specific enough to apply directly)
|
|
611
|
-
- Not already saved (you searched first, right?)
|
|
610
|
+
IMPORTANT: You MUST call search_learnings first to check for duplicates.
|
|
612
611
|
|
|
613
612
|
Args:
|
|
614
|
-
title: Concise, searchable title
|
|
615
|
-
learning: The insight - specific and actionable.
|
|
616
|
-
context: When/where this applies
|
|
617
|
-
tags: Categories for organization
|
|
618
|
-
namespace: Access scope - "global" (shared) or "user" (private).
|
|
613
|
+
title: Concise, searchable title.
|
|
614
|
+
learning: The insight or context - specific and actionable.
|
|
615
|
+
context: When/where this applies.
|
|
616
|
+
tags: Categories for organization.
|
|
619
617
|
|
|
620
618
|
Returns:
|
|
621
619
|
Confirmation message.
|
|
622
620
|
"""
|
|
623
|
-
effective_namespace = namespace or default_namespace or "global"
|
|
624
|
-
|
|
625
621
|
success = await self.asave(
|
|
626
622
|
title=title,
|
|
627
623
|
learning=learning,
|
|
@@ -630,11 +626,11 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
630
626
|
user_id=user_id,
|
|
631
627
|
agent_id=agent_id,
|
|
632
628
|
team_id=team_id,
|
|
633
|
-
namespace=
|
|
629
|
+
namespace=namespace,
|
|
634
630
|
)
|
|
635
631
|
if success:
|
|
636
632
|
self.learning_saved = True
|
|
637
|
-
return f"Learning saved: {title} (namespace: {
|
|
633
|
+
return f"Learning saved: {title} (namespace: {namespace})"
|
|
638
634
|
return "Failed to save learning"
|
|
639
635
|
|
|
640
636
|
return save_learning
|
|
@@ -645,6 +641,7 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
645
641
|
|
|
646
642
|
def _create_search_learnings_tool(
|
|
647
643
|
self,
|
|
644
|
+
namespace: str,
|
|
648
645
|
user_id: Optional[str] = None,
|
|
649
646
|
) -> Callable:
|
|
650
647
|
"""Create the search_learnings tool for the agent."""
|
|
@@ -652,7 +649,6 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
652
649
|
def search_learnings(
|
|
653
650
|
query: str,
|
|
654
651
|
limit: int = 5,
|
|
655
|
-
namespace: Optional[str] = None,
|
|
656
652
|
) -> str:
|
|
657
653
|
"""Search for relevant insights in the knowledge base.
|
|
658
654
|
|
|
@@ -662,9 +658,8 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
662
658
|
|
|
663
659
|
Args:
|
|
664
660
|
query: Keywords describing what you're looking for.
|
|
665
|
-
Examples: "cloud costs", "API rate limiting", "
|
|
661
|
+
Examples: "cloud costs", "API rate limiting", "team goals"
|
|
666
662
|
limit: Maximum results (default: 5)
|
|
667
|
-
namespace: Filter by scope (None = all, "global", "user", or custom)
|
|
668
663
|
|
|
669
664
|
Returns:
|
|
670
665
|
List of relevant learnings, or message if none found.
|
|
@@ -686,6 +681,7 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
686
681
|
|
|
687
682
|
def _create_async_search_learnings_tool(
|
|
688
683
|
self,
|
|
684
|
+
namespace: str,
|
|
689
685
|
user_id: Optional[str] = None,
|
|
690
686
|
) -> Callable:
|
|
691
687
|
"""Create the async search_learnings tool for the agent."""
|
|
@@ -693,7 +689,6 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
693
689
|
async def search_learnings(
|
|
694
690
|
query: str,
|
|
695
691
|
limit: int = 5,
|
|
696
|
-
namespace: Optional[str] = None,
|
|
697
692
|
) -> str:
|
|
698
693
|
"""Search for relevant insights in the knowledge base.
|
|
699
694
|
|
|
@@ -703,9 +698,8 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
703
698
|
|
|
704
699
|
Args:
|
|
705
700
|
query: Keywords describing what you're looking for.
|
|
706
|
-
Examples: "cloud costs", "API rate limiting", "
|
|
701
|
+
Examples: "cloud costs", "API rate limiting", "team goals"
|
|
707
702
|
limit: Maximum results (default: 5)
|
|
708
|
-
namespace: Filter by scope (None = all, "global", "user", or custom)
|
|
709
703
|
|
|
710
704
|
Returns:
|
|
711
705
|
List of relevant learnings, or message if none found.
|
|
@@ -893,7 +887,7 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
893
887
|
log_warning("LearnedKnowledgeStore.save: no knowledge base configured")
|
|
894
888
|
return False
|
|
895
889
|
|
|
896
|
-
effective_namespace = namespace or
|
|
890
|
+
effective_namespace = namespace or self.config.namespace
|
|
897
891
|
|
|
898
892
|
# Validate "user" namespace has user_id
|
|
899
893
|
if effective_namespace == "user" and not user_id:
|
|
@@ -963,7 +957,7 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
963
957
|
log_warning("LearnedKnowledgeStore.asave: no knowledge base configured")
|
|
964
958
|
return False
|
|
965
959
|
|
|
966
|
-
effective_namespace = namespace or
|
|
960
|
+
effective_namespace = namespace or self.config.namespace
|
|
967
961
|
|
|
968
962
|
# Validate "user" namespace has user_id
|
|
969
963
|
if effective_namespace == "user" and not user_id:
|
|
@@ -988,7 +982,7 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
988
982
|
learning_obj = self.schema(**learning_data)
|
|
989
983
|
text_content = self._to_text_content(learning=learning_obj)
|
|
990
984
|
|
|
991
|
-
# Build metadata for filtering
|
|
985
|
+
# Build metadata for filtering
|
|
992
986
|
# Metadata must be passed separately to insert for filters to work
|
|
993
987
|
filter_metadata: dict[str, Any] = {
|
|
994
988
|
"namespace": effective_namespace,
|
|
@@ -1181,49 +1175,48 @@ class LearnedKnowledgeStore(LearningStore):
|
|
|
1181
1175
|
from agno.models.message import Message
|
|
1182
1176
|
|
|
1183
1177
|
system_prompt = dedent("""\
|
|
1184
|
-
You are a Learning Extractor. Your job is to identify
|
|
1185
|
-
from conversations
|
|
1178
|
+
You are a Learning Extractor. Your job is to identify knowledge worth preserving
|
|
1179
|
+
from conversations for future use.
|
|
1180
|
+
|
|
1181
|
+
## What to Save
|
|
1186
1182
|
|
|
1187
|
-
|
|
1183
|
+
**1. Discovered Insights** - Knowledge that emerged through the conversation:
|
|
1184
|
+
- Non-obvious (required reasoning or investigation)
|
|
1185
|
+
- Reusable (applies to a category of problems)
|
|
1186
|
+
- Actionable (specific enough to apply directly)
|
|
1187
|
+
- Durable (won't become outdated quickly)
|
|
1188
1188
|
|
|
1189
|
-
|
|
1190
|
-
-
|
|
1191
|
-
-
|
|
1192
|
-
-
|
|
1193
|
-
-
|
|
1194
|
-
- **Durable**: It won't become outdated quickly
|
|
1189
|
+
**2. Organizational Context** - Explicit directives shared by the user:
|
|
1190
|
+
- Explicit save requests: "remember that...", "note that...", "keep in mind..."
|
|
1191
|
+
- Team/org goals: "we're trying to...", "our goal is...", "our priority is..."
|
|
1192
|
+
- Constraints: "we can't use...", "we need to avoid..."
|
|
1193
|
+
- Policies: "our policy is...", "we always...", "we never..."
|
|
1195
1194
|
|
|
1196
1195
|
## What NOT to Save
|
|
1197
1196
|
|
|
1198
|
-
-
|
|
1199
|
-
-
|
|
1200
|
-
-
|
|
1201
|
-
-
|
|
1202
|
-
-
|
|
1203
|
-
-
|
|
1197
|
+
- Raw facts (use search for retrieval)
|
|
1198
|
+
- Individual user preferences (belongs in user memory)
|
|
1199
|
+
- Common knowledge (everyone knows this)
|
|
1200
|
+
- One-off answers (not generalizable)
|
|
1201
|
+
- Summaries without insight
|
|
1202
|
+
- Uncertain conclusions
|
|
1204
1203
|
|
|
1205
|
-
## Examples
|
|
1204
|
+
## Examples
|
|
1206
1205
|
|
|
1207
|
-
|
|
1206
|
+
Good - Discovered insight:
|
|
1208
1207
|
> **Title:** Debugging intermittent PostgreSQL connection timeouts
|
|
1209
|
-
> **Learning:**
|
|
1210
|
-
>
|
|
1211
|
-
> long-running transactions that hold connections.
|
|
1212
|
-
> **Context:** Diagnosing database connectivity issues in production
|
|
1208
|
+
> **Learning:** Check for connection pool exhaustion before investigating network issues.
|
|
1209
|
+
> **Context:** Diagnosing database connectivity issues
|
|
1213
1210
|
|
|
1214
|
-
|
|
1215
|
-
> **Title:**
|
|
1216
|
-
> **Learning:**
|
|
1217
|
-
>
|
|
1218
|
-
> data model unchanged - you get audit without the full event sourcing overhead.
|
|
1219
|
-
> **Context:** Evaluating architecture patterns when audit trails are required
|
|
1211
|
+
Good - Organizational context:
|
|
1212
|
+
> **Title:** Team goal: reduce cloud egress costs
|
|
1213
|
+
> **Learning:** Factor egress costs into architecture decisions.
|
|
1214
|
+
> **Context:** Infrastructure and vendor decisions
|
|
1220
1215
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
- "
|
|
1224
|
-
- "
|
|
1225
|
-
- "We discussed three options for the database" (summary, no insight)
|
|
1226
|
-
- "Always write tests" (too vague to be actionable)
|
|
1216
|
+
Bad (don't save):
|
|
1217
|
+
- "The error was a typo on line 42" (one-off)
|
|
1218
|
+
- "React is popular" (common knowledge)
|
|
1219
|
+
- "We discussed options" (summary, no insight)
|
|
1227
1220
|
|
|
1228
1221
|
""")
|
|
1229
1222
|
|
|
@@ -1239,14 +1232,14 @@ These insights are already in the knowledge base. Do not save variations of thes
|
|
|
1239
1232
|
system_prompt += dedent("""\
|
|
1240
1233
|
## Your Task
|
|
1241
1234
|
|
|
1242
|
-
Review the conversation below.
|
|
1243
|
-
|
|
1235
|
+
Review the conversation below. Save anything that fits the criteria above:
|
|
1236
|
+
- Discovered insights worth preserving
|
|
1237
|
+
- Organizational context the user shared (goals, constraints, policies)
|
|
1244
1238
|
|
|
1245
1239
|
**Important:**
|
|
1246
|
-
- Most conversations will NOT produce a learning. That's expected
|
|
1240
|
+
- Most conversations will NOT produce a learning. That's expected.
|
|
1247
1241
|
- When in doubt, don't save. Quality over quantity.
|
|
1248
|
-
-
|
|
1249
|
-
- It's perfectly fine to do nothing if there's nothing worth saving.\
|
|
1242
|
+
- It's fine to do nothing if there's nothing worth saving.\
|
|
1250
1243
|
""")
|
|
1251
1244
|
|
|
1252
1245
|
return [
|
|
@@ -1262,7 +1255,7 @@ These insights are already in the knowledge base. Do not save variations of thes
|
|
|
1262
1255
|
namespace: Optional[str] = None,
|
|
1263
1256
|
) -> List[Callable]:
|
|
1264
1257
|
"""Get sync extraction tools."""
|
|
1265
|
-
effective_namespace = namespace or
|
|
1258
|
+
effective_namespace = namespace or self.config.namespace
|
|
1266
1259
|
|
|
1267
1260
|
def save_learning(
|
|
1268
1261
|
title: str,
|
|
@@ -1270,18 +1263,12 @@ These insights are already in the knowledge base. Do not save variations of thes
|
|
|
1270
1263
|
context: Optional[str] = None,
|
|
1271
1264
|
tags: Optional[List[str]] = None,
|
|
1272
1265
|
) -> str:
|
|
1273
|
-
"""Save a
|
|
1274
|
-
|
|
1275
|
-
Only call this if you've identified something that:
|
|
1276
|
-
- Required investigation or reasoning to discover
|
|
1277
|
-
- Would help with similar future tasks
|
|
1278
|
-
- Isn't already captured in existing learnings
|
|
1279
|
-
- Is specific and actionable enough to apply directly
|
|
1266
|
+
"""Save a reusable insight or organizational context.
|
|
1280
1267
|
|
|
1281
1268
|
Args:
|
|
1282
|
-
title: Concise, searchable title
|
|
1283
|
-
learning: The insight
|
|
1284
|
-
context: When/where this applies
|
|
1269
|
+
title: Concise, searchable title.
|
|
1270
|
+
learning: The insight or context - specific and actionable.
|
|
1271
|
+
context: When/where this applies.
|
|
1285
1272
|
tags: Categories for organization.
|
|
1286
1273
|
|
|
1287
1274
|
Returns:
|
|
@@ -1309,7 +1296,7 @@ These insights are already in the knowledge base. Do not save variations of thes
|
|
|
1309
1296
|
namespace: Optional[str] = None,
|
|
1310
1297
|
) -> List[Callable]:
|
|
1311
1298
|
"""Get async extraction tools."""
|
|
1312
|
-
effective_namespace = namespace or
|
|
1299
|
+
effective_namespace = namespace or self.config.namespace
|
|
1313
1300
|
|
|
1314
1301
|
async def save_learning(
|
|
1315
1302
|
title: str,
|
|
@@ -1317,18 +1304,12 @@ These insights are already in the knowledge base. Do not save variations of thes
|
|
|
1317
1304
|
context: Optional[str] = None,
|
|
1318
1305
|
tags: Optional[List[str]] = None,
|
|
1319
1306
|
) -> str:
|
|
1320
|
-
"""Save a
|
|
1321
|
-
|
|
1322
|
-
Only call this if you've identified something that:
|
|
1323
|
-
- Required investigation or reasoning to discover
|
|
1324
|
-
- Would help with similar future tasks
|
|
1325
|
-
- Isn't already captured in existing learnings
|
|
1326
|
-
- Is specific and actionable enough to apply directly
|
|
1307
|
+
"""Save a reusable insight or organizational context.
|
|
1327
1308
|
|
|
1328
1309
|
Args:
|
|
1329
|
-
title: Concise, searchable title
|
|
1330
|
-
learning: The insight
|
|
1331
|
-
context: When/where this applies
|
|
1310
|
+
title: Concise, searchable title.
|
|
1311
|
+
learning: The insight or context - specific and actionable.
|
|
1312
|
+
context: When/where this applies.
|
|
1332
1313
|
tags: Categories for organization.
|
|
1333
1314
|
|
|
1334
1315
|
Returns:
|
|
@@ -1399,10 +1380,6 @@ These insights are already in the knowledge base. Do not save variations of thes
|
|
|
1399
1380
|
# Private Helpers
|
|
1400
1381
|
# =========================================================================
|
|
1401
1382
|
|
|
1402
|
-
def _build_learning_id(self, title: str) -> str:
|
|
1403
|
-
"""Build a unique learning ID from title."""
|
|
1404
|
-
return f"learning_{title.lower().replace(' ', '_')[:32]}"
|
|
1405
|
-
|
|
1406
1383
|
def _parse_result(self, result: Any) -> Optional[Any]:
|
|
1407
1384
|
"""Parse a search result into a learning object."""
|
|
1408
1385
|
import json
|
agno/run/workflow.py
CHANGED
|
@@ -311,6 +311,9 @@ class ConditionExecutionCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
311
311
|
condition_result: Optional[bool] = None
|
|
312
312
|
executed_steps: Optional[int] = None
|
|
313
313
|
|
|
314
|
+
# Which branch was executed: "if", "else", or None (condition false with no else_steps)
|
|
315
|
+
branch: Optional[str] = None
|
|
316
|
+
|
|
314
317
|
# Results from executed steps
|
|
315
318
|
step_results: List[StepOutput] = field(default_factory=list)
|
|
316
319
|
|
agno/tools/mcp/mcp.py
CHANGED
|
@@ -74,6 +74,13 @@ class MCPTools(Toolkit):
|
|
|
74
74
|
Only relevant with HTTP transports (Streamable HTTP or SSE).
|
|
75
75
|
Creates a new session per agent run with dynamic headers merged into connection config.
|
|
76
76
|
"""
|
|
77
|
+
# Extract these before super().__init__() to bypass early validation
|
|
78
|
+
# (tools aren't available until build_tools() is called)
|
|
79
|
+
requires_confirmation_tools = kwargs.pop("requires_confirmation_tools", None)
|
|
80
|
+
external_execution_required_tools = kwargs.pop("external_execution_required_tools", None)
|
|
81
|
+
stop_after_tool_call_tools = kwargs.pop("stop_after_tool_call_tools", None)
|
|
82
|
+
show_result_tools = kwargs.pop("show_result_tools", None)
|
|
83
|
+
|
|
77
84
|
super().__init__(name="MCPTools", **kwargs)
|
|
78
85
|
|
|
79
86
|
if url is not None:
|
|
@@ -92,6 +99,10 @@ class MCPTools(Toolkit):
|
|
|
92
99
|
# because tools are not available until `initialize()` is called.
|
|
93
100
|
self.include_tools = include_tools
|
|
94
101
|
self.exclude_tools = exclude_tools
|
|
102
|
+
self.requires_confirmation_tools = requires_confirmation_tools or []
|
|
103
|
+
self.external_execution_required_tools = external_execution_required_tools or []
|
|
104
|
+
self.stop_after_tool_call_tools = stop_after_tool_call_tools or []
|
|
105
|
+
self.show_result_tools = show_result_tools or []
|
|
95
106
|
self.refresh_connection = refresh_connection
|
|
96
107
|
self.tool_name_prefix = tool_name_prefix
|
|
97
108
|
|
|
@@ -575,13 +586,27 @@ class MCPTools(Toolkit):
|
|
|
575
586
|
mcp_tools_instance=self,
|
|
576
587
|
)
|
|
577
588
|
# Create a Function for the tool
|
|
589
|
+
# Apply toolkit-level settings
|
|
590
|
+
tool_name = tool.name
|
|
591
|
+
stop_after = tool_name in self.stop_after_tool_call_tools
|
|
592
|
+
show_result = tool_name in self.show_result_tools or stop_after
|
|
593
|
+
|
|
578
594
|
f = Function(
|
|
579
|
-
name=tool_name_prefix +
|
|
595
|
+
name=tool_name_prefix + tool_name,
|
|
580
596
|
description=tool.description,
|
|
581
597
|
parameters=tool.inputSchema,
|
|
582
598
|
entrypoint=entrypoint,
|
|
583
599
|
# Set skip_entrypoint_processing to True to avoid processing the entrypoint
|
|
584
600
|
skip_entrypoint_processing=True,
|
|
601
|
+
# Apply toolkit-level settings for HITL and control flow
|
|
602
|
+
requires_confirmation=tool_name in self.requires_confirmation_tools,
|
|
603
|
+
external_execution=tool_name in self.external_execution_required_tools,
|
|
604
|
+
stop_after_tool_call=stop_after,
|
|
605
|
+
show_result=show_result,
|
|
606
|
+
# Apply toolkit-level cache settings
|
|
607
|
+
cache_results=self.cache_results,
|
|
608
|
+
cache_dir=self.cache_dir,
|
|
609
|
+
cache_ttl=self.cache_ttl,
|
|
585
610
|
)
|
|
586
611
|
|
|
587
612
|
# Register the Function with the toolkit
|
|
@@ -57,7 +57,7 @@ def print_response_stream(
|
|
|
57
57
|
accumulated_tool_calls: List = []
|
|
58
58
|
|
|
59
59
|
with Live(console=console) as live_log:
|
|
60
|
-
status = Status("
|
|
60
|
+
status = Status("Working...", spinner="aesthetic", speed=0.4, refresh_per_second=10)
|
|
61
61
|
live_log.update(status)
|
|
62
62
|
response_timer = Timer()
|
|
63
63
|
response_timer.start()
|
|
@@ -224,7 +224,7 @@ def print_response_stream(
|
|
|
224
224
|
|
|
225
225
|
response_timer.stop()
|
|
226
226
|
|
|
227
|
-
# Final update to remove the "
|
|
227
|
+
# Final update to remove the "Working..." status
|
|
228
228
|
panels = [p for p in panels if not isinstance(p, Status)]
|
|
229
229
|
live_log.update(Group(*panels))
|
|
230
230
|
|
|
@@ -262,7 +262,7 @@ async def aprint_response_stream(
|
|
|
262
262
|
accumulated_tool_calls: List = []
|
|
263
263
|
|
|
264
264
|
with Live(console=console) as live_log:
|
|
265
|
-
status = Status("
|
|
265
|
+
status = Status("Working...", spinner="aesthetic", speed=0.4, refresh_per_second=10)
|
|
266
266
|
live_log.update(status)
|
|
267
267
|
response_timer = Timer()
|
|
268
268
|
response_timer.start()
|
|
@@ -429,7 +429,7 @@ async def aprint_response_stream(
|
|
|
429
429
|
|
|
430
430
|
response_timer.stop()
|
|
431
431
|
|
|
432
|
-
# Final update to remove the "
|
|
432
|
+
# Final update to remove the "Working..." status
|
|
433
433
|
panels = [p for p in panels if not isinstance(p, Status)]
|
|
434
434
|
live_log.update(Group(*panels))
|
|
435
435
|
|
|
@@ -571,7 +571,7 @@ def print_response(
|
|
|
571
571
|
**kwargs: Any,
|
|
572
572
|
):
|
|
573
573
|
with Live(console=console) as live_log:
|
|
574
|
-
status = Status("
|
|
574
|
+
status = Status("Working...", spinner="aesthetic", speed=0.4, refresh_per_second=10)
|
|
575
575
|
live_log.update(status)
|
|
576
576
|
response_timer = Timer()
|
|
577
577
|
response_timer.start()
|
|
@@ -659,7 +659,7 @@ def print_response(
|
|
|
659
659
|
live_log.update(Group(*panels))
|
|
660
660
|
agent.session_summary_manager.summaries_updated = False
|
|
661
661
|
|
|
662
|
-
# Final update to remove the "
|
|
662
|
+
# Final update to remove the "Working..." status
|
|
663
663
|
panels = [p for p in panels if not isinstance(p, Status)]
|
|
664
664
|
live_log.update(Group(*panels))
|
|
665
665
|
|
|
@@ -691,7 +691,7 @@ async def aprint_response(
|
|
|
691
691
|
**kwargs: Any,
|
|
692
692
|
):
|
|
693
693
|
with Live(console=console) as live_log:
|
|
694
|
-
status = Status("
|
|
694
|
+
status = Status("Working...", spinner="aesthetic", speed=0.4, refresh_per_second=10)
|
|
695
695
|
live_log.update(status)
|
|
696
696
|
response_timer = Timer()
|
|
697
697
|
response_timer.start()
|
|
@@ -779,7 +779,7 @@ async def aprint_response(
|
|
|
779
779
|
panels.append(summary_panel)
|
|
780
780
|
live_log.update(Group(*panels))
|
|
781
781
|
|
|
782
|
-
# Final update to remove the "
|
|
782
|
+
# Final update to remove the "Working..." status
|
|
783
783
|
panels = [p for p in panels if not isinstance(p, Status)]
|
|
784
784
|
live_log.update(Group(*panels))
|
|
785
785
|
|