jarvis-ai-assistant 0.1.16__tar.gz → 0.1.18__tar.gz

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.

Potentially problematic release.


This version of jarvis-ai-assistant might be problematic. Click here for more details.

Files changed (57) hide show
  1. {jarvis_ai_assistant-0.1.16/src/jarvis_ai_assistant.egg-info → jarvis_ai_assistant-0.1.18}/PKG-INFO +137 -1
  2. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/README.md +136 -0
  3. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/pyproject.toml +1 -1
  4. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/setup.py +1 -1
  5. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/__init__.py +1 -1
  6. jarvis_ai_assistant-0.1.18/src/jarvis/__pycache__/__init__.cpython-313.pyc +0 -0
  7. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/__pycache__/agent.cpython-313.pyc +0 -0
  8. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/__pycache__/main.cpython-313.pyc +0 -0
  9. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/agent.py +8 -6
  10. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/main.py +6 -3
  11. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/models/__pycache__/kimi.cpython-313.pyc +0 -0
  12. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/models/kimi.py +1 -3
  13. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__init__.py +2 -0
  14. jarvis_ai_assistant-0.1.18/src/jarvis/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  15. jarvis_ai_assistant-0.1.18/src/jarvis/tools/__pycache__/base.cpython-313.pyc +0 -0
  16. jarvis_ai_assistant-0.1.18/src/jarvis/tools/__pycache__/calculator.cpython-313.pyc +0 -0
  17. jarvis_ai_assistant-0.1.18/src/jarvis/tools/__pycache__/calculator_tool.cpython-313.pyc +0 -0
  18. jarvis_ai_assistant-0.1.18/src/jarvis/tools/__pycache__/file_ops.cpython-313.pyc +0 -0
  19. jarvis_ai_assistant-0.1.18/src/jarvis/tools/__pycache__/generator.cpython-313.pyc +0 -0
  20. jarvis_ai_assistant-0.1.18/src/jarvis/tools/__pycache__/shell.cpython-313.pyc +0 -0
  21. jarvis_ai_assistant-0.1.18/src/jarvis/tools/base.py +195 -0
  22. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/file_ops.py +22 -3
  23. jarvis_ai_assistant-0.1.18/src/jarvis/tools/generator.py +223 -0
  24. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/shell.py +23 -6
  25. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18/src/jarvis_ai_assistant.egg-info}/PKG-INFO +137 -1
  26. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis_ai_assistant.egg-info/SOURCES.txt +4 -0
  27. jarvis_ai_assistant-0.1.16/src/jarvis/__pycache__/__init__.cpython-313.pyc +0 -0
  28. jarvis_ai_assistant-0.1.16/src/jarvis/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  29. jarvis_ai_assistant-0.1.16/src/jarvis/tools/__pycache__/base.cpython-313.pyc +0 -0
  30. jarvis_ai_assistant-0.1.16/src/jarvis/tools/__pycache__/file_ops.cpython-313.pyc +0 -0
  31. jarvis_ai_assistant-0.1.16/src/jarvis/tools/__pycache__/shell.cpython-313.pyc +0 -0
  32. jarvis_ai_assistant-0.1.16/src/jarvis/tools/base.py +0 -113
  33. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/LICENSE +0 -0
  34. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/MANIFEST.in +0 -0
  35. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/setup.cfg +0 -0
  36. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/__pycache__/models.cpython-313.pyc +0 -0
  37. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/__pycache__/tools.cpython-313.pyc +0 -0
  38. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/__pycache__/utils.cpython-313.pyc +0 -0
  39. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/__pycache__/zte_llm.cpython-313.pyc +0 -0
  40. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/models/__init__.py +0 -0
  41. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/models/__pycache__/__init__.cpython-313.pyc +0 -0
  42. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/models/__pycache__/base.cpython-313.pyc +0 -0
  43. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/models/base.py +0 -0
  44. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/bing_search.cpython-313.pyc +0 -0
  45. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/python_script.cpython-313.pyc +0 -0
  46. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/rag.cpython-313.pyc +0 -0
  47. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/search.cpython-313.pyc +0 -0
  48. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/sub_agent.cpython-313.pyc +0 -0
  49. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/user_confirmation.cpython-313.pyc +0 -0
  50. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/user_input.cpython-313.pyc +0 -0
  51. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/user_interaction.cpython-313.pyc +0 -0
  52. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/tools/__pycache__/webpage.cpython-313.pyc +0 -0
  53. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis/utils.py +0 -0
  54. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis_ai_assistant.egg-info/dependency_links.txt +0 -0
  55. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis_ai_assistant.egg-info/entry_points.txt +0 -0
  56. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis_ai_assistant.egg-info/requires.txt +0 -0
  57. {jarvis_ai_assistant-0.1.16 → jarvis_ai_assistant-0.1.18}/src/jarvis_ai_assistant.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.1.16
