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/tools/README.md
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# Kader Tools Documentation
|
|
2
|
+
|
|
3
|
+
Kader Tools provides a versatile, provider-agnostic framework for creating and managing agentic tools that can be used with any LLM provider (OpenAI, Google, Anthropic, Mistral, and others).
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Available Tools](#available-tools)
|
|
8
|
+
2. [Using Tools](#using-tools)
|
|
9
|
+
3. [Creating Custom Tools](#creating-custom-tools)
|
|
10
|
+
4. [Tool Registry](#tool-registry)
|
|
11
|
+
5. [Provider Compatibility](#provider-compatibility)
|
|
12
|
+
|
|
13
|
+
## Available Tools
|
|
14
|
+
|
|
15
|
+
### File System Tools
|
|
16
|
+
|
|
17
|
+
- **ReadFileTool**: Read the contents of a file with optional line range selection
|
|
18
|
+
- **ReadDirectoryTool**: List directory contents with recursive option
|
|
19
|
+
- **WriteFileTool**: Write content to a file, with optional directory creation
|
|
20
|
+
- **ReplaceLinesTool**: Replace or insert lines in a file
|
|
21
|
+
- **SearchInDirectoryTool**: Search for files in a directory by name or content
|
|
22
|
+
|
|
23
|
+
### Web Tools
|
|
24
|
+
|
|
25
|
+
- **WebSearchTool**: Search the web for information with configurable result limit
|
|
26
|
+
- **WebFetchTool**: Fetch and extract text content from a web page
|
|
27
|
+
|
|
28
|
+
### Command Execution Tool
|
|
29
|
+
|
|
30
|
+
- **CommandExecutorTool**: Execute command line operations with OS-appropriate validation
|
|
31
|
+
|
|
32
|
+
### RAG (Retrieval Augmented Generation) Tools
|
|
33
|
+
|
|
34
|
+
- **RAGSearchTool**: Search through local files using semantic embeddings
|
|
35
|
+
- **RAGIndex**: Build and manage semantic indexes of your local files
|
|
36
|
+
|
|
37
|
+
## Using Tools
|
|
38
|
+
|
|
39
|
+
Here's how to use the available tools in your applications:
|
|
40
|
+
|
|
41
|
+
### Basic Tool Usage
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from kader.tools import ReadFileTool
|
|
45
|
+
|
|
46
|
+
# Create a tool instance
|
|
47
|
+
read_tool = ReadFileTool()
|
|
48
|
+
|
|
49
|
+
# Execute the tool
|
|
50
|
+
content = read_tool.execute(path="README.md")
|
|
51
|
+
print(content)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Using with Tool Registry
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from kader.tools import ToolRegistry, ReadFileTool, WriteFileTool
|
|
58
|
+
|
|
59
|
+
# Create a registry and register tools
|
|
60
|
+
registry = ToolRegistry()
|
|
61
|
+
registry.register(ReadFileTool())
|
|
62
|
+
registry.register(WriteFileTool())
|
|
63
|
+
|
|
64
|
+
# Get tools
|
|
65
|
+
available_tools = registry.tools
|
|
66
|
+
for tool in available_tools:
|
|
67
|
+
print(f"Available tool: {tool.name}")
|
|
68
|
+
|
|
69
|
+
# Get a specific tool by name
|
|
70
|
+
read_tool = registry.get("read_file")
|
|
71
|
+
if read_tool:
|
|
72
|
+
result = read_tool.execute(path="README.md")
|
|
73
|
+
print(result)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Asynchronous Execution
|
|
77
|
+
|
|
78
|
+
All tools support both synchronous and asynchronous execution:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import asyncio
|
|
82
|
+
from kader.tools import ReadFileTool
|
|
83
|
+
|
|
84
|
+
async def async_example():
|
|
85
|
+
tool = ReadFileTool()
|
|
86
|
+
|
|
87
|
+
# Synchronous execution
|
|
88
|
+
sync_result = tool.execute(path="README.md")
|
|
89
|
+
|
|
90
|
+
# Asynchronous execution
|
|
91
|
+
async_result = await tool.aexecute(path="README.md")
|
|
92
|
+
|
|
93
|
+
return sync_result, async_result
|
|
94
|
+
|
|
95
|
+
# Run the async function
|
|
96
|
+
sync_result, async_result = asyncio.run(async_example())
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Creating Custom Tools
|
|
100
|
+
|
|
101
|
+
Creating custom tools is straightforward with the `BaseTool` class:
|
|
102
|
+
|
|
103
|
+
### Basic Custom Tool
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from kader.tools.base import BaseTool, ParameterSchema, ToolCategory
|
|
107
|
+
|
|
108
|
+
class GreetingTool(BaseTool[str]):
|
|
109
|
+
"""
|
|
110
|
+
A simple tool that generates a personalized greeting.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self):
|
|
114
|
+
super().__init__(
|
|
115
|
+
name="greeting_tool",
|
|
116
|
+
description="Generate a personalized greeting message",
|
|
117
|
+
parameters=[
|
|
118
|
+
ParameterSchema(
|
|
119
|
+
name="name",
|
|
120
|
+
type="string",
|
|
121
|
+
description="The name to greet",
|
|
122
|
+
),
|
|
123
|
+
ParameterSchema(
|
|
124
|
+
name="greeting_type",
|
|
125
|
+
type="string",
|
|
126
|
+
description="Type of greeting (formal, casual, friendly)",
|
|
127
|
+
required=False,
|
|
128
|
+
default="casual",
|
|
129
|
+
enum=["formal", "casual", "friendly"]
|
|
130
|
+
),
|
|
131
|
+
],
|
|
132
|
+
category=ToolCategory.UTILITY,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def execute(self, name: str, greeting_type: str = "casual") -> str:
|
|
136
|
+
"""
|
|
137
|
+
Execute the greeting tool.
|
|
138
|
+
"""
|
|
139
|
+
greetings = {
|
|
140
|
+
"formal": f"Good day, {name}!",
|
|
141
|
+
"casual": f"Hello, {name}!",
|
|
142
|
+
"friendly": f"Hey there, {name}! How's it going?"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return greetings.get(greeting_type, greetings["casual"])
|
|
146
|
+
|
|
147
|
+
async def aexecute(self, name: str, greeting_type: str = "casual") -> str:
|
|
148
|
+
"""
|
|
149
|
+
Asynchronous execution of the tool.
|
|
150
|
+
"""
|
|
151
|
+
import asyncio
|
|
152
|
+
# Simulate async operation if needed
|
|
153
|
+
await asyncio.sleep(0.01) # Placeholder for actual async work
|
|
154
|
+
return self.execute(name, greeting_type)
|
|
155
|
+
|
|
156
|
+
# Using the custom tool
|
|
157
|
+
tool = GreetingTool()
|
|
158
|
+
print(tool.execute(name="Alice", greeting_type="friendly"))
|
|
159
|
+
# Output: Hey there, Alice! How's it going?
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Tool with Complex Return Types
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from typing import Dict, Any
|
|
166
|
+
from kader.tools.base import BaseTool, ParameterSchema, ToolCategory
|
|
167
|
+
|
|
168
|
+
class MathCalculatorTool(BaseTool[Dict[str, Any]]):
|
|
169
|
+
"""
|
|
170
|
+
A tool that performs basic mathematical operations.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(self):
|
|
174
|
+
super().__init__(
|
|
175
|
+
name="math_calculator",
|
|
176
|
+
description="Perform basic mathematical operations",
|
|
177
|
+
parameters=[
|
|
178
|
+
ParameterSchema(
|
|
179
|
+
name="operation",
|
|
180
|
+
type="string",
|
|
181
|
+
description="The operation to perform (add, subtract, multiply, divide)",
|
|
182
|
+
enum=["add", "subtract", "multiply", "divide"]
|
|
183
|
+
),
|
|
184
|
+
ParameterSchema(
|
|
185
|
+
name="a",
|
|
186
|
+
type="number",
|
|
187
|
+
description="First operand"
|
|
188
|
+
),
|
|
189
|
+
ParameterSchema(
|
|
190
|
+
name="b",
|
|
191
|
+
type="number",
|
|
192
|
+
description="Second operand"
|
|
193
|
+
)
|
|
194
|
+
],
|
|
195
|
+
category=ToolCategory.UTILITY,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def execute(self, operation: str, a: float, b: float) -> Dict[str, Any]:
|
|
199
|
+
"""
|
|
200
|
+
Execute the mathematical operation.
|
|
201
|
+
"""
|
|
202
|
+
operations = {
|
|
203
|
+
"add": lambda x, y: x + y,
|
|
204
|
+
"subtract": lambda x, y: x - y,
|
|
205
|
+
"multiply": lambda x, y: x * y,
|
|
206
|
+
"divide": lambda x, y: x / y if y != 0 else float('inf')
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if operation not in operations:
|
|
210
|
+
return {
|
|
211
|
+
"error": f"Invalid operation '{operation}'. Supported operations: {list(operations.keys())}",
|
|
212
|
+
"result": None
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
result = operations[operation](a, b)
|
|
217
|
+
return {
|
|
218
|
+
"operation": f"{a} {operation} {b}",
|
|
219
|
+
"result": result,
|
|
220
|
+
"success": True
|
|
221
|
+
}
|
|
222
|
+
except Exception as e:
|
|
223
|
+
return {
|
|
224
|
+
"error": f"Error during calculation: {str(e)}",
|
|
225
|
+
"result": None,
|
|
226
|
+
"success": False
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async def aexecute(self, operation: str, a: float, b: float) -> Dict[str, Any]:
|
|
230
|
+
"""
|
|
231
|
+
Asynchronous execution of the calculator.
|
|
232
|
+
"""
|
|
233
|
+
import asyncio
|
|
234
|
+
await asyncio.sleep(0.01) # Simulate async operation
|
|
235
|
+
return self.execute(operation, a, b)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Command Execution Tool Example
|
|
239
|
+
|
|
240
|
+
The CommandExecutorTool included in this package demonstrates advanced features:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from kader.tools.exec_commands import CommandExecutorTool
|
|
244
|
+
|
|
245
|
+
# Create the command executor tool
|
|
246
|
+
cmd_tool = CommandExecutorTool()
|
|
247
|
+
|
|
248
|
+
# Execute a command (with OS validation)
|
|
249
|
+
result = cmd_tool.execute(command="echo 'Hello, World!'", timeout=10)
|
|
250
|
+
print(result)
|
|
251
|
+
|
|
252
|
+
# On Windows, this would warn about Unix-specific commands:
|
|
253
|
+
result = cmd_tool.execute(command="ls -la") # Shows validation error
|
|
254
|
+
print(result)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Tool Registry
|
|
258
|
+
|
|
259
|
+
The `ToolRegistry` provides a centralized way to manage multiple tools and execute them by name:
|
|
260
|
+
|
|
261
|
+
### Basic Registry Usage
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from kader.tools import ToolRegistry, ReadFileTool, WriteFileTool, CommandExecutorTool
|
|
265
|
+
|
|
266
|
+
# Create a registry
|
|
267
|
+
registry = ToolRegistry()
|
|
268
|
+
|
|
269
|
+
# Register individual tools
|
|
270
|
+
registry.register(ReadFileTool())
|
|
271
|
+
registry.register(WriteFileTool())
|
|
272
|
+
registry.register(CommandExecutorTool())
|
|
273
|
+
|
|
274
|
+
# Or register multiple tools at once
|
|
275
|
+
from kader.tools.filesys import get_filesystem_tools
|
|
276
|
+
filesystem_tools = get_filesystem_tools()
|
|
277
|
+
for tool in filesystem_tools:
|
|
278
|
+
registry.register(tool)
|
|
279
|
+
|
|
280
|
+
# Get tools by name
|
|
281
|
+
read_tool = registry.get("read_file")
|
|
282
|
+
if read_tool:
|
|
283
|
+
result = read_tool.execute(path="README.md")
|
|
284
|
+
print(result)
|
|
285
|
+
|
|
286
|
+
# Get all registered tool names
|
|
287
|
+
tool_names = registry.names
|
|
288
|
+
print("Registered tools:", tool_names)
|
|
289
|
+
|
|
290
|
+
# Get all tools
|
|
291
|
+
all_tools = registry.tools
|
|
292
|
+
print(f"Total tools: {len(all_tools)}")
|
|
293
|
+
|
|
294
|
+
# Get tool schemas for LLM provider integration
|
|
295
|
+
schemas = registry.to_provider_format(provider="openai")
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Registry with Provider Compatibility
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
from kader.tools import ToolRegistry
|
|
302
|
+
from kader.tools.filesys import ReadFileTool
|
|
303
|
+
from kader.tools.web import WebSearchTool
|
|
304
|
+
|
|
305
|
+
# Create registry and register tools
|
|
306
|
+
registry = ToolRegistry()
|
|
307
|
+
registry.register(ReadFileTool())
|
|
308
|
+
registry.register(WebSearchTool())
|
|
309
|
+
|
|
310
|
+
# Get schemas formatted for different providers
|
|
311
|
+
openai_schemas = registry.to_provider_format("openai")
|
|
312
|
+
anthropic_schemas = registry.to_provider_format("anthropic")
|
|
313
|
+
google_schemas = registry.to_provider_format("google")
|
|
314
|
+
mistral_schemas = registry.to_provider_format("mistral")
|
|
315
|
+
ollama_schemas = registry.to_provider_format("ollama")
|
|
316
|
+
|
|
317
|
+
# Use with your LLM provider
|
|
318
|
+
# Example with OpenAI:
|
|
319
|
+
# openai_client.chat.completions.create(
|
|
320
|
+
# model="gpt-4",
|
|
321
|
+
# messages=[...],
|
|
322
|
+
# tools=openai_schemas,
|
|
323
|
+
# tool_choice="auto",
|
|
324
|
+
# )
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Working with Tool Results
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
from kader.tools import ToolRegistry, ReadFileTool, ToolCall
|
|
331
|
+
from kader.tools.base import ToolResult
|
|
332
|
+
|
|
333
|
+
registry = ToolRegistry()
|
|
334
|
+
registry.register(ReadFileTool())
|
|
335
|
+
|
|
336
|
+
# Create a tool call (this would typically come from an LLM response)
|
|
337
|
+
tool_call = ToolCall(
|
|
338
|
+
id="call_123",
|
|
339
|
+
name="read_file",
|
|
340
|
+
arguments={"path": "README.md"},
|
|
341
|
+
raw_arguments='{"path": "README.md"}'
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Execute the tool call through the registry
|
|
345
|
+
result = registry.run(tool_call)
|
|
346
|
+
|
|
347
|
+
# ToolResult has status, content, and optional data
|
|
348
|
+
print(f"Status: {result.status}")
|
|
349
|
+
print(f"Content: {result.content}")
|
|
350
|
+
|
|
351
|
+
# Convert to provider-specific format
|
|
352
|
+
openai_format = result.to_provider_format("openai")
|
|
353
|
+
anthropic_format = result.to_provider_format("anthropic")
|
|
354
|
+
|
|
355
|
+
# Working with the CommandExecutorTool specifically
|
|
356
|
+
cmd_tool_call = ToolCall(
|
|
357
|
+
id="cmd_call_456",
|
|
358
|
+
name="execute_command",
|
|
359
|
+
arguments={"command": "echo Hello from command executor"},
|
|
360
|
+
raw_arguments='{"command": "echo Hello from command executor"}'
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
cmd_result = registry.run(cmd_tool_call)
|
|
364
|
+
print(f"Command execution result: {cmd_result.content}")
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Provider Compatibility
|
|
368
|
+
|
|
369
|
+
Kader Tools are designed to work with multiple LLM providers seamlessly:
|
|
370
|
+
|
|
371
|
+
### OpenAI Integration
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
from kader.tools import ToolRegistry
|
|
375
|
+
from kader.tools.filesys import ReadFileTool
|
|
376
|
+
|
|
377
|
+
registry = ToolRegistry()
|
|
378
|
+
registry.add_tool(ReadFileTool())
|
|
379
|
+
|
|
380
|
+
# Get tools in OpenAI format
|
|
381
|
+
openai_tools = registry.get_tool_schemas(provider="openai")
|
|
382
|
+
|
|
383
|
+
# Use with OpenAI
|
|
384
|
+
import openai
|
|
385
|
+
response = openai.ChatCompletion.create(
|
|
386
|
+
model="gpt-4",
|
|
387
|
+
messages=[
|
|
388
|
+
{"role": "user", "content": "Read the README.md file"}
|
|
389
|
+
],
|
|
390
|
+
tools=openai_tools,
|
|
391
|
+
tool_choice="auto"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Process tool calls from response
|
|
395
|
+
for choice in response.choices:
|
|
396
|
+
if choice.message.tool_calls:
|
|
397
|
+
for tool_call in choice.message.tool_calls:
|
|
398
|
+
result = registry.execute_tool_from_call(tool_call, "openai")
|
|
399
|
+
print(result.content)
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Anthropic Integration
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
from kader.tools import ToolRegistry
|
|
406
|
+
from kader.tools.filesys import ReadFileTool
|
|
407
|
+
|
|
408
|
+
registry = ToolRegistry()
|
|
409
|
+
registry.add_tool(ReadFileTool())
|
|
410
|
+
|
|
411
|
+
# Get tools in Anthropic format
|
|
412
|
+
anthropic_tools = registry.get_tool_schemas(provider="anthropic")
|
|
413
|
+
|
|
414
|
+
# Use with Anthropic
|
|
415
|
+
import anthropic
|
|
416
|
+
client = anthropic.Anthropic()
|
|
417
|
+
response = client.messages.create(
|
|
418
|
+
model="claude-3-opus-20240229",
|
|
419
|
+
max_tokens=1000,
|
|
420
|
+
messages=[
|
|
421
|
+
{"role": "user", "content": "Read the configuration file"}
|
|
422
|
+
],
|
|
423
|
+
tools=anthropic_tools
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Process tool use blocks from response
|
|
427
|
+
for content_block in response.content:
|
|
428
|
+
if content_block.type == "tool_use":
|
|
429
|
+
result = registry.execute_tool_from_call(content_block, "anthropic")
|
|
430
|
+
print(result.content)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Google (Gemini) Integration
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
from kader.tools import ToolRegistry
|
|
437
|
+
from kader.tools.filesys import ReadFileTool
|
|
438
|
+
|
|
439
|
+
registry = ToolRegistry()
|
|
440
|
+
registry.add_tool(ReadFileTool())
|
|
441
|
+
|
|
442
|
+
# Get tools in Google format
|
|
443
|
+
google_tools = registry.get_tool_schemas(provider="google")
|
|
444
|
+
|
|
445
|
+
# Use with Google Gemini
|
|
446
|
+
import google.generativeai as genai
|
|
447
|
+
model = genai.GenerativeModel(model_name="gemini-pro", tools=google_tools)
|
|
448
|
+
|
|
449
|
+
response = model.generate_content("Read the project's README file")
|
|
450
|
+
|
|
451
|
+
# Process function calls from response
|
|
452
|
+
for part in response.parts:
|
|
453
|
+
if hasattr(part, 'function_call'):
|
|
454
|
+
result = registry.execute_tool_from_call(part.function_call, "google")
|
|
455
|
+
print(result.content)
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Best Practices
|
|
459
|
+
|
|
460
|
+
1. **Always validate inputs** - Use the parameter schema validation provided by BaseTool
|
|
461
|
+
2. **Handle errors gracefully** - Implement proper error handling in your tool's execute method
|
|
462
|
+
3. **Follow the category system** - Use appropriate ToolCategory values for organization
|
|
463
|
+
4. **Provide clear descriptions** - Write clear, concise descriptions for your tools and their parameters
|
|
464
|
+
5. **Consider security** - For file system operations, always validate paths to prevent directory traversal
|
|
465
|
+
6. **Support both sync and async** - Implement both `execute` and `aexecute` methods
|
|
466
|
+
7. **Use structured return types** - When possible, return structured data that can be processed by LLMs
|
|
467
|
+
|
|
468
|
+
## File System Security
|
|
469
|
+
|
|
470
|
+
All file system tools operate relative to the current working directory (CWD) for security. Paths are validated to ensure they don't escape the allowed directory:
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
from pathlib import Path
|
|
474
|
+
from kader.tools.filesys import ReadFileTool
|
|
475
|
+
|
|
476
|
+
# Tools default to CWD but you can specify a different base
|
|
477
|
+
custom_tool = ReadFileTool(base_path=Path("/safe/directory"))
|
|
478
|
+
|
|
479
|
+
# This will raise an error if path attempts to escape the base path:
|
|
480
|
+
# content = custom_tool.execute(path="../../../forbidden.txt") # ValueError!
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
For more information on creating custom tools or using the registry, check out the examples directory or the source code documentation.
|
kader/tools/__init__.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kader Tools - Agentic tool definitions.
|
|
3
|
+
|
|
4
|
+
This module provides a provider-agnostic base class for defining tools
|
|
5
|
+
that can be used with any LLM provider.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from kader.tools.exec_commands import (
|
|
9
|
+
CommandExecutorTool,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .base import (
|
|
13
|
+
# Core classes
|
|
14
|
+
BaseTool,
|
|
15
|
+
FunctionTool,
|
|
16
|
+
ParameterSchema,
|
|
17
|
+
# Type aliases
|
|
18
|
+
ParameterType,
|
|
19
|
+
ToolCall,
|
|
20
|
+
# Enums
|
|
21
|
+
ToolCategory,
|
|
22
|
+
ToolRegistry,
|
|
23
|
+
ToolResult,
|
|
24
|
+
ToolResultStatus,
|
|
25
|
+
# Schemas and data classes
|
|
26
|
+
ToolSchema,
|
|
27
|
+
# Decorator
|
|
28
|
+
tool,
|
|
29
|
+
)
|
|
30
|
+
from .filesys import (
|
|
31
|
+
EditFileTool,
|
|
32
|
+
GlobTool,
|
|
33
|
+
GrepTool,
|
|
34
|
+
ReadDirectoryTool,
|
|
35
|
+
ReadFileTool,
|
|
36
|
+
SearchInDirectoryTool,
|
|
37
|
+
WriteFileTool,
|
|
38
|
+
get_filesystem_tools,
|
|
39
|
+
)
|
|
40
|
+
from .filesystem import (
|
|
41
|
+
FilesystemBackend,
|
|
42
|
+
)
|
|
43
|
+
from .rag import (
|
|
44
|
+
DEFAULT_EMBEDDING_MODEL,
|
|
45
|
+
DocumentChunk,
|
|
46
|
+
RAGIndex,
|
|
47
|
+
RAGSearchTool,
|
|
48
|
+
SearchResult,
|
|
49
|
+
)
|
|
50
|
+
from .todo import TodoTool
|
|
51
|
+
from .web import (
|
|
52
|
+
WebFetchTool,
|
|
53
|
+
WebSearchTool,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_default_registry() -> ToolRegistry:
|
|
58
|
+
"""
|
|
59
|
+
Get a registry populated with all standard tools.
|
|
60
|
+
|
|
61
|
+
Includes:
|
|
62
|
+
- Filesystem tools
|
|
63
|
+
- Command executor
|
|
64
|
+
- Web tools (search, fetch)
|
|
65
|
+
"""
|
|
66
|
+
registry = ToolRegistry()
|
|
67
|
+
|
|
68
|
+
# 1. Filesystem Tools
|
|
69
|
+
for t in get_filesystem_tools():
|
|
70
|
+
registry.register(t)
|
|
71
|
+
|
|
72
|
+
# 2. Command Execution
|
|
73
|
+
# 2. Command Execution
|
|
74
|
+
registry.register(CommandExecutorTool())
|
|
75
|
+
|
|
76
|
+
# 3. Web Tools
|
|
77
|
+
# Note: These might fail if ollama is missing, so we wrap safely
|
|
78
|
+
try:
|
|
79
|
+
registry.register(WebSearchTool())
|
|
80
|
+
registry.register(WebFetchTool())
|
|
81
|
+
except ImportError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
return registry
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
# Core classes
|
|
89
|
+
"BaseTool",
|
|
90
|
+
"FunctionTool",
|
|
91
|
+
"ToolRegistry",
|
|
92
|
+
# Schemas and data classes
|
|
93
|
+
"ToolSchema",
|
|
94
|
+
"ParameterSchema",
|
|
95
|
+
"ToolCall",
|
|
96
|
+
"ToolResult",
|
|
97
|
+
# Enums
|
|
98
|
+
"ToolCategory",
|
|
99
|
+
# Decorator
|
|
100
|
+
"tool",
|
|
101
|
+
# Type aliases
|
|
102
|
+
"ParameterType",
|
|
103
|
+
"ToolResultStatus",
|
|
104
|
+
# RAG
|
|
105
|
+
"RAGIndex",
|
|
106
|
+
"RAGSearchTool",
|
|
107
|
+
"DocumentChunk",
|
|
108
|
+
"SearchResult",
|
|
109
|
+
"DEFAULT_EMBEDDING_MODEL",
|
|
110
|
+
# File System Tools
|
|
111
|
+
"ReadFileTool",
|
|
112
|
+
"ReadDirectoryTool",
|
|
113
|
+
"WriteFileTool",
|
|
114
|
+
"EditFileTool",
|
|
115
|
+
"GrepTool",
|
|
116
|
+
"GlobTool",
|
|
117
|
+
"SearchInDirectoryTool",
|
|
118
|
+
"get_filesystem_tools",
|
|
119
|
+
"FilesystemBackend",
|
|
120
|
+
# Web Tools
|
|
121
|
+
"WebSearchTool",
|
|
122
|
+
"WebFetchTool",
|
|
123
|
+
# Command Execution Tool
|
|
124
|
+
# Command Execution Tool
|
|
125
|
+
"CommandExecutorTool",
|
|
126
|
+
# Todo Tool
|
|
127
|
+
"TodoTool",
|
|
128
|
+
# Helpers
|
|
129
|
+
"get_default_registry",
|
|
130
|
+
]
|