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.
Files changed (55) hide show
  1. jarvis/__init__.py +3 -0
  2. jarvis/__pycache__/__init__.cpython-313.pyc +0 -0
  3. jarvis/__pycache__/agent.cpython-313.pyc +0 -0
  4. jarvis/__pycache__/main.cpython-313.pyc +0 -0
  5. jarvis/__pycache__/models.cpython-313.pyc +0 -0
  6. jarvis/__pycache__/tools.cpython-313.pyc +0 -0
  7. jarvis/__pycache__/utils.cpython-313.pyc +0 -0
  8. jarvis/__pycache__/zte_llm.cpython-313.pyc +0 -0
  9. jarvis/agent.py +289 -0
  10. jarvis/main.py +148 -0
  11. jarvis/models/__init__.py +3 -0
  12. jarvis/models/__pycache__/__init__.cpython-313.pyc +0 -0
  13. jarvis/models/__pycache__/base.cpython-313.pyc +0 -0
  14. jarvis/models/__pycache__/kimi.cpython-313.pyc +0 -0
  15. jarvis/models/__pycache__/openai.cpython-313.pyc +0 -0
  16. jarvis/models/__pycache__/oyi.cpython-313.pyc +0 -0
  17. jarvis/models/__pycache__/registry.cpython-313.pyc +0 -0
  18. jarvis/models/base.py +39 -0
  19. jarvis/models/kimi.py +389 -0
  20. jarvis/models/openai.py +96 -0
  21. jarvis/models/oyi.py +271 -0
  22. jarvis/models/registry.py +199 -0
  23. jarvis/tools/__init__.py +5 -0
  24. jarvis/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  25. jarvis/tools/__pycache__/base.cpython-313.pyc +0 -0
  26. jarvis/tools/__pycache__/bing_search.cpython-313.pyc +0 -0
  27. jarvis/tools/__pycache__/calculator.cpython-313.pyc +0 -0
  28. jarvis/tools/__pycache__/calculator_tool.cpython-313.pyc +0 -0
  29. jarvis/tools/__pycache__/file_ops.cpython-313.pyc +0 -0
  30. jarvis/tools/__pycache__/generator.cpython-313.pyc +0 -0
  31. jarvis/tools/__pycache__/methodology.cpython-313.pyc +0 -0
  32. jarvis/tools/__pycache__/python_script.cpython-313.pyc +0 -0
  33. jarvis/tools/__pycache__/rag.cpython-313.pyc +0 -0
  34. jarvis/tools/__pycache__/registry.cpython-313.pyc +0 -0
  35. jarvis/tools/__pycache__/search.cpython-313.pyc +0 -0
  36. jarvis/tools/__pycache__/shell.cpython-313.pyc +0 -0
  37. jarvis/tools/__pycache__/sub_agent.cpython-313.pyc +0 -0
  38. jarvis/tools/__pycache__/user_confirmation.cpython-313.pyc +0 -0
  39. jarvis/tools/__pycache__/user_input.cpython-313.pyc +0 -0
  40. jarvis/tools/__pycache__/user_interaction.cpython-313.pyc +0 -0
  41. jarvis/tools/__pycache__/webpage.cpython-313.pyc +0 -0
  42. jarvis/tools/base.py +23 -0
  43. jarvis/tools/file_ops.py +110 -0
  44. jarvis/tools/generator.py +172 -0
  45. jarvis/tools/methodology.py +145 -0
  46. jarvis/tools/registry.py +183 -0
  47. jarvis/tools/shell.py +78 -0
  48. jarvis/tools/sub_agent.py +82 -0
  49. jarvis/utils.py +202 -0
  50. jarvis_ai_assistant-0.1.32.dist-info/LICENSE +21 -0
  51. jarvis_ai_assistant-0.1.32.dist-info/METADATA +345 -0
  52. jarvis_ai_assistant-0.1.32.dist-info/RECORD +55 -0
  53. jarvis_ai_assistant-0.1.32.dist-info/WHEEL +5 -0
  54. jarvis_ai_assistant-0.1.32.dist-info/entry_points.txt +2 -0
  55. 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"
@@ -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