3
+ Version: 0.1.18
4
4
  Summary: Jarvis: An AI assistant that uses tools to interact with the system
5
5
  Home-page: https://github.com/skyfireitdiy/Jarvis
6
6
  Author: skyfire
@@ -87,6 +87,9 @@ Dynamic: requires-python
87
87
  - File operations (read/write/append)
88
88
  - Task automation
89
89
  - Predefined task support
90
+ - Dynamic tool system with auto-loading
91
+ - AI-powered tool generation
92
+ - Custom tool development
90
93
 
91
94
  🔄 **Interactive Experience**
92
95
  - Natural language understanding
@@ -95,6 +98,139 @@ Dynamic: requires-python
95
98
  - Multi-line input support
96
99
  - Colored output with progress indicators
97
100
 
101
+ ## 🛠️ Custom Tools
102
+
103
+ ### Tool Locations
104
+ - Built-in tools: `src/jarvis/tools/`
105
+ - User tools: `~/.jarvis_tools/` (automatically created)
106
+
107
+ ### Creating Tools
108
+
109
+ #### 1. Using AI Generator (Recommended)
110
+ ```yaml
111
+ <START_TOOL_CALL>
112
+ name: generate_tool
113
+ arguments:
114
+ tool_name: calculator
115
+ class_name: CalculatorTool
116
+ description: Basic math calculations
117
+ parameters:
118
+ type: object
119
+ properties:
120
+ operation:
121
+ type: string
122
+ enum: ["add", "subtract", "multiply", "divide"]
123
+ numbers:
124
+ type: array
125
+ items:
126
+ type: number
127
+ required: ["operation", "numbers"]
128
+ <END_TOOL_CALL>
129
+ ```
130
+
131
+ #### 2. Manual Creation
132
+ Create a new Python file in `~/.jarvis_tools/`:
133
+
134
+ ```python
135
+ from typing import Dict, Any, Protocol, Optional
136
+ from enum import Enum
137
+
138
+ class OutputType(Enum):
139
+ INFO = "info"
140
+ ERROR = "error"
141
+
142
+ class OutputHandler(Protocol):
143
+ def print(self, text: str, output_type: OutputType) -> None: ...
144
+
145
+ class ModelHandler(Protocol):
146
+ def chat(self, message: str) -> str: ...
147
+
148
+ class CustomTool:
149
+ name = "tool_name" # Tool name for invocation
150
+ description = "Tool description" # Tool purpose
151
+ parameters = { # JSON Schema for parameters
152
+ "type": "object",
153
+ "properties": {
154
+ "param1": {"type": "string"}
155
+ },
156
+ "required": ["param1"]
157
+ }
158
+
159
+ def __init__(self, **kwargs):
160
+ """Initialize tool with optional dependencies
161
+
162
+ Args:
163
+ model: AI model for advanced features
164
+ output_handler: For consistent output formatting
165
+ register: Access to tool registry
166
+ """
167
+ self.model = kwargs.get('model')
168
+ self.output = kwargs.get('output_handler')
169
+ self.register = kwargs.get('register')
170
+
171
+ def _print(self, text: str, output_type: OutputType = OutputType.INFO):
172
+ """Print formatted output"""
173
+ if self.output:
174
+ self.output.print(text, output_type)
175
+
176
+ def execute(self, args: Dict) -> Dict[str, Any]:
177
+ """Execute tool functionality
178
+
179
+ Args:
180
+ args: Parameters passed to the tool
181
+
182
+ Returns:
183
+ Dict with execution results:
184
+ {
185
+ "success": bool,
186
+ "stdout": str, # On success
187
+ "stderr": str, # Optional error details
188
+ "error": str # On failure
189
+ }
190
+ """
191
+ try:
192
+ # Implement tool logic here
193
+ result = "Tool execution result"
194
+
195
+ return {
196
+ "success": True,
197
+ "stdout": result,
198
+ "stderr": ""
199
+ }
200
+ except Exception as e:
201
+ self._print(str(e), OutputType.ERROR)
202
+ return {
203
+ "success": False,
204
+ "error": str(e)
205
+ }
206
+ ```
207
+
208
+ ### Development Guidelines
209
+
210
+ 1. **Tool Structure**
211
+ - Clear name and description
212
+ - Well-defined parameters schema
213
+ - Proper error handling
214
+ - Consistent output format
215
+
216
+ 2. **Best Practices**
217
+ - Use `_print` for output
218
+ - Handle all required parameters
219
+ - Document functionality
220
+ - Return standardized results
221
+ - Keep tools focused and simple
222
+
223
+ 3. **Testing**
224
+ - Verify parameter validation
225
+ - Test error handling
226
+ - Check output format
227
+ - Ensure proper cleanup
228
+
229
+ 4. **Integration**
230
+ - Tools are auto-loaded on startup
231
+ - No manual registration needed
232
+ - Hot-reload supported
233
+ - Dependencies injected automatically
98
234
 
