jarvis-ai-assistant 0.1.32__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- jarvis/__init__.py +3 -0
- jarvis/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/__pycache__/agent.cpython-313.pyc +0 -0
- jarvis/__pycache__/main.cpython-313.pyc +0 -0
- jarvis/__pycache__/models.cpython-313.pyc +0 -0
- jarvis/__pycache__/tools.cpython-313.pyc +0 -0
- jarvis/__pycache__/utils.cpython-313.pyc +0 -0
- jarvis/__pycache__/zte_llm.cpython-313.pyc +0 -0
- jarvis/agent.py +289 -0
- jarvis/main.py +148 -0
- jarvis/models/__init__.py +3 -0
- jarvis/models/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/base.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/kimi.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/openai.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/oyi.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/registry.cpython-313.pyc +0 -0
- jarvis/models/base.py +39 -0
- jarvis/models/kimi.py +389 -0
- jarvis/models/openai.py +96 -0
- jarvis/models/oyi.py +271 -0
- jarvis/models/registry.py +199 -0
- jarvis/tools/__init__.py +5 -0
- jarvis/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/base.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/bing_search.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/calculator.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/calculator_tool.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/file_ops.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/generator.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/methodology.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/python_script.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/rag.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/registry.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/search.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/shell.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/sub_agent.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_confirmation.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_input.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_interaction.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/webpage.cpython-313.pyc +0 -0
- jarvis/tools/base.py +23 -0
- jarvis/tools/file_ops.py +110 -0
- jarvis/tools/generator.py +172 -0
- jarvis/tools/methodology.py +145 -0
- jarvis/tools/registry.py +183 -0
- jarvis/tools/shell.py +78 -0
- jarvis/tools/sub_agent.py +82 -0
- jarvis/utils.py +202 -0
- jarvis_ai_assistant-0.1.32.dist-info/LICENSE +21 -0
- jarvis_ai_assistant-0.1.32.dist-info/METADATA +345 -0
- jarvis_ai_assistant-0.1.32.dist-info/RECORD +55 -0
- jarvis_ai_assistant-0.1.32.dist-info/WHEEL +5 -0
- jarvis_ai_assistant-0.1.32.dist-info/entry_points.txt +2 -0
- jarvis_ai_assistant-0.1.32.dist-info/top_level.txt +1 -0
jarvis/models/kimi.py
ADDED
@@ -0,0 +1,389 @@
|
|
1
|
+
from typing import Dict, List
|
2
|
+
import requests
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import mimetypes
|
6
|
+
import time
|
7
|
+
from jarvis.models.base import BaseModel
|
8
|
+
from jarvis.utils import PrettyOutput, OutputType
|
9
|
+
from jarvis.utils import while_success
|
10
|
+
|
11
|
+
class KimiModel(BaseModel):
|
12
|
+
"""Kimi模型实现"""
|
13
|
+
|
14
|
+
model_name = "kimi"
|
15
|
+
|
16
|
+
def __init__(self):
|
17
|
+
"""
|
18
|
+
初始化Kimi模型
|
19
|
+
"""
|
20
|
+
self.api_key = os.getenv("KIMI_API_KEY")
|
21
|
+
if not self.api_key:
|
22
|
+
PrettyOutput.info("\n需要设置 KIMI_API_KEY 才能使用 Jarvis。请按以下步骤操作:")
|
23
|
+
PrettyOutput.info("\n1. 获取 Kimi API Key:")
|
24
|
+
PrettyOutput.info(" • 访问 Kimi AI 平台: https://kimi.moonshot.cn")
|
25
|
+
PrettyOutput.info(" • 登录您的账号")
|
26
|
+
PrettyOutput.info(" • 打开浏览器开发者工具 (F12 或右键 -> 检查)")
|
27
|
+
PrettyOutput.info(" • 切换到 Network 标签页")
|
28
|
+
PrettyOutput.info(" • 发送任意消息")
|
29
|
+
PrettyOutput.info(" • 在请求中找到 Authorization 头部")
|
30
|
+
PrettyOutput.info(" • 复制 token 值(去掉 'Bearer ' 前缀)")
|
31
|
+
PrettyOutput.info("\n2. 设置环境变量:")
|
32
|
+
PrettyOutput.info(" 方法 1: 创建或编辑 ~/.jarvis_env 文件:")
|
33
|
+
PrettyOutput.info(" echo 'KIMI_API_KEY=your_key_here' > ~/.jarvis_env")
|
34
|
+
PrettyOutput.info("\n 方法 2: 直接设置环境变量:")
|
35
|
+
PrettyOutput.info(" export KIMI_API_KEY=your_key_here")
|
36
|
+
PrettyOutput.info("\n设置完成后重新运行 Jarvis。")
|
37
|
+
raise Exception("KIMI_API_KEY is not set")
|
38
|
+
self.auth_header = f"Bearer {self.api_key}"
|
39
|
+
self.chat_id = ""
|
40
|
+
self.uploaded_files = [] # 存储已上传文件的信息
|
41
|
+
self.first_chat = True # 添加标记,用于判断是否是第一次对话
|
42
|
+
self.system_message = ""
|
43
|
+
|
44
|
+
def set_system_message(self, message: str):
|
45
|
+
"""设置系统消息"""
|
46
|
+
self.system_message = message
|
47
|
+
|
48
|
+
def _create_chat(self) -> bool:
|
49
|
+
"""创建新的对话会话"""
|
50
|
+
url = "https://kimi.moonshot.cn/api/chat"
|
51
|
+
payload = json.dumps({
|
52
|
+
"name": "未命名会话",
|
53
|
+
"is_example": False,
|
54
|
+
"kimiplus_id": "kimi"
|
55
|
+
})
|
56
|
+
headers = {
|
57
|
+
'Authorization': self.auth_header,
|
58
|
+
'Content-Type': 'application/json'
|
59
|
+
}
|
60
|
+
try:
|
61
|
+
response = while_success(lambda: requests.request("POST", url, headers=headers, data=payload), sleep_time=5)
|
62
|
+
self.chat_id = response.json()["id"]
|
63
|
+
return True
|
64
|
+
except Exception as e:
|
65
|
+
PrettyOutput.print(f"Error: Failed to create chat: {e}", OutputType.ERROR)
|
66
|
+
return False
|
67
|
+
|
68
|
+
def _get_presigned_url(self, filename: str, action: str) -> Dict:
|
69
|
+
"""获取预签名上传URL"""
|
70
|
+
url = "https://kimi.moonshot.cn/api/pre-sign-url"
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
payload = json.dumps({
|
75
|
+
"action": action,
|
76
|
+
"name": os.path.basename(filename)
|
77
|
+
}, ensure_ascii=False)
|
78
|
+
|
79
|
+
headers = {
|
80
|
+
'Authorization': self.auth_header,
|
81
|
+
'Content-Type': 'application/json'
|
82
|
+
}
|
83
|
+
|
84
|
+
response = while_success(lambda: requests.post(url, headers=headers, data=payload), sleep_time=5)
|
85
|
+
return response.json()
|
86
|
+
|
87
|
+
def _upload_file(self, file_path: str, presigned_url: str) -> bool:
|
88
|
+
"""上传文件到预签名URL"""
|
89
|
+
try:
|
90
|
+
with open(file_path, 'rb') as f:
|
91
|
+
content = f.read()
|
92
|
+
response = while_success(lambda: requests.put(presigned_url, data=content), sleep_time=5)
|
93
|
+
return response.status_code == 200
|
94
|
+
except Exception as e:
|
95
|
+
PrettyOutput.print(f"Error: Failed to upload file: {e}", OutputType.ERROR)
|
96
|
+
return False
|
97
|
+
|
98
|
+
def _get_file_info(self, file_data: Dict, name: str, file_type: str) -> Dict:
|
99
|
+
"""获取文件信息"""
|
100
|
+
url = "https://kimi.moonshot.cn/api/file"
|
101
|
+
payload = json.dumps({
|
102
|
+
"type": file_type,
|
103
|
+
"name": name,
|
104
|
+
"object_name": file_data["object_name"],
|
105
|
+
"chat_id": self.chat_id,
|
106
|
+
"file_id": file_data.get("file_id", "")
|
107
|
+
}, ensure_ascii=False)
|
108
|
+
|
109
|
+
headers = {
|
110
|
+
'Authorization': self.auth_header,
|
111
|
+
'Content-Type': 'application/json'
|
112
|
+
}
|
113
|
+
|
114
|
+
response = while_success(lambda: requests.post(url, headers=headers, data=payload), sleep_time=5)
|
115
|
+
return response.json()
|
116
|
+
|
117
|
+
def _wait_for_parse(self, file_id: str) -> bool:
|
118
|
+
"""等待文件解析完成"""
|
119
|
+
url = "https://kimi.moonshot.cn/api/file/parse_process"
|
120
|
+
headers = {
|
121
|
+
'Authorization': self.auth_header,
|
122
|
+
'Content-Type': 'application/json'
|
123
|
+
}
|
124
|
+
|
125
|
+
max_retries = 30
|
126
|
+
retry_count = 0
|
127
|
+
|
128
|
+
while retry_count < max_retries:
|
129
|
+
payload = json.dumps({"ids": [file_id]}, ensure_ascii=False)
|
130
|
+
response = while_success(lambda: requests.post(url, headers=headers, data=payload, stream=True), sleep_time=5)
|
131
|
+
|
132
|
+
for line in response.iter_lines():
|
133
|
+
if not line:
|
134
|
+
continue
|
135
|
+
|
136
|
+
line = line.decode('utf-8')
|
137
|
+
if not line.startswith("data: "):
|
138
|
+
continue
|
139
|
+
|
140
|
+
try:
|
141
|
+
data = json.loads(line[6:])
|
142
|
+
if data.get("event") == "resp":
|
143
|
+
status = data.get("file_info", {}).get("status")
|
144
|
+
if status == "parsed":
|
145
|
+
return True
|
146
|
+
elif status == "failed":
|
147
|
+
return False
|
148
|
+
except json.JSONDecodeError:
|
149
|
+
continue
|
150
|
+
|
151
|
+
retry_count += 1
|
152
|
+
time.sleep(1)
|
153
|
+
|
154
|
+
return False
|
155
|
+
def upload_files(self, file_list: List[str]) -> List[Dict]:
|
156
|
+
"""上传文件列表并返回文件信息"""
|
157
|
+
if not file_list:
|
158
|
+
return []
|
159
|
+
|
160
|
+
PrettyOutput.print("Progress: 开始处理文件上传...", OutputType.PROGRESS)
|
161
|
+
|
162
|
+
if not self.chat_id:
|
163
|
+
PrettyOutput.print("创建新的对话会话...", OutputType.PROGRESS)
|
164
|
+
if not self._create_chat():
|
165
|
+
raise Exception("Failed to create chat session")
|
166
|
+
|
167
|
+
uploaded_files = []
|
168
|
+
for index, file_path in enumerate(file_list, 1):
|
169
|
+
try:
|
170
|
+
PrettyOutput.print(f"处理文件 [{index}/{len(file_list)}]: {file_path}", OutputType.PROGRESS)
|
171
|
+
|
172
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
173
|
+
action = "image" if mime_type and mime_type.startswith('image/') else "file"
|
174
|
+
|
175
|
+
# 获取预签名URL
|
176
|
+
PrettyOutput.print("获取上传URL...", OutputType.PROGRESS)
|
177
|
+
presigned_data = self._get_presigned_url(file_path, action)
|
178
|
+
|
179
|
+
# 上传文件
|
180
|
+
PrettyOutput.print("上传文件内容...", OutputType.PROGRESS)
|
181
|
+
if self._upload_file(file_path, presigned_data["url"]):
|
182
|
+
# 获取文件信息
|
183
|
+
PrettyOutput.print("获取文件信息...", OutputType.PROGRESS)
|
184
|
+
file_info = self._get_file_info(presigned_data, os.path.basename(file_path), action)
|
185
|
+
# 等待文件解析
|
186
|
+
PrettyOutput.print("等待文件解析完成...", OutputType.PROGRESS)
|
187
|
+
|
188
|
+
# 只有文件需要解析
|
189
|
+
if action == "file":
|
190
|
+
if self._wait_for_parse(file_info["id"]):
|
191
|
+
uploaded_files.append(file_info)
|
192
|
+
PrettyOutput.print(f"Success: 文件处理成功: {file_path}", OutputType.SUCCESS)
|
193
|
+
else:
|
194
|
+
PrettyOutput.print(f"✗ 文件解析失败: {file_path}", OutputType.ERROR)
|
195
|
+
else:
|
196
|
+
uploaded_files.append(file_info)
|
197
|
+
PrettyOutput.print(f"Success: 文件处理成功: {file_path}", OutputType.SUCCESS)
|
198
|
+
else:
|
199
|
+
PrettyOutput.print(f"Error: 文件上传失败: {file_path}", OutputType.ERROR)
|
200
|
+
|
201
|
+
except Exception as e:
|
202
|
+
PrettyOutput.print(f"✗ 处理文件出错 {file_path}: {str(e)}", OutputType.ERROR)
|
203
|
+
continue
|
204
|
+
|
205
|
+
if uploaded_files:
|
206
|
+
PrettyOutput.print(f"成功处理 {len(uploaded_files)}/{len(file_list)} 个文件", OutputType.SUCCESS)
|
207
|
+
else:
|
208
|
+
PrettyOutput.print("没有文件成功处理", OutputType.ERROR)
|
209
|
+
|
210
|
+
self.uploaded_files = uploaded_files
|
211
|
+
return uploaded_files
|
212
|
+
|
213
|
+
def chat(self, message: str) -> str:
|
214
|
+
"""发送消息并获取响应"""
|
215
|
+
if not self.chat_id:
|
216
|
+
PrettyOutput.print("创建新的对话会话...", OutputType.PROGRESS)
|
217
|
+
if not self._create_chat():
|
218
|
+
raise Exception("Failed to create chat session")
|
219
|
+
|
220
|
+
url = f"https://kimi.moonshot.cn/api/chat/{self.chat_id}/completion/stream"
|
221
|
+
|
222
|
+
# 只在第一次对话时带上文件引用
|
223
|
+
refs = []
|
224
|
+
refs_file = []
|
225
|
+
if self.first_chat:
|
226
|
+
if self.uploaded_files:
|
227
|
+
PrettyOutput.print(f"首次对话,引用 {len(self.uploaded_files)} 个文件...", OutputType.PROGRESS)
|
228
|
+
refs = [f["id"] for f in self.uploaded_files]
|
229
|
+
refs_file = self.uploaded_files
|
230
|
+
message = self.system_message + "\n" + message
|
231
|
+
self.first_chat = False
|
232
|
+
|
233
|
+
PrettyOutput.print("发送请求...", OutputType.PROGRESS)
|
234
|
+
payload = {
|
235
|
+
"messages": [{"role": "user", "content": message}],
|
236
|
+
"use_search": True,
|
237
|
+
"extend": {"sidebar": True},
|
238
|
+
"kimiplus_id": "kimi",
|
239
|
+
"use_research": False,
|
240
|
+
"use_math": False,
|
241
|
+
"refs": refs,
|
242
|
+
"refs_file": refs_file
|
243
|
+
}
|
244
|
+
|
245
|
+
headers = {
|
246
|
+
'Authorization': self.auth_header,
|
247
|
+
'Content-Type': 'application/json'
|
248
|
+
}
|
249
|
+
|
250
|
+
try:
|
251
|
+
response = while_success(lambda: requests.post(url, headers=headers, json=payload, stream=True), sleep_time=5)
|
252
|
+
full_response = ""
|
253
|
+
|
254
|
+
# 收集搜索和引用结果
|
255
|
+
search_results = []
|
256
|
+
ref_sources = []
|
257
|
+
|
258
|
+
PrettyOutput.print("接收响应...", OutputType.PROGRESS)
|
259
|
+
for line in response.iter_lines():
|
260
|
+
if not line:
|
261
|
+
continue
|
262
|
+
|
263
|
+
line = line.decode('utf-8')
|
264
|
+
if not line.startswith("data: "):
|
265
|
+
continue
|
266
|
+
|
267
|
+
try:
|
268
|
+
data = json.loads(line[6:])
|
269
|
+
event = data.get("event")
|
270
|
+
|
271
|
+
if event == "cmpl":
|
272
|
+
# 处理补全文本
|
273
|
+
text = data.get("text", "")
|
274
|
+
if text:
|
275
|
+
PrettyOutput.print_stream(text)
|
276
|
+
full_response += text
|
277
|
+
|
278
|
+
elif event == "search_plus":
|
279
|
+
# 收集搜索结果
|
280
|
+
msg = data.get("msg", {})
|
281
|
+
if msg.get("type") == "get_res":
|
282
|
+
search_results.append({
|
283
|
+
"date": msg.get("date", ""),
|
284
|
+
"site_name": msg.get("site_name", ""),
|
285
|
+
"snippet": msg.get("snippet", ""),
|
286
|
+
"title": msg.get("title", ""),
|
287
|
+
"type": msg.get("type", ""),
|
288
|
+
"url": msg.get("url", "")
|
289
|
+
})
|
290
|
+
|
291
|
+
elif event == "ref_docs":
|
292
|
+
# 收集引用来源
|
293
|
+
ref_cards = data.get("ref_cards", [])
|
294
|
+
for card in ref_cards:
|
295
|
+
ref_sources.append({
|
296
|
+
"idx_s": card.get("idx_s", ""),
|
297
|
+
"idx_z": card.get("idx_z", ""),
|
298
|
+
"ref_id": card.get("ref_id", ""),
|
299
|
+
"url": card.get("url", ""),
|
300
|
+
"title": card.get("title", ""),
|
301
|
+
"abstract": card.get("abstract", ""),
|
302
|
+
"source": card.get("source_label", ""),
|
303
|
+
"rag_segments": card.get("rag_segments", []),
|
304
|
+
"origin": card.get("origin", {})
|
305
|
+
})
|
306
|
+
|
307
|
+
except json.JSONDecodeError:
|
308
|
+
continue
|
309
|
+
|
310
|
+
PrettyOutput.print_stream_end()
|
311
|
+
|
312
|
+
|
313
|
+
# 显示搜索结果摘要
|
314
|
+
if search_results:
|
315
|
+
PrettyOutput.print("\n搜索结果:", OutputType.PROGRESS)
|
316
|
+
for result in search_results:
|
317
|
+
PrettyOutput.print(f"- {result['title']}", OutputType.PROGRESS)
|
318
|
+
if result['date']:
|
319
|
+
PrettyOutput.print(f" 日期: {result['date']}", OutputType.PROGRESS)
|
320
|
+
PrettyOutput.print(f" 来源: {result['site_name']}", OutputType.PROGRESS)
|
321
|
+
if result['snippet']:
|
322
|
+
PrettyOutput.print(f" 摘要: {result['snippet']}", OutputType.PROGRESS)
|
323
|
+
PrettyOutput.print(f" 链接: {result['url']}", OutputType.PROGRESS)
|
324
|
+
PrettyOutput.print("", OutputType.PROGRESS)
|
325
|
+
|
326
|
+
# 显示引用来源
|
327
|
+
if ref_sources:
|
328
|
+
PrettyOutput.print("\n引用来源:", OutputType.PROGRESS)
|
329
|
+
for source in ref_sources:
|
330
|
+
PrettyOutput.print(f"- [{source['ref_id']}] {source['title']} ({source['source']})", OutputType.PROGRESS)
|
331
|
+
PrettyOutput.print(f" 链接: {source['url']}", OutputType.PROGRESS)
|
332
|
+
if source['abstract']:
|
333
|
+
PrettyOutput.print(f" 摘要: {source['abstract']}", OutputType.PROGRESS)
|
334
|
+
|
335
|
+
# 显示相关段落
|
336
|
+
if source['rag_segments']:
|
337
|
+
PrettyOutput.print(" 相关段落:", OutputType.PROGRESS)
|
338
|
+
for segment in source['rag_segments']:
|
339
|
+
text = segment.get('text', '').replace('\n', ' ').strip()
|
340
|
+
if text:
|
341
|
+
PrettyOutput.print(f" - {text}", OutputType.PROGRESS)
|
342
|
+
|
343
|
+
# 显示原文引用
|
344
|
+
origin = source['origin']
|
345
|
+
if origin:
|
346
|
+
text = origin.get('text', '')
|
347
|
+
if text:
|
348
|
+
PrettyOutput.print(f" 原文: {text}", OutputType.PROGRESS)
|
349
|
+
|
350
|
+
PrettyOutput.print("", OutputType.PROGRESS)
|
351
|
+
|
352
|
+
return full_response
|
353
|
+
|
354
|
+
except Exception as e:
|
355
|
+
raise Exception(f"Chat failed: {str(e)}")
|
356
|
+
|
357
|
+
def delete_chat(self) -> bool:
|
358
|
+
"""删除当前会话"""
|
359
|
+
if not self.chat_id:
|
360
|
+
return True # 如果没有会话ID,视为删除成功
|
361
|
+
|
362
|
+
url = f"https://kimi.moonshot.cn/api/chat/{self.chat_id}"
|
363
|
+
headers = {
|
364
|
+
'Authorization': self.auth_header,
|
365
|
+
'Content-Type': 'application/json'
|
366
|
+
}
|
367
|
+
|
368
|
+
try:
|
369
|
+
response = while_success(lambda: requests.delete(url, headers=headers), sleep_time=5)
|
370
|
+
if response.status_code == 200:
|
371
|
+
PrettyOutput.print("会话已删除", OutputType.SUCCESS)
|
372
|
+
self.reset()
|
373
|
+
return True
|
374
|
+
else:
|
375
|
+
PrettyOutput.print(f"删除会话失败: HTTP {response.status_code}", OutputType.ERROR)
|
376
|
+
return False
|
377
|
+
except Exception as e:
|
378
|
+
PrettyOutput.print(f"删除会话时发生错误: {str(e)}", OutputType.ERROR)
|
379
|
+
return False
|
380
|
+
|
381
|
+
def reset(self):
|
382
|
+
"""重置对话"""
|
383
|
+
self.chat_id = ""
|
384
|
+
self.uploaded_files = []
|
385
|
+
self.first_chat = True # 重置first_chat标记
|
386
|
+
|
387
|
+
def name(self) -> str:
|
388
|
+
"""模型名称"""
|
389
|
+
return "kimi"
|
jarvis/models/openai.py
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
from typing import Dict, List
|
2
|
+
import os
|
3
|
+
from openai import OpenAI
|
4
|
+
from jarvis.models.base import BaseModel
|
5
|
+
from jarvis.utils import PrettyOutput, OutputType
|
6
|
+
|
7
|
+
class OpenAIModel(BaseModel):
|
8
|
+
"""DeepSeek模型实现"""
|
9
|
+
|
10
|
+
model_name = "openai"
|
11
|
+
|
12
|
+
def __init__(self):
|
13
|
+
"""
|
14
|
+
初始化DeepSeek模型
|
15
|
+
"""
|
16
|
+
self.api_key = os.getenv("OPENAI_API_KEY")
|
17
|
+
if not self.api_key:
|
18
|
+
PrettyOutput.print("\n需要设置以下环境变量才能使用 OpenAI 模型:", OutputType.INFO)
|
19
|
+
PrettyOutput.print(" • OPENAI_API_KEY: API 密钥", OutputType.INFO)
|
20
|
+
PrettyOutput.print(" • OPENAI_API_BASE: (可选) API 基础地址,默认使用 https://api.deepseek.com", OutputType.INFO)
|
21
|
+
PrettyOutput.print("\n可以通过以下方式设置:", OutputType.INFO)
|
22
|
+
PrettyOutput.print("1. 创建或编辑 ~/.jarvis_env 文件:", OutputType.INFO)
|
23
|
+
PrettyOutput.print(" OPENAI_API_KEY=your_api_key", OutputType.INFO)
|
24
|
+
PrettyOutput.print(" OPENAI_API_BASE=your_api_base", OutputType.INFO)
|
25
|
+
PrettyOutput.print(" OPENAI_MODEL_NAME=your_model_name", OutputType.INFO)
|
26
|
+
PrettyOutput.print("\n2. 或者直接设置环境变量:", OutputType.INFO)
|
27
|
+
PrettyOutput.print(" export OPENAI_API_KEY=your_api_key", OutputType.INFO)
|
28
|
+
PrettyOutput.print(" export OPENAI_API_BASE=your_api_base", OutputType.INFO)
|
29
|
+
PrettyOutput.print(" export OPENAI_MODEL_NAME=your_model_name", OutputType.INFO)
|
30
|
+
raise Exception("OPENAI_API_KEY is not set")
|
31
|
+
|
32
|
+
self.base_url = os.getenv("OPENAI_API_BASE", "https://api.deepseek.com")
|
33
|
+
self.model_name = os.getenv("OPENAI_MODEL_NAME", "deepseek-chat")
|
34
|
+
|
35
|
+
self.client = OpenAI(
|
36
|
+
api_key=self.api_key,
|
37
|
+
base_url=self.base_url
|
38
|
+
)
|
39
|
+
self.messages: List[Dict[str, str]] = []
|
40
|
+
self.system_message = ""
|
41
|
+
|
42
|
+
def set_system_message(self, message: str):
|
43
|
+
"""设置系统消息"""
|
44
|
+
self.system_message = message
|
45
|
+
self.messages.append({"role": "system", "content": self.system_message})
|
46
|
+
|
47
|
+
def chat(self, message: str) -> str:
|
48
|
+
"""执行对话"""
|
49
|
+
try:
|
50
|
+
PrettyOutput.print("发送请求...", OutputType.PROGRESS)
|
51
|
+
|
52
|
+
# 添加用户消息到历史记录
|
53
|
+
self.messages.append({"role": "user", "content": message})
|
54
|
+
|
55
|
+
response = self.client.chat.completions.create(
|
56
|
+
model=self.model_name, # 使用配置的模型名称
|
57
|
+
messages=self.messages,
|
58
|
+
stream=True
|
59
|
+
)
|
60
|
+
|
61
|
+
PrettyOutput.print("接收响应...", OutputType.PROGRESS)
|
62
|
+
full_response = ""
|
63
|
+
|
64
|
+
for chunk in response:
|
65
|
+
if chunk.choices[0].delta.content:
|
66
|
+
text = chunk.choices[0].delta.content
|
67
|
+
PrettyOutput.print_stream(text)
|
68
|
+
full_response += text
|
69
|
+
|
70
|
+
PrettyOutput.print_stream_end()
|
71
|
+
|
72
|
+
# 添加助手回复到历史记录
|
73
|
+
self.messages.append({"role": "assistant", "content": full_response})
|
74
|
+
|
75
|
+
return full_response
|
76
|
+
|
77
|
+
except Exception as e:
|
78
|
+
PrettyOutput.print(f"对话失败: {str(e)}", OutputType.ERROR)
|
79
|
+
raise Exception(f"Chat failed: {str(e)}")
|
80
|
+
|
81
|
+
def name(self) -> str:
|
82
|
+
"""返回模型名称"""
|
83
|
+
return self.model_name
|
84
|
+
|
85
|
+
def reset(self):
|
86
|
+
"""重置模型状态"""
|
87
|
+
# 清空对话历史,只保留system message
|
88
|
+
if self.system_message:
|
89
|
+
self.messages = [{"role": "system", "content": self.system_message}]
|
90
|
+
else:
|
91
|
+
self.messages = []
|
92
|
+
|
93
|
+
def delete_chat(self)->bool:
|
94
|
+
"""删除对话"""
|
95
|
+
self.reset()
|
96
|
+
return True
|