kader 0.1.6__py3-none-any.whl → 1.1.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
@@ -23,7 +23,9 @@ from kader.providers.base import (
23
23
  Message,
24
24
  ModelConfig,
25
25
  StreamChunk,
26
+ Usage,
26
27
  )
28
+ from kader.providers.google import GoogleProvider
27
29
  from kader.providers.ollama import OllamaProvider
28
30
  from kader.tools import BaseTool, ToolRegistry
29
31
 
@@ -48,11 +50,15 @@ class BaseAgent:
48
50
  provider: Optional[BaseLLMProvider] = None,
49
51
  memory: Optional[ConversationManager] = None,
50
52
  retry_attempts: int = 3,
53
+ retry_wait_min: int = 1,
54
+ retry_wait_max: int = 5,
51
55
  model_name: str = "qwen3-coder:480b-cloud",
52
56
  session_id: Optional[str] = None,
53
57
  use_persistence: bool = False,
54
58
  interrupt_before_tool: bool = True,
55
59
  tool_confirmation_callback: Optional[callable] = None,
60
+ direct_execution_callback: Optional[callable] = None,
61
+ tool_execution_result_callback: Optional[callable] = None,
56
62
  ) -> None:
57
63
  """
58
64
  Initialize the Base Agent.
@@ -75,8 +81,12 @@ class BaseAgent:
75
81
  self.name = name
76
82
  self.system_prompt = system_prompt
77
83
  self.retry_attempts = retry_attempts
84
+ self.retry_wait_min = retry_wait_min
85
+ self.retry_wait_max = retry_wait_max
78
86
  self.interrupt_before_tool = interrupt_before_tool
79
87
  self.tool_confirmation_callback = tool_confirmation_callback
88
+ self.direct_execution_callback = direct_execution_callback
89
+ self.tool_execution_result_callback = tool_execution_result_callback
80
90
 
81
91
  # Persistence Configuration
82
92
  self.session_id = session_id
@@ -214,6 +224,8 @@ class BaseAgent:
214
224
  provider_type = "openai"
215
225
  if isinstance(self.provider, OllamaProvider):
216
226
  provider_type = "ollama"
227
+ elif isinstance(self.provider, GoogleProvider):
228
+ provider_type = "google"
217
229
 
218
230
  base_config = ModelConfig(
219
231
  temperature=base_config.temperature,
@@ -339,6 +351,31 @@ class BaseAgent:
339
351
  if llm_content and len(llm_content) > 0:
340
352
  display_str = f"{llm_content}\n\n{display_str}"
341
353
 
354
+ # Extract tool name for direct execution check
355
+ fn_info = tool_call_dict.get("function", {})
356
+ if not fn_info and "name" in tool_call_dict:
357
+ fn_info = tool_call_dict
358
+ tool_name = fn_info.get("name", "")
359
+
360
+ # List of tools to execute directly (show message but don't ask for confirmation)
361
+ direct_execution_tools = {
362
+ "read_file",
363
+ "glob",
364
+ "grep",
365
+ "read_directory",
366
+ "read_dir",
367
+ }
368
+
369
+ # Direct execution for specific tools - applies regardless of callback
370
+ if tool_name in direct_execution_tools:
371
+ # Notify via direct_execution_callback if available (for CLI/TUI display)
372
+ if self.direct_execution_callback:
373
+ self.direct_execution_callback(display_str, tool_name)
374
+ else:
375
+ # Fallback: print to console
376
+ print(display_str)
377
+ return True, None
378
+
342
379
  # Use callback if provided (e.g., for GUI/TUI)
343
380
  if self.tool_confirmation_callback:
344
381
  return self.tool_confirmation_callback(display_str)
@@ -435,6 +472,18 @@ class BaseAgent:
435
472
  # Execute tool
436
473
  tool_result = self._tool_registry.run(tool_call)
437
474
 
475
+ # Notify about tool execution result if callback available
476
+ if self.tool_execution_result_callback:
477
+ # Handle both enum and string status
478
+ status = tool_result.status
479
+ status_value = (
480
+ status.value if hasattr(status, "value") else str(status)
481
+ )
482
+ success = status_value == "success"
483
+ self.tool_execution_result_callback(
484
+ tool_call.name, success, tool_result.content
485
+ )
486
+
438
487
  # add result to memory
439
488
  # But here we just return messages, caller handles memory add
440
489
  tool_msg = Message.tool(
@@ -482,6 +531,18 @@ class BaseAgent:
482
531
  # Execute tool async
483
532
  tool_result = await self._tool_registry.arun(tool_call)
484
533
 
534
+ # Notify about tool execution result if callback available
535
+ if self.tool_execution_result_callback:
536
+ # Handle both enum and string status
537
+ status = tool_result.status
538
+ status_value = (
539
+ status.value if hasattr(status, "value") else str(status)
540
+ )
541
+ success = status_value == "success"
542
+ self.tool_execution_result_callback(
543
+ tool_call.name, success, tool_result.content
544
+ )
545
+
485
546
  tool_msg = Message.tool(
486
547
  tool_call_id=tool_result.tool_call_id, content=tool_result.content
487
548
  )
@@ -509,7 +570,9 @@ class BaseAgent:
509
570
 
510
571
  runner = Retrying(
511
572
  stop=stop_after_attempt(self.retry_attempts),
512
- wait=wait_exponential(multiplier=1, min=4, max=10),
573
+ wait=wait_exponential(
574
+ multiplier=1, min=self.retry_wait_min, max=self.retry_wait_max
575
+ ),
513
576
  reraise=True,
514
577
  )
515
578
 
@@ -565,7 +628,12 @@ class BaseAgent:
565
628
  )
566
629
 
567
630
  # estimate the cost...
568
- estimated_cost = self.provider.estimate_cost(token_usage)
631
+ usage_obj = Usage(
632
+ prompt_tokens=token_usage["prompt_tokens"],
633
+ completion_tokens=token_usage["completion_tokens"],
634
+ total_tokens=token_usage["total_tokens"],
635
+ )
636
+ estimated_cost = self.provider.estimate_cost(usage_obj)
569
637
 
570
638
  # Calculate and log cost
571
639
  agent_logger.calculate_cost(
@@ -652,7 +720,9 @@ class BaseAgent:
652
720
 
653
721
  runner = Retrying(
654
722
  stop=stop_after_attempt(self.retry_attempts),
655
- wait=wait_exponential(multiplier=1, min=4, max=10),
723
+ wait=wait_exponential(
724
+ multiplier=1, min=self.retry_wait_min, max=self.retry_wait_max
725
+ ),
656
726
  reraise=True,
657
727
  )
658
728
 
@@ -689,7 +759,9 @@ class BaseAgent:
689
759
 
690
760
  runner = AsyncRetrying(
691
761
  stop=stop_after_attempt(self.retry_attempts),
692
- wait=wait_exponential(multiplier=1, min=4, max=10),
762
+ wait=wait_exponential(
763
+ multiplier=1, min=self.retry_wait_min, max=self.retry_wait_max
764
+ ),
693
765
  reraise=True,
694
766
  )
695
767
 
@@ -733,7 +805,12 @@ class BaseAgent:
733
805
  )
734
806
 
735
807
  # estimate the cost...
736
- estimated_cost = self.provider.estimate_cost(token_usage)
808
+ usage_obj = Usage(
809
+ prompt_tokens=token_usage["prompt_tokens"],
810
+ completion_tokens=token_usage["completion_tokens"],
811
+ total_tokens=token_usage["total_tokens"],
812
+ )
813
+ estimated_cost = self.provider.estimate_cost(usage_obj)
737
814
 
738
815
  # Calculate and log cost
739
816
  agent_logger.calculate_cost(
@@ -905,9 +982,9 @@ class BaseAgent:
905
982
  if registry is None:
906
983
  # Lazy import to avoid circular dependencies if any
907
984
  try:
908
- from kader.tools import get_default_registry
985
+ from kader.tools import get_cached_default_registry
909
986
 
910
- registry = get_default_registry()
987
+ registry = get_cached_default_registry()
911
988
  except ImportError:
912
989
  pass
913
990
 
kader/config.py CHANGED
@@ -69,13 +69,21 @@ def ensure_kader_directory():
69
69
  def ensure_env_file(kader_dir):
70
70
  """