99
235
  ## ⚙️ Environment Setup
100
236
 
@@ -34,6 +34,9 @@
34
34
  - File operations (read/write/append)
35
35
  - Task automation
36
36
  - Predefined task support
37
+ - Dynamic tool system with auto-loading
38
+ - AI-powered tool generation
39
+ - Custom tool development
37
40
 
38
41
  🔄 **Interactive Experience**
39
42
  - Natural language understanding
@@ -42,6 +45,139 @@
42
45
  - Multi-line input support
43
46
  - Colored output with progress indicators
44
47
 
48
+ ## 🛠️ Custom Tools
49
+
50
+ ### Tool Locations
51
+ - Built-in tools: `src/jarvis/tools/`
52
+ - User tools: `~/.jarvis_tools/` (automatically created)
53
+
54
+ ### Creating Tools
55
+
56
+ #### 1. Using AI Generator (Recommended)
57
+ ```yaml
58
+ <START_TOOL_CALL>
59
+ name: generate_tool
60
+ arguments:
61
+ tool_name: calculator
62
+ class_name: CalculatorTool
63
+ description: Basic math calculations
64
+ parameters:
65
+ type: object
66
+ properties:
67
+ operation:
68
+ type: string
69
+ enum: ["add", "subtract", "multiply", "divide"]
70
+ numbers:
71
+ type: array
72
+ items:
73
+ type: number
74
+ required: ["operation", "numbers"]
75
+ <END_TOOL_CALL>
76
+ ```
77
+
78
+ #### 2. Manual Creation
79
+ Create a new Python file in `~/.jarvis_tools/`:
80
+
81
+ ```python
82
+ from typing import Dict, Any, Protocol, Optional
83
+ from enum import Enum
84
+
85
+ class OutputType(Enum):
86
+ INFO = "info"
87
+ ERROR = "error"
88
+
89
+ class OutputHandler(Protocol):
90
+ def print(self, text: str, output_type: OutputType) -> None: ...
91
+
92
+ class ModelHandler(Protocol):
93
+ def chat(self, message: str) -> str: ...
94
+
95
+ class CustomTool:
96
+ name = "tool_name" # Tool name for invocation
97
+ description = "Tool description" # Tool purpose
98
+ parameters = { # JSON Schema for parameters
99
+ "type": "object",
100
+ "properties": {
101
+ "param1": {"type": "string"}
102
+ },
103
+ "required": ["param1"]
104
+ }
105
+
106
+ def __init__(self, **kwargs):
107
+ """Initialize tool with optional dependencies
108
+
109
+ Args:
110
+ model: AI model for advanced features
111
+ output_handler: For consistent output formatting
112
+ register: Access to tool registry
113
+ """
114
+ self.model = kwargs.get('model')
115
+ self.output = kwargs.get('output_handler')
116
+ self.register = kwargs.get('register')
117
+
118
+ def _print(self, text: str, output_type: OutputType = OutputType.INFO):
119
+ """Print formatted output"""
120
+ if self.output:
121
+ self.output.print(text, output_type)
122
+
123
+ def execute(self, args: Dict) -> Dict[str, Any]:
124
+ """Execute tool functionality
125
+
126
+ Args:
127
+ args: Parameters passed to the tool
128
+
129
+ Returns:
130
+ Dict with execution results:
131
+ {
132
+ "success": bool,
133
+ "stdout": str, # On success
134
+ "stderr": str, # Optional error details
135
+ "error": str # On failure
136
+ }
137
+ """
138
+ try:
139
+ # Implement tool logic here
140
+ result = "Tool execution result"
141
+
142
+ return {
143
+ "success": True,
144
+ "stdout": result,
145
+ "stderr": ""
146
+ }
147
+ except Exception as e:
148
+ self._print(str(e), OutputType.ERROR)
149
+ return {
150
+ "success": False,
151
+ "error": str(e)
152
+ }
153
+ ```
154
+
155
+ ### Development Guidelines
156
+
157
+ 1. **Tool Structure**
158
+ - Clear name and description
159
+ - Well-defined parameters schema
160
+ - Proper error handling
161
+ - Consistent output format
162
+
163
+ 2. **Best Practices**
164
+ - Use `_print` for output
165
+ - Handle all required parameters
166
+ - Document functionality
167
+ - Return standardized results
168
+ - Keep tools focused and simple
169
+
170
+ 3. **Testing**
171
+ - Verify parameter validation
172
+ - Test error handling
173
+ - Check output format
174
+ - Ensure proper cleanup
175
+
176
+ 4. **Integration**
177
+ - Tools are auto-loaded on startup
178
+ - No manual registration needed
179
+ - Hot-reload supported
180
+ - Dependencies injected automatically
45
181
 
