kader 0.1.5__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/README.md +169 -0
- cli/__init__.py +5 -0
- cli/__main__.py +6 -0
- cli/app.py +707 -0
- cli/app.tcss +664 -0
- cli/utils.py +68 -0
- cli/widgets/__init__.py +13 -0
- cli/widgets/confirmation.py +309 -0
- cli/widgets/conversation.py +55 -0
- cli/widgets/loading.py +59 -0
- kader/__init__.py +22 -0
- kader/agent/__init__.py +8 -0
- kader/agent/agents.py +126 -0
- kader/agent/base.py +927 -0
- kader/agent/logger.py +170 -0
- kader/config.py +139 -0
- kader/memory/__init__.py +66 -0
- kader/memory/conversation.py +409 -0
- kader/memory/session.py +385 -0
- kader/memory/state.py +211 -0
- kader/memory/types.py +116 -0
- kader/prompts/__init__.py +9 -0
- kader/prompts/agent_prompts.py +27 -0
- kader/prompts/base.py +81 -0
- kader/prompts/templates/planning_agent.j2 +26 -0
- kader/prompts/templates/react_agent.j2 +18 -0
- kader/providers/__init__.py +9 -0
- kader/providers/base.py +581 -0
- kader/providers/mock.py +96 -0
- kader/providers/ollama.py +447 -0
- kader/tools/README.md +483 -0
- kader/tools/__init__.py +130 -0
- kader/tools/base.py +955 -0
- kader/tools/exec_commands.py +249 -0
- kader/tools/filesys.py +650 -0
- kader/tools/filesystem.py +607 -0
- kader/tools/protocol.py +456 -0
- kader/tools/rag.py +555 -0
- kader/tools/todo.py +210 -0
- kader/tools/utils.py +456 -0
- kader/tools/web.py +246 -0
- kader-0.1.5.dist-info/METADATA +321 -0
- kader-0.1.5.dist-info/RECORD +45 -0
- kader-0.1.5.dist-info/WHEEL +4 -0
- kader-0.1.5.dist-info/entry_points.txt +2 -0
kader/agent/logger.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logger module for Kader agents.
|
|
3
|
+
|
|
4
|
+
This module provides logging functionality for agents with memory sessions.
|
|
5
|
+
Logs are written to files in ~/.kader/logs without affecting agent performance.
|
|
6
|
+
Only agents with memory sessions will generate logs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from threading import Lock
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
from loguru import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentLogger:
|
|
18
|
+
"""
|
|
19
|
+
Logger class for Kader agents that logs to files with thread-safe operations.
|
|
20
|
+
Only agents with memory sessions will be logged.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self._loggers = {}
|
|
25
|
+
self._lock = Lock()
|
|
26
|
+
|
|
27
|
+
def setup_logger(
|
|
28
|
+
self, agent_name: str, session_id: Optional[str] = None
|
|
29
|
+
) -> Optional[str]:
|
|
30
|
+
"""
|
|
31
|
+
Set up logger for an agent with memory session.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
agent_name: Name of the agent
|
|
35
|
+
session_id: Session ID for the agent
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Logger ID if successful, None otherwise
|
|
39
|
+
"""
|
|
40
|
+
if not session_id:
|
|
41
|
+
# Only log agents with memory sessions
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
# Create log file path
|
|
45
|
+
logs_dir = Path.home() / ".kader" / "logs"
|
|
46
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
|
|
48
|
+
log_filename = f"{agent_name}_{session_id}.log"
|
|
49
|
+
log_file_path = logs_dir / log_filename
|
|
50
|
+
|
|
51
|
+
logger_id = f"{agent_name}_{session_id}"
|
|
52
|
+
|
|
53
|
+
# Add file sink with thread-safe configuration
|
|
54
|
+
with self._lock:
|
|
55
|
+
if logger_id not in self._loggers:
|
|
56
|
+
# Remove default handler to avoid console output
|
|
57
|
+
# We don't remove default handlers globally as other parts of the app might use them
|
|
58
|
+
# Instead, create a new logger instance for our file logging
|
|
59
|
+
new_logger = logger.bind(name=logger_id)
|
|
60
|
+
|
|
61
|
+
# Remove all existing handlers from this logger instance
|
|
62
|
+
new_logger.remove()
|
|
63
|
+
|
|
64
|
+
# Add file sink with rotation and compression - NO CONSOLE OUTPUT
|
|
65
|
+
handler_id = new_logger.add(
|
|
66
|
+
log_file_path,
|
|
67
|
+
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
|
|
68
|
+
level="INFO",
|
|
69
|
+
rotation="10 MB",
|
|
70
|
+
retention="7 days",
|
|
71
|
+
compression="zip",
|
|
72
|
+
enqueue=True, # Enables thread-safe logging in a separate thread
|
|
73
|
+
serialize=False,
|
|
74
|
+
backtrace=True,
|
|
75
|
+
diagnose=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
self._loggers[logger_id] = (new_logger, handler_id)
|
|
79
|
+
return logger_id
|
|
80
|
+
|
|
81
|
+
return logger_id
|
|
82
|
+
|
|
83
|
+
def log_token_usage(
|
|
84
|
+
self,
|
|
85
|
+
logger_id: str,
|
|
86
|
+
prompt_tokens: int,
|
|
87
|
+
completion_tokens: int,
|
|
88
|
+
total_tokens: int,
|
|
89
|
+
):
|
|
90
|
+
"""Log token usage information."""
|
|
91
|
+
if logger_id in self._loggers:
|
|
92
|
+
logger_instance, _ = self._loggers[logger_id]
|
|
93
|
+
logger_instance.info(
|
|
94
|
+
f"TOKEN_USAGE | Prompt tokens: {prompt_tokens}, "
|
|
95
|
+
f"Completion tokens: {completion_tokens}, "
|
|
96
|
+
f"Total tokens: {total_tokens}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def calculate_cost(
|
|
100
|
+
self,
|
|
101
|
+
logger_id: str,
|
|
102
|
+
total_cost: float,
|
|
103
|
+
):
|
|
104
|
+
"""Calculate and log cost based on token usage."""
|
|
105
|
+
|
|
106
|
+
self.log_cost(logger_id, total_cost)
|
|
107
|
+
return total_cost
|
|
108
|
+
|
|
109
|
+
def log_llm_response(self, logger_id: str, response: Any):
|
|
110
|
+
"""Log LLM response."""
|
|
111
|
+
if logger_id in self._loggers:
|
|
112
|
+
logger_instance, _ = self._loggers[logger_id]
|
|
113
|
+
logger_instance.info(f"LLM_RESPONSE | Response: {response}")
|
|
114
|
+
|
|
115
|
+
def log_tool_usage(self, logger_id: str, tool_name: str, arguments: Dict[str, Any]):
|
|
116
|
+
"""Log tool usage with arguments."""
|
|
117
|
+
if logger_id in self._loggers:
|
|
118
|
+
logger_instance, _ = self._loggers[logger_id]
|
|
119
|
+
logger_instance.info(
|
|
120
|
+
f"TOOL_USAGE | Tool: {tool_name}, Arguments: {json.dumps(arguments)}"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def log_cost(self, logger_id: str, cost: float):
|
|
124
|
+
"""Log cost information."""
|
|
125
|
+
if logger_id in self._loggers:
|
|
126
|
+
logger_instance, _ = self._loggers[logger_id]
|
|
127
|
+
logger_instance.info(f"COST | Cost: ${cost:.6f}")
|
|
128
|
+
|
|
129
|
+
def log_interaction(
|
|
130
|
+
self,
|
|
131
|
+
logger_id: str,
|
|
132
|
+
input_msg: str,
|
|
133
|
+
output_msg: str,
|
|
134
|
+
token_usage: Optional[Dict[str, int]] = None,
|
|
135
|
+
cost: Optional[float] = None,
|
|
136
|
+
tools_used: Optional[Dict[str, Any]] = None,
|
|
137
|
+
):
|
|
138
|
+
"""Log a complete agent interaction with all relevant information."""
|
|
139
|
+
if logger_id in self._loggers:
|
|
140
|
+
logger_instance, _ = self._loggers[logger_id]
|
|
141
|
+
|
|
142
|
+
log_parts = [f"INTERACTION | Input: {input_msg}"]
|
|
143
|
+
|
|
144
|
+
if output_msg:
|
|
145
|
+
log_parts.append(f"Output: {output_msg}")
|
|
146
|
+
|
|
147
|
+
if token_usage:
|
|
148
|
+
log_parts.append(
|
|
149
|
+
f"Tokens - Prompt: {token_usage.get('prompt_tokens', 0)}, "
|
|
150
|
+
f"Completion: {token_usage.get('completion_tokens', 0)}, "
|
|
151
|
+
f"Total: {token_usage.get('total_tokens', 0)}"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if cost is not None:
|
|
155
|
+
log_parts.append(f"Cost: ${cost:.6f}")
|
|
156
|
+
|
|
157
|
+
if tools_used:
|
|
158
|
+
log_parts.append(f"Tools: {json.dumps(tools_used)}")
|
|
159
|
+
|
|
160
|
+
logger_instance.info(" | ".join(log_parts))
|
|
161
|
+
|
|
162
|
+
def log_event(self, logger_id: str, event_type: str, data: Dict[str, Any]):
|
|
163
|
+
"""Log a general event with custom data."""
|
|
164
|
+
if logger_id in self._loggers:
|
|
165
|
+
logger_instance, _ = self._loggers[logger_id]
|
|
166
|
+
logger_instance.info(f"{event_type.upper()} | {json.dumps(data)}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# Global logger instance
|
|
170
|
+
agent_logger = AgentLogger()
|
kader/config.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kader configuration management module.
|
|
3
|
+
|
|
4
|
+
This module handles the creation and management of the .kader directory
|
|
5
|
+
in the user's home directory, including creating the required .env file
|
|
6
|
+
with OLLAMA_API_KEY and loading environment variables.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_env_file(env_file_path):
|
|
15
|
+
"""
|
|
16
|
+
Load environment variables from a .env file.
|
|
17
|
+
This function reads the .env file and sets the variables in os.environ.
|
|
18
|
+
"""
|
|
19
|
+
if not env_file_path.exists():
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
with open(env_file_path, "r", encoding="utf-8") as file:
|
|
24
|
+
for line in file:
|
|
25
|
+
line = line.strip()
|
|
26
|
+
# Skip comments and empty lines
|
|
27
|
+
if line and not line.startswith("#") and "=" in line:
|
|
28
|
+
key, value = line.split("=", 1)
|
|
29
|
+
key = key.strip()
|
|
30
|
+
value = value.strip()
|
|
31
|
+
|
|
32
|
+
# Remove surrounding quotes if present
|
|
33
|
+
if value.startswith('"') and value.endswith('"'):
|
|
34
|
+
value = value[1:-1]
|
|
35
|
+
elif value.startswith("'") and value.endswith("'"):
|
|
36
|
+
value = value[1:-1]
|
|
37
|
+
|
|
38
|
+
# Set the environment variable if it's not already set
|
|
39
|
+
if key not in os.environ:
|
|
40
|
+
os.environ[key] = value
|
|
41
|
+
return True
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"Error loading .env file: {str(e)}")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_kader_directory():
|
|
48
|
+
"""Get the path to the .kader directory in the user's home directory."""
|
|
49
|
+
return Path.home() / ".kader"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def ensure_kader_directory():
|
|
53
|
+
"""
|
|
54
|
+
Ensure that the .kader directory exists in the user's home directory.
|
|
55
|
+
Creates it if it doesn't exist.
|
|
56
|
+
"""
|
|
57
|
+
kader_dir = get_kader_directory()
|
|
58
|
+
|
|
59
|
+
# Create the directory if it doesn't exist
|
|
60
|
+
kader_dir.mkdir(exist_ok=True)
|
|
61
|
+
|
|
62
|
+
# Ensure the directory has appropriate permissions on Unix-like systems
|
|
63
|
+
if not sys.platform.startswith("win"):
|
|
64
|
+
kader_dir.chmod(0o755)
|
|
65
|
+
|
|
66
|
+
return kader_dir
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def ensure_env_file(kader_dir):
|
|
70
|
+
"""
|
|
71
|
+
Ensure that the .env file exists in the .kader directory with the
|
|
72
|
+
required OLLAMA_API_KEY configuration.
|
|
73
|
+
"""
|
|
74
|
+
env_file = kader_dir / ".env"
|
|
75
|
+
|
|
76
|
+
# Create the .env file if it doesn't exist
|
|
77
|
+
if not env_file.exists():
|
|
78
|
+
env_file.write_text("OLLAMA_API_KEY=''\n", encoding="utf-8")
|
|
79
|
+
|
|
80
|
+
# Set appropriate permissions for the .env file on Unix-like systems
|
|
81
|
+
if not sys.platform.startswith("win"):
|
|
82
|
+
env_file.chmod(0o644)
|
|
83
|
+
|
|
84
|
+
return env_file
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def initialize_kader_config():
|
|
88
|
+
"""
|
|
89
|
+
Initialize the .kader directory in the user's home directory with required configuration files.
|
|
90
|
+
This function creates the directory, sets up the .env file with OLLAMA_API_KEY,
|
|
91
|
+
and loads all environment variables from the .env file.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
# Ensure the .kader directory exists
|
|
95
|
+
kader_dir = ensure_kader_directory()
|
|
96
|
+
|
|
97
|
+
# Ensure the .env file exists with the required configuration
|
|
98
|
+
ensure_env_file(kader_dir)
|
|
99
|
+
|
|
100
|
+
# Load environment variables from the .env file
|
|
101
|
+
env_file_path = kader_dir / ".env"
|
|
102
|
+
load_env_file(env_file_path)
|
|
103
|
+
|
|
104
|
+
# Optionally add the .kader directory to the Python path so it can be accessed
|
|
105
|
+
kader_dir_str = str(kader_dir)
|
|
106
|
+
if kader_dir_str not in sys.path:
|
|
107
|
+
sys.path.insert(0, kader_dir_str)
|
|
108
|
+
|
|
109
|
+
return kader_dir, True
|
|
110
|
+
|
|
111
|
+
except PermissionError as e:
|
|
112
|
+
print(
|
|
113
|
+
f"Permission denied: Unable to create .kader directory in {Path.home()}. {str(e)}"
|
|
114
|
+
)
|
|
115
|
+
return None, False
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(f"Error initializing Kader config: {str(e)}")
|
|
118
|
+
return None, False
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Initialize the configuration when the module is imported
|
|
122
|
+
kader_dir, success = initialize_kader_config()
|
|
123
|
+
|
|
124
|
+
if success:
|
|
125
|
+
# Define constants for other modules to use
|
|
126
|
+
KADER_DIR = kader_dir
|
|
127
|
+
ENV_FILE_PATH = kader_dir / ".env"
|
|
128
|
+
|
|
129
|
+
__version__ = "0.1.0"
|
|
130
|
+
__author__ = "Kader Project"
|
|
131
|
+
__all__ = [
|
|
132
|
+
"KADER_DIR",
|
|
133
|
+
"ENV_FILE_PATH",
|
|
134
|
+
"initialize_kader_config",
|
|
135
|
+
"get_kader_directory",
|
|
136
|
+
"ensure_kader_directory",
|
|
137
|
+
"ensure_env_file",
|
|
138
|
+
"load_env_file",
|
|
139
|
+
]
|
kader/memory/__init__.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kader Memory Module
|
|
3
|
+
|
|
4
|
+
Provides memory management for agents following the AWS Strands agents SDK hierarchy:
|
|
5
|
+
- State Management: AgentState for persistent state, RequestState for request-scoped context
|
|
6
|
+
- Session Management: FileSessionManager for filesystem-based persistence
|
|
7
|
+
- Conversation Management: SlidingWindowConversationManager for context windowing
|
|
8
|
+
|
|
9
|
+
Memory is stored locally in $HOME/.kader/memory as directories and JSON files.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# Core types
|
|
13
|
+
# Conversation management
|
|
14
|
+
from .conversation import (
|
|
15
|
+
ConversationManager,
|
|
16
|
+
ConversationMessage,
|
|
17
|
+
NullConversationManager,
|
|
18
|
+
SlidingWindowConversationManager,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Session management
|
|
22
|
+
from .session import (
|
|
23
|
+
FileSessionManager,
|
|
24
|
+
Session,
|
|
25
|
+
SessionManager,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# State management
|
|
29
|
+
from .state import (
|
|
30
|
+
AgentState,
|
|
31
|
+
RequestState,
|
|
32
|
+
)
|
|
33
|
+
from .types import (
|
|
34
|
+
MemoryConfig,
|
|
35
|
+
SessionType,
|
|
36
|
+
decode_bytes_values,
|
|
37
|
+
encode_bytes_values,
|
|
38
|
+
get_default_memory_dir,
|
|
39
|
+
get_timestamp,
|
|
40
|
+
load_json,
|
|
41
|
+
save_json,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
# Types
|
|
46
|
+
"SessionType",
|
|
47
|
+
"MemoryConfig",
|
|
48
|
+
"get_timestamp",
|
|
49
|
+
"get_default_memory_dir",
|
|
50
|
+
"save_json",
|
|
51
|
+
"load_json",
|
|
52
|
+
"encode_bytes_values",
|
|
53
|
+
"decode_bytes_values",
|
|
54
|
+
# State
|
|
55
|
+
"AgentState",
|
|
56
|
+
"RequestState",
|
|
57
|
+
# Session
|
|
58
|
+
"Session",
|
|
59
|
+
"SessionManager",
|
|
60
|
+
"FileSessionManager",
|
|
61
|
+
# Conversation
|
|
62
|
+
"ConversationMessage",
|
|
63
|
+
"ConversationManager",
|
|
64
|
+
"SlidingWindowConversationManager",
|
|
65
|
+
"NullConversationManager",
|
|
66
|
+
]
|