jarvis-ai-assistant 0.2.2__py3-none-any.whl → 0.2.4__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.
Files changed (39) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/edit_file_handler.py +5 -0
  3. jarvis/jarvis_agent/jarvis.py +22 -25
  4. jarvis/jarvis_agent/main.py +6 -6
  5. jarvis/jarvis_agent/prompts.py +26 -4
  6. jarvis/jarvis_code_agent/code_agent.py +279 -11
  7. jarvis/jarvis_code_analysis/code_review.py +21 -19
  8. jarvis/jarvis_data/config_schema.json +86 -18
  9. jarvis/jarvis_git_squash/main.py +3 -3
  10. jarvis/jarvis_git_utils/git_commiter.py +32 -11
  11. jarvis/jarvis_mcp/sse_mcp_client.py +4 -6
  12. jarvis/jarvis_mcp/streamable_mcp_client.py +5 -9
  13. jarvis/jarvis_platform/tongyi.py +9 -9
  14. jarvis/jarvis_rag/cli.py +79 -23
  15. jarvis/jarvis_rag/query_rewriter.py +61 -12
  16. jarvis/jarvis_rag/rag_pipeline.py +143 -34
  17. jarvis/jarvis_rag/retriever.py +6 -6
  18. jarvis/jarvis_smart_shell/main.py +2 -2
  19. jarvis/jarvis_stats/__init__.py +13 -0
  20. jarvis/jarvis_stats/cli.py +337 -0
  21. jarvis/jarvis_stats/stats.py +433 -0
  22. jarvis/jarvis_stats/storage.py +329 -0
  23. jarvis/jarvis_stats/visualizer.py +443 -0
  24. jarvis/jarvis_tools/cli/main.py +84 -15
  25. jarvis/jarvis_tools/generate_new_tool.py +22 -1
  26. jarvis/jarvis_tools/registry.py +35 -16
  27. jarvis/jarvis_tools/search_web.py +3 -3
  28. jarvis/jarvis_tools/virtual_tty.py +315 -26
  29. jarvis/jarvis_utils/config.py +98 -11
  30. jarvis/jarvis_utils/git_utils.py +8 -16
  31. jarvis/jarvis_utils/globals.py +29 -8
  32. jarvis/jarvis_utils/input.py +114 -121
  33. jarvis/jarvis_utils/utils.py +213 -37
  34. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/METADATA +99 -9
  35. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/RECORD +39 -34
  36. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/entry_points.txt +2 -0
  37. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/WHEEL +0 -0
  38. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/licenses/LICENSE +0 -0
  39. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,30 @@
1
1
  # -*- coding: utf-8 -*-
2
- import fcntl
3
2
  import os
4
- import pty
5
- import select
6
- import signal
3
+ import sys
7
4
  import time
8
- from typing import Any, Dict
5
+ from typing import Any, Dict, TYPE_CHECKING
6
+
7
+ # 为了类型检查,总是导入这些模块
8
+ if TYPE_CHECKING:
9
+ import fcntl
10
+ import pty
11
+ import select
12
+ import signal
13
+ import subprocess
14
+ import threading
15
+ import queue
16
+
17
+ # 平台相关的导入
18
+ if sys.platform != "win32":
19
+ import fcntl
20
+ import pty
21
+ import select
22
+ import signal
23
+ else:
24
+ # Windows平台的导入
25
+ import subprocess
26
+ import threading
27
+ import queue
9
28
 
10
29
 
11
30
  class VirtualTTYTool:
@@ -14,6 +33,7 @@ class VirtualTTYTool:
14
33
  "控制虚拟终端执行各种操作,如启动终端、输入命令、获取输出等。"
15
34
  + "与execute_script不同,此工具会创建一个持久的虚拟终端会话,可以连续执行多个命令,并保持终端状态。"
16
35
  + "适用于需要交互式操作的场景,如运行需要用户输入的交互式程序(如:ssh连接、sftp传输、gdb/dlv调试等)。"
36
+ + "注意:Windows平台功能有限,某些Unix特有功能可能不可用。"
17
37
  )
18
38
  parameters = {
19
39
  "type": "object",
@@ -76,11 +96,21 @@ class VirtualTTYTool:
76
96
 
77
97
  # 如果指定的tty_id不存在,为其创建一个新的tty_data
78
98
  if tty_id not in agent.tty_sessions:
79
- agent.tty_sessions[tty_id] = {
80
- "master_fd": None,
81
- "pid": None,
82
- "shell": "/bin/bash",
83
- }
99
+ if sys.platform == "win32":
100
+ import queue as _queue # pylint: disable=import-outside-toplevel
101
+
102
+ agent.tty_sessions[tty_id] = {
103
+ "process": None,
104
+ "output_queue": _queue.Queue(),
105
+ "output_thread": None,
106
+ "shell": "cmd.exe",
107
+ }
108
+ else:
109
+ agent.tty_sessions[tty_id] = {
110
+ "master_fd": None,
111
+ "pid": None,
112
+ "shell": "/bin/bash",
113
+ }
84
114
 
85
115
  action = args.get("action", "").strip().lower()
86
116
 
@@ -164,13 +194,25 @@ class VirtualTTYTool:
164
194
 
165
195
  def _launch_tty(self, agent: Any, tty_id: str) -> Dict[str, Any]:
166
196
  """启动虚拟终端"""
197
+ if sys.platform == "win32":
198
+ return self._launch_tty_windows(agent, tty_id)
199
+ else:
200
+ return self._launch_tty_unix(agent, tty_id)
201
+
202
+ def _launch_tty_unix(self, agent: Any, tty_id: str) -> Dict[str, Any]:
203
+ """Unix/Linux平台启动虚拟终端"""
167
204
  try:
168
205
  # 如果该ID的终端已经启动,先关闭它
169
206
  if agent.tty_sessions[tty_id]["master_fd"] is not None:
170
207
  self._close_tty(agent, tty_id)
171
208
 
209
+ # 在Unix平台上导入需要的模块
210
+ import pty as _pty # pylint: disable=import-outside-toplevel
211
+ import fcntl as _fcntl # pylint: disable=import-outside-toplevel
212
+ import select as _select # pylint: disable=import-outside-toplevel
213
+
172
214
  # 创建伪终端
173
- pid, master_fd = pty.fork()
215
+ pid, master_fd = _pty.fork()
174
216
 
175
217
  if pid == 0: # 子进程
176
218
  # 执行shell
@@ -180,7 +222,7 @@ class VirtualTTYTool:
180
222
  )
181
223
  else: # 父进程
182
224
  # 设置非阻塞模式
183
- fcntl.fcntl(master_fd, fcntl.F_SETFL, os.O_NONBLOCK)
225
+ _fcntl.fcntl(master_fd, _fcntl.F_SETFL, os.O_NONBLOCK)
184
226
 
185
227
  # 保存终端状态
186
228
  agent.tty_sessions[tty_id]["master_fd"] = master_fd
@@ -191,7 +233,7 @@ class VirtualTTYTool:
191
233
  start_time = time.time()
192
234
  while time.time() - start_time < 2.0: # 最多等待2秒
193
235
  try:
194
- r, _, _ = select.select([master_fd], [], [], 0.1)
236
+ r, _, _ = _select.select([master_fd], [], [], 0.1)
195
237
  if r:
196
238
  data = os.read(master_fd, 1024)
197
239
  if data:
@@ -211,6 +253,74 @@ class VirtualTTYTool:
211
253
  "stderr": f"启动虚拟终端 [{tty_id}] 失败: {str(e)}",
212
254
  }
213
255
 
256
+ def _launch_tty_windows(self, agent: Any, tty_id: str) -> Dict[str, Any]:
257
+ """Windows平台启动虚拟终端"""
258
+ try:
259
+ # 如果该ID的终端已经启动,先关闭它
260
+ if agent.tty_sessions[tty_id]["process"] is not None:
261
+ self._close_tty(agent, tty_id)
262
+
263
+ # 在Windows平台上导入需要的模块
264
+ import subprocess as _subprocess # pylint: disable=import-outside-toplevel
265
+ import threading as _threading # pylint: disable=import-outside-toplevel
266
+ import queue as _queue # pylint: disable=import-outside-toplevel
267
+
268
+ # 创建子进程
269
+ process = _subprocess.Popen(
270
+ agent.tty_sessions[tty_id]["shell"],
271
+ stdin=_subprocess.PIPE,
272
+ stdout=_subprocess.PIPE,
273
+ stderr=_subprocess.STDOUT,
274
+ shell=True,
275
+ text=True,
276
+ bufsize=0,
277
+ encoding="utf-8",
278
+ errors="replace",
279
+ )
280
+
281
+ # 保存进程对象
282
+ agent.tty_sessions[tty_id]["process"] = process
283
+
284
+ # 创建输出读取线程
285
+ def read_output():
286
+ while True:
287
+ if process is None or process.poll() is not None:
288
+ break
289
+ try:
290
+ if process.stdout is None:
291
+ break
292
+ line = process.stdout.readline()
293
+ if line:
294
+ agent.tty_sessions[tty_id]["output_queue"].put(line)
295
+ except:
296
+ break
297
+
298
+ output_thread = _threading.Thread(target=read_output, daemon=True)
299
+ output_thread.start()
300
+ agent.tty_sessions[tty_id]["output_thread"] = output_thread
301
+
302
+ # 读取初始输出
303
+ output = ""
304
+ start_time = time.time()
305
+ while time.time() - start_time < 2.0: # 最多等待2秒
306
+ try:
307
+ line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
308
+ output += line
309
+ except _queue.Empty:
310
+ continue
311
+
312
+ if output:
313
+ print(f"📤 终端 [{tty_id}]: {output}")
314
+
315
+ return {"success": True, "stdout": output, "stderr": ""}
316
+
317
+ except Exception as e:
318
+ return {
319
+ "success": False,
320
+ "stdout": "",
321
+ "stderr": f"启动虚拟终端 [{tty_id}] 失败: {str(e)}",
322
+ }
323
+
214
324
  def _input_command(
215
325
  self,
216
326
  agent: Any,
@@ -225,6 +335,22 @@ class VirtualTTYTool:
225
335
  command: 要输入的单行命令
226
336
  add_enter: 是否在命令末尾添加回车符
227
337
  """
338
+ if sys.platform == "win32":
339
+ return self._input_command_windows(
340
+ agent, tty_id, command, timeout, add_enter
341
+ )
342
+ else:
343
+ return self._input_command_unix(agent, tty_id, command, timeout, add_enter)
344
+
345
+ def _input_command_unix(
346
+ self,
347
+ agent: Any,
348
+ tty_id: str,
349
+ command: str,
350
+ timeout: float,
351
+ add_enter: bool = True,
352
+ ) -> Dict[str, Any]:
353
+ """Unix/Linux平台输入命令"""
228
354
  if agent.tty_sessions[tty_id]["master_fd"] is None:
229
355
  return {
230
356
  "success": False,
@@ -251,7 +377,9 @@ class VirtualTTYTool:
251
377
  while time.time() - start_time < timeout:
252
378
  try:
253
379
  # 使用select等待数据可读
254
- r, _, _ = select.select(
380
+ import select as _select # pylint: disable=import-outside-toplevel
381
+
382
+ r, _, _ = _select.select(
255
383
  [agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
256
384
  )
257
385
  if r:
@@ -270,10 +398,68 @@ class VirtualTTYTool:
270
398
  "stderr": f"在终端 [{tty_id}] 执行命令失败: {str(e)}",
271
399
  }
272
400
 
401
+ def _input_command_windows(
402
+ self,
403
+ agent: Any,
404
+ tty_id: str,
405
+ command: str,
406
+ timeout: float,
407
+ add_enter: bool = True,
408
+ ) -> Dict[str, Any]:
409
+ """Windows平台输入命令"""
410
+ if agent.tty_sessions[tty_id]["process"] is None:
411
+ return {
412
+ "success": False,
413
+ "stdout": "",
414
+ "stderr": f"虚拟终端 [{tty_id}] 未启动",
415
+ }
416
+
417
+ # 严格检查并拒绝多行输入
418
+ if "\n" in command:
419
+ return {"success": False, "stdout": "", "stderr": "错误:禁止多行输入"}
420
+
421
+ try:
422
+ # 根据add_enter参数决定是否添加回车符
423
+ if add_enter:
424
+ command = command + "\n"
425
+
426
+ # 发送命令
427
+ agent.tty_sessions[tty_id]["process"].stdin.write(command)
428
+ agent.tty_sessions[tty_id]["process"].stdin.flush()
429
+
430
+ # 等待输出
431
+ output = ""
432
+ start_time = time.time()
433
+ while time.time() - start_time < timeout:
434
+ try:
435
+ line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
436
+ output += line
437
+ except Exception: # queue.Empty
438
+ continue
439
+
440
+ print(f"📤 终端 [{tty_id}]: {output}")
441
+ return {"success": True, "stdout": output, "stderr": ""}
442
+
443
+ except Exception as e:
444
+ return {
445
+ "success": False,
446
+ "stdout": "",
447
+ "stderr": f"在终端 [{tty_id}] 执行命令失败: {str(e)}",
448
+ }
449
+
273
450
  def _get_output(
274
451
  self, agent: Any, tty_id: str, timeout: float = 5.0
275
452
  ) -> Dict[str, Any]:
276
453
  """获取终端输出"""
454
+ if sys.platform == "win32":
455
+ return self._get_output_windows(agent, tty_id, timeout)
456
+ else:
457
+ return self._get_output_unix(agent, tty_id, timeout)
458
+
459
+ def _get_output_unix(
460
+ self, agent: Any, tty_id: str, timeout: float = 5.0
461
+ ) -> Dict[str, Any]:
462
+ """Unix/Linux平台获取输出"""
277
463
  if agent.tty_sessions[tty_id]["master_fd"] is None:
278
464
  return {
279
465
  "success": False,
@@ -287,7 +473,9 @@ class VirtualTTYTool:
287
473
 
288
474
  while time.time() - start_time < timeout:
289
475
  # 使用select等待数据可读
290
- r, _, _ = select.select(
476
+ import select as _select # pylint: disable=import-outside-toplevel
477
+
478
+ r, _, _ = _select.select(
291
479
  [agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
292
480
  )
293
481
  if r:
@@ -313,8 +501,47 @@ class VirtualTTYTool:
313
501
  "stderr": f"获取终端 [{tty_id}] 输出失败: {str(e)}",
314
502
  }
315
503
 
504
+ def _get_output_windows(
505
+ self, agent: Any, tty_id: str, timeout: float = 5.0
506
+ ) -> Dict[str, Any]:
507
+ """Windows平台获取输出"""
508
+ if agent.tty_sessions[tty_id]["process"] is None:
509
+ return {
510
+ "success": False,
511
+ "stdout": "",
512
+ "stderr": f"虚拟终端 [{tty_id}] 未启动",
513
+ }
514
+
515
+ try:
516
+ output = ""
517
+ start_time = time.time()
518
+
519
+ while time.time() - start_time < timeout:
520
+ try:
521
+ line = agent.tty_sessions[tty_id]["output_queue"].get(timeout=0.1)
522
+ output += line
523
+ except Exception: # queue.Empty
524
+ continue
525
+
526
+ print(f"📤 终端 [{tty_id}]: {output}")
527
+ return {"success": True, "stdout": output, "stderr": ""}
528
+
529
+ except Exception as e:
530
+ return {
531
+ "success": False,
532
+ "stdout": "",
533
+ "stderr": f"获取终端 [{tty_id}] 输出失败: {str(e)}",
534
+ }
535
+
316
536
  def _close_tty(self, agent: Any, tty_id: str) -> Dict[str, Any]:
317
537
  """关闭虚拟终端"""
538
+ if sys.platform == "win32":
539
+ return self._close_tty_windows(agent, tty_id)
540
+ else:
541
+ return self._close_tty_unix(agent, tty_id)
542
+
543
+ def _close_tty_unix(self, agent: Any, tty_id: str) -> Dict[str, Any]:
544
+ """Unix/Linux平台关闭终端"""
318
545
  if agent.tty_sessions[tty_id]["master_fd"] is None:
319
546
  return {
320
547
  "success": True,
@@ -328,7 +555,9 @@ class VirtualTTYTool:
328
555
 
329
556
  # 终止子进程
330
557
  if agent.tty_sessions[tty_id]["pid"]:
331
- os.kill(agent.tty_sessions[tty_id]["pid"], signal.SIGTERM)
558
+ import signal as _signal # pylint: disable=import-outside-toplevel
559
+
560
+ os.kill(agent.tty_sessions[tty_id]["pid"], _signal.SIGTERM)
332
561
 
333
562
  # 重置终端数据
334
563
  agent.tty_sessions[tty_id] = {
@@ -350,8 +579,53 @@ class VirtualTTYTool:
350
579
  "stderr": f"关闭虚拟终端 [{tty_id}] 失败: {str(e)}",
351
580
  }
352
581
 
582
+ def _close_tty_windows(self, agent: Any, tty_id: str) -> Dict[str, Any]:
583
+ """Windows平台关闭终端"""
584
+ if agent.tty_sessions[tty_id]["process"] is None:
585
+ return {
586
+ "success": True,
587
+ "stdout": f"没有正在运行的虚拟终端 [{tty_id}]",
588
+ "stderr": "",
589
+ }
590
+
591
+ try:
592
+ # 终止进程
593
+ agent.tty_sessions[tty_id]["process"].terminate()
594
+ agent.tty_sessions[tty_id]["process"].wait()
595
+
596
+ # 重置终端数据
597
+ import queue as _queue # pylint: disable=import-outside-toplevel
598
+
599
+ agent.tty_sessions[tty_id] = {
600
+ "process": None,
601
+ "output_queue": _queue.Queue(),
602
+ "output_thread": None,
603
+ "shell": "cmd.exe",
604
+ }
605
+
606
+ return {
607
+ "success": True,
608
+ "stdout": f"虚拟终端 [{tty_id}] 已关闭",
609
+ "stderr": "",
610
+ }
611
+
612
+ except Exception as e:
613
+ return {
614
+ "success": False,
615
+ "stdout": "",
616
+ "stderr": f"关闭虚拟终端 [{tty_id}] 失败: {str(e)}",
617
+ }
618
+
353
619
  def _get_screen(self, agent: Any, tty_id: str) -> Dict[str, Any]:
354
620
  """获取当前终端屏幕内容"""
621
+ if sys.platform == "win32":
622
+ # Windows平台暂不支持获取屏幕内容
623
+ return {
624
+ "success": False,
625
+ "stdout": "",
626
+ "stderr": "Windows平台暂不支持获取屏幕内容功能",
627
+ }
628
+
355
629
  if agent.tty_sessions[tty_id]["master_fd"] is None:
356
630
  return {
357
631
  "success": False,
@@ -371,7 +645,9 @@ class VirtualTTYTool:
371
645
  start_time = time.time()
372
646
  while time.time() - start_time < 2.0: # 最多等待2秒
373
647
  try:
374
- r, _, _ = select.select(
648
+ import select as _select # pylint: disable=import-outside-toplevel
649
+
650
+ r, _, _ = _select.select(
375
651
  [agent.tty_sessions[tty_id]["master_fd"]], [], [], 0.1
376
652
  )
377
653
  if r:
@@ -404,15 +680,28 @@ class VirtualTTYTool:
404
680
  active_ttys = []
405
681
 
406
682
  for tty_id, tty_data in agent.tty_sessions.items():
407
- status = "活动" if tty_data["master_fd"] is not None else "关闭"
408
- active_ttys.append(
409
- {
410
- "id": tty_id,
411
- "status": status,
412
- "pid": tty_data["pid"] if tty_data["pid"] else None,
413
- "shell": tty_data["shell"],
414
- }
415
- )
683
+ if sys.platform == "win32":
684
+ status = "活动" if tty_data["process"] is not None else "关闭"
685
+ active_ttys.append(
686
+ {
687
+ "id": tty_id,
688
+ "status": status,
689
+ "pid": tty_data["process"].pid
690
+ if tty_data["process"]
691
+ else None,
692
+ "shell": tty_data["shell"],
693
+ }
694
+ )
695
+ else:
696
+ status = "活动" if tty_data["master_fd"] is not None else "关闭"
697
+ active_ttys.append(
698
+ {
699
+ "id": tty_id,
700
+ "status": status,
701
+ "pid": tty_data["pid"] if tty_data["pid"] else None,
702
+ "shell": tty_data["shell"],
703
+ }
704
+ )
416
705
 
417
706
  # 格式化输出
418
707
  output = "虚拟终端列表:\n"
@@ -115,22 +115,26 @@ def get_shell_name() -> str:
115
115
  return os.path.basename(shell_path).lower()
116
116
 
117
117
 
118
- def _get_resolved_model_config(model_group_override: Optional[str] = None) -> Dict[str, Any]:
118
+ def _get_resolved_model_config(
119
+ model_group_override: Optional[str] = None,
120
+ ) -> Dict[str, Any]:
119
121
  """
120
122
  解析并合并模型配置,处理模型组。
121
123
 
122
124
  优先级顺序:
123
125
  1. 单独的环境变量 (JARVIS_PLATFORM, JARVIS_MODEL, etc.)
124
- 2. JARVIS_MODEL_GROUP 中定义的组配置
126
+ 2. JARVIS_LLM_GROUP 中定义的组配置
125
127
  3. 代码中的默认值
126
128
 
127
129
  返回:
128
130
  Dict[str, Any]: 解析后的模型配置字典
129
131
  """
130
132
  group_config = {}
131
- model_group_name = model_group_override or GLOBAL_CONFIG_DATA.get("JARVIS_MODEL_GROUP")
133
+ model_group_name = model_group_override or GLOBAL_CONFIG_DATA.get(
134
+ "JARVIS_LLM_GROUP"
135
+ )
132
136
  # The format is a list of single-key dicts: [{'group_name': {...}}, ...]
133
- model_groups = GLOBAL_CONFIG_DATA.get("JARVIS_MODEL_GROUPS", [])
137
+ model_groups = GLOBAL_CONFIG_DATA.get("JARVIS_LLM_GROUPS", [])
134
138
 
135
139
  if model_group_name and isinstance(model_groups, list):
136
140
  for group_item in model_groups:
@@ -202,7 +206,9 @@ def get_thinking_model_name(model_group_override: Optional[str] = None) -> str:
202
206
  """
203
207
  config = _get_resolved_model_config(model_group_override)
204
208
  # Fallback to normal model if thinking model is not specified
205
- return config.get("JARVIS_THINKING_MODEL", get_normal_model_name(model_group_override))
209
+ return config.get(
210
+ "JARVIS_THINKING_MODEL", get_normal_model_name(model_group_override)
211
+ )
206
212
 
207
213
 
208
214
  def is_execute_tool_confirm() -> bool:
@@ -256,6 +262,12 @@ def get_pretty_output() -> bool:
256
262
  返回:
257
263
  bool: 如果启用PrettyOutput则返回True,默认为True
258
264
  """
265
+ import platform
266
+
267
+ # Windows系统强制设置为False
268
+ if platform.system() == "Windows":
269
+ return False
270
+
259
271
  return GLOBAL_CONFIG_DATA.get("JARVIS_PRETTY_OUTPUT", False) == True
260
272
 
261
273
 
@@ -334,14 +346,65 @@ def get_mcp_config() -> List[Dict[str, Any]]:
334
346
  # ==============================================================================
335
347
 
336
348
 
337
- def get_rag_config() -> Dict[str, Any]:
349
+ DEFAULT_RAG_GROUPS = [
350
+ {
351
+ "text": {
352
+ "embedding_model": "BAAI/bge-m3",
353
+ "rerank_model": "BAAI/bge-reranker-v2-m3",
354
+ "use_bm25": True,
355
+ "use_rerank": True,
356
+ }
357
+ },
358
+ {
359
+ "code": {
360
+ "embedding_model": "Qodo/Qodo-Embed-1-7B",
361
+ "use_bm25": False,
362
+ "use_rerank": False,
363
+ }
364
+ },
365
+ ]
366
+
367
+
368
+ def _get_resolved_rag_config(
369
+ rag_group_override: Optional[str] = None,
370
+ ) -> Dict[str, Any]:
338
371
  """
339
- 获取RAG框架的配置。
372
+ 解析并合并RAG配置,处理RAG组。
373
+
374
+ 优先级顺序:
375
+ 1. JARVIS_RAG 中的顶级设置 (embedding_model, etc.)
376
+ 2. JARVIS_RAG_GROUP 中定义的组配置
377
+ 3. 代码中的默认值
340
378
 
341
379
  返回:
342
- Dict[str, Any]: RAG配置字典
380
+ Dict[str, Any]: 解析后的RAG配置字典
343
381
  """
344
- return GLOBAL_CONFIG_DATA.get("JARVIS_RAG", {})
382
+ group_config = {}
383
+ rag_group_name = rag_group_override or GLOBAL_CONFIG_DATA.get("JARVIS_RAG_GROUP")
384
+ rag_groups = GLOBAL_CONFIG_DATA.get("JARVIS_RAG_GROUPS", DEFAULT_RAG_GROUPS)
385
+
386
+ if rag_group_name and isinstance(rag_groups, list):
387
+ for group_item in rag_groups:
388
+ if isinstance(group_item, dict) and rag_group_name in group_item:
389
+ group_config = group_item[rag_group_name]
390
+ break
391
+
392
+ # Start with group config
393
+ resolved_config = group_config.copy()
394
+
395
+ # Override with specific settings from the top-level JARVIS_RAG dict
396
+ top_level_rag_config = GLOBAL_CONFIG_DATA.get("JARVIS_RAG", {})
397
+ if isinstance(top_level_rag_config, dict):
398
+ for key in [
399
+ "embedding_model",
400
+ "rerank_model",
401
+ "use_bm25",
402
+ "use_rerank",
403
+ ]:
404
+ if key in top_level_rag_config:
405
+ resolved_config[key] = top_level_rag_config[key]
406
+
407
+ return resolved_config
345
408
 
346
409
 
347
410
  def get_rag_embedding_model() -> str:
@@ -351,7 +414,8 @@ def get_rag_embedding_model() -> str:
351
414
  返回:
352
415
  str: 嵌入模型的名称
353
416
  """
354
- return get_rag_config().get("embedding_model", "BAAI/bge-base-zh-v1.5")
417
+ config = _get_resolved_rag_config()
418
+ return config.get("embedding_model", "BAAI/bge-m3")
355
419
 
356
420
 
357
421
  def get_rag_rerank_model() -> str:
@@ -361,7 +425,8 @@ def get_rag_rerank_model() -> str:
361
425
  返回:
362
426
  str: rerank模型的名称
363
427
  """
364
- return get_rag_config().get("rerank_model", "BAAI/bge-reranker-base")
428
+ config = _get_resolved_rag_config()
429
+ return config.get("rerank_model", "BAAI/bge-reranker-v2-m3")
365
430
 
366
431
 
367
432
  def get_rag_embedding_cache_path() -> str:
@@ -382,3 +447,25 @@ def get_rag_vector_db_path() -> str:
382
447
  str: 数据库路径
383
448
  """
384
449
  return ".jarvis/rag/vectordb"
450
+
451
+
452
+ def get_rag_use_bm25() -> bool:
453
+ """
454
+ 获取RAG是否使用BM25。
455
+
456
+ 返回:
457
+ bool: 如果使用BM25则返回True,默认为True
458
+ """
459
+ config = _get_resolved_rag_config()
460
+ return config.get("use_bm25", True) is True
461
+
462
+
463
+ def get_rag_use_rerank() -> bool:
464
+ """
465
+ 获取RAG是否使用rerank。
466
+
467
+ 返回:
468
+ bool: 如果使用rerank则返回True,默认为True
469
+ """
470
+ config = _get_resolved_rag_config()
471
+ return config.get("use_rerank", True) is True
@@ -214,9 +214,7 @@ def handle_commit_workflow() -> bool:
214
214
  Returns:
215
215
  bool: 提交是否成功
216
216
  """
217
- if is_confirm_before_apply_patch() and not user_confirm(
218
- "是否要提交代码?", default=True
219
- ):
217
+ if is_confirm_before_apply_patch() and not user_confirm("是否要提交代码?", default=True):
220
218
  revert_change()
221
219
  return False
222
220
 
@@ -429,9 +427,7 @@ def check_and_update_git_repo(repo_path: str) -> bool:
429
427
  if not in_venv and (
430
428
  "Permission denied" in error_msg or "not writeable" in error_msg
431
429
  ):
432
- if user_confirm(
433
- "检测到权限问题,是否尝试用户级安装(--user)?", True
434
- ):
430
+ if user_confirm("检测到权限问题,是否尝试用户级安装(--user)?", True):
435
431
  user_result = subprocess.run(
436
432
  install_cmd + ["--user"],
437
433
  cwd=git_root,
@@ -446,9 +442,7 @@ def check_and_update_git_repo(repo_path: str) -> bool:
446
442
  PrettyOutput.print(f"代码安装失败: {error_msg}", OutputType.ERROR)
447
443
  return False
448
444
  except Exception as e:
449
- PrettyOutput.print(
450
- f"安装过程中发生意外错误: {str(e)}", OutputType.ERROR
451
- )
445
+ PrettyOutput.print(f"安装过程中发生意外错误: {str(e)}", OutputType.ERROR)
452
446
  return False
453
447
  # 更新检查日期文件
454
448
  with open(last_check_file, "w") as f:
@@ -482,9 +476,7 @@ def get_diff_file_list() -> List[str]:
482
476
  subprocess.run(["git", "reset"], check=True)
483
477
 
484
478
  if result.returncode != 0:
485
- PrettyOutput.print(
486
- f"获取差异文件列表失败: {result.stderr}", OutputType.ERROR
487
- )
479
+ PrettyOutput.print(f"获取差异文件列表失败: {result.stderr}", OutputType.ERROR)
488
480
  return []
489
481
 
490
482
  return [f for f in result.stdout.splitlines() if f]
@@ -533,8 +525,10 @@ def get_recent_commits_with_files() -> List[Dict[str, Any]]:
533
525
  ],
534
526
  capture_output=True,
535
527
  text=True,
528
+ encoding="utf-8",
529
+ errors="replace",
536
530
  )
537
- if result.returncode != 0:
531
+ if result.returncode != 0 or result.stdout is None:
538
532
  return []
539
533
 
540
534
  # 解析提交信息
@@ -632,9 +626,7 @@ def confirm_add_new_files() -> None:
632
626
  need_confirm = True
633
627
 
634
628
  if binary_files:
635
- output_lines.append(
636
- f"检测到{len(binary_files)}个二进制文件(选择N将重新检测)"
637
- )
629
+ output_lines.append(f"检测到{len(binary_files)}个二进制文件(选择N将重新检测)")
638
630
  output_lines.append("二进制文件列表:")
639
631
  output_lines.extend(f" - {file}" for file in binary_files)
640
632
  need_confirm = True