kader 0.1.5__py3-none-any.whl → 1.0.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.
- cli/app.py +98 -61
- cli/app.tcss +27 -382
- cli/utils.py +1 -6
- cli/widgets/conversation.py +50 -4
- kader/__init__.py +2 -0
- kader/agent/agents.py +8 -0
- kader/agent/base.py +68 -5
- kader/memory/types.py +60 -0
- kader/prompts/__init__.py +9 -1
- kader/prompts/agent_prompts.py +28 -0
- kader/prompts/templates/executor_agent.j2 +70 -0
- kader/prompts/templates/kader_planner.j2 +71 -0
- kader/providers/ollama.py +2 -2
- kader/tools/__init__.py +26 -0
- kader/tools/agent.py +452 -0
- kader/tools/filesys.py +1 -1
- kader/tools/todo.py +43 -2
- kader/utils/__init__.py +10 -0
- kader/utils/checkpointer.py +371 -0
- kader/utils/context_aggregator.py +347 -0
- kader/workflows/__init__.py +13 -0
- kader/workflows/base.py +71 -0
- kader/workflows/planner_executor.py +251 -0
- {kader-0.1.5.dist-info → kader-1.0.0.dist-info}/METADATA +38 -1
- {kader-0.1.5.dist-info → kader-1.0.0.dist-info}/RECORD +27 -18
- {kader-0.1.5.dist-info → kader-1.0.0.dist-info}/WHEEL +0 -0
- {kader-0.1.5.dist-info → kader-1.0.0.dist-info}/entry_points.txt +0 -0
kader/agent/base.py
CHANGED
|
@@ -48,11 +48,15 @@ class BaseAgent:
|
|
|
48
48
|
provider: Optional[BaseLLMProvider] = None,
|
|
49
49
|
memory: Optional[ConversationManager] = None,
|
|
50
50
|
retry_attempts: int = 3,
|
|
51
|
+
retry_wait_min: int = 1,
|
|
52
|
+
retry_wait_max: int = 5,
|
|
51
53
|
model_name: str = "qwen3-coder:480b-cloud",
|
|
52
54
|
session_id: Optional[str] = None,
|
|
53
55
|
use_persistence: bool = False,
|
|
54
56
|
interrupt_before_tool: bool = True,
|
|
55
57
|
tool_confirmation_callback: Optional[callable] = None,
|
|
58
|
+
direct_execution_callback: Optional[callable] = None,
|
|
59
|
+
tool_execution_result_callback: Optional[callable] = None,
|
|
56
60
|
) -> None:
|
|
57
61
|
"""
|
|
58
62
|
Initialize the Base Agent.
|
|
@@ -75,8 +79,12 @@ class BaseAgent:
|
|
|
75
79
|
self.name = name
|
|
76
80
|
self.system_prompt = system_prompt
|
|
77
81
|
self.retry_attempts = retry_attempts
|
|
82
|
+
self.retry_wait_min = retry_wait_min
|
|
83
|
+
self.retry_wait_max = retry_wait_max
|
|
78
84
|
self.interrupt_before_tool = interrupt_before_tool
|
|
79
85
|
self.tool_confirmation_callback = tool_confirmation_callback
|
|
86
|
+
self.direct_execution_callback = direct_execution_callback
|
|
87
|
+
self.tool_execution_result_callback = tool_execution_result_callback
|
|
80
88
|
|
|
81
89
|
# Persistence Configuration
|
|
82
90
|
self.session_id = session_id
|
|
@@ -339,6 +347,31 @@ class BaseAgent:
|
|
|
339
347
|
if llm_content and len(llm_content) > 0:
|
|
340
348
|
display_str = f"{llm_content}\n\n{display_str}"
|
|
341
349
|
|
|
350
|
+
# Extract tool name for direct execution check
|
|
351
|
+
fn_info = tool_call_dict.get("function", {})
|
|
352
|
+
if not fn_info and "name" in tool_call_dict:
|
|
353
|
+
fn_info = tool_call_dict
|
|
354
|
+
tool_name = fn_info.get("name", "")
|
|
355
|
+
|
|
356
|
+
# List of tools to execute directly (show message but don't ask for confirmation)
|
|
357
|
+
direct_execution_tools = {
|
|
358
|
+
"read_file",
|
|
359
|
+
"glob",
|
|
360
|
+
"grep",
|
|
361
|
+
"read_directory",
|
|
362
|
+
"read_dir",
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
# Direct execution for specific tools - applies regardless of callback
|
|
366
|
+
if tool_name in direct_execution_tools:
|
|
367
|
+
# Notify via direct_execution_callback if available (for CLI/TUI display)
|
|
368
|
+
if self.direct_execution_callback:
|
|
369
|
+
self.direct_execution_callback(display_str, tool_name)
|
|
370
|
+
else:
|
|
371
|
+
# Fallback: print to console
|
|
372
|
+
print(display_str)
|
|
373
|
+
return True, None
|
|
374
|
+
|
|
342
375
|
# Use callback if provided (e.g., for GUI/TUI)
|
|
343
376
|
if self.tool_confirmation_callback:
|
|
344
377
|
return self.tool_confirmation_callback(display_str)
|
|
@@ -435,6 +468,18 @@ class BaseAgent:
|
|
|
435
468
|
# Execute tool
|
|
436
469
|
tool_result = self._tool_registry.run(tool_call)
|
|
437
470
|
|
|
471
|
+
# Notify about tool execution result if callback available
|
|
472
|
+
if self.tool_execution_result_callback:
|
|
473
|
+
# Handle both enum and string status
|
|
474
|
+
status = tool_result.status
|
|
475
|
+
status_value = (
|
|
476
|
+
status.value if hasattr(status, "value") else str(status)
|
|
477
|
+
)
|
|
478
|
+
success = status_value == "success"
|
|
479
|
+
self.tool_execution_result_callback(
|
|
480
|
+
tool_call.name, success, tool_result.content
|
|
481
|
+
)
|
|
482
|
+
|
|
438
483
|
# add result to memory
|
|
439
484
|
# But here we just return messages, caller handles memory add
|
|
440
485
|
tool_msg = Message.tool(
|
|
@@ -482,6 +527,18 @@ class BaseAgent:
|
|
|
482
527
|
# Execute tool async
|
|
483
528
|
tool_result = await self._tool_registry.arun(tool_call)
|
|
484
529
|
|
|
530
|
+
# Notify about tool execution result if callback available
|
|
531
|
+
if self.tool_execution_result_callback:
|
|
532
|
+
# Handle both enum and string status
|
|
533
|
+
status = tool_result.status
|
|
534
|
+
status_value = (
|
|
535
|
+
status.value if hasattr(status, "value") else str(status)
|
|
536
|
+
)
|
|
537
|
+
success = status_value == "success"
|
|
538
|
+
self.tool_execution_result_callback(
|
|
539
|
+
tool_call.name, success, tool_result.content
|
|
540
|
+
)
|
|
541
|
+
|
|
485
542
|
tool_msg = Message.tool(
|
|
486
543
|
tool_call_id=tool_result.tool_call_id, content=tool_result.content
|
|
487
544
|
)
|
|
@@ -509,7 +566,9 @@ class BaseAgent:
|
|
|
509
566
|
|
|
510
567
|
runner = Retrying(
|
|
511
568
|
stop=stop_after_attempt(self.retry_attempts),
|
|
512
|
-
wait=wait_exponential(
|
|
569
|
+
wait=wait_exponential(
|
|
570
|
+
multiplier=1, min=self.retry_wait_min, max=self.retry_wait_max
|
|
571
|
+
),
|
|
513
572
|
reraise=True,
|
|
514
573
|
)
|
|
515
574
|
|
|
@@ -652,7 +711,9 @@ class BaseAgent:
|
|
|
652
711
|
|
|
653
712
|
runner = Retrying(
|
|
654
713
|
stop=stop_after_attempt(self.retry_attempts),
|
|
655
|
-
wait=wait_exponential(
|
|
714
|
+
wait=wait_exponential(
|
|
715
|
+
multiplier=1, min=self.retry_wait_min, max=self.retry_wait_max
|
|
716
|
+
),
|
|
656
717
|
reraise=True,
|
|
657
718
|
)
|
|
658
719
|
|
|
@@ -689,7 +750,9 @@ class BaseAgent:
|
|
|
689
750
|
|
|
690
751
|
runner = AsyncRetrying(
|
|
691
752
|
stop=stop_after_attempt(self.retry_attempts),
|
|
692
|
-
wait=wait_exponential(
|
|
753
|
+
wait=wait_exponential(
|
|
754
|
+
multiplier=1, min=self.retry_wait_min, max=self.retry_wait_max
|
|
755
|
+
),
|
|
693
756
|
reraise=True,
|
|
694
757
|
)
|
|
695
758
|
|
|
@@ -905,9 +968,9 @@ class BaseAgent:
|
|
|
905
968
|
if registry is None:
|
|
906
969
|
# Lazy import to avoid circular dependencies if any
|
|
907
970
|
try:
|
|
908
|
-
from kader.tools import
|
|
971
|
+
from kader.tools import get_cached_default_registry
|
|
909
972
|
|
|
910
|
-
registry =
|
|
973
|
+
registry = get_cached_default_registry()
|
|
911
974
|
except ImportError:
|
|
912
975
|
pass
|
|
913
976
|
|
kader/memory/types.py
CHANGED
|
@@ -114,3 +114,63 @@ def load_json(path: Path) -> dict[str, Any]:
|
|
|
114
114
|
return {}
|
|
115
115
|
with open(path, "r", encoding="utf-8") as f:
|
|
116
116
|
return decode_bytes_values(json.load(f))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# --- Async File I/O Utilities ---
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def aread_text(path: Path, encoding: str = "utf-8") -> str:
|
|
123
|
+
"""Asynchronously read text from a file.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
path: Path to the file
|
|
127
|
+
encoding: File encoding (default: utf-8)
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
File contents as string
|
|
131
|
+
"""
|
|
132
|
+
import aiofiles
|
|
133
|
+
|
|
134
|
+
async with aiofiles.open(path, "r", encoding=encoding) as f:
|
|
135
|
+
return await f.read()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def awrite_text(path: Path, content: str, encoding: str = "utf-8") -> None:
|
|
139
|
+
"""Asynchronously write text to a file.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
path: Path to the file
|
|
143
|
+
content: Text content to write
|
|
144
|
+
encoding: File encoding (default: utf-8)
|
|
145
|
+
"""
|
|
146
|
+
import aiofiles
|
|
147
|
+
|
|
148
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
async with aiofiles.open(path, "w", encoding=encoding) as f:
|
|
150
|
+
await f.write(content)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def asave_json(path: Path, data: dict[str, Any]) -> None:
|
|
154
|
+
"""Asynchronously save data to a JSON file.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
path: Path to the JSON file
|
|
158
|
+
data: Data to save
|
|
159
|
+
"""
|
|
160
|
+
content = json.dumps(encode_bytes_values(data), indent=2, ensure_ascii=False)
|
|
161
|
+
await awrite_text(path, content)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
async def aload_json(path: Path) -> dict[str, Any]:
|
|
165
|
+
"""Asynchronously load data from a JSON file.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
path: Path to the JSON file
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Loaded data, or empty dict if file doesn't exist
|
|
172
|
+
"""
|
|
173
|
+
if not path.exists():
|
|
174
|
+
return {}
|
|
175
|
+
content = await aread_text(path)
|
|
176
|
+
return decode_bytes_values(json.loads(content))
|
kader/prompts/__init__.py
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
from .agent_prompts import
|
|
1
|
+
from .agent_prompts import (
|
|
2
|
+
BasicAssistancePrompt,
|
|
3
|
+
ExecutorAgentPrompt,
|
|
4
|
+
KaderPlannerPrompt,
|
|
5
|
+
PlanningAgentPrompt,
|
|
6
|
+
ReActAgentPrompt,
|
|
7
|
+
)
|
|
2
8
|
from .base import PromptBase
|
|
3
9
|
|
|
4
10
|
__all__ = [
|
|
@@ -6,4 +12,6 @@ __all__ = [
|
|
|
6
12
|
"BasicAssistancePrompt",
|
|
7
13
|
"ReActAgentPrompt",
|
|
8
14
|
"PlanningAgentPrompt",
|
|
15
|
+
"KaderPlannerPrompt",
|
|
16
|
+
"ExecutorAgentPrompt",
|
|
9
17
|
]
|
kader/prompts/agent_prompts.py
CHANGED
|
@@ -25,3 +25,31 @@ class PlanningAgentPrompt(PromptBase):
|
|
|
25
25
|
|
|
26
26
|
def __init__(self, **kwargs: Any) -> None:
|
|
27
27
|
super().__init__(template_path="planning_agent.j2", **kwargs)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class KaderPlannerPrompt(PromptBase):
|
|
31
|
+
"""
|
|
32
|
+
Prompt for Kader Planner Agent.
|
|
33
|
+
|
|
34
|
+
Enhanced planning prompt with specific instructions for:
|
|
35
|
+
- Using Agent as a Tool with proper task/context parameters
|
|
36
|
+
- Tracking completed actions to avoid repetition
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
40
|
+
super().__init__(template_path="kader_planner.j2", **kwargs)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ExecutorAgentPrompt(PromptBase):
|
|
44
|
+
"""
|
|
45
|
+
Prompt for Executor Agent (sub-agents in PlannerExecutorWorkflow).
|
|
46
|
+
|
|
47
|
+
Emphasizes:
|
|
48
|
+
- Careful thinking before each action
|
|
49
|
+
- Safe execution with error handling
|
|
50
|
+
- Detailed step-by-step reporting of what was done
|
|
51
|
+
- Structured final answer with files created, summary, and issues
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
55
|
+
super().__init__(template_path="executor_agent.j2", **kwargs)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
You are Kader, an Executor Agent specialized in deep and complex tasks.
|
|
2
|
+
|
|
3
|
+
You excel at:
|
|
4
|
+
- Writing high-quality code and implementing features
|
|
5
|
+
- Deep research and comprehensive analysis
|
|
6
|
+
- Complex problem-solving and debugging
|
|
7
|
+
- Thorough documentation and testing
|
|
8
|
+
|
|
9
|
+
You have access to the following tools:
|
|
10
|
+
|
|
11
|
+
{% for tool in tools %}
|
|
12
|
+
{{ tool.name }}: {{ tool.description }}
|
|
13
|
+
{% endfor %}
|
|
14
|
+
|
|
15
|
+
## Your Objective
|
|
16
|
+
|
|
17
|
+
Complete the assigned task safely and thoroughly. Think carefully before each action.
|
|
18
|
+
|
|
19
|
+
## Execution Process
|
|
20
|
+
|
|
21
|
+
1. **Analyze the Task**: Before taking any action, carefully think about:
|
|
22
|
+
- What exactly needs to be done
|
|
23
|
+
- What could go wrong and how to prevent it
|
|
24
|
+
- The safest approach to complete the task
|
|
25
|
+
|
|
26
|
+
2. **Plan Your Steps**: Create a mental action plan before executing
|
|
27
|
+
|
|
28
|
+
3. **Execute Safely**: For each action:
|
|
29
|
+
- Think about potential side effects
|
|
30
|
+
- Verify inputs before executing
|
|
31
|
+
- Handle errors gracefully
|
|
32
|
+
|
|
33
|
+
4. **Track Your Work**: Keep track of:
|
|
34
|
+
- Files created or modified
|
|
35
|
+
- Commands executed
|
|
36
|
+
- Any issues encountered
|
|
37
|
+
|
|
38
|
+
## Response Format
|
|
39
|
+
|
|
40
|
+
Use the following format for reasoning:
|
|
41
|
+
|
|
42
|
+
Thought: Analyze what needs to be done and plan the approach
|
|
43
|
+
Action: the action to take, should be one of [{{ tool_names }}]
|
|
44
|
+
Action Input: the input to the action
|
|
45
|
+
Observation: the result of the action
|
|
46
|
+
... (this Thought/Action/Action Input/Observation can repeat N times)
|
|
47
|
+
Thought: I have completed the task
|
|
48
|
+
Final Answer: [Your detailed execution report]
|
|
49
|
+
|
|
50
|
+
## CRITICAL: Final Answer Requirements
|
|
51
|
+
|
|
52
|
+
Your Final Answer MUST include a structured execution report with:
|
|
53
|
+
|
|
54
|
+
### What Has Been Done
|
|
55
|
+
- List each action completed with details
|
|
56
|
+
|
|
57
|
+
### Files Created/Modified
|
|
58
|
+
- Full path and purpose of each file
|
|
59
|
+
- If no files: state "No files were created or modified"
|
|
60
|
+
|
|
61
|
+
### Execution Summary
|
|
62
|
+
- Brief summary of the overall execution
|
|
63
|
+
- Whether the task was completed successfully
|
|
64
|
+
|
|
65
|
+
### Issues/Notes
|
|
66
|
+
- Any errors encountered and how they were resolved
|
|
67
|
+
- Any warnings or important notes for the caller
|
|
68
|
+
- If no issues: state "No issues encountered"
|
|
69
|
+
|
|
70
|
+
Begin!
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
You are Kader, an intelligent planning agent. You have access to the following tools:
|
|
2
|
+
|
|
3
|
+
{% for tool in tools %}
|
|
4
|
+
{{ tool.name }}: {{ tool.description }}
|
|
5
|
+
{% endfor %}
|
|
6
|
+
|
|
7
|
+
{% if context %}
|
|
8
|
+
## Previous Context (What Has Been Done)
|
|
9
|
+
|
|
10
|
+
The following summarizes what has been accomplished in previous rounds. Consider this context when planning your next steps to avoid repeating completed work:
|
|
11
|
+
|
|
12
|
+
{{ context }}
|
|
13
|
+
|
|
14
|
+
{% endif %}
|
|
15
|
+
Your goal is to complete the user's request by creating a plan and executing it step-by-step.
|
|
16
|
+
|
|
17
|
+
## Core Instructions
|
|
18
|
+
|
|
19
|
+
1. Break down the user request into logical steps.
|
|
20
|
+
2. For each step, determine if a tool is needed.
|
|
21
|
+
3. Execute necessary tools.
|
|
22
|
+
4. CRITICAL: Always create a TODO using the TODO Tool for the plan.
|
|
23
|
+
5. CRITICAL: Follow the instructions of the TODO list strictly. The current item in the TODO list is your PRIMARY instruction.
|
|
24
|
+
6. CRITICAL: After completing a step (e.g., creating a file, running a test), you MUST immediately use the TODO Tool to update the status of that item to 'completed'. Do not proceed to the next item without updating the TODO list.
|
|
25
|
+
7. If you have enough information, provide the final answer.
|
|
26
|
+
|
|
27
|
+
## Using Agent as a Tool
|
|
28
|
+
|
|
29
|
+
When delegating tasks to sub-agents using the Agent Tool, follow these guidelines:
|
|
30
|
+
|
|
31
|
+
### Task Parameter
|
|
32
|
+
- **Primary Instruction**: This MUST come directly from the current item in your TODO list (e.g., "Implement unit tests", "Create database schema").
|
|
33
|
+
- **Secondary Instructions (Objectives)**: These are the supporting details or objectives for the sub-agent (e.g., "View the code to understand the context", "Ensure 100% coverage").
|
|
34
|
+
- **Structure**:
|
|
35
|
+
- Start with the Primary Instruction.
|
|
36
|
+
- Follow with "Objectives:" listing the Secondary Instructions.
|
|
37
|
+
- **Example**:
|
|
38
|
+
- If the TODO item is "Implement feature X", and you need the agent to also read the docs:
|
|
39
|
+
- Task: "Implement feature X. Objectives: 1. Read documentation at doc/feature.md. 2. Verify implementation with tests."
|
|
40
|
+
- **REQUIRED**: Include instruction that the agent MUST return:
|
|
41
|
+
- What has been done (actions completed)
|
|
42
|
+
- What files have been written or modified (if any)
|
|
43
|
+
- A summary of the execution
|
|
44
|
+
- Any issues or errors that occurred during execution
|
|
45
|
+
|
|
46
|
+
### Context Parameter (REQUIRED)
|
|
47
|
+
The context parameter MUST include:
|
|
48
|
+
1. **Brief about latest actions**: Summarize what has been done so far and the current state
|
|
49
|
+
2. **Completed actions to avoid repetition**: List actions that were successfully completed so the sub-agent does NOT repeat them
|
|
50
|
+
|
|
51
|
+
Example Agent Tool usage:
|
|
52
|
+
```
|
|
53
|
+
task: "Create unit tests for the user authentication module"
|
|
54
|
+
context: "We are building a user management system. So far, the User model and AuthService have been implemented in src/auth/. Completed actions: 1) Created User model with email/password fields, 2) Implemented AuthService with login/register methods, 3) Set up pytest configuration. Do NOT recreate these files."
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Example Planning Flow
|
|
58
|
+
|
|
59
|
+
Task: Implement a ML Inference Endpoint
|
|
60
|
+
|
|
61
|
+
Plan:
|
|
62
|
+
1. Analyze the requirements and design the system architecture
|
|
63
|
+
2. Implement the endpoint using a web framework
|
|
64
|
+
3. Test the endpoint to ensure it works as expected
|
|
65
|
+
4. Deploy the endpoint to a production environment
|
|
66
|
+
|
|
67
|
+
When executing this plan:
|
|
68
|
+
- Create the TODO list first
|
|
69
|
+
- For each step, consider if delegation to an Agent Tool is appropriate
|
|
70
|
+
- Update TODO status after completing each step
|
|
71
|
+
- Provide context to sub-agents about completed work
|
kader/providers/ollama.py
CHANGED
|
@@ -433,11 +433,11 @@ class OllamaProvider(BaseLLMProvider):
|
|
|
433
433
|
models_config = {}
|
|
434
434
|
for model in models:
|
|
435
435
|
models_config[model] = client.show(model)
|
|
436
|
+
accepted_capabilities = ["completion", "tools"]
|
|
436
437
|
return [
|
|
437
438
|
model
|
|
438
439
|
for model, config in models_config.items()
|
|
439
|
-
if config.capabilities
|
|
440
|
-
in [["completion", "tools", "thinking"], ["completion", "tools"]]
|
|
440
|
+
if set(accepted_capabilities).issubset(set(config.capabilities))
|
|
441
441
|
]
|
|
442
442
|
except Exception:
|
|
443
443
|
return []
|
kader/tools/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from kader.tools.exec_commands import (
|
|
|
9
9
|
CommandExecutorTool,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
+
from .agent import AgentTool
|
|
12
13
|
from .base import (
|
|
13
14
|
# Core classes
|
|
14
15
|
BaseTool,
|
|
@@ -84,6 +85,28 @@ def get_default_registry() -> ToolRegistry:
|
|
|
84
85
|
return registry
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
# Module-level cached registry singleton
|
|
89
|
+
_cached_default_registry: ToolRegistry | None = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_cached_default_registry() -> ToolRegistry:
|
|
93
|
+
"""
|
|
94
|
+
Get a cached registry populated with all standard tools.
|
|
95
|
+
|
|
96
|
+
This is more efficient than get_default_registry() when called multiple times,
|
|
97
|
+
as it avoids repeated tool instantiation and registration.
|
|
98
|
+
|
|
99
|
+
The cached registry is created once and reused for all subsequent calls.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Cached ToolRegistry with all standard tools registered.
|
|
103
|
+
"""
|
|
104
|
+
global _cached_default_registry
|
|
105
|
+
if _cached_default_registry is None:
|
|
106
|
+
_cached_default_registry = get_default_registry()
|
|
107
|
+
return _cached_default_registry
|
|
108
|
+
|
|
109
|
+
|
|
87
110
|
__all__ = [
|
|
88
111
|
# Core classes
|
|
89
112
|
"BaseTool",
|
|
@@ -125,6 +148,9 @@ __all__ = [
|
|
|
125
148
|
"CommandExecutorTool",
|
|
126
149
|
# Todo Tool
|
|
127
150
|
"TodoTool",
|
|
151
|
+
# Agent Tool
|
|
152
|
+
"AgentTool",
|
|
128
153
|
# Helpers
|
|
129
154
|
"get_default_registry",
|
|
155
|
+
"get_cached_default_registry",
|
|
130
156
|
]
|