jarvis-ai-assistant 0.1.32__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 +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
|