71
71
  Ensure that the .env file exists in the .kader directory with the
72
- required OLLAMA_API_KEY configuration.
72
+ required API key configurations.
73
73
  """
74
74
  env_file = kader_dir / ".env"
75
75
 
76
76
  # Create the .env file if it doesn't exist
77
77
  if not env_file.exists():
78
- env_file.write_text("OLLAMA_API_KEY=''\n", encoding="utf-8")
78
+ default_env_content = """# Kader Configuration
79
+ # Ollama API Key (for local Ollama models)
80
+ OLLAMA_API_KEY=''
81
+
82
+ # Google Gemini API Key (for Google Gemini models)
83
+ # Get your API key from: https://aistudio.google.com/apikey
84
+ GEMINI_API_KEY=''
85
+ """
86
+ env_file.write_text(default_env_content, encoding="utf-8")
79
87
 
80
88
  # Set appropriate permissions for the .env file on Unix-like systems
81
89
  if not sys.platform.startswith("win"):
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
@@ -1,9 +1,11 @@
1
1
  from .base import Message
2
+ from .google import GoogleProvider
2
3
  from .mock import MockLLM
3
4
  from .ollama import OllamaProvider
4
5
 
5
6
  __all__ = [
6
7
  "Message",
7
8
  "OllamaProvider",
9
+ "GoogleProvider",
8
10
  "MockLLM",
9
11
  ]