jarvis-ai-assistant 0.1.138__py3-none-any.whl → 0.1.141__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +62 -14
- jarvis/jarvis_agent/builtin_input_handler.py +4 -14
- jarvis/jarvis_agent/main.py +1 -1
- jarvis/jarvis_agent/patch.py +37 -40
- jarvis/jarvis_agent/shell_input_handler.py +2 -3
- jarvis/jarvis_code_agent/code_agent.py +23 -30
- jarvis/jarvis_code_analysis/checklists/__init__.py +3 -0
- jarvis/jarvis_code_analysis/checklists/c_cpp.py +50 -0
- jarvis/jarvis_code_analysis/checklists/csharp.py +75 -0
- jarvis/jarvis_code_analysis/checklists/data_format.py +82 -0
- jarvis/jarvis_code_analysis/checklists/devops.py +107 -0
- jarvis/jarvis_code_analysis/checklists/docs.py +87 -0
- jarvis/jarvis_code_analysis/checklists/go.py +52 -0
- jarvis/jarvis_code_analysis/checklists/infrastructure.py +98 -0
- jarvis/jarvis_code_analysis/checklists/java.py +66 -0
- jarvis/jarvis_code_analysis/checklists/javascript.py +73 -0
- jarvis/jarvis_code_analysis/checklists/kotlin.py +107 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +76 -0
- jarvis/jarvis_code_analysis/checklists/php.py +77 -0
- jarvis/jarvis_code_analysis/checklists/python.py +56 -0
- jarvis/jarvis_code_analysis/checklists/ruby.py +107 -0
- jarvis/jarvis_code_analysis/checklists/rust.py +58 -0
- jarvis/jarvis_code_analysis/checklists/shell.py +75 -0
- jarvis/jarvis_code_analysis/checklists/sql.py +72 -0
- jarvis/jarvis_code_analysis/checklists/swift.py +77 -0
- jarvis/jarvis_code_analysis/checklists/web.py +97 -0
- jarvis/jarvis_code_analysis/code_review.py +660 -0
- jarvis/jarvis_dev/main.py +61 -88
- jarvis/jarvis_git_squash/main.py +3 -3
- jarvis/jarvis_git_utils/git_commiter.py +242 -0
- jarvis/jarvis_init/main.py +62 -0
- jarvis/jarvis_platform/base.py +4 -0
- jarvis/jarvis_platform/kimi.py +173 -5
- jarvis/jarvis_platform/openai.py +3 -0
- jarvis/jarvis_platform/registry.py +1 -0
- jarvis/jarvis_platform/yuanbao.py +275 -5
- jarvis/jarvis_tools/ask_codebase.py +6 -9
- jarvis/jarvis_tools/ask_user.py +17 -5
- jarvis/jarvis_tools/base.py +3 -1
- jarvis/jarvis_tools/chdir.py +1 -0
- jarvis/jarvis_tools/create_code_agent.py +4 -3
- jarvis/jarvis_tools/create_sub_agent.py +1 -0
- jarvis/jarvis_tools/execute_script.py +170 -0
- jarvis/jarvis_tools/file_analyzer.py +90 -239
- jarvis/jarvis_tools/file_operation.py +99 -31
- jarvis/jarvis_tools/{find_methodolopy.py → find_methodology.py} +2 -1
- jarvis/jarvis_tools/lsp_get_diagnostics.py +2 -0
- jarvis/jarvis_tools/methodology.py +11 -11
- jarvis/jarvis_tools/read_code.py +2 -0
- jarvis/jarvis_tools/read_webpage.py +33 -196
- jarvis/jarvis_tools/registry.py +68 -131
- jarvis/jarvis_tools/search_web.py +14 -6
- jarvis/jarvis_tools/virtual_tty.py +399 -0
- jarvis/jarvis_utils/config.py +29 -3
- jarvis/jarvis_utils/embedding.py +0 -317
- jarvis/jarvis_utils/file_processors.py +343 -0
- jarvis/jarvis_utils/input.py +0 -1
- jarvis/jarvis_utils/methodology.py +94 -435
- jarvis/jarvis_utils/utils.py +207 -9
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/METADATA +4 -4
- jarvis_ai_assistant-0.1.141.dist-info/RECORD +94 -0
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/entry_points.txt +4 -4
- jarvis/jarvis_code_agent/file_select.py +0 -202
- jarvis/jarvis_platform/ai8.py +0 -268
- jarvis/jarvis_platform/ollama.py +0 -137
- jarvis/jarvis_platform/oyi.py +0 -307
- jarvis/jarvis_rag/file_processors.py +0 -138
- jarvis/jarvis_rag/main.py +0 -1734
- jarvis/jarvis_tools/code_review.py +0 -333
- jarvis/jarvis_tools/execute_python_script.py +0 -58
- jarvis/jarvis_tools/execute_shell.py +0 -97
- jarvis/jarvis_tools/execute_shell_script.py +0 -58
- jarvis/jarvis_tools/find_caller.py +0 -278
- jarvis/jarvis_tools/find_symbol.py +0 -295
- jarvis/jarvis_tools/function_analyzer.py +0 -331
- jarvis/jarvis_tools/git_commiter.py +0 -167
- jarvis/jarvis_tools/project_analyzer.py +0 -304
- jarvis/jarvis_tools/rag.py +0 -143
- jarvis/jarvis_tools/tool_generator.py +0 -221
- jarvis_ai_assistant-0.1.138.dist-info/RECORD +0 -85
- /jarvis/{jarvis_rag → jarvis_init}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.138.dist-info → jarvis_ai_assistant-0.1.141.dist-info}/top_level.txt +0 -0
jarvis/jarvis_platform/kimi.py
CHANGED
|
@@ -7,6 +7,8 @@ import time
|
|
|
7
7
|
from jarvis.jarvis_platform.base import BasePlatform
|
|
8
8
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
9
|
from jarvis.jarvis_utils.utils import while_success
|
|
10
|
+
from yaspin import yaspin
|
|
11
|
+
|
|
10
12
|
class KimiModel(BasePlatform):
|
|
11
13
|
"""Kimi model implementation"""
|
|
12
14
|
|
|
@@ -14,7 +16,10 @@ class KimiModel(BasePlatform):
|
|
|
14
16
|
|
|
15
17
|
def get_model_list(self) -> List[Tuple[str, str]]:
|
|
16
18
|
"""Get model list"""
|
|
17
|
-
return [
|
|
19
|
+
return [
|
|
20
|
+
("kimi", "基于网页的 Kimi,免费接口"),
|
|
21
|
+
("k1", "基于网页的 Kimi,深度思考模型")
|
|
22
|
+
]
|
|
18
23
|
|
|
19
24
|
def __init__(self):
|
|
20
25
|
"""
|
|
@@ -44,9 +49,12 @@ class KimiModel(BasePlatform):
|
|
|
44
49
|
PrettyOutput.print(message, OutputType.INFO)
|
|
45
50
|
PrettyOutput.print("KIMI_API_KEY 未设置", OutputType.WARNING)
|
|
46
51
|
self.auth_header = f"Bearer {self.api_key}"
|
|
52
|
+
self.uploaded_files = [] # 存储已上传文件的信息
|
|
47
53
|
self.chat_id = ""
|
|
48
54
|
self.first_chat = True # 添加标记,用于判断是否是第一次对话
|
|
49
55
|
self.system_message = ""
|
|
56
|
+
self.model_name = "kimi"
|
|
57
|
+
self.web = os.getenv("KIMI_WEB", "false") == "true"
|
|
50
58
|
|
|
51
59
|
def set_system_message(self, message: str):
|
|
52
60
|
"""Set system message"""
|
|
@@ -54,7 +62,7 @@ class KimiModel(BasePlatform):
|
|
|
54
62
|
|
|
55
63
|
def set_model_name(self, model_name: str):
|
|
56
64
|
"""Set model name"""
|
|
57
|
-
|
|
65
|
+
self.model_name = model_name
|
|
58
66
|
|
|
59
67
|
def _create_chat(self) -> bool:
|
|
60
68
|
"""Create a new chat session"""
|
|
@@ -75,6 +83,156 @@ class KimiModel(BasePlatform):
|
|
|
75
83
|
except Exception as e:
|
|
76
84
|
PrettyOutput.print(f"错误:创建会话失败:{e}", OutputType.ERROR)
|
|
77
85
|
return False
|
|
86
|
+
|
|
87
|
+
def _get_presigned_url(self, filename: str, action: str) -> Dict:
|
|
88
|
+
"""Get presigned upload URL"""
|
|
89
|
+
url = "https://kimi.moonshot.cn/api/pre-sign-url"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
payload = json.dumps({
|
|
94
|
+
"action": action,
|
|
95
|
+
"name": os.path.basename(filename)
|
|
96
|
+
}, ensure_ascii=False)
|
|
97
|
+
|
|
98
|
+
headers = {
|
|
99
|
+
'Authorization': self.auth_header,
|
|
100
|
+
'Content-Type': 'application/json'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
response = while_success(lambda: requests.post(url, headers=headers, data=payload), sleep_time=5)
|
|
104
|
+
return response.json()
|
|
105
|
+
|
|
106
|
+
def _upload_file(self, file_path: str, presigned_url: str) -> bool:
|
|
107
|
+
"""Upload file to presigned URL"""
|
|
108
|
+
try:
|
|
109
|
+
with open(file_path, 'rb') as f:
|
|
110
|
+
content = f.read()
|
|
111
|
+
response = while_success(lambda: requests.put(presigned_url, data=content), sleep_time=5)
|
|
112
|
+
return response.status_code == 200
|
|
113
|
+
except Exception as e:
|
|
114
|
+
PrettyOutput.print(f"错误:上传文件失败:{e}", OutputType.ERROR)
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
def _get_file_info(self, file_data: Dict, name: str, file_type: str) -> Dict:
|
|
118
|
+
"""Get file information"""
|
|
119
|
+
url = "https://kimi.moonshot.cn/api/file"
|
|
120
|
+
payload = json.dumps({
|
|
121
|
+
"type": file_type,
|
|
122
|
+
"name": name,
|
|
123
|
+
"object_name": file_data["object_name"],
|
|
124
|
+
"chat_id": self.chat_id,
|
|
125
|
+
"file_id": file_data.get("file_id", "")
|
|
126
|
+
}, ensure_ascii=False)
|
|
127
|
+
|
|
128
|
+
headers = {
|
|
129
|
+
'Authorization': self.auth_header,
|
|
130
|
+
'Content-Type': 'application/json'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
response = while_success(lambda: requests.post(url, headers=headers, data=payload), sleep_time=5)
|
|
134
|
+
return response.json()
|
|
135
|
+
|
|
136
|
+
def _wait_for_parse(self, file_id: str) -> bool:
|
|
137
|
+
"""Wait for file parsing to complete"""
|
|
138
|
+
url = "https://kimi.moonshot.cn/api/file/parse_process"
|
|
139
|
+
headers = {
|
|
140
|
+
'Authorization': self.auth_header,
|
|
141
|
+
'Content-Type': 'application/json'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
max_retries = 30
|
|
145
|
+
retry_count = 0
|
|
146
|
+
|
|
147
|
+
while retry_count < max_retries:
|
|
148
|
+
payload = json.dumps({"ids": [file_id]}, ensure_ascii=False)
|
|
149
|
+
response = while_success(lambda: requests.post(url, headers=headers, data=payload, stream=True), sleep_time=5)
|
|
150
|
+
|
|
151
|
+
for line in response.iter_lines():
|
|
152
|
+
if not line:
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
line = line.decode('utf-8')
|
|
156
|
+
if not line.startswith("data: "):
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
data = json.loads(line[6:])
|
|
161
|
+
if data.get("event") == "resp":
|
|
162
|
+
status = data.get("file_info", {}).get("status")
|
|
163
|
+
if status == "parsed":
|
|
164
|
+
return True
|
|
165
|
+
elif status == "failed":
|
|
166
|
+
return False
|
|
167
|
+
except json.JSONDecodeError:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
retry_count += 1
|
|
171
|
+
time.sleep(1)
|
|
172
|
+
|
|
173
|
+
return False
|
|
174
|
+
def upload_files(self, file_list: List[str]) -> bool:
|
|
175
|
+
"""Upload file list and return file information"""
|
|
176
|
+
if not file_list:
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
from yaspin import yaspin
|
|
180
|
+
|
|
181
|
+
if not self.chat_id:
|
|
182
|
+
with yaspin(text="创建聊天会话...", color="yellow") as spinner:
|
|
183
|
+
if not self._create_chat():
|
|
184
|
+
yaspin.text = "创建聊天会话失败"
|
|
185
|
+
spinner.fail("❌")
|
|
186
|
+
return False
|
|
187
|
+
spinner.text = "创建聊天会话成功"
|
|
188
|
+
spinner.ok("✅")
|
|
189
|
+
|
|
190
|
+
uploaded_files = []
|
|
191
|
+
for index, file_path in enumerate(file_list, 1):
|
|
192
|
+
file_name = os.path.basename(file_path)
|
|
193
|
+
with yaspin(text=f"处理文件 [{index}/{len(file_list)}]: {file_name}", color="yellow") as spinner:
|
|
194
|
+
try:
|
|
195
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
196
|
+
action = "image" if mime_type and mime_type.startswith('image/') else "file"
|
|
197
|
+
|
|
198
|
+
# 获取预签名URL
|
|
199
|
+
spinner.text = f"获取上传URL: {file_name}"
|
|
200
|
+
presigned_data = self._get_presigned_url(file_path, action)
|
|
201
|
+
|
|
202
|
+
# 上传文件
|
|
203
|
+
spinner.text = f"上传文件: {file_name}"
|
|
204
|
+
if self._upload_file(file_path, presigned_data["url"]):
|
|
205
|
+
# 获取文件信息
|
|
206
|
+
spinner.text = f"获取文件信息: {file_name}"
|
|
207
|
+
file_info = self._get_file_info(presigned_data, file_name, action)
|
|
208
|
+
|
|
209
|
+
# 只有文件需要解析
|
|
210
|
+
if action == "file":
|
|
211
|
+
spinner.text = f"等待文件解析: {file_name}"
|
|
212
|
+
if self._wait_for_parse(file_info["id"]):
|
|
213
|
+
uploaded_files.append(file_info)
|
|
214
|
+
spinner.text = f"文件处理完成: {file_name}"
|
|
215
|
+
spinner.ok("✅")
|
|
216
|
+
else:
|
|
217
|
+
spinner.text = f"❌文件解析失败: {file_name}"
|
|
218
|
+
spinner.fail("")
|
|
219
|
+
return False
|
|
220
|
+
else:
|
|
221
|
+
uploaded_files.append(file_info)
|
|
222
|
+
spinner.write( f"✅图片处理完成: {file_name}")
|
|
223
|
+
else:
|
|
224
|
+
spinner.text = f"文件上传失败: {file_name}"
|
|
225
|
+
spinner.fail("❌")
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
spinner.text = f"处理文件出错 {file_path}: {str(e)}"
|
|
230
|
+
spinner.fail("❌")
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
self.uploaded_files = uploaded_files
|
|
234
|
+
self.chat_until_success("我上传了文件,收到请回复“收到”")
|
|
235
|
+
return True
|
|
78
236
|
|
|
79
237
|
|
|
80
238
|
def chat(self, message: str) -> str:
|
|
@@ -85,17 +243,27 @@ class KimiModel(BasePlatform):
|
|
|
85
243
|
|
|
86
244
|
url = f"https://kimi.moonshot.cn/api/chat/{self.chat_id}/completion/stream"
|
|
87
245
|
|
|
246
|
+
refs = []
|
|
247
|
+
refs_file = []
|
|
248
|
+
if self.uploaded_files:
|
|
249
|
+
refs = [f["id"] for f in self.uploaded_files]
|
|
250
|
+
refs_file = self.uploaded_files
|
|
251
|
+
self.uploaded_files = []
|
|
88
252
|
|
|
253
|
+
if self.first_chat:
|
|
254
|
+
message = self.system_message + "\n" + message
|
|
255
|
+
self.first_chat = False
|
|
89
256
|
|
|
90
257
|
payload = {
|
|
91
258
|
"messages": [{"role": "user", "content": message}],
|
|
92
|
-
"use_search": True,
|
|
259
|
+
"use_search": True if self.web else False,
|
|
93
260
|
"extend": {"sidebar": True},
|
|
94
261
|
"kimiplus_id": "kimi",
|
|
95
262
|
"use_research": False,
|
|
96
263
|
"use_math": False,
|
|
97
|
-
"refs":
|
|
98
|
-
"refs_file":
|
|
264
|
+
"refs": refs,
|
|
265
|
+
"refs_file": refs_file,
|
|
266
|
+
"model": self.model_name,
|
|
99
267
|
}
|
|
100
268
|
|
|
101
269
|
headers = {
|
jarvis/jarvis_platform/openai.py
CHANGED
|
@@ -43,6 +43,9 @@ class OpenAIModel(BasePlatform):
|
|
|
43
43
|
self.messages: List[Dict[str, str]] = []
|
|
44
44
|
self.system_message = ""
|
|
45
45
|
|
|
46
|
+
def upload_files(self, file_list: List[str]) -> bool:
|
|
47
|
+
return False
|
|
48
|
+
|
|
46
49
|
def get_model_list(self) -> List[Tuple[str, str]]:
|
|
47
50
|
"""Get model list"""
|
|
48
51
|
try:
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
from typing import List, Tuple
|
|
1
|
+
from typing import Dict, List, Tuple
|
|
2
2
|
import requests
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import mimetypes
|
|
6
|
+
import hmac
|
|
7
|
+
import hashlib
|
|
8
|
+
import time
|
|
9
|
+
import urllib.parse
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from PIL import Image
|
|
12
|
+
from yaspin import yaspin
|
|
13
|
+
from yaspin.spinners import Spinners
|
|
5
14
|
from jarvis.jarvis_platform.base import BasePlatform
|
|
6
15
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
7
16
|
from jarvis.jarvis_utils.utils import while_success
|
|
@@ -51,6 +60,7 @@ class YuanbaoPlatform(BasePlatform):
|
|
|
51
60
|
self.system_message = "" # 系统消息,用于初始化对话
|
|
52
61
|
self.first_chat = True # 标识是否为第一次对话
|
|
53
62
|
self.model_name = "deep_seek_v3" # 默认模型名称,使用下划线保持一致
|
|
63
|
+
self.multimedia = []
|
|
54
64
|
|
|
55
65
|
def set_system_message(self, message: str):
|
|
56
66
|
"""Set system message"""
|
|
@@ -114,8 +124,268 @@ class YuanbaoPlatform(BasePlatform):
|
|
|
114
124
|
PrettyOutput.print(f"错误:创建会话失败:{e}", OutputType.ERROR)
|
|
115
125
|
return False
|
|
116
126
|
|
|
127
|
+
def upload_files(self, file_list: List[str]) -> bool:
|
|
128
|
+
"""Upload files to Yuanbao platform
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
file_list: List of file paths to upload
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of file metadata dictionaries for use in chat messages
|
|
135
|
+
"""
|
|
136
|
+
if not self.cookies:
|
|
137
|
+
PrettyOutput.print("未设置YUANBAO_COOKIES,无法上传文件", OutputType.ERROR)
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
uploaded_files = []
|
|
141
|
+
|
|
142
|
+
for file_path in file_list:
|
|
143
|
+
file_name = os.path.basename(file_path)
|
|
144
|
+
with yaspin(Spinners.dots, text=f"上传文件 {file_name}") as spinner:
|
|
145
|
+
try:
|
|
146
|
+
|
|
147
|
+
# 1. Prepare the file information
|
|
148
|
+
spinner.text = f"准备文件信息: {file_name}"
|
|
149
|
+
file_size = os.path.getsize(file_path)
|
|
150
|
+
file_extension = os.path.splitext(file_path)[1].lower().lstrip('.')
|
|
151
|
+
|
|
152
|
+
# Determine file_type using mimetypes
|
|
153
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
154
|
+
|
|
155
|
+
# Default to txt if mime_type couldn't be determined
|
|
156
|
+
if not mime_type:
|
|
157
|
+
file_type = "txt"
|
|
158
|
+
# Image types
|
|
159
|
+
elif mime_type.startswith('image/'):
|
|
160
|
+
file_type = "image"
|
|
161
|
+
# PDF type
|
|
162
|
+
elif mime_type == 'application/pdf':
|
|
163
|
+
file_type = "pdf"
|
|
164
|
+
# Document types
|
|
165
|
+
elif mime_type in ['application/msword',
|
|
166
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
167
|
+
'application/rtf',
|
|
168
|
+
'application/vnd.oasis.opendocument.text']:
|
|
169
|
+
file_type = "doc"
|
|
170
|
+
# Default to txt for all other types
|
|
171
|
+
else:
|
|
172
|
+
file_type = "txt"
|
|
173
|
+
|
|
174
|
+
# 2. Generate upload information
|
|
175
|
+
spinner.text = f"获取上传信息: {file_name}"
|
|
176
|
+
upload_info = self._generate_upload_info(file_name)
|
|
177
|
+
if not upload_info:
|
|
178
|
+
spinner.text = f"无法获取文件 {file_name} 的上传信息"
|
|
179
|
+
spinner.fail("❌")
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
# 3. Upload the file to COS
|
|
183
|
+
spinner.text = f"上传文件到云存储: {file_name}"
|
|
184
|
+
upload_success = self._upload_file_to_cos(file_path, upload_info)
|
|
185
|
+
if not upload_success:
|
|
186
|
+
spinner.text = f"上传文件 {file_name} 失败"
|
|
187
|
+
spinner.fail("❌")
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
# 4. Create file metadata for chat
|
|
191
|
+
spinner.text = f"生成文件元数据: {file_name}"
|
|
192
|
+
file_metadata = {
|
|
193
|
+
"type": file_type,
|
|
194
|
+
"docType": file_extension if file_extension else file_type,
|
|
195
|
+
"url": upload_info.get("resourceUrl", ""),
|
|
196
|
+
"fileName": file_name,
|
|
197
|
+
"size": file_size,
|
|
198
|
+
"width": 0,
|
|
199
|
+
"height": 0
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Get image dimensions if it's an image file
|
|
203
|
+
if file_type == "img":
|
|
204
|
+
try:
|
|
205
|
+
with Image.open(file_path) as img:
|
|
206
|
+
file_metadata["width"] = img.width
|
|
207
|
+
file_metadata["height"] = img.height
|
|
208
|
+
except Exception as e:
|
|
209
|
+
spinner.write(f"⚠️ 无法获取图片 {file_name} 的尺寸: {str(e)}")
|
|
210
|
+
|
|
211
|
+
uploaded_files.append(file_metadata)
|
|
212
|
+
spinner.text = f"文件 {file_name} 上传成功"
|
|
213
|
+
spinner.ok("✅")
|
|
214
|
+
|
|
215
|
+
except Exception as e:
|
|
216
|
+
spinner.text = f"上传文件 {file_path} 时出错: {str(e)}"
|
|
217
|
+
spinner.fail("❌")
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
self.multimedia = uploaded_files
|
|
221
|
+
return True
|
|
222
|
+
|
|
223
|
+
def _generate_upload_info(self, file_name: str) -> Dict:
|
|
224
|
+
"""Generate upload information from Yuanbao API
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
file_name: Name of the file to upload
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Dictionary containing upload information or empty dict if failed
|
|
231
|
+
"""
|
|
232
|
+
url = "https://yuanbao.tencent.com/api/resource/genUploadInfo"
|
|
233
|
+
|
|
234
|
+
headers = self._get_base_headers()
|
|
235
|
+
|
|
236
|
+
payload = {
|
|
237
|
+
"fileName": file_name,
|
|
238
|
+
"docFrom": "localDoc",
|
|
239
|
+
"docOpenId": ""
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
response = while_success(
|
|
244
|
+
lambda: requests.post(url, headers=headers, json=payload),
|
|
245
|
+
sleep_time=5
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if response.status_code != 200:
|
|
249
|
+
PrettyOutput.print(f"获取上传信息失败,状态码: {response.status_code}", OutputType.ERROR)
|
|
250
|
+
if hasattr(response, 'text'):
|
|
251
|
+
PrettyOutput.print(f"响应: {response.text}", OutputType.ERROR)
|
|
252
|
+
return {}
|
|
253
|
+
|
|
254
|
+
upload_info = response.json()
|
|
255
|
+
return upload_info
|
|
256
|
+
|
|
257
|
+
except Exception as e:
|
|
258
|
+
PrettyOutput.print(f"获取上传信息时出错: {str(e)}", OutputType.ERROR)
|
|
259
|
+
return {}
|
|
260
|
+
|
|
261
|
+
def _upload_file_to_cos(self, file_path: str, upload_info: Dict) -> bool:
|
|
262
|
+
"""Upload file to Tencent COS using the provided upload information
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
file_path: Path to the file to upload
|
|
266
|
+
upload_info: Upload information from generate_upload_info
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Boolean indicating success or failure
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
# Extract required information from upload_info
|
|
273
|
+
bucket_url = f"https://{upload_info['bucketName']}.{upload_info.get('accelerateDomain', 'cos.accelerate.myqcloud.com')}"
|
|
274
|
+
object_path = upload_info.get('location', '')
|
|
275
|
+
url = f"{bucket_url}{object_path}"
|
|
276
|
+
|
|
277
|
+
# Security credentials
|
|
278
|
+
tmp_secret_id = upload_info.get('encryptTmpSecretId', '')
|
|
279
|
+
tmp_secret_key = upload_info.get('encryptTmpSecretKey', '')
|
|
280
|
+
token = upload_info.get('encryptToken', '')
|
|
281
|
+
start_time = upload_info.get('startTime', int(time.time()))
|
|
282
|
+
expired_time = upload_info.get('expiredTime', start_time + 600)
|
|
283
|
+
key_time = f"{start_time};{expired_time}"
|
|
284
|
+
|
|
285
|
+
# Read file content
|
|
286
|
+
with open(file_path, 'rb') as file:
|
|
287
|
+
file_content = file.read()
|
|
288
|
+
|
|
289
|
+
# Prepare headers for PUT request
|
|
290
|
+
host = f"{upload_info['bucketName']}.{upload_info.get('accelerateDomain', 'cos.accelerate.myqcloud.com')}"
|
|
291
|
+
headers = {
|
|
292
|
+
'Host': host,
|
|
293
|
+
'Content-Length': str(len(file_content)),
|
|
294
|
+
'Content-Type': 'application/octet-stream',
|
|
295
|
+
'x-cos-security-token': token
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
# Generate signature for COS request (per Tencent Cloud documentation)
|
|
299
|
+
signature = self._generate_cos_signature(
|
|
300
|
+
secret_key=tmp_secret_key,
|
|
301
|
+
method="PUT",
|
|
302
|
+
path=urllib.parse.quote(object_path),
|
|
303
|
+
params={},
|
|
304
|
+
headers={'host': host, 'content-length': headers['Content-Length']},
|
|
305
|
+
key_time=key_time
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Add Authorization header with signature
|
|
309
|
+
headers['Authorization'] = (
|
|
310
|
+
f"q-sign-algorithm=sha1&q-ak={tmp_secret_id}&q-sign-time={key_time}&"
|
|
311
|
+
f"q-key-time={key_time}&q-header-list=content-length;host&"
|
|
312
|
+
f"q-url-param-list=&q-signature={signature}"
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Upload the file
|
|
316
|
+
response = requests.put(url, headers=headers, data=file_content)
|
|
317
|
+
|
|
318
|
+
if response.status_code not in [200, 204]:
|
|
319
|
+
PrettyOutput.print(f"文件上传到COS失败,状态码: {response.status_code}", OutputType.ERROR)
|
|
320
|
+
if hasattr(response, 'text'):
|
|
321
|
+
PrettyOutput.print(f"响应: {response.text}", OutputType.ERROR)
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
return True
|
|
325
|
+
|
|
326
|
+
except Exception as e:
|
|
327
|
+
PrettyOutput.print(f"上传文件到COS时出错: {str(e)}", OutputType.ERROR)
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
def _generate_cos_signature(self, secret_key: str, method: str, path: str,
|
|
331
|
+
params: Dict, headers: Dict, key_time: str) -> str:
|
|
332
|
+
"""Generate COS signature according to Tencent Cloud COS documentation
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
secret_key: Temporary secret key
|
|
336
|
+
method: HTTP method (GET, PUT, etc.)
|
|
337
|
+
path: Object path
|
|
338
|
+
params: URL parameters
|
|
339
|
+
headers: HTTP headers
|
|
340
|
+
key_time: Time range for signature
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Signature string
|
|
344
|
+
"""
|
|
345
|
+
try:
|
|
346
|
+
# 1. Generate SignKey
|
|
347
|
+
sign_key = hmac.new(
|
|
348
|
+
secret_key.encode('utf-8'),
|
|
349
|
+
key_time.encode('utf-8'),
|
|
350
|
+
hashlib.sha1
|
|
351
|
+
).hexdigest()
|
|
352
|
+
|
|
353
|
+
# 2. Format parameters and headers
|
|
354
|
+
formatted_params = '&'.join([f"{k.lower()}={urllib.parse.quote(str(v), safe='')}"
|
|
355
|
+
for k, v in sorted(params.items())])
|
|
356
|
+
|
|
357
|
+
formatted_headers = '&'.join([f"{k.lower()}={urllib.parse.quote(str(v), safe='')}"
|
|
358
|
+
for k, v in sorted(headers.items())])
|
|
359
|
+
|
|
360
|
+
# 3. Generate HttpString
|
|
361
|
+
http_string = f"{method.lower()}\n{path}\n{formatted_params}\n{formatted_headers}\n"
|
|
362
|
+
|
|
363
|
+
# 4. Generate StringToSign
|
|
364
|
+
string_to_sign = f"sha1\n{key_time}\n{hashlib.sha1(http_string.encode('utf-8')).hexdigest()}\n"
|
|
365
|
+
|
|
366
|
+
# 5. Generate Signature
|
|
367
|
+
signature = hmac.new(
|
|
368
|
+
sign_key.encode('utf-8'),
|
|
369
|
+
string_to_sign.encode('utf-8'),
|
|
370
|
+
hashlib.sha1
|
|
371
|
+
).hexdigest()
|
|
372
|
+
|
|
373
|
+
return signature
|
|
374
|
+
|
|
375
|
+
except Exception as e:
|
|
376
|
+
PrettyOutput.print(f"生成签名时出错: {str(e)}", OutputType.ERROR)
|
|
377
|
+
raise e
|
|
378
|
+
|
|
117
379
|
def chat(self, message: str) -> str:
|
|
118
|
-
"""Send message and get response
|
|
380
|
+
"""Send message and get response with optional file attachments
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
message: Message text to send
|
|
384
|
+
file_list: Optional list of file paths to upload and attach
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Response from the model
|
|
388
|
+
"""
|
|
119
389
|
if not self.conversation_id:
|
|
120
390
|
if not self._create_conversation():
|
|
121
391
|
raise Exception("Failed to create conversation session")
|
|
@@ -138,7 +408,7 @@ class YuanbaoPlatform(BasePlatform):
|
|
|
138
408
|
"intentionStatus": True
|
|
139
409
|
}
|
|
140
410
|
},
|
|
141
|
-
"multimedia":
|
|
411
|
+
"multimedia": self.multimedia,
|
|
142
412
|
"agentId": self.agent_id,
|
|
143
413
|
"supportHint": 1,
|
|
144
414
|
"version": "v2",
|
|
@@ -146,10 +416,11 @@ class YuanbaoPlatform(BasePlatform):
|
|
|
146
416
|
"chatModelId": self.model_name,
|
|
147
417
|
}
|
|
148
418
|
|
|
419
|
+
self.multimedia = []
|
|
420
|
+
|
|
149
421
|
if self.web:
|
|
150
422
|
payload["supportFunctions"] = ["supportInternetSearch"]
|
|
151
423
|
|
|
152
|
-
|
|
153
424
|
# 添加系统消息(如果是第一次对话)
|
|
154
425
|
if self.first_chat and self.system_message:
|
|
155
426
|
payload["prompt"] = f"{self.system_message}\n\n{message}"
|
|
@@ -217,7 +488,6 @@ class YuanbaoPlatform(BasePlatform):
|
|
|
217
488
|
except Exception as e:
|
|
218
489
|
raise Exception(f"对话失败: {str(e)}")
|
|
219
490
|
|
|
220
|
-
|
|
221
491
|
def delete_chat(self) -> bool:
|
|
222
492
|
"""Delete current session"""
|
|
223
493
|
if not self.conversation_id:
|
|
@@ -23,7 +23,8 @@ class AskCodebaseTool:
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
name = "ask_codebase"
|
|
26
|
-
description = "
|
|
26
|
+
description = "查询代码库中特定功能的位置和实现原理,适合定位功能所在文件和理解单点实现"
|
|
27
|
+
labels = ['code', 'analysis', 'qa']
|
|
27
28
|
parameters = {
|
|
28
29
|
"type": "object",
|
|
29
30
|
"properties": {
|
|
@@ -44,7 +45,7 @@ class AskCodebaseTool:
|
|
|
44
45
|
self.auto_complete = auto_complete
|
|
45
46
|
|
|
46
47
|
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
47
|
-
"""Execute codebase analysis using an Agent with
|
|
48
|
+
"""Execute codebase analysis using an Agent with execute_script
|
|
48
49
|
|
|
49
50
|
Args:
|
|
50
51
|
args: Dictionary containing:
|
|
@@ -86,7 +87,7 @@ class AskCodebaseTool:
|
|
|
86
87
|
# Create tools registry
|
|
87
88
|
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
88
89
|
tool_registry = ToolRegistry()
|
|
89
|
-
tool_registry.use_tools(["
|
|
90
|
+
tool_registry.use_tools(["execute_script", "read_code", "methodology"])
|
|
90
91
|
|
|
91
92
|
# Create and run Agent
|
|
92
93
|
analyzer_agent = Agent(
|
|
@@ -141,7 +142,7 @@ class AskCodebaseTool:
|
|
|
141
142
|
- 代码库根目录: {git_root}
|
|
142
143
|
|
|
143
144
|
## 工具使用优先级
|
|
144
|
-
1. **绝对优先使用
|
|
145
|
+
1. **绝对优先使用 execute_script**:
|
|
145
146
|
- 使用 fd 查找文件: `fd -t f -e py` 查找Python文件等
|
|
146
147
|
- 使用 rg 搜索代码: `rg "pattern" --type py` 在Python文件中搜索等
|
|
147
148
|
- 使用 loc 统计代码: `loc"` 统计代码量
|
|
@@ -150,10 +151,6 @@ class AskCodebaseTool:
|
|
|
150
151
|
- 找到相关文件后优先使用read_code读取文件内容
|
|
151
152
|
- 对大文件使用行范围参数读取指定区域
|
|
152
153
|
|
|
153
|
-
3. **避免使用 rag**:
|
|
154
|
-
- 仅在fd、rg和read_code无法解决问题时作为最后手段
|
|
155
|
-
- 使用rag前必须先尝试使用shell命令解决问题
|
|
156
|
-
|
|
157
154
|
## 分析策略
|
|
158
155
|
1. 首先理解问题,确定需要查找的关键信息和代码组件
|
|
159
156
|
2. 使用fd命令查找可能相关的文件
|
|
@@ -161,7 +158,7 @@ class AskCodebaseTool:
|
|
|
161
158
|
4. 使用read_code工具直接读取和分析相关文件内容
|
|
162
159
|
5. 只有在fd、rg和read_code都无法解决问题时才考虑使用RAG工具
|
|
163
160
|
6. 根据文件内容提供具体、准确的回答
|
|
164
|
-
7.
|
|
161
|
+
7. 确保分析的完整性,收集充分的信息后再得出结论,不要在只掌握部分信息就得出结论
|
|
165
162
|
8. 优先查阅README文件、文档目录和项目文档
|
|
166
163
|
|
|
167
164
|
## 分析步骤
|