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

@@ -10,11 +10,9 @@ import sys
10
10
  import argparse
11
11
  import os
12
12
 
13
- from jarvis.jarvis_utils.config import INPUT_WINDOW_REVERSE_SIZE, get_max_input_token_count
14
- from jarvis.jarvis_utils.embedding import get_context_token_count
15
13
  from jarvis.jarvis_utils.git_utils import find_git_root, has_uncommitted_changes
16
14
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
17
- from jarvis.jarvis_utils.utils import init_env, chat_with_files
15
+ from jarvis.jarvis_utils.utils import init_env, is_context_overflow
18
16
  from jarvis.jarvis_utils.tag import ot, ct
19
17
 
20
18
 
@@ -107,7 +105,7 @@ class GitCommitTool:
107
105
  diff = process.communicate()[0].decode()
108
106
  spinner.write(f"✅ 获取差异 ({file_count} 个文件)")
109
107
  try:
110
- temp_diff_file = None
108
+ temp_diff_file_path = None
111
109
  # 生成提交信息
112
110
  spinner.text = "正在生成提交消息..."
113
111
 
@@ -132,13 +130,12 @@ class GitCommitTool:
132
130
 
133
131
  # 获取模型并尝试上传文件
134
132
  platform = PlatformRegistry().get_normal_platform()
133
+ upload_success = False
135
134
 
136
- # 检查文件大小
137
- is_large_file = get_context_token_count(diff) > get_max_input_token_count() - INPUT_WINDOW_REVERSE_SIZE
135
+ # Check if content is too large
136
+ is_large_content = is_context_overflow(diff)
138
137
 
139
- if is_large_file:
140
- # 文件较大,尝试上传
141
- upload_success = False
138
+ if is_large_content and hasattr(platform, 'upload_files'):
142
139
  spinner.text = "正在上传代码差异文件..."
143
140
  try:
144
141
  with spinner.hidden():
@@ -148,75 +145,49 @@ class GitCommitTool:
148
145
  temp_diff_file.write(diff)
149
146
  temp_diff_file.flush()
150
147
  spinner.write(f"✅ 差异内容已写入临时文件")
151
-
152
- if hasattr(platform, 'upload_files'):
153
- upload_success = platform.upload_files([temp_diff_file_path])
154
- if upload_success:
155
- spinner.write(" 成功上传代码差异文件")
156
- else:
157
- spinner.write("⚠️ 上传代码差异文件失败,将使用chat_with_files处理")
158
- else:
159
- spinner.write("⚠️ 模型不支持文件上传,将使用chat_with_files处理")
148
+ upload_success = platform.upload_files([temp_diff_file_path])
149
+ if upload_success:
150
+ spinner.write("✅ 成功上传代码差异文件")
151
+ else:
152
+ spinner.write("⚠️ 上传代码差异文件失败,将使用分块处理")
160
153
  except Exception as e:
161
154
  spinner.write(f"⚠️ 上传文件时出错: {str(e)}")
162
155
  upload_success = False
163
-
164
- if upload_success:
165
- # 使用上传的文件
166
- prompt = base_prompt + f'''
156
+
157
+ # 根据上传状态准备完整的提示
158
+ if upload_success:
159
+ # 使用上传的文件
160
+ prompt = base_prompt + f'''
167
161
  # 变更概述
168
162
  - 变更文件数量: {file_count} 个文件
169
163
  - 已上传包含完整代码差异的文件
170
164
 
171
165
  请详细分析已上传的代码差异文件,生成符合上述格式的提交信息。