46
182
  ## ⚙️ Environment Setup
47
183
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jarvis-ai-assistant"
7
- version = "0.1.16"
7
+ version = "0.1.18"
8
8
  description = "Jarvis: An AI assistant that uses tools to interact with the system"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Your Name", email = "your.email@example.com" }]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="jarvis-ai-assistant",
5
- version="0.1.16",
5
+ version="0.1.18",
6
6
  author="skyfire",
7
7
  author_email="skyfireitdiy@hotmail.com",
8
8
  description="An AI assistant that uses various tools to interact with the system",
@@ -1,3 +1,3 @@
1
1
  """Jarvis AI Assistant"""
2
2
 
3
- __version__ = "0.1.16"
3
+ __version__ = "0.1.18"
@@ -77,12 +77,13 @@ class Agent:
77
77
  except Exception as e:
78
78
  raise Exception(f"{self.name}: 模型调用失败: {str(e)}")
79
79
 
80
- def run(self, user_input: str, file_list: Optional[List[str]] = None):
80
+ def run(self, user_input: str, file_list: Optional[List[str]] = None, keep_history: bool = False):
81
81
  """处理用户输入并返回响应,返回任务总结报告
82
82
 
83
83
  Args:
84
84
  user_input: 用户输入的任务描述
85
85
  file_list: 可选的文件列表,默认为None
86
+ keep_history: 是否保留对话历史,默认为False
86
87
 
87
88
  Returns:
88
89
  str: 任务总结报告
@@ -245,11 +246,12 @@ arguments:
245
246
  return f"Task failed: {str(e)}"
246
247
 
247
248
  finally:
248
- # 确保在所有情况下都删除会话
249
- try:
250
- self.model.delete_chat()
251
- except Exception as e:
252
- PrettyOutput.print(f"清理会话时发生错误: {str(e)}", OutputType.ERROR)
249
+ # 只在不保留历史时删除会话
250
+ if not keep_history:
251
+ try:
252
+ self.model.delete_chat()
253
+ except Exception as e:
254
+ PrettyOutput.print(f"清理会话时发生错误: {str(e)}", OutputType.ERROR)
253
255
 
254
256
  def clear_history(self):
255
257
  """清除对话历史,只保留系统提示"""
@@ -90,6 +90,7 @@ def main():
90
90
  # Add argument parser
91
91
  parser = argparse.ArgumentParser(description='Jarvis AI Assistant')
92
92
  parser.add_argument('-f', '--files', nargs='*', help='List of files to process')
93
+ parser.add_argument('--keep-history', action='store_true', help='Keep chat history (do not delete chat session)')
93
94
  args = parser.parse_args()
94
95
 
95
96
  load_env_from_file()
@@ -120,11 +121,13 @@ def main():
120
121
 
121
122
  model = KimiModel(kimi_api_key)
122
123
 
123
- tool_registry = ToolRegistry(model)
124
+ tool_registry = ToolRegistry()
124
125
  agent = Agent(model, tool_registry)
125
126
 
126
127
  # 欢迎信息
127
128
  PrettyOutput.print(f"Jarvis 已初始化 - With Kimi", OutputType.SYSTEM)
129
+ if args.keep_history:
130
+ PrettyOutput.print("已启用历史保留模式", OutputType.INFO)
128
131
 
129
132
  # 加载预定义任务
130
133
  tasks = load_tasks()
@@ -132,7 +135,7 @@ def main():
132
135
  selected_task = select_task(tasks)
133
136
  if selected_task:
134
137
  PrettyOutput.print(f"\n执行任务: {selected_task}", OutputType.INFO)
135
- agent.run(selected_task, args.files)
138
+ agent.run(selected_task, args.files, keep_history=args.keep_history)
136
139
  return 0
137
140
 
138
141
  # 如果没有选择预定义任务,进入交互模式
@@ -141,7 +144,7 @@ def main():
141
144
  user_input = get_multiline_input("请输入您的任务(输入空行退出):")
142
145
  if not user_input or user_input == "__interrupt__":
143
146
  break
144
- agent.run(user_input, args.files)
147
+ agent.run(user_input, args.files, keep_history=args.keep_history)
145
148
  except Exception as e:
146
149
  PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
147
150
 
@@ -261,7 +261,7 @@ class KimiModel(BaseModel):
261
261
  response = requests.delete(url, headers=headers)
262
262
  if response.status_code == 200:
263
263
  PrettyOutput.print("会话已删除", OutputType.SUCCESS)
264
- self.chat_id = "" # 清除会话ID
264
+ self.reset()
265
265
  return True
266
266
  else:
267
267
  PrettyOutput.print(f"删除会话失败: HTTP {response.status_code}", OutputType.ERROR)
@@ -272,8 +272,6 @@ class KimiModel(BaseModel):
272
272
 
273
273
  def reset(self):
274
274
  """重置对话"""
275
- if self.chat_id:
276
- self.delete_chat() # 删除现有会话
277
275
  self.chat_id = ""
278
276
  self.uploaded_files = []
279
277
  self.first_chat = True # 重置first_chat标记
@@ -1,10 +1,12 @@
1
1
  from .base import Tool, ToolRegistry
2
2
  from .file_ops import FileOperationTool
3
3
  from .shell import ShellTool
4
+ from .generator import ToolGeneratorTool
4
5
 
5
6
  __all__ = [
6
7
  'Tool',
7
8
  'ToolRegistry',
8
9
  'FileOperationTool',
9
10
  'ShellTool',
11
+ 'ToolGeneratorTool',
10
12
  ]
@@ -0,0 +1,195 @@
1
+ from typing import Dict, Any, List, Optional, Callable, Type
2
+ import json
3
+ import os
4
+ import importlib.util
5
+ from pathlib import Path
6
+ import sys
7
+
8
+ from jarvis.models.kimi import KimiModel
9
+
10
+ from ..utils import PrettyOutput, OutputType
11
+ from ..models import BaseModel
12
+
13
+ class Tool:
14
+ def __init__(self, name: str, description: str, parameters: Dict, func: Callable):
15
+ self.name = name
16
+ self.description = description
17
+ self.parameters = parameters
18
+ self.func = func
19
+
20
+ def to_dict(self) -> Dict:
21
+ """转换为工具格式"""
22
+ return {
23
+ "name": self.name,
24
+ "description": self.description,
25
+ "parameters": json.dumps(self.parameters)
26
+ }
27
+
28
+ def execute(self, arguments: Dict) -> Dict[str, Any]:
29
+ """执行工具函数"""
30
+ return self.func(arguments)
31
+
32
+ class ToolRegistry:
33
+ def __init__(self, output_handler=None):
34
+ """初始化工具注册器
35
+
36
+ Args:
37
+ output_handler: 输出处理器 (可选)
38
+ """
39
+ self.tools: Dict[str, Tool] = {}
40
+ self.output_handler = output_handler or PrettyOutput
41
+
42
+ # 加载内置工具和外部工具
43
+ self._load_builtin_tools()
44
+ self._load_external_tools()
45
+
46
+ def _load_builtin_tools(self):
47
+ """从内置tools目录加载工具"""
48
+ tools_dir = Path(__file__).parent
49
+
50
+ # 遍历目录下的所有.py文件
51
+ for file_path in tools_dir.glob("*.py"):
52
+ # 跳过基础文件和__init__.py
53
+ if file_path.name in ["base.py", "__init__.py"]:
54
+ continue
55
+
56
+ self.register_tool_by_file(file_path)
57
+
58
+ def _load_external_tools(self):
59
+ """从~/.jarvis_tools加载外部工具"""
60
+ external_tools_dir = Path.home() / '.jarvis_tools'
61
+ if not external_tools_dir.exists():
62
+ return
63
+
64
+ # 遍历目录下的所有.py文件
65
+ for file_path in external_tools_dir.glob("*.py"):
66
+ # 跳过__init__.py
67
+ if file_path.name == "__init__.py":
68
+ continue
69
+
70
+ self.register_tool_by_file(file_path)
71
+
72
+ def register_tool_by_file(self, file_path: str):
73
+ """从指定文件加载并注册工具
74
+
75
+ Args:
76
+ file_path: 工具文件的路径
77
+
78
+ Returns:
79
+ bool: 是否成功加载工具
80
+ """
81
+ try:
82
+ file_path = Path(file_path).resolve() # 获取绝对路径
83
+ if not file_path.exists() or not file_path.is_file():
84
+ self.output_handler.print(f"文件不存在: {file_path}", OutputType.ERROR)
85
+ return False
86
+
87
+ # 动态导入模块
88
+ module_name = file_path.stem
89
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
90
+ if not spec or not spec.loader:
91
+ self.output_handler.print(f"无法加载模块: {file_path}", OutputType.ERROR)
92
+ return False
93
+
94
+ module = importlib.util.module_from_spec(spec)
95
+ sys.modules[module_name] = module # 添加到 sys.modules 以支持相对导入
96
+ spec.loader.exec_module(module)
97
+
98
+ # 查找模块中的工具类
99
+ tool_found = False
100
+ for item_name in dir(module):
101
+ item = getattr(module, item_name)
102
+ # 检查是否是类,并且有必要的属性
103
+ if (isinstance(item, type) and
104
+ hasattr(item, 'name') and
105
+ hasattr(item, 'description') and
106
+ hasattr(item, 'parameters')):
107
+
108
+ # 实例化工具类,传入模型和输出处理器
109
+ tool_instance = item(model=KimiModel(), register=self, output_handler=self.output_handler)
110
+
111
+ # 注册工具
112
+ self.register_tool(
113
+ name=tool_instance.name,
114
+ description=tool_instance.description,
115
+ parameters=tool_instance.parameters,
116
+ func=tool_instance.execute
117
+ )
118
+ self.output_handler.print(f"已加载工具: {tool_instance.name}", OutputType.INFO)
119
+ tool_found = True
120
+
121
+ if not tool_found:
122
+ self.output_handler.print(f"文件中未找到有效的工具类: {file_path}", OutputType.WARNING)
123
+ return False
124
+
125
+ return True
126
+
127
+ except Exception as e:
128
+ self.output_handler.print(f"加载工具失败 {file_path.name}: {str(e)}", OutputType.ERROR)
129
+ return False
130
+
131
+ def register_tool(self, name: str, description: str, parameters: Dict, func: Callable):
132
+ """注册新工具"""
133
+ self.tools[name] = Tool(name, description, parameters, func)
134
+
135
+ def get_tool(self, name: str) -> Optional[Tool]:
136
+ """获取工具"""
137
+ return self.tools.get(name)
138
+
139
+ def get_all_tools(self) -> List[Dict]:
140
+ """获取所有工具的Ollama格式定义"""
141
+ return [tool.to_dict() for tool in self.tools.values()]
142
+
143
+ def execute_tool(self, name: str, arguments: Dict) -> Dict[str, Any]:
144
+ """执行指定工具"""
145
+ tool = self.get_tool(name)
146
+ if tool is None:
147
+ return {"success": False, "error": f"Tool {name} does not exist"}
148
+ return tool.execute(arguments)
149
+
150
+ def handle_tool_calls(self, tool_calls: List[Dict]) -> str:
151
+ """处理工具调用,只处理第一个工具"""
152
+ if not tool_calls:
153
+ return ""
154
+
155
+ # 只处理第一个工具调用
156
+ tool_call = tool_calls[0]
157
+ name = tool_call["name"]
158
+ args = tool_call["arguments"]
159
+
160
+ if isinstance(args, str):
161
+ try:
162
+ args = json.loads(args)
163
+ except json.JSONDecodeError:
164
+ PrettyOutput.print(f"工具参数格式无效: {name}", OutputType.ERROR)
165
+ return ""
166
+
167
+ # 显示工具调用信息
168
+ PrettyOutput.section(f"执行工具: {name}", OutputType.TOOL)
169
+ if isinstance(args, dict):
170
+ for key, value in args.items():
171
+ PrettyOutput.print(f"参数: {key} = {value}", OutputType.DEBUG)
172
+ else:
173
+ PrettyOutput.print(f"参数: {args}", OutputType.DEBUG)
174
+
175
+ # 执行工具调用
176
+ result = self.execute_tool(name, args)
177
+
178
+ # 处理结果
179
+ if result["success"]:
180
+ stdout = result["stdout"]
181
+ stderr = result.get("stderr", "")
182
+ output_parts = []
183
+ if stdout:
184
+ output_parts.append(f"输出:\n{stdout}")
185
+ if stderr:
186
+ output_parts.append(f"错误:\n{stderr}")
187
+ output = "\n\n".join(output_parts)
188
+ output = "没有输出和错误" if not output else output
189
+ PrettyOutput.section("执行成功", OutputType.SUCCESS)
190
+ else:
191
+ error_msg = result["error"]
192
+ output = f"执行失败: {error_msg}"
193
+ PrettyOutput.section("执行失败", OutputType.ERROR)
194
+
195
+ return output