jarvis-ai-assistant 0.3.24__py3-none-any.whl → 0.3.25__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.
jarvis/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Jarvis AI Assistant"""
3
3
 
4
- __version__ = "0.3.24"
4
+ __version__ = "0.3.25"
@@ -384,21 +384,21 @@ def handle_builtin_config_selector(
384
384
  table.add_column("描述", style="white")
385
385
 
386
386
  for idx, opt in enumerate(options, 1):
387
- category = opt.get("category", "")
388
- name = opt.get("name", "")
389
- file_path = opt.get("file", "")
387
+ category = str(opt.get("category", ""))
388
+ name = str(opt.get("name", ""))
389
+ file_path = str(opt.get("file", ""))
390
390
  # 描述列显示配置描述;若为 roles 同时显示角色数量与列表
391
391
  if category == "roles":
392
392
  count = opt.get("roles_count")
393
- details = opt.get("details", "")
394
- parts = []
393
+ details_val = opt.get("details", "")
394
+ parts: list[str] = []
395
395
  if isinstance(count, int) and count > 0:
396
396
  parts.append(f"{count} 个角色")
397
- if details:
398
- parts.append(details)
397
+ if isinstance(details_val, str) and details_val:
398
+ parts.append(details_val)
399
399
  desc_display = "\n".join(parts) if parts else ""
400
400
  else:
401
- desc_display = opt.get("desc", "")
401
+ desc_display = str(opt.get("desc", ""))
402
402
  table.add_row(str(idx), category, name, file_path, desc_display)
403
403
 
404
404
  Console().print(table)
@@ -412,29 +412,29 @@ def handle_builtin_config_selector(
412
412
  index = int(choice.strip())
413
413
  if 1 <= index <= len(options):
414
414
  sel = options[index - 1]
415
- args = []
415
+ args: list[str] = []
416
416
 
417
417
  if sel["category"] == "agent":
418
418
  # jarvis-agent 支持 -f/--config(全局配置)与 -c/--agent-definition
419
- args = [sel["cmd"], "-c", sel["file"]]
419
+ args = [str(sel["cmd"]), "-c", str(sel["file"])]
420
420
  if model_group:
421
- args += ["-g", model_group]
421
+ args += ["-g", str(model_group)]
422
422
  if config_file:
423
- args += ["-f", config_file]
423
+ args += ["-f", str(config_file)]
424
424
  if task:
425
- args += ["--task", task]
425
+ args += ["--task", str(task)]
426
426
 
427
427
  elif sel["category"] == "multi_agent":
428
428
  # jarvis-multi-agent 需要 -c/--config,用户输入通过 -i/--input 传递
429
- args = [sel["cmd"], "-c", sel["file"]]
429
+ args = [str(sel["cmd"]), "-c", str(sel["file"])]
430
430
  if task:
431
- args += ["-i", task]
431
+ args += ["-i", str(task)]
432
432
 
433
433
  elif sel["category"] == "roles":
434
434
  # jarvis-platform-manager role 子命令,支持 -c/-t/-g
435
- args = [sel["cmd"], "role", "-c", sel["file"]]
435
+ args = [str(sel["cmd"]), "role", "-c", str(sel["file"])]
436
436
  if model_group:
437
- args += ["-g", model_group]
437
+ args += ["-g", str(model_group)]
438
438
 
439
439
  if args:
440
440
  PrettyOutput.print(
@@ -165,6 +165,13 @@
165
165
  "JARVIS_MAX_INPUT_TOKEN_COUNT": {
166
166
  "type": "number",
167
167
  "default": 32000
168
+ },
169
+ "ENV": {
170
+ "type": "object",
171
+ "description": "该模型组特定的环境变量,会覆盖全局 ENV 配置",
172
+ "additionalProperties": {
173
+ "type": "string"
174
+ }
168
175
  }
169
176
  },
170
177
  "required": [
@@ -113,9 +113,9 @@ class GitCommitTool:
113
113
  # 获取文件列表
114
114
  files_cmd = ["git", "diff", "--cached", "--name-only"]
115
115
  process = subprocess.Popen(
116
- files_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
116
+ files_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
117
117
  )
118
- files_output = process.communicate()[0].decode()
118
+ files_output = process.communicate()[0]
119
119
  files = [f.strip() for f in files_output.split("\n") if f.strip()]
120
120
  file_count = len(files)
121
121
 
@@ -124,8 +124,9 @@ class GitCommitTool:
124
124
  ["git", "diff", "--cached", "--exit-code"],
125
125
  stdout=subprocess.PIPE,
126
126
  stderr=subprocess.PIPE,
127
+ text=True,
127
128
  )
128
- diff = process.communicate()[0].decode(errors="ignore")
129
+ diff = process.communicate()[0]
129
130
 
130
131
  try:
131
132
  temp_diff_file_path = None
@@ -9,6 +9,7 @@ from typing_extensions import Self
9
9
  from rich import box # type: ignore
10
10
  from rich.live import Live # type: ignore
11
11
  from rich.panel import Panel # type: ignore
12
+ from rich.status import Status # type: ignore
12
13
  from rich.text import Text # type: ignore
13
14
 
14
15
  from jarvis.jarvis_utils.config import (
@@ -120,23 +121,44 @@ class BasePlatform(ABC):
120
121
  else:
121
122
  response = ""
122
123
 
123
- text_content = Text(overflow="fold") # 添加文本溢出处理
124
- panel = Panel(
125
- text_content,
126
- title=f"[bold cyan]{self.name()}[/bold cyan]",
127
- subtitle="[dim]思考中... (按 Ctrl+C 中断)[/dim]",
128
- border_style="bright_blue",
129
- box=box.ROUNDED,
130
- expand=True # 允许面板自动调整大小
131
- )
132
-
133
124
  if not self.suppress_output:
134
125
  if get_pretty_output():
135
- # 优化Live输出,减少闪烁
126
+ chat_iterator = self.chat(message)
127
+ first_chunk = None
128
+
129
+ with Status(
130
+ f"🤔 {self.name()} 正在思考中...", spinner="dots", console=console
131
+ ):
132
+ try:
133
+ while True:
134
+ first_chunk = next(chat_iterator)
135
+ if first_chunk:
136
+ break
137
+ except StopIteration:
138
+ return ""
139
+
140
+ text_content = Text(overflow="fold")
141
+ panel = Panel(
142
+ text_content,
143
+ title=f"[bold cyan]{self.name()}[/bold cyan]",
144
+ subtitle="[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]",
145
+ border_style="bright_blue",
146
+ box=box.ROUNDED,
147
+ expand=True, # 允许面板自动调整大小
148
+ )
149
+
136
150
  buffer = []
137
151
  buffer_count = 0
138
152
  with Live(panel, refresh_per_second=4, transient=False) as live:
139
- for s in self.chat(message):
153
+ # Process first chunk
154
+ response += first_chunk
155
+ buffer.append(first_chunk)
156
+ buffer_count += 1
157
+
158
+ # Process rest of the chunks
159
+ for s in chat_iterator:
160
+ if not s:
161
+ continue
140
162
  response += s # Accumulate the full response string
141
163
  buffer.append(s)
142
164
  buffer_count += 1
@@ -144,13 +166,17 @@ class BasePlatform(ABC):
144
166
  # 积累一定量或达到最后再更新,减少闪烁
145
167
  if buffer_count >= 5 or s == "":
146
168
  # Append buffered content to the Text object
147
- text_content.append("".join(buffer), style="bright_white")
169
+ text_content.append(
170
+ "".join(buffer), style="bright_white"
171
+ )
148
172
  buffer.clear()
149
173
  buffer_count = 0
150
174
 
151
175
  # --- Scrolling Logic ---
152
176
  # Calculate available height in the panel
153
- max_text_height = console.height - 5 # Leave space for borders/titles
177
+ max_text_height = (
178
+ console.height - 5
179
+ ) # Leave space for borders/titles
154
180
  if max_text_height <= 0:
155
181
  max_text_height = 1
156
182
 
@@ -163,11 +189,15 @@ class BasePlatform(ABC):
163
189
  # If content overflows, truncate to show only the last few lines
164
190
  if len(lines) > max_text_height:
165
191
  new_text = "\n".join(
166
- text_content.plain.splitlines()[-max_text_height:]
192
+ text_content.plain.splitlines()[
193
+ -max_text_height:
194
+ ]
167
195
  )
168
196
  text_content.plain = new_text
169
197
 
170
- panel.subtitle = "[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]"
198
+ panel.subtitle = (
199
+ "[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]"
200
+ )
171
201
  live.update(panel)
172
202
 
173
203
  if is_immediate_abort() and get_interrupt():
@@ -175,7 +205,9 @@ class BasePlatform(ABC):
175
205
 
176
206
  # Ensure any remaining content in the buffer is displayed
177
207
  if buffer:
178
- text_content.append("".join(buffer), style="bright_white")
208
+ text_content.append(
209
+ "".join(buffer), style="bright_white"
210
+ )
179
211
 
180
212
  # At the end, display the entire response
181
213
  text_content.plain = response
jarvis/jarvis_rag/cli.py CHANGED
@@ -357,6 +357,12 @@ def retrieve(
357
357
  None, "--db-path", help="向量数据库的路径。覆盖全局配置。"
358
358
  ),
359
359
  n_results: int = typer.Option(5, "--top-n", help="要检索的文档数量。"),
360
+ rewrite: bool = typer.Option(
361
+ True,
362
+ "--rewrite/--no-rewrite",
363
+ help="是否对查询进行LLM重写以提升召回,默认开启。",
364
+ show_default=True,
365
+ ),
360
366
  ):
361
367
  """仅从RAG知识库检索文档并打印结果。"""
362
368
  try:
@@ -371,6 +377,7 @@ def retrieve(
371
377
  collection_name=collection_name,
372
378
  use_bm25=use_bm25,
373
379
  use_rerank=use_rerank,
380
+ use_query_rewrite=rewrite,
374
381
  )
375
382
 
376
383
  PrettyOutput.print(f"正在为问题检索文档: '{question}'", OutputType.INFO)
@@ -1,6 +1,9 @@
1
1
  import torch
2
+ import os
2
3
  from typing import List, cast
3
4
  from langchain_huggingface import HuggingFaceEmbeddings
5
+ from huggingface_hub import snapshot_download
6
+
4
7
 
5
8
  from .cache import EmbeddingCache
6
9
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -38,18 +41,135 @@ class EmbeddingManager:
38
41
  encode_kwargs = {"normalize_embeddings": True}
39
42
 
40
43
  try:
41
- # First try to load model locally
44
+ # First try to load model from local cache without any network access
42
45
  try:
43
46
  from sentence_transformers import SentenceTransformer
44
- local_model = SentenceTransformer(self.model_name, device=model_kwargs["device"])
47
+ local_dir = None
48
+ # Prefer explicit local dir via env or direct path
49
+
50
+ if os.path.isdir(self.model_name):
51
+ return HuggingFaceEmbeddings(
52
+ model_name=self.model_name,
53
+ model_kwargs=model_kwargs,
54
+ encode_kwargs=encode_kwargs,
55
+ show_progress=False,
56
+ )
57
+
58
+ # Try common local cache directories for sentence-transformers and HF hub
59
+ try:
60
+ home = os.path.expanduser("~")
61
+ st_home = os.path.join(home, ".cache", "sentence_transformers")
62
+ torch_st_home = os.path.join(home, ".cache", "torch", "sentence_transformers")
63
+ # Build common name variants found in local caches
64
+ org, name = (
65
+ self.model_name.split("/", 1)
66
+ if "/" in self.model_name
67
+ else ("", self.model_name)
68
+ )
69
+ san1 = self.model_name.replace("/", "_")
70
+ san2 = self.model_name.replace("/", "__")
71
+ san3 = self.model_name.replace("/", "--")
72
+ # include plain 'name' for caches that drop org prefix
73
+ name_variants = list(dict.fromkeys([self.model_name, san1, san2, san3, name]))
74
+ candidates = []
75
+ for base in [st_home, torch_st_home]:
76
+ for nv in name_variants:
77
+ p = os.path.join(base, nv)
78
+ if os.path.isdir(p):
79
+ candidates.append(p)
80
+ # Fuzzy scan cache directory for entries that include variants
81
+ try:
82
+ for entry in os.listdir(base):
83
+ ep = os.path.join(base, entry)
84
+ if not os.path.isdir(ep):
85
+ continue
86
+ if (
87
+ (org and entry.startswith(f"{org}__") and name in entry)
88
+ or (san1 in entry)
89
+ or (name in entry)
90
+ ):
91
+ candidates.append(ep)
92
+ except Exception:
93
+ pass
94
+
95
+ # Hugging Face Hub cache snapshots
96
+ hf_cache = os.path.join(home, ".cache", "huggingface", "hub")
97
+ if "/" in self.model_name:
98
+ org, name = self.model_name.split("/", 1)
99
+ models_dir = os.path.join(hf_cache, f"models--{org}--{name}", "snapshots")
100
+ if os.path.isdir(models_dir):
101
+ try:
102
+ snaps = sorted(
103
+ [os.path.join(models_dir, d) for d in os.listdir(models_dir)],
104
+ key=lambda p: os.path.getmtime(p),
105
+ reverse=True,
106
+ )
107
+ except Exception:
108
+ snaps = [os.path.join(models_dir, d) for d in os.listdir(models_dir)]
109
+ for sp in snaps:
110
+ if os.path.isdir(sp):
111
+ candidates.append(sp)
112
+ break
113
+
114
+ for cand in candidates:
115
+ try:
116
+ return HuggingFaceEmbeddings(
117
+ model_name=cand,
118
+ model_kwargs=model_kwargs,
119
+ encode_kwargs=encode_kwargs,
120
+ show_progress=False,
121
+ )
122
+ except Exception:
123
+ continue
124
+ except Exception:
125
+ pass
126
+
127
+ try:
128
+ # Try resolve local cached directory; do not hit network
129
+ local_dir = snapshot_download(repo_id=self.model_name, local_files_only=True)
130
+ except Exception:
131
+ local_dir = None
132
+
133
+ if local_dir:
134
+ return HuggingFaceEmbeddings(
135
+ model_name=local_dir,
136
+ model_kwargs=model_kwargs,
137
+ encode_kwargs=encode_kwargs,
138
+ show_progress=False,
139
+ )
140
+
141
+
142
+
143
+ # Fall back to remote download if local cache not found and not offline
45
144
  return HuggingFaceEmbeddings(
46
- client=local_model,
47
145
  model_name=self.model_name,
48
146
  model_kwargs=model_kwargs,
49
147
  encode_kwargs=encode_kwargs,
148
+ show_progress=True,
50
149
  )
51
- except Exception:
52
- # Fall back to remote download if local loading fails
150
+ except Exception as _e:
151
+ # 如果已检测到本地候选路径(直接目录 / 本地缓存快照),则视为本地加载失败,
152
+ # 为避免在用户期望“本地优先不联网”的情况下触发联网,直接抛错并给出修复建议。
153
+ had_local_candidate = False
154
+ try:
155
+ had_local_candidate = (
156
+ os.path.isdir(self.model_name)
157
+ # 如果上面 snapshot_download 命中了本地缓存,会将 local_dir 设为非 None
158
+ or (locals().get("local_dir") is not None)
159
+ )
160
+ except Exception:
161
+ pass
162
+
163
+ if had_local_candidate:
164
+ PrettyOutput.print(
165
+ "检测到本地模型路径但加载失败。为避免触发网络访问,已中止远程回退。\n"
166
+ "请确认本地目录包含完整的 Transformers/Tokenizer 文件(如 config.json、model.safetensors、tokenizer.json/merges.txt 等),\n"
167
+ "或在配置中将 embedding_model 设置为该本地目录,或将模型放置到默认的 Hugging Face 缓存目录(例如 ~/.cache/huggingface/hub)。",
168
+ OutputType.ERROR,
169
+ )
170
+ raise
171
+
172
+ # 未发现任何本地候选,则保持原有行为:回退至远程下载
53
173
  return HuggingFaceEmbeddings(
54
174
  model_name=self.model_name,
55
175
  model_kwargs=model_kwargs,
@@ -34,6 +34,7 @@ class JarvisRAGPipeline:
34
34
  collection_name: str = "jarvis_rag_collection",
35
35
  use_bm25: bool = True,
36
36
  use_rerank: bool = True,
37
+ use_query_rewrite: bool = True,
37
38
  ):
38
39
  """
39
40
  初始化RAG管道。
@@ -69,6 +70,8 @@ class JarvisRAGPipeline:
69
70
  self.collection_name = collection_name
70
71
  self.use_bm25 = use_bm25
71
72
  self.use_rerank = use_rerank
73
+ # 查询重写开关(默认开启,可由CLI控制)
74
+ self.use_query_rewrite = use_query_rewrite
72
75
 
73
76
  # 延迟加载的组件
74
77
  self._embedding_manager: Optional[EmbeddingManager] = None
@@ -229,8 +232,15 @@ class JarvisRAGPipeline:
229
232
  """
230
233
  # 0. 检测索引变更并可选更新(在重写query之前)
231
234
  self._pre_search_update_index_if_needed()
232
- # 1. 将原始查询重写为多个查询
233
- rewritten_queries = self._get_query_rewriter().rewrite(query_text)
235
+ # 1. 将原始查询重写为多个查询(可配置)
236
+ if self.use_query_rewrite:
237
+ rewritten_queries = self._get_query_rewriter().rewrite(query_text)
238
+ else:
239
+ PrettyOutput.print(
240
+ "已关闭查询重写,将直接使用原始查询进行检索。",
241
+ OutputType.INFO,
242
+ )
243
+ rewritten_queries = [query_text]
234
244
 
235
245
  # 2. 为每个重写的查询检索初始候选文档
236
246
  PrettyOutput.print(
@@ -303,8 +313,15 @@ class JarvisRAGPipeline:
303
313
  """
304
314
  # 0. 检测索引变更并可选更新(在重写query之前)
305
315
  self._pre_search_update_index_if_needed()
306
- # 1. 重写查询
307
- rewritten_queries = self._get_query_rewriter().rewrite(query_text)
316
+ # 1. 重写查询(可配置)
317
+ if self.use_query_rewrite:
318
+ rewritten_queries = self._get_query_rewriter().rewrite(query_text)
319
+ else:
320
+ PrettyOutput.print(
321
+ "已关闭查询重写,将直接使用原始查询进行检索。",
322
+ OutputType.INFO,
323
+ )
324
+ rewritten_queries = [query_text]
308
325
 
309
326
  # 2. 检索候选文档
310
327
  PrettyOutput.print(
@@ -1,9 +1,11 @@
1
1
  from typing import List
2
+ import os
2
3
 
3
4
  from langchain.docstore.document import Document
4
5
  from sentence_transformers.cross_encoder import ( # type: ignore
5
6
  CrossEncoder,
6
7
  )
8
+ from huggingface_hub import snapshot_download
7
9
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
8
10
 
9
11
 
@@ -21,8 +23,28 @@ class Reranker:
21
23
  model_name (str): 要使用的Cross-Encoder模型的名称。
22
24
  """
23
25
  PrettyOutput.print(f"正在初始化重排模型: {model_name}...", OutputType.INFO)
24
- self.model = CrossEncoder(model_name)
25
- PrettyOutput.print("重排模型初始化成功。", OutputType.SUCCESS)
26
+ try:
27
+ local_dir = None
28
+
29
+ if os.path.isdir(model_name):
30
+ self.model = CrossEncoder(model_name)
31
+ PrettyOutput.print("重排模型初始化成功。", OutputType.SUCCESS)
32
+ return
33
+ try:
34
+ # Prefer local cache; avoid any network access
35
+ local_dir = snapshot_download(repo_id=model_name, local_files_only=True)
36
+ except Exception:
37
+ local_dir = None
38
+
39
+ if local_dir:
40
+ self.model = CrossEncoder(local_dir)
41
+ else:
42
+ self.model = CrossEncoder(model_name)
43
+
44
+ PrettyOutput.print("重排模型初始化成功。", OutputType.SUCCESS)
45
+ except Exception as e:
46
+ PrettyOutput.print(f"初始化重排模型失败: {e}", OutputType.ERROR)
47
+ raise
26
48
 
27
49
  def rerank(
28
50
  self, query: str, documents: List[Document], top_n: int = 5
@@ -24,20 +24,11 @@ Example:
24
24
 
25
25
  def execute_command(command: str, should_run: bool) -> None:
26
26
  """Print command without execution"""
27
- PrettyOutput.print(command, OutputType.CODE, lang="bash")
27
+ print(command)
28
28
  if should_run:
29
29
  os.system(command)
30
30
 
31
31
 
32
- def _check_fish_shell() -> bool:
33
- """Check if current shell is fish
34
-
35
- Returns:
36
- bool: True if fish shell, False otherwise
37
- """
38
- return get_shell_name() == "fish"
39
-
40
-
41
32
  def _get_config_file() -> str:
42
33
  """Get fish config file path
43
34
 
@@ -122,8 +122,8 @@ class FileSearchReplaceTool:
122
122
 
123
123
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
124
124
 
125
- stdout_messages = []
126
- stderr_messages = []
125
+ stdout_messages: list[str] = []
126
+ stderr_messages: list[str] = []
127
127
  overall_success = False
128
128
  file_results = []
129
129
 
@@ -168,10 +168,10 @@ class FileSearchReplaceTool:
168
168
  )
169
169
 
170
170
  # 整合所有错误信息到stderr
171
- all_stderr = []
172
- for result in file_results:
173
- if not result["success"]:
174
- all_stderr.append(f"文件 {result['file']} 处理失败: {result['stderr']}")
171
+ all_stderr: list[str] = []
172
+ for file_result in file_results:
173
+ if not file_result["success"]:
174
+ all_stderr.append(f"文件 {file_result['file']} 处理失败: {file_result['stderr']}")
175
175
 
176
176
  return {
177
177
  "success": overall_success,
@@ -74,8 +74,7 @@ class ScriptTool:
74
74
  stream.feed(data)
75
75
 
76
76
  # 清理每行右侧空格,并过滤空行
77
- cleaned = []
78
- cleaned = []
77
+ cleaned: list[str] = []
79
78
  for y in range(screen.lines):
80
79
  line = screen.buffer[y]
81
80
  stripped = "".join(char.data for char in line.values()).rstrip()
@@ -64,12 +64,6 @@ class SubAgentTool:
64
64
  f"背景信息:\n{background}\n\n任务:\n{task}" if background else task
65
65
  )
66
66
 
67
- # 读取背景信息并组合任务
68
- background: str = str(args.get("background", "")).strip()
69
- enhanced_task = (
70
- f"背景信息:\n{background}\n\n任务:\n{task}" if background else task
71
- )
72
-
73
67
  # 继承父Agent的运行参数(用于覆盖默认值);若无父Agent则使用默认/全局配置
74
68
  parent_agent = args.get("agent")
75
69
  # 如未注入父Agent,尝试从全局获取当前或任一已注册Agent
@@ -160,7 +154,10 @@ class SubAgentTool:
160
154
  try:
161
155
  model_name = parent_agent.model.name() # type: ignore[attr-defined]
162
156
  if model_name:
163
- agent.model.set_model_name(model_name) # type: ignore[attr-defined]
157
+ from typing import Any
158
+ model_obj: Any = getattr(agent, "model", None)
159
+ if model_obj is not None:
160
+ model_obj.set_model_name(model_name)
164
161
  except Exception:
165
162
  pass
166
163
  if use_tools:
@@ -161,7 +161,10 @@ class SubCodeAgentTool:
161
161
  try:
162
162
  parent_model_name = parent_agent.model.name() # type: ignore[attr-defined]
163
163
  if parent_model_name:
164
- code_agent.agent.model.set_model_name(parent_model_name) # type: ignore[attr-defined]
164
+ from typing import Any
165
+ model_obj: Any = getattr(code_agent.agent, "model", None)
166
+ if model_obj is not None:
167
+ model_obj.set_model_name(parent_model_name)
165
168
  except Exception:
166
169
  pass
167
170
  except Exception:
@@ -115,6 +115,14 @@ def get_shell_name() -> str:
115
115
  return os.path.basename(shell_path).lower()
116
116
 
117
117
 
118
+ def _apply_llm_group_env_override(group_config: Dict[str, Any]) -> None:
119
+ """如果模型组配置中包含ENV,则应用环境变量覆盖"""
120
+ if "ENV" in group_config and isinstance(group_config["ENV"], dict):
121
+ os.environ.update(
122
+ {str(k): str(v) for k, v in group_config["ENV"].items() if v is not None}
123
+ )
124
+
125
+
118
126
  def _get_resolved_model_config(
119
127
  model_group_override: Optional[str] = None,
120
128
  ) -> Dict[str, Any]:
@@ -141,6 +149,8 @@ def _get_resolved_model_config(
141
149
  if isinstance(group_item, dict) and model_group_name in group_item:
142
150
  group_config = group_item[model_group_name]
143
151
  break
152
+
153
+ _apply_llm_group_env_override(group_config)
144
154
 
145
155
  # Start with group config
146
156
  resolved_config = group_config.copy()
@@ -702,7 +702,7 @@ def get_multiline_input(tip: str, print_on_empty: bool = True) -> str:
702
702
  "未检测到 fzf,无法打开文件选择器。", OutputType.WARNING
703
703
  )
704
704
  else:
705
- files: list[str] = []
705
+ files = []
706
706
  try:
707
707
  r = subprocess.run(
708
708
  ["git", "ls-files"],
@@ -835,7 +835,7 @@ def get_multiline_input(tip: str, print_on_empty: bool = True) -> str:
835
835
  "未检测到 fzf,无法打开文件选择器。", OutputType.WARNING
836
836
  )
837
837
  else:
838
- files: list[str] = []
838
+ files = []
839
839
  try:
840
840
  import os as _os
841
841
 
@@ -1085,6 +1085,14 @@ def _collect_optional_config_interactively(
1085
1085
  )
1086
1086
  or changed
1087
1087
  )
1088
+ changed = (
1089
+ _ask_and_set_int(
1090
+ "JARVIS_TOOL_FILTER_THRESHOLD",
1091
+ "设置AI工具筛选阈值 (当可用工具数超过此值时触发AI筛选, 默认30)",
1092
+ 30,
1093
+ )
1094
+ or changed
1095
+ )
1088
1096
 
1089
1097
  # 目录类配置(逗号分隔)
1090
1098
  changed = (
@@ -1164,7 +1172,7 @@ def _collect_optional_config_interactively(
1164
1172
  try:
1165
1173
  if "JARVIS_RAG" not in config_data:
1166
1174
  if get_yes_no("是否配置 RAG 检索增强参数?", default=False):
1167
- rag_conf = {}
1175
+ rag_conf: Dict[str, Any] = {}
1168
1176
  emb = get_single_line_input(
1169
1177
  f"RAG 嵌入模型(留空使用默认: {rag_default_embed}):",
1170
1178
  default="",
@@ -1468,20 +1476,15 @@ def while_success(func: Callable[[], Any], sleep_time: float = 0.1) -> Any:
1468
1476
  返回:
1469
1477
  函数执行结果
1470
1478
  """
1471
- logs: List[str] = []
1472
1479
  result: Any = None
1473
- success = False
1474
1480
  while True:
1475
1481
  try:
1476
1482
  result = func()
1477
- success = True
1478
1483
  break
1479
1484
  except Exception:
1480
- logs.append(f"重试中,等待 {sleep_time}s...")
1485
+ PrettyOutput.print(f"重试中,等待 {sleep_time}s...", OutputType.WARNING)
1481
1486
  time.sleep(sleep_time)
1482
1487
  continue
1483
- if logs:
1484
- PrettyOutput.print("\n".join(logs), OutputType.WARNING)
1485
1488
  return result
1486
1489
 
1487
1490
 
@@ -1499,16 +1502,13 @@ def while_true(func: Callable[[], bool], sleep_time: float = 0.1) -> Any:
1499
1502
  与while_success不同,此函数只检查返回是否为True,
1500
1503
  不捕获异常,异常会直接抛出
1501
1504
  """
1502
- logs: List[str] = []
1503
1505
  ret: bool = False
1504
1506
  while True:
1505
1507
  ret = func()
1506
1508
  if ret:
1507
1509
  break
1508
- logs.append(f"重试中,等待 {sleep_time}s...")
1510
+ PrettyOutput.print(f"重试中,等待 {sleep_time}s...", OutputType.WARNING)
1509
1511
  time.sleep(sleep_time)
1510
- if logs:
1511
- PrettyOutput.print("\n".join(logs), OutputType.WARNING)
1512
1512
  return ret
1513
1513
 
1514
1514
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.3.24
3
+ Version: 0.3.25
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
@@ -1,11 +1,11 @@
1
- jarvis/__init__.py,sha256=sNrDvwFba715bqcu57BUGzZ7SeFI3Oc-7DTacWo5JxM,74
1
+ jarvis/__init__.py,sha256=Y5YWCRMaj8nleHAZXhZE59v10sX-ZvJ5CW8wpIDG-SM,74
2
2
  jarvis/jarvis_agent/__init__.py,sha256=eLSiwd-Eo6KqG0qLJ5peI-symxWUrTsxWCBnOgEO474,37524
3
3
  jarvis/jarvis_agent/agent_manager.py,sha256=mmDe26Wa1Dnyp8CkTKupcKipC-5HSuCnk-oVJseOdTg,2801
4
4
  jarvis/jarvis_agent/builtin_input_handler.py,sha256=wS-FqpT3pIXwHn1dfL3SpXonUKWgVThbQueUIeyRc2U,2917
5
5
  jarvis/jarvis_agent/config_editor.py,sha256=IHyuwI4SRDrqxz8lO0NNgt2F3uYAw1WEKXMxQV49bYI,1928
6
6
  jarvis/jarvis_agent/edit_file_handler.py,sha256=5sFz84jqy2gpc0aLOre2bvz8_DitlBoWZs_cQwftWLw,11570
7
7
  jarvis/jarvis_agent/file_methodology_manager.py,sha256=PwDUQwq7HVIyPInsN8fgWyMXLwi8heIXPrqfBZJhVHs,4260
8
- jarvis/jarvis_agent/jarvis.py,sha256=gTqu7-3lXBYGXE8cpmu6ypjc_Jm1GmVhsCsOcoDiXS8,22241
8
+ jarvis/jarvis_agent/jarvis.py,sha256=UNI4PoJpf4L7wp9y8-A6AfMXMQv7ikRk2YXmN5pRKUc,22383
9
9
  jarvis/jarvis_agent/main.py,sha256=bFcwXWC6O05jQiXy6ED_bHjdjDo5VwV_i1BoBEAzgP0,3336
10
10
  jarvis/jarvis_agent/memory_manager.py,sha256=Ckt6wmXOduu97jfyWQbRDmNH6yX11XzLW2nMTeVhFeY,5316
11
11
  jarvis/jarvis_agent/methodology_share_manager.py,sha256=AB_J9BwRgaeENQfL6bH83FOLeLrgHhppMb7psJNevKs,6874
@@ -44,11 +44,11 @@ jarvis/jarvis_code_analysis/checklists/shell.py,sha256=aRFYhQQvTgbYd-uY5pc8UHIUA
44
44
  jarvis/jarvis_code_analysis/checklists/sql.py,sha256=vR0T6qC7b4dURjJVAd7kSVxyvZEQXPG1Jqc2sNTGp5c,2355
45
45
  jarvis/jarvis_code_analysis/checklists/swift.py,sha256=TPx4I6Gupvs6tSerRKmTSKEPQpOLEbH2Y7LXg1uBgxc,2566
46
46
  jarvis/jarvis_code_analysis/checklists/web.py,sha256=25gGD7pDadZQybNFvALYxWvK0VRjGQb1NVJQElwjyk0,3943
47
- jarvis/jarvis_data/config_schema.json,sha256=GIBSroMb9YE1oW0rZQRVjj1ceQOOFPxeYXQ4eTnLoL8,12323
47
+ jarvis/jarvis_data/config_schema.json,sha256=_O_FQwmEldoGpTJ0EjyDjI6Zc2wkyr_7yVn00WA_Qf4,12575
48
48
  jarvis/jarvis_data/tiktoken/9b5ad71b2ce5302211f9c61530b329a4922fc6a4,sha256=Ijkht27pm96ZW3_3OFE-7xAPtR0YyTWXoRO8_-hlsqc,1681126
49
49
  jarvis/jarvis_git_squash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  jarvis/jarvis_git_squash/main.py,sha256=6PECdAbTbrsJBRLK1pXBh4hdJ_LADh-XXSic1xJi97E,2255
51
- jarvis/jarvis_git_utils/git_commiter.py,sha256=q9O__HfoGZCV5tSl3lI0Gp2hynHUzXV5jDlRaFYk_6o,14554
51
+ jarvis/jarvis_git_utils/git_commiter.py,sha256=mtrZpdq42567dnX-Q8OJ6ZtSRaqRDDDQxHAGRMXdej0,14559
52
52
  jarvis/jarvis_mcp/__init__.py,sha256=OPMtjD-uq9xAaKCRIDyKIosaFfBe1GBPu1az-mQ0rVM,2048
53
53
  jarvis/jarvis_mcp/sse_mcp_client.py,sha256=UIDBaFNuuaJE0YiKmtbZTqwZpkDI5SaS0my1DIJj-3g,22831
54
54
  jarvis/jarvis_mcp/stdio_mcp_client.py,sha256=APYUksYKlMx7AVNODKOLrTkKZPnp4kqTQIYIuNDDKko,11286
@@ -60,7 +60,7 @@ jarvis/jarvis_multi_agent/__init__.py,sha256=Ygp4DBBkmEXCRcQV3nr07zOxo2Tb4149mi4
60
60
  jarvis/jarvis_multi_agent/main.py,sha256=b9IThFMeUZCYSlgT-VT8r7xeBdrEE_zNT11awEc8IdY,1853
61
61
  jarvis/jarvis_platform/__init__.py,sha256=WLQHSiE87PPket2M50_hHzjdMIgPIBx2VF8JfB_NNRk,105
62
62
  jarvis/jarvis_platform/ai8.py,sha256=g8JkqPGs9SEbqstNMCc5rCHO0QcPHX9LNvb7HMWwB-Q,11471
63
- jarvis/jarvis_platform/base.py,sha256=dnd3nidsf7mqwimAzmQSV9s_wGviQ7VjRqOLUkRVLYc,12151
63
+ jarvis/jarvis_platform/base.py,sha256=5RLs5d4iYQ6PdSgKXj9idoCkln68Kmrv7-Sh7HBGbyw,13434
64
64
  jarvis/jarvis_platform/human.py,sha256=jWjW8prEag79e6ddqTPV4nz_Gz6zFBfO4a1EbvP8QWA,4908
65
65
  jarvis/jarvis_platform/kimi.py,sha256=dLES_E0VmDQ3TwjTZk5vCGdbvdBeSVvvlXR90m6vPfY,15711
66
66
  jarvis/jarvis_platform/openai.py,sha256=0YSeDGHRSPQP2haEzFARx_aZH_d_UZ-HSCsJLh2hW5k,8037
@@ -72,15 +72,15 @@ jarvis/jarvis_platform_manager/main.py,sha256=tU25oVQzEFPB8aOqPec8SgnxsapCFuHBkY
72
72
  jarvis/jarvis_platform_manager/service.py,sha256=WG2r0JRBGfSLGdsKxqYvmaacU_ne3PTn3RkczTzUb_M,14899
73
73
  jarvis/jarvis_rag/__init__.py,sha256=HRTXgnQxDuaE9x-e3r6SYqhJ5d4DSI_rrIxy2IGY6qk,320
74
74
  jarvis/jarvis_rag/cache.py,sha256=Tqx_Oe-AhuWlMXHGHUaIuG6OEHoHBVZq7mL3kldtFFU,2723
75
- jarvis/jarvis_rag/cli.py,sha256=ZuyySys8sV8Mv2B_-WC2pejr2hAbh0c7qhtz0AEEOxw,17219
76
- jarvis/jarvis_rag/embedding_manager.py,sha256=7JAylkU0Y22X5x8kv5WiwMAgU-MSJEZG8PmhrWOZH4k,4307
75
+ jarvis/jarvis_rag/cli.py,sha256=l2TsLEC9plcJ5C2CF3JAYLigM-64QOPZR9Ywlb5FGiE,17454
76
+ jarvis/jarvis_rag/embedding_manager.py,sha256=SF7BW8hoZWzDhDqlOdhnX6XTw42Fx2GKs7Y_RUT1ydo,10400
77
77
  jarvis/jarvis_rag/llm_interface.py,sha256=DH46Vm3AgzK1o41X7wUUYUbbOOVPgmDCxS5l6djm9eA,4561
78
78
  jarvis/jarvis_rag/query_rewriter.py,sha256=LGAWZ8kwH_dpquuYqc4QWC7IXmHX3SsnPSYMWOn-nDE,4072
79
- jarvis/jarvis_rag/rag_pipeline.py,sha256=sPvCucAshr_luGJLx7WiCfQKVHrrHHpb4-SVdAZdJc8,13085
80
- jarvis/jarvis_rag/reranker.py,sha256=Uzn4n1bNj4kWyQu9-z-jK_5dAU6drn5jEugML-kFHg8,1885
79
+ jarvis/jarvis_rag/rag_pipeline.py,sha256=aaTuxiArtVaPemWeySApw64q204JeLNXpOEtq7Adn1I,13797
80
+ jarvis/jarvis_rag/reranker.py,sha256=7Azc3y5fngwfPKtzZ8tx6iGKNeqC8uDy8yo8VCyLyL4,2670
81
81
  jarvis/jarvis_rag/retriever.py,sha256=BNEFZAgxTbmkxzBP1uy1wz3MX8wJN1wx5EtfsHqakqE,18374
82
82
  jarvis/jarvis_smart_shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
- jarvis/jarvis_smart_shell/main.py,sha256=J5O58K5KvUvB_ghbT5XwIi5QGXD3DiBlGDs0wYYvy-w,15197
83
+ jarvis/jarvis_smart_shell/main.py,sha256=JizPfVI-7-GqnYxdg77RHietGet6hZE6xUZoUCwKShE,14971
84
84
  jarvis/jarvis_stats/__init__.py,sha256=jJzgP43nxzLbNGs8Do4Jfta1PNCJMf1Oq9YTPd6EnFM,342
85
85
  jarvis/jarvis_stats/cli.py,sha256=iWuhA1AQAIOccXVUaO-k87T2b10s08wyeKAY5lUQ4og,12702
86
86
  jarvis/jarvis_stats/stats.py,sha256=AiQck1ma78YGR4hjT-leab-6GRMIQQmCp7wcG5SRKWw,19379
@@ -90,8 +90,8 @@ jarvis/jarvis_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
90
90
  jarvis/jarvis_tools/ask_user.py,sha256=M6DdLNryCE8y1JcdZHEifUgZkPUEPNKc-zDW5p0Mb1k,2029
91
91
  jarvis/jarvis_tools/base.py,sha256=tFZkRlbV_a-pbjM-ci9AYmXVJm__FXuzVWKbQEyz4Ao,1639
92
92
  jarvis/jarvis_tools/clear_memory.py,sha256=_GlwqlCAsoHeB24Y1CnjLdMawRTc6cq55AA8Yi5AZg4,8249
93
- jarvis/jarvis_tools/edit_file.py,sha256=BUz0kfv44kqfNDr3tZKS-7Cn5DDhYl8xhHO7X8x7HQI,7086
94
- jarvis/jarvis_tools/execute_script.py,sha256=kASNTShHVGlHm7pZZxUeyEZHzHAYiZ-87AzrYVyORMw,6231
93
+ jarvis/jarvis_tools/edit_file.py,sha256=4Q0-ofyUVd3R5TAoQqQfTZCBlE5fgvgD3bf7_6wiZ3I,7139
94
+ jarvis/jarvis_tools/execute_script.py,sha256=UeOTv5Vf6KqmPH-gSwFf2xz4pRAADo9KTUUpi6yU138,6221
95
95
  jarvis/jarvis_tools/file_analyzer.py,sha256=jzVb8fAJn3dWwpCiYH-Wuxva4kpHqBB2_V3x3mzY0Gs,4158
96
96
  jarvis/jarvis_tools/generate_new_tool.py,sha256=OCHkBOCQflgMZXRP4vgX3blnJOUq-_QkcBFRQFj3sj0,7894
97
97
  jarvis/jarvis_tools/methodology.py,sha256=_K4GIDUodGEma3SvNRo7Qs5rliijgNespVLyAPN35JU,5233
@@ -102,28 +102,28 @@ jarvis/jarvis_tools/retrieve_memory.py,sha256=hQ3s4IgBHtmUPzuWFl5Pc2FLEuT2gdi4l4
102
102
  jarvis/jarvis_tools/rewrite_file.py,sha256=CuvjWPTbUaPbex9FKSmw_Ru4r6R-CX_3vqTqCTp8nHA,6959
103
103
  jarvis/jarvis_tools/save_memory.py,sha256=QKj6iYZL2XxPX0NnU9HqvoDOpJZ38mJmatDmHLK2r74,7012
104
104
  jarvis/jarvis_tools/search_web.py,sha256=8ugOcGBsAEg59pX_WS0WADuu7Klw7XGR6guEMz4OnwI,6181
105
- jarvis/jarvis_tools/sub_agent.py,sha256=y9SuuFBVPpQQxS0SXO4T1125YG8zfwKuBXLlHiVPrF8,8084
106
- jarvis/jarvis_tools/sub_code_agent.py,sha256=9YEYK8hbciGhvC44MU3Y5vQc4hYgOAiahiUbUK_w2iI,7696
105
+ jarvis/jarvis_tools/sub_agent.py,sha256=GTn6JXIsomHw9M46HTybIYgyFHUc72AFrbI4l9cwUro,7983
106
+ jarvis/jarvis_tools/sub_code_agent.py,sha256=JSQKJNBO3zQsCNk3x-CPzqTIRPeOt8rvP4WPC-JQOwI,7848
107
107
  jarvis/jarvis_tools/virtual_tty.py,sha256=1AYrVNP5QCX1HGo-xi3XEP93cMWZUOkil4ki2gwf7dI,25504
108
108
  jarvis/jarvis_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
109
109
  jarvis/jarvis_tools/cli/main.py,sha256=pyvSz0-hBduOqQShduv76UcB5wrK0v6MLonpub9AuXg,9334
110
110
  jarvis/jarvis_utils/__init__.py,sha256=67h0ldisGlh3oK4DAeNEL2Bl_VsI3tSmfclasyVlueM,850
111
111
  jarvis/jarvis_utils/builtin_replace_map.py,sha256=4BurljGuiG_I93EBs7mlFlPm9wYC_4CmdTG5tQWpF6g,1712
112
112
  jarvis/jarvis_utils/clipboard.py,sha256=D3wzQeqg_yiH7Axs4d6MRxyNa9XxdnenH-ND2uj2WVQ,2967
113
- jarvis/jarvis_utils/config.py,sha256=6wWqByse15Nsi5ciWvaqlC0AfxvK-7DXy95sUCyraKQ,18427
113
+ jarvis/jarvis_utils/config.py,sha256=44MIH-CLRYpv8ltpirnq5_EccE1fVVGQciCiWRuDYmo,18824
114
114
  jarvis/jarvis_utils/embedding.py,sha256=oEOEM2qf16DMYwPsQe6srET9BknyjOdY2ef0jsp3Or8,2714
115
115
  jarvis/jarvis_utils/file_processors.py,sha256=XiM248SHS7lLgQDCbORVFWqinbVDUawYxWDOsLXDxP8,3043
116
116
  jarvis/jarvis_utils/git_utils.py,sha256=AkczUiRcGcOnPfz2v3mdLwV1S41IopiAYD2tjeMTDrE,23586
117
117
  jarvis/jarvis_utils/globals.py,sha256=aTrOHcCgPAeZFLFIWMAMiJCYlmr4XhdFZf5gZ745hnE,8900
118
118
  jarvis/jarvis_utils/http.py,sha256=eRhV3-GYuWmQ0ogq9di9WMlQkFcVb1zGCrySnOgT1x0,4392
119
- jarvis/jarvis_utils/input.py,sha256=iLY6kRm-kjKpsDyQQM49ppnXCZs_6jZVqLbvUWglrls,36657
119
+ jarvis/jarvis_utils/input.py,sha256=tGIr3-t46gT3dD-Z6Bcb5xagIifM62cAHRkk3w9p7_Q,36635
120
120
  jarvis/jarvis_utils/methodology.py,sha256=3PAMHVF85IflBBblV_mMwkYXMelUML0nZYZ8MOAYUJ8,12918
121
121
  jarvis/jarvis_utils/output.py,sha256=svCHLuXHe3Ta_qfoT5DxO80nyYyMfBFIKgziQS__Nmg,13632
122
122
  jarvis/jarvis_utils/tag.py,sha256=f211opbbbTcSyzCDwuIK_oCnKhXPNK-RknYyGzY1yD0,431
123
- jarvis/jarvis_utils/utils.py,sha256=KdNXmXkwZZZwfjoFT-UivjGBBaB3feFNUHQ13U_HApo,62210
124
- jarvis_ai_assistant-0.3.24.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
125
- jarvis_ai_assistant-0.3.24.dist-info/METADATA,sha256=g1hsQNJc_yGSieqNhztNwmB7KQR6YjCEoqp2c8icNww,18790
126
- jarvis_ai_assistant-0.3.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
127
- jarvis_ai_assistant-0.3.24.dist-info/entry_points.txt,sha256=4GcWKFxRJD-QU14gw_3ZaW4KuEVxOcZK9i270rwPdjA,1395
128
- jarvis_ai_assistant-0.3.24.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
129
- jarvis_ai_assistant-0.3.24.dist-info/RECORD,,
123
+ jarvis/jarvis_utils/utils.py,sha256=sK0-A6DVxqyxJE54K7j2vKGIAVQBpd7O54xOtBWLFt4,62268
124
+ jarvis_ai_assistant-0.3.25.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
125
+ jarvis_ai_assistant-0.3.25.dist-info/METADATA,sha256=x_-BQj684awisXcDf-gV-9T7ycgni8QWZiAAZI6KSrg,18790
126
+ jarvis_ai_assistant-0.3.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
127
+ jarvis_ai_assistant-0.3.25.dist-info/entry_points.txt,sha256=4GcWKFxRJD-QU14gw_3ZaW4KuEVxOcZK9i270rwPdjA,1395
128
+ jarvis_ai_assistant-0.3.25.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
129
+ jarvis_ai_assistant-0.3.25.dist-info/RECORD,,