172
166
  '''
173
- else:
174
- # 使用chat_with_files处理大文件
175
- spinner.write("使用chat_with_files处理大文件...")
176
- commit_message = chat_with_files(
177
- [temp_diff_file_path],
178
- platform,
179
- base_prompt,
180
- "请基于以上代码差异生成符合格式的提交信息。"
181
- )
182
- commit_message = self._extract_commit_message(commit_message)
183
- if commit_message:
184
- spinner.write("✅ 生成提交消息")
185
- # 执行提交
186
- spinner.text = "正在准备提交..."
187
- with tempfile.NamedTemporaryFile(mode='w', delete=True) as tmp_file:
188
- tmp_file.write(commit_message)
189
- tmp_file.flush()
190
- spinner.text = "正在执行提交..."
191
- commit_cmd = ["git", "commit", "-F", tmp_file.name]
192
- subprocess.Popen(
193
- commit_cmd,
194
- stdout=subprocess.DEVNULL,
195
- stderr=subprocess.DEVNULL
196
- ).wait()
197
- spinner.write("✅ 提交")
198
- return {
199
- "success": True,
200
- "stdout": yaml.safe_dump({
201
- "commit_hash": self._get_last_commit_hash(),
202
- "commit_message": commit_message
203
- }),
204
- "stderr": ""
205
- }
167
+ commit_message = platform.chat_until_success(prompt)
206
168
  else:
207
- # 文件较小,直接在提示中包含差异内容
208
- prompt = base_prompt + f'''
169
+ # 如果上传失败但内容较大,使用chat_big_content
170
+ if is_large_content and hasattr(platform, 'chat_big_content'):
171
+ spinner.text = "正在使用分块处理生成提交信息..."
172
+ commit_message = platform.chat_big_content(diff, base_prompt)
173
+ else:
174
+ # 直接在提示中包含差异内容
175
+ prompt = base_prompt + f'''
209
176
  # 分析材料
210
177
  {diff}
211
178
  '''
179
+ commit_message = platform.chat_until_success(prompt)
212
180
 
213
181
  # 尝试生成提交信息
214
182
  spinner.text = "正在生成提交消息..."
215
183
  while True:
216
- commit_message = platform.chat_until_success(prompt)
217
- commit_message = self._extract_commit_message(commit_message)
184
+ # 只在特定情况下重新获取commit_message
185
+ if not upload_success and not is_large_content and not commit_message:
186
+ commit_message = platform.chat_until_success(prompt)
187
+ extracted_message = self._extract_commit_message(commit_message)
218
188
  # 如果成功提取,就跳出循环
219
- if commit_message:
189
+ if extracted_message:
190
+ commit_message = extracted_message
220
191
  break
221
192
  prompt = f"""格式错误,请按照以下格式重新生成提交信息:
222
193
  {ot("COMMIT_MESSAGE")}
@@ -225,6 +196,7 @@ class GitCommitTool:
225
196
  [可选] 详细描述变更内容和原因
226
197
  {ct("COMMIT_MESSAGE")}
227
198
  """
199
+ commit_message = platform.chat_until_success(prompt)
228
200
  spinner.write("✅ 生成提交消息")
229
201
 
230
202
  # 执行提交
@@ -246,7 +218,7 @@ class GitCommitTool:
246
218
  spinner.ok("✅")
247
219
  finally:
248
220
  # 清理临时差异文件
249
- if temp_diff_file is not None and os.path.exists(temp_diff_file_path):
221
+ if temp_diff_file_path is not None and os.path.exists(temp_diff_file_path):
250
222
  try:
251
223
  os.unlink(temp_diff_file_path)
252
224
  except Exception as e:
@@ -11,14 +11,14 @@ import hashlib
11
11
  import os
12
12
  import json
13
13
  import argparse
14
- import yaml
14
+ import yaml # type: ignore
15
15
  from jarvis.jarvis_utils.methodology import (
16
16
  _get_methodology_directory,
17
17
  _load_all_methodologies
18
18
  )
19
19
  from jarvis.jarvis_platform.registry import PlatformRegistry
20
20
  from jarvis.jarvis_utils.output import PrettyOutput, OutputType
21
- from yaspin import yaspin
21
+ from yaspin import yaspin # type: ignore
22
22
 
23
23
  def import_methodology(input_file):
24
24
  """导入方法论文件(合并策略)"""
@@ -1,6 +1,8 @@
1
1
  from abc import ABC, abstractmethod
2
2
  import re
3
3
  from typing import List, Tuple
4
+ from jarvis.jarvis_utils.config import get_max_input_token_count
5
+ from jarvis.jarvis_utils.embedding import split_text_into_chunks
4
6
  from jarvis.jarvis_utils.globals import clear_read_file_record
5
7
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
8
  from jarvis.jarvis_utils.utils import get_context_token_count, while_success, while_true
@@ -37,38 +39,49 @@ class BasePlatform(ABC):
37
39
  @abstractmethod
38
40
  def upload_files(self, file_list: List[str]) -> bool:
39
41
  raise NotImplementedError("upload_files is not implemented")
42
+
43
+ def chat_big_content(self, content: str, prompt: str) -> str:
44
+ prefix_prompt = f"""
45
+ 我将分多次提供大量的上下文内容,在我明确告诉你内容已经全部提供完毕之前,每次仅需要输出“已收到”。
46
+ """
47
+ self.chat_until_success(prefix_prompt)
48
+ split_content = split_text_into_chunks(content, get_max_input_token_count() - 1024)
49
+ for chunk in split_content:
50
+ self.chat_until_success(f"<part_content>{chunk}</part_content>")
51
+ return self.chat_until_success(f"内容已经全部提供完毕\n\n{prompt}")
40
52
 
53
+
54
+ def _chat(self, message: str):
55
+ import time
56
+ start_time = time.time()
57
+ response = self.chat(message)
58
+
59
+ end_time = time.time()
60
+ duration = end_time - start_time
61
+ char_count = len(response)
62
+
63
+ # Calculate token count and tokens per second
64
+ try:
65
+ token_count = get_context_token_count(response)
66
+ tokens_per_second = token_count / duration if duration > 0 else 0
67
+ except Exception as e:
68
+ PrettyOutput.print(f"Tokenization failed: {str(e)}", OutputType.WARNING)
69
+ token_count = 0
70
+ tokens_per_second = 0
71
+
72
+ # Print statistics
73
+ if not self.suppress_output:
74
+ PrettyOutput.print(
75
+ f"对话完成 - 耗时: {duration:.2f}秒, 输出字符数: {char_count}, 输出Token数量: {token_count}, 每秒Token数量: {tokens_per_second:.2f}",
76
+ OutputType.INFO,
77
+ )
78
+
79
+ # Keep original think tag handling
80
+ response = re.sub(ot("think")+r'.*?'+ct("think"), '', response, flags=re.DOTALL)
81
+ return response
82
+
41
83
  def chat_until_success(self, message: str) -> str:
42
- def _chat():
43
- import time
44
- start_time = time.time()
45
- response = self.chat(message)
46
-
47
- end_time = time.time()
48
- duration = end_time - start_time
49
- char_count = len(response)
50
-
51
- # Calculate token count and tokens per second
52
- try:
53
- token_count = get_context_token_count(response)
54
- tokens_per_second = token_count / duration if duration > 0 else 0
55
- except Exception as e:
56
- PrettyOutput.print(f"Tokenization failed: {str(e)}", OutputType.WARNING)
57
- token_count = 0
58
- tokens_per_second = 0
59
-
60
- # Print statistics
61
- if not self.suppress_output:
62
- PrettyOutput.print(
63
- f"对话完成 - 耗时: {duration:.2f}秒, 输出字符数: {char_count}, 输出Token数量: {token_count}, 每秒Token数量: {tokens_per_second:.2f}",
64
- OutputType.INFO,
65
- )
66
-
67
- # Keep original think tag handling
68
- response = re.sub(ot("think")+r'.*?'+ct("think"), '', response, flags=re.DOTALL)
69
- return response
70
-
71
- return while_true(lambda: while_success(lambda: _chat(), 5), 5)
84
+ return while_true(lambda: while_success(lambda: self._chat(message), 5), 5)
72
85
 
73
86
  @abstractmethod
74
87
  def name(self) -> str:
@@ -1,5 +1,5 @@
1
1
  from typing import Dict, List, Tuple
2
- import requests
2
+ import requests # type: ignore
3
3
  import json
4
4
  import os
5
5
  import mimetypes
@@ -178,7 +178,7 @@ class KimiModel(BasePlatform):
178
178
  if not file_list:
179
179
  return True
180
180
 
181
- from yaspin import yaspin
181
+ from yaspin import yaspin # type: ignore
182
182
 
183
183
  if not self.chat_id:
184
184
  with yaspin(text="创建聊天会话...", color="yellow") as spinner:
@@ -3,8 +3,8 @@ import os
3
3
 
4
4
  from jarvis.jarvis_platform.registry import PlatformRegistry
5
5
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
- from yaspin import yaspin
7
- from yaspin.spinners import Spinners
6
+ from yaspin import yaspin # type: ignore
7
+ from yaspin.spinners import Spinners # type: ignore
8
8
 
9
9
  class FileAnalyzerTool:
10
10
  name = "file_analyzer"
@@ -2,7 +2,7 @@ from typing import Dict, Any
2
2
  import os
3
3
  from pathlib import Path
4
4
 
5
- from yaspin import yaspin
5
+ from yaspin import yaspin # type: ignore
6
6
 
7
7
  from jarvis.jarvis_utils.globals import add_read_file_record
8
8
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -31,7 +31,7 @@ class WebpageTool:
31
31
 
32
32
  # Create Yuanbao model instance
33
33
  model = PlatformRegistry().get_normal_platform()
34
- model.web = True
34
+ model.set_web(True)
35
35
  model.set_suppress_output(False) # type: ignore
36
36
 
37
37
  # Construct prompt based on want parameter
@@ -3,6 +3,7 @@ from pathlib import Path
3
3
  import re
4
4
  import sys
5
5
  import tempfile
6
+ import os
6
7
  from typing import Any, Callable, Dict, List, Optional, Tuple
7
8
 
8
9
  import yaml
@@ -10,14 +11,9 @@ import yaml
10
11
  from jarvis.jarvis_agent.output_handler import OutputHandler
11
12
  from jarvis.jarvis_platform.registry import PlatformRegistry
12
13
  from jarvis.jarvis_tools.base import Tool
13
- from jarvis.jarvis_utils.config import (
14
- INPUT_WINDOW_REVERSE_SIZE,
15
- get_max_input_token_count,
16
- get_data_dir,
17
- )
18
- from jarvis.jarvis_utils.embedding import get_context_token_count
14
+ from jarvis.jarvis_utils.config import get_data_dir
19
15
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
20
- from jarvis.jarvis_utils.utils import init_env
16
+ from jarvis.jarvis_utils.utils import init_env, is_context_overflow
21
17
  from jarvis.jarvis_utils.tag import ot, ct
22
18
  from jarvis.jarvis_mcp.stdio_mcp_client import StdioMcpClient
23
19
  from jarvis.jarvis_mcp.sse_mcp_client import SSEMcpClient
@@ -175,9 +171,6 @@ class ToolRegistry(OutputHandler):
175
171
  self._load_builtin_tools()
176
172
  self._load_external_tools()
177
173
  self._load_mcp_tools()
178
- self.max_input_token_count = (
179
- get_max_input_token_count() - INPUT_WINDOW_REVERSE_SIZE
180
- )
181
174
 
182
175
  def use_tools(self, name: List[str]) -> None:
183
176
  """使用指定工具
@@ -618,46 +611,45 @@ class ToolRegistry(OutputHandler):
618
611
  result["stdout"], result.get("stderr", "")
619
612
  )
620
613
 
621
- # 处理结果
622
- if get_context_token_count(output) > self.max_input_token_count:
623
- with tempfile.NamedTemporaryFile(
624
- mode="w", suffix=".txt", delete=False
625
- ) as tmp_file:
614
+ # 检查内容是否过大
615
+ is_large_content = is_context_overflow(output)
616
+
617
+ if is_large_content:
618
+ # 创建临时文件
619
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as tmp_file:
626
620
  output_file = tmp_file.name
627
621
  tmp_file.write(output)
628
622
  tmp_file.flush()
629
- platform: Any = PlatformRegistry().get_normal_platform()
630
- if platform:
631
- platform.set_suppress_output(False)
632
- try:
623
+
624
+ try:
625
+ # 获取平台实例
626
+ platform = PlatformRegistry().get_normal_platform()
627
+ if platform and hasattr(platform, 'upload_files'):
628
+ platform.set_suppress_output(False)
633
629
  # 尝试上传文件
634
- if platform.upload_files([output_file]):
635
- prompt = f"""该文件为工具执行结果,请阅读文件内容,并根据文件提取出以下信息:{want}
636
-
637
- 注意:
638
- 1. 请仔细阅读文件内容,确保提取的信息准确完整
639
- 2. 如果文件内容包含错误信息,请特别关注并提取
640
- 3. 提取的信息应该直接回答用户的问题,不要包含无关内容"""
630
+ upload_success = platform.upload_files([output_file])
631
+
632
+ if upload_success:
633
+ # 使用上传的文件生成摘要
634
+ prompt = f"该文件为工具执行结果,请阅读文件内容,并根据文件提取出以下信息:{want}"
641
635
  return f"""工具调用原始输出过长,以下是根据输出提出的信息:
642
636
 
643
637
  {platform.chat_until_success(prompt)}"""
644
- else:
645
- # 上传失败,使用chat_with_files
646
- PrettyOutput.print("⚠️ 上传文件失败,将使用chat_with_files处理", OutputType.WARNING)
647
- from jarvis.jarvis_utils.utils import chat_with_files
648
- prompt = f"""请阅读以下文件内容,并根据文件提取出以下信息:{want}
649
-
650
- 注意:
651
- 1. 请仔细阅读文件内容,确保提取的信息准确完整
652
- 2. 如果文件内容包含错误信息,请特别关注并提取
653
- 3. 提取的信息应该直接回答用户的问题,不要包含无关内容"""
638
+ elif hasattr(platform, 'chat_big_content'):
639
+ # 如果上传失败但支持大内容处理,使用chat_big_content
640
+ prompt = f"以下内容为工具执行结果,请阅读内容,并根据内容提取出以下信息:{want}\n\n{output}"
654
641
  return f"""工具调用原始输出过长,以下是根据输出提出的信息:
655
642
 
656
- {chat_with_files([output_file], platform, prompt, "")}"""
657
- except Exception as e:
658
- PrettyOutput.print(f"处理大文件时出错: {str(e)}", OutputType.ERROR)
659
- return self._truncate_output(output)
660
- return self._truncate_output(output)
643
+ {platform.chat_big_content(output, prompt)}"""
644
+
645
+ # 如果都不支持,返回截断的输出
646
+ return self._truncate_output(output)
647
+ finally:
648
+ # 清理临时文件
649
+ try:
650
+ os.unlink(output_file)
651
+ except Exception:
652
+ pass
661
653
 
662
654
  return output
663
655
 
@@ -16,7 +16,7 @@ class SearchWebTool:
16
16
  def execute(self, args: Dict[str, Any]) -> Dict[str, Any]: # type: ignore
17
17
  query = args.get("query")
18
18
  model = PlatformRegistry().get_normal_platform()
19
- model.web = True
19
+ model.set_web(True)
20
20
  model.set_suppress_output(False) # type: ignore
21
21
  return {
22
22
  "stdout": model.chat_until_success(query), # type: ignore
@@ -82,29 +82,20 @@ class FileCompleter(Completer):
82
82
  # 计算替换长度
83
83
  replace_length = len(text_after_at) + 1
84
84
 
85
- # 从replace_map获取建议列表
86
- default_suggestions = [
87
- (ot(tag), desc) for tag, desc in [
88
- (tag, self._get_description(tag))
89
- for tag in self.replace_map.keys()
90
- ]
91
- ]
92
- # 添加特殊标记
93
- default_suggestions.extend([
85
+ # 获取所有可能的补全项
86
+ all_completions = []
87
+
88
+ # 1. 添加特殊标记
89
+ all_completions.extend([
90
+ (ot(tag), self._get_description(tag))
91
+ for tag in self.replace_map.keys()
92
+ ])
93
+ all_completions.extend([
94
94
  (ot("Summary"), '总结'),
95
95
  (ot("Clear"), '清除历史'),
96
96
  ])
97
- for name, desc in default_suggestions:
98
- yield Completion(
99
- text=f"'{name}'",
100
- start_position=-replace_length,
101
- display=name,
102
- display_meta=desc
103
- ) # type: ignore
104
-
105
-
106
- # 使用git ls-files获取所有可能的文件
107
- all_files = []
97
+
98
+ # 2. 添加文件列表
108
99
  try:
109
100
  import subprocess
110
101
  result = subprocess.run(['git', 'ls-files'],
@@ -112,28 +103,40 @@ class FileCompleter(Completer):
112
103
  stderr=subprocess.PIPE,
113
104
  text=True)
114
105
  if result.returncode == 0:
115
- all_files = [line.strip() for line in result.stdout.splitlines() if line.strip()]
106
+ all_completions.extend([
107
+ (path, "File")
108
+ for path in result.stdout.splitlines()
109
+ if path.strip()
110
+ ])
116
111
  except Exception:
117
112
  pass
118
- # 生成补全建议
119
- if not file_path:
120
- scored_files = [(path, 100) for path in all_files[:self.max_suggestions]]
121
- else:
122
- scored_files_data = process.extract(file_path, all_files, limit=self.max_suggestions)
123
- scored_files = [(m[0], m[1]) for m in scored_files_data]
124
- scored_files.sort(key=lambda x: x[1], reverse=True)
125
- scored_files = scored_files[:self.max_suggestions]
126
- # 生成补全项
127
- for path, score in scored_files:
128
- if not file_path or score > self.min_score:
129
- display_text = path
130
- if file_path and score < 100:
131
- display_text = f"{path} ({score}%)"
113
+
114
+ # 统一过滤和排序
115
+ if file_path:
116
+ # 使用模糊匹配过滤
117
+ scored_items = process.extract(file_path, [item[0] for item in all_completions], limit=self.max_suggestions)
118
+ scored_items = [(item[0], item[1]) for item in scored_items if item[1] > self.min_score]
119
+ # 创建映射以便查找描述
120
+ completion_map = {item[0]: item[1] for item in all_completions}
121
+ # 生成补全项
122
+ for text, score in scored_items:
123
+ display_text = text
124
+ if score < 100:
125
+ display_text = f"{text} ({score}%)"
132
126
  yield Completion(
133
- text=f"'{path}'",
127
+ text=f"'{text}'",
134
128
  start_position=-replace_length,
135
129
  display=display_text,
136
- display_meta="File"
130
+ display_meta=completion_map.get(text, "")
131
+ ) # type: ignore
132
+ else:
133
+ # 没有输入时返回前max_suggestions个建议
134
+ for text, desc in all_completions[:self.max_suggestions]:
135
+ yield Completion(
136
+ text=f"'{text}'",
137
+ start_position=-replace_length,
138
+ display=text,
139
+ display_meta=desc
137
140
  ) # type: ignore
138
141
 
139
142
  def _get_description(self, tag: str) -> str: