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.
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(multiplier=1, min=4, max=10),
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(multiplier=1, min=4, max=10),
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(multiplier=1, min=4, max=10),
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 get_default_registry
971
+ from kader.tools import get_cached_default_registry
909
972
 
910
- registry = get_default_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 BasicAssistancePrompt, PlanningAgentPrompt, ReActAgentPrompt
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
  ]
@@ -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
  ]