jarvis-ai-assistant 0.1.149__py3-none-any.whl → 0.1.151__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.

Potentially problematic release.


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

@@ -156,10 +156,9 @@ class AskCodebaseTool:
156
156
  2. 使用fd命令查找可能相关的文件
157
157
  3. 使用rg命令搜索关键词和代码模式
158
158
  4. 使用read_code工具直接读取和分析相关文件内容
159
- 5. 只有在fd、rg和read_code都无法解决问题时才考虑使用RAG工具
160
- 6. 根据文件内容提供具体、准确的回答
161
- 7. 确保分析的完整性,收集充分的信息后再得出结论,不要在只掌握部分信息就得出结论
162
- 8. 优先查阅README文件、文档目录和项目文档
159
+ 5. 根据文件内容提供具体、准确的回答
160
+ 6. 确保分析的完整性,收集充分的信息后再得出结论,不要在只掌握部分信息就得出结论
161
+ 7. 优先查阅README文件、文档目录和项目文档
163
162
 
164
163
  ## 分析步骤
165
164
  1. **确定项目的编程语言**:
@@ -186,12 +185,6 @@ class AskCodebaseTool:
186
185
  - 提供基于直接分析代码的具体回答
187
186
  - 引用具体文件和代码片段作为依据
188
187
 
189
- ## 关于RAG工具使用
190
- - RAG工具应作为最后选择,仅在fd、rg和read_code都无法解决问题时使用
191
- - 必须通过查看实际代码文件验证RAG返回的每条重要信息
192
- - 对于关键发现,始终使用`read_code`工具查看原始文件内容进行求证
193
- - 如发现RAG结果与实际代码不符,以实际代码为准
194
-
195
188
  ## 输出要求
196
189
  - 提供准确、具体的回答,避免模糊不清的描述
197
190
  - 引用具体文件路径和代码片段作为依据
@@ -8,15 +8,14 @@ from jarvis.jarvis_utils.globals import add_read_file_record
8
8
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
9
9
  # 导入文件处理器
10
10
  from jarvis.jarvis_utils.file_processors import (
11
- TextFileProcessor, PDFProcessor, DocxProcessor,
12
- PPTProcessor, ExcelProcessor
11
+ TextFileProcessor
13
12
  )
14
13
 
15
14
 
16
15
 
17
16
  class FileOperationTool:
18
17
  name = "file_operation"
19
- description = "文件批量操作工具,可批量读写多个文件,支持文本、PDF、Word、Excel、PPT等格式,适用于需要同时处理多个文件的场景(读取配置文件、保存生成内容等)"
18
+ description = "文件批量操作工具,可批量读写多个文件,仅支持文本文件,适用于需要同时处理多个文件的场景(读取配置文件、保存生成内容等)"
20
19
  parameters = {
21
20
  "type": "object",
22
21
  "properties": {
@@ -44,10 +43,6 @@ class FileOperationTool:
44
43
  def _get_file_processor(self, file_path: str):
45
44
  """获取适合处理指定文件的处理器"""
46
45
  processors = [
47
- PDFProcessor, # PDF文件处理器
48
- DocxProcessor, # Word文档处理器
49
- PPTProcessor, # PowerPoint演示文稿处理器
50
- ExcelProcessor, # Excel表格处理器
51
46
  TextFileProcessor # 文本文件处理器(放在最后作为兜底)
52
47
  ]
53
48
 
@@ -126,25 +121,11 @@ class FileOperationTool:
126
121
  "stderr": f"读取文本文件失败: {str(e)}"
127
122
  }
128
123
  else:
129
- # 使用专用处理器来提取非文本文件的内容
130
- try:
131
- spinner.text = f"使用 {processor.__name__} 提取 {abs_path} 的内容..."
132
- content = processor.extract_text(abs_path)
133
- # 获取文件类型友好名称
134
- file_type_names = {
135
- PDFProcessor: "PDF文档",
136
- DocxProcessor: "Word文档",
137
- PPTProcessor: "PowerPoint演示文稿",
138
- ExcelProcessor: "Excel表格"
139
- }
140
- file_type = file_type_names.get(processor, file_extension)
141
- file_info = f"\n文件: {abs_path} ({file_type})"
142
- except Exception as e:
143
- return {
144
- "success": False,
145
- "stdout": "",
146
- "stderr": f"提取 {file_extension} 文件内容失败: {str(e)}"
147
- }
124
+ return {
125
+ "success": False,
126
+ "stdout": "",
127
+ "stderr": f"不支持的文件类型: {file_extension}"
128
+ }
148
129
 
149
130
  # 构建输出信息
150
131
  output = f"{file_info}\n{content}" + "\n\n"
@@ -3,6 +3,7 @@ import json
3
3
  import hashlib
4
4
  from typing import Dict, Any
5
5
 
6
+ from jarvis.jarvis_utils.config import get_data_dir
6
7
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
7
8
 
8
9
 
@@ -35,7 +36,7 @@ class MethodologyTool:
35
36
 
36
37
  def __init__(self):
37
38
  """初始化经验管理工具"""
38
- self.methodology_dir = os.path.expanduser("~/.jarvis/methodologies")
39
+ self.methodology_dir = os.path.join(get_data_dir(), "methodologies")
39
40
  self._ensure_dir_exists()
40
41
 
41
42
  def _ensure_dir_exists(self):
@@ -10,10 +10,13 @@ import yaml
10
10
  from jarvis.jarvis_agent.output_handler import OutputHandler
11
11
  from jarvis.jarvis_platform.registry import PlatformRegistry
12
12
  from jarvis.jarvis_tools.base import Tool
13
- from jarvis.jarvis_utils.config import INPUT_WINDOW_REVERSE_SIZE, get_max_input_token_count
13
+ from jarvis.jarvis_utils.config import INPUT_WINDOW_REVERSE_SIZE, get_max_input_token_count, get_data_dir
14
14
  from jarvis.jarvis_utils.embedding import get_context_token_count
15
15
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
16
16
  from jarvis.jarvis_utils.utils import ct, ot, init_env
17
+ from jarvis.jarvis_mcp.local_mcp_client import LocalMcpClient
18
+ from jarvis.jarvis_mcp.remote_mcp_client import RemoteMcpClient
19
+ from jarvis.jarvis_mcp import McpClient
17
20
 
18
21
 
19
22
 
@@ -131,11 +134,12 @@ class ToolRegistry(OutputHandler):
131
134
  # 加载内置工具和外部工具
132
135
  self._load_builtin_tools()
133
136
  self._load_external_tools()
137
+ self._load_mcp_tools()
134
138
  self.max_input_token_count = get_max_input_token_count() - INPUT_WINDOW_REVERSE_SIZE
135
139
 
136
140
  def use_tools(self, name: List[str]) -> None:
137
141
  """使用指定工具
138
-
142
+
139
143
  参数:
140
144
  name: 要使用的工具名称列表
141
145
  """
@@ -146,12 +150,22 @@ class ToolRegistry(OutputHandler):
146
150
 
147
151
  def dont_use_tools(self, names: List[str]) -> None:
148
152
  """从注册表中移除指定工具
149
-
153
+
150
154
  参数:
151
155
  names: 要移除的工具名称列表
152
156
  """
153
157
  self.tools = {name: tool for name, tool in self.tools.items() if name not in names}
154
158
 
159
+ def _load_mcp_tools(self) -> None:
160
+ """从jarvis_data/tools/mcp加载工具"""
161
+ mcp_tools_dir = Path(get_data_dir()) / 'mcp'
162
+ if not mcp_tools_dir.exists():
163
+ return
164
+
165
+ # 遍历目录中的所有.yaml文件
166
+ for file_path in mcp_tools_dir.glob("*.yaml"):
167
+ self.register_mcp_tool_by_file(str(file_path))
168
+
155
169
  def _load_builtin_tools(self) -> None:
156
170
  """从内置工具目录加载工具"""
157
171
  tools_dir = Path(__file__).parent
@@ -165,8 +179,8 @@ class ToolRegistry(OutputHandler):
165
179
  self.register_tool_by_file(str(file_path))
166
180
 
167
181
  def _load_external_tools(self) -> None:
168
- """从~/.jarvis/tools加载外部工具"""
169
- external_tools_dir = Path.home() / '.jarvis/tools'
182
+ """从jarvis_data/tools加载外部工具"""
183
+ external_tools_dir = Path(get_data_dir()) / 'tools'
170
184
  if not external_tools_dir.exists():
171
185
  return
172
186
 
@@ -178,6 +192,139 @@ class ToolRegistry(OutputHandler):
178
192
 
179
193
  self.register_tool_by_file(str(file_path))
180
194
 
195
+ def register_mcp_tool_by_file(self, file_path: str) -> bool:
196
+ """从指定文件加载并注册工具
197
+
198
+ 参数:
199
+ file_path: 工具文件的路径
200
+
201
+ 返回:
202
+ bool: 工具是否加载成功
203
+ """
204
+ try:
205
+ config = yaml.safe_load(open(file_path, 'r', encoding='utf-8'))
206
+ if 'type' not in config:
207
+ PrettyOutput.print(f"文件 {file_path} 缺少type字段", OutputType.WARNING)
208
+ return False
209
+
210
+ # 检查enable标志
211
+ if not config.get('enable', True):
212
+ PrettyOutput.print(f"文件 {file_path} 已禁用(enable=false),跳过注册", OutputType.INFO)
213
+ return False
214
+
215
+ name = config.get('name', Path(file_path).stem)
216
+
217
+
218
+ # 注册资源工具
219
+ def create_resource_list_func(client: McpClient):
220
+ def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
221
+ args = arguments.copy()
222
+ args.pop('agent', None)
223
+ args.pop('want', None)
224
+ ret = client.get_resource_list()
225
+ PrettyOutput.print(f"MCP {name} 资源列表:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
226
+ return {
227
+ 'success': True,
228
+ 'stdout': yaml.safe_dump(ret),
229
+ 'stderr': ''
230
+ }
231
+ return execute
232
+
233
+ def create_resource_get_func(client: McpClient):
234
+ def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
235
+ args = arguments.copy()
236
+ args.pop('agent', None)
237
+ args.pop('want', None)
238
+ if 'uri' not in args:
239
+ return {
240
+ 'success': False,
241
+ 'stdout': '',
242
+ 'stderr': '缺少必需的uri参数'
243
+ }
244
+ ret = client.get_resource(args['uri'])
245
+ PrettyOutput.print(f"MCP {name} 获取资源:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
246
+ return ret
247
+ return execute
248
+
249
+ def create_mcp_execute_func(tool_name: str, client: McpClient):
250
+ def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
251
+ args = arguments.copy()
252
+ args.pop('agent', None)
253
+ args.pop('want', None)
254
+ ret = client.execute(tool_name, args)
255
+ PrettyOutput.print(f"MCP {name} {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
256
+ return ret
257
+ return execute
258
+
259
+ if config['type'] == 'local':
260
+ if 'command' not in config:
261
+ PrettyOutput.print(f"文件 {file_path} 缺少command字段", OutputType.WARNING)
262
+ return False
263
+ elif config['type'] == 'remote':
264
+ if 'base_url' not in config:
265
+ PrettyOutput.print(f"文件 {file_path} 缺少base_url字段", OutputType.WARNING)
266
+ return False
267
+ else:
268
+ PrettyOutput.print(f"文件 {file_path} 类型错误: {config['type']}", OutputType.WARNING)
269
+ return False
270
+
271
+ # 创建MCP客户端
272
+ mcp_client: McpClient = LocalMcpClient(config) if config['type'] == 'local' else RemoteMcpClient(config)
273
+
274
+ # 获取工具信息
275
+ tools = mcp_client.get_tool_list()
276
+ if not tools:
277
+ PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
278
+ return False
279
+
280
+ # 注册每个工具
281
+ for tool in tools:
282
+
283
+ # 注册工具
284
+ self.register_tool(
285
+ name=f"{name}.tool_call.{tool['name']}",
286
+ description=tool['description'],
287
+ parameters=tool['parameters'],
288
+ func=create_mcp_execute_func(tool['name'], mcp_client)
289
+ )
290
+
291
+
292
+ # 注册资源列表工具
293
+ self.register_tool(
294
+ name=f"{name}.resource.get_resource_list",
295
+ description=f"获取{name}MCP服务器上的资源列表",
296
+ parameters={
297
+ 'type': 'object',
298
+ 'properties': {},
299
+ 'required': []
300
+ },
301
+ func=create_resource_list_func(mcp_client)
302
+ )
303
+
304
+ # 注册获取资源工具
305
+ self.register_tool(
306
+ name=f"{name}.resource.get_resource",
307
+ description=f"获取{name}MCP服务器上的指定资源",
308
+ parameters={
309
+ 'type': 'object',
310
+ 'properties': {
311
+ 'uri': {
312
+ 'type': 'string',
313
+ 'description': '资源的URI标识符'
314
+ }
315
+ },
316
+ 'required': ['uri']
317
+ },
318
+ func=create_resource_get_func(mcp_client)
319
+ )
320
+
321
+ return True
322
+
323
+
324
+ except Exception as e:
325
+ PrettyOutput.print(f"文件 {file_path} 加载失败: {str(e)}", OutputType.WARNING)
326
+ return False
327
+
181
328
  def register_tool_by_file(self, file_path: str) -> bool:
182
329
  """从指定文件加载并注册工具
183
330
 
@@ -243,12 +390,12 @@ class ToolRegistry(OutputHandler):
243
390
  except Exception as e:
244
391
  PrettyOutput.print(f"从 {Path(file_path).name} 加载工具失败: {str(e)}", OutputType.ERROR)
245
392
  return False
246
-
393
+
247
394
  @staticmethod
248
395
  def _has_tool_calls_block(content: str) -> bool:
249
396
  """从内容中提取工具调用块"""
250
397
  return re.search(ot("TOOL_CALL")+r'(.*?)'+ct("TOOL_CALL"), content, re.DOTALL) is not None
251
-
398
+
252
399
  @staticmethod
253
400
  def _extract_tool_calls(content: str) -> Tuple[Dict[str, Dict[str, Any]], str]:
254
401
  """从内容中提取工具调用。
@@ -271,20 +418,20 @@ class ToolRegistry(OutputHandler):
271
418
  if 'name' in msg and 'arguments' in msg and 'want' in msg:
272
419
  ret.append(msg)
273
420
  else:
274
- return {}, f"""工具调用格式错误,请检查工具调用格式。
275
-
276
- {tool_call_help}"""
421
+ return {}, f"""工具调用格式错误,请检查工具调用格式。
422
+
423
+ {tool_call_help}"""
277
424
  except Exception as e:
278
- return {}, f"""工具调用格式错误,请检查工具调用格式。
279
-
280
- {tool_call_help}"""
425
+ return {}, f"""工具调用格式错误,请检查工具调用格式。
426
+
427
+ {tool_call_help}"""
281
428
  if len(ret) > 1:
282
429
  return {}, "检测到多个工具调用,请一次只处理一个工具调用。"
283
430
  return ret[0] if ret else {}, ""
284
431
 
285
- def register_tool(self, name: str, description: str, parameters: Dict[str, Dict[str, Any]], func: Callable[..., Dict[str, Any]]) -> None:
432
+ def register_tool(self, name: str, description: str, parameters: Any, func: Callable[..., Dict[str, Any]]) -> None:
286
433
  """注册新工具
287
-
434
+
288
435
  参数:
289
436
  name: 工具名称
290
437
  description: 工具描述
@@ -295,10 +442,10 @@ class ToolRegistry(OutputHandler):
295
442
 
296
443
  def get_tool(self, name: str) -> Optional[Tool]:
297
444
  """获取工具
298
-
445
+
299
446
  参数:
300
447
  name: 工具名称
301
-
448
+
302
449
  返回:
303
450
  Optional[Tool]: 找到的工具实例,如果不存在则返回None
304
451
  """
@@ -306,7 +453,7 @@ class ToolRegistry(OutputHandler):
306
453
 
307
454
  def get_all_tools(self) -> List[Dict[str, Any]]:
308
455
  """获取所有工具(Ollama格式定义)
309
-
456
+
310
457
  返回:
311
458
  List[Dict[str, Any]]: 包含所有工具信息的列表
312
459
  """
@@ -314,11 +461,11 @@ class ToolRegistry(OutputHandler):
314
461
 
315
462
  def execute_tool(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
316
463
  """执行指定工具
317
-
464
+
318
465
  参数:
319
466
  name: 工具名称
320
467
  arguments: 工具参数
321
-
468
+
322
469
  返回:
323
470
  Dict[str, Any]: 包含执行结果的字典,包含success、stdout和stderr字段
324
471
  """
@@ -380,7 +527,7 @@ class ToolRegistry(OutputHandler):
380
527
  return f"""工具调用原始输出过长,以下是根据输出提出的信息:
381
528
 
382
529
  {platform.chat_until_success(prompt)}"""
383
-
530
+
384
531
  return output
385
532
 
386
533
  except Exception as e:
@@ -29,16 +29,8 @@ def get_max_input_token_count() -> int:
29
29
  返回:
30
30
  int: 模型能处理的最大输入token数量。
31
31
  """
32
- return int(os.getenv('JARVIS_MAX_INPUT_TOKEN_COUNT', '32000'))
32
+ return int(os.getenv('JARVIS_MAX_INPUT_TOKEN_COUNT', '64000'))
33
33
 
34
- def get_thread_count() -> int:
35
- """
36
- 获取用于并行处理的线程数。
37
-
38
- 返回:
39
- int: 线程数,默认为1
40
- """
41
- return int(os.getenv('JARVIS_THREAD_COUNT', '1'))
42
34
 
43
35
  def is_auto_complete() -> bool:
44
36
  """
@@ -118,3 +110,17 @@ def get_max_tool_call_count() -> int:
118
110
  int: 最大连续工具调用次数,默认为20
119
111
  """
120
112
  return int(os.getenv('JARVIS_MAX_TOOL_CALL_COUNT', '20'))
113
+
114
+
115
+ def get_data_dir() -> str:
116
+ """
117
+ 获取Jarvis数据存储目录路径。
118
+
119
+ 返回:
120
+ str: 数据目录路径,优先从JARVIS_DATA_PATH环境变量获取,
121
+ 如果未设置或为空,则使用~/.jarvis作为默认值
122
+ """
123
+ data_path = os.getenv('JARVIS_DATA_PATH', '').strip()
124
+ if not data_path:
125
+ return os.path.expanduser('~/.jarvis')
126
+ return data_path
@@ -4,9 +4,9 @@ from typing import List
4
4
  import functools
5
5
 
6
6
  from jarvis.jarvis_utils.output import PrettyOutput, OutputType
7
+ from jarvis.jarvis_utils.config import get_data_dir
7
8
 
8
9
  # 全局缓存,避免重复加载模型
9
- _global_models = {}
10
10
  _global_tokenizers = {}
11
11
 
12
12
  def get_context_token_count(text: str) -> int:
@@ -155,7 +155,7 @@ def load_tokenizer() -> AutoTokenizer:
155
155
  AutoTokenizer: 加载的分词器
156
156
  """
157
157
  model_name = "gpt2"
158
- cache_dir = os.path.expanduser("~/.cache/huggingface/hub")
158
+ cache_dir = os.path.join(get_data_dir(), "huggingface", "hub")
159
159
 
160
160
  # 检查全局缓存
161
161
  if model_name in _global_tokenizers: