jarvis-ai-assistant 0.1.9__py3-none-any.whl → 0.1.11__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 +1 -1
- 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__/zte_llm.cpython-313.pyc +0 -0
- jarvis/agent.py +93 -150
- jarvis/main.py +14 -89
- jarvis/models/__init__.py +11 -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/base.py +19 -0
- jarvis/models/kimi.py +258 -0
- jarvis/tools/__init__.py +0 -4
- jarvis/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/base.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/sub_agent.cpython-313.pyc +0 -0
- jarvis/tools/base.py +0 -6
- {jarvis_ai_assistant-0.1.9.dist-info → jarvis_ai_assistant-0.1.11.dist-info}/METADATA +1 -6
- jarvis_ai_assistant-0.1.11.dist-info/RECORD +39 -0
- jarvis/models.py +0 -122
- jarvis/tools/bing_search.py +0 -38
- jarvis/tools/search.py +0 -132
- jarvis/tools/sub_agent.py +0 -83
- jarvis/tools/webpage.py +0 -76
- jarvis/zte_llm.py +0 -135
- jarvis_ai_assistant-0.1.9.dist-info/RECORD +0 -39
- {jarvis_ai_assistant-0.1.9.dist-info → jarvis_ai_assistant-0.1.11.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.9.dist-info → jarvis_ai_assistant-0.1.11.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.9.dist-info → jarvis_ai_assistant-0.1.11.dist-info}/top_level.txt +0 -0
jarvis/models/kimi.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import mimetypes
|
|
6
|
+
from .base import BaseModel
|
|
7
|
+
from ..utils import PrettyOutput, OutputType
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
class KimiModel(BaseModel):
|
|
11
|
+
"""Kimi模型实现"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, api_key: str = None):
|
|
14
|
+
"""
|
|
15
|
+
初始化Kimi模型
|
|
16
|
+
Args:
|
|
17
|
+
api_key: Kimi API密钥,如果不提供则从环境变量获取
|
|
18
|
+
"""
|
|
19
|
+
self.api_key = api_key or os.getenv("KIMI_API_KEY")
|
|
20
|
+
if not self.api_key:
|
|
21
|
+
raise Exception("KIMI_API_KEY is not set")
|
|
22
|
+
self.auth_header = f"Bearer {self.api_key}"
|
|
23
|
+
self.chat_id = ""
|
|
24
|
+
self.uploaded_files = [] # 存储已上传文件的信息
|
|
25
|
+
self.first_chat = True # 添加标记,用于判断是否是第一次对话
|
|
26
|
+
|
|
27
|
+
def _create_chat(self) -> bool:
|
|
28
|
+
"""创建新的对话会话"""
|
|
29
|
+
url = "https://kimi.moonshot.cn/api/chat"
|
|
30
|
+
payload = json.dumps({
|
|
31
|
+
"name": "未命名会话",
|
|
32
|
+
"is_example": False,
|
|
33
|
+
"kimiplus_id": "kimi"
|
|
34
|
+
})
|
|
35
|
+
headers = {
|
|
36
|
+
'Authorization': self.auth_header,
|
|
37
|
+
'Content-Type': 'application/json'
|
|
38
|
+
}
|
|
39
|
+
try:
|
|
40
|
+
response = requests.request("POST", url, headers=headers, data=payload)
|
|
41
|
+
self.chat_id = response.json()["id"]
|
|
42
|
+
return True
|
|
43
|
+
except Exception as e:
|
|
44
|
+
PrettyOutput.print(f"Failed to create chat: {e}", OutputType.ERROR)
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
def _get_presigned_url(self, filename: str) -> Dict:
|
|
48
|
+
"""获取预签名上传URL"""
|
|
49
|
+
url = "https://kimi.moonshot.cn/api/pre-sign-url"
|
|
50
|
+
mime_type, _ = mimetypes.guess_type(filename)
|
|
51
|
+
action = "image" if mime_type and mime_type.startswith('image/') else "file"
|
|
52
|
+
|
|
53
|
+
payload = json.dumps({
|
|
54
|
+
"action": action,
|
|
55
|
+
"name": os.path.basename(filename)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
headers = {
|
|
59
|
+
'Authorization': self.auth_header,
|
|
60
|
+
'Content-Type': 'application/json'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
response = requests.post(url, headers=headers, data=payload)
|
|
64
|
+
return response.json()
|
|
65
|
+
|
|
66
|
+
def _upload_file(self, file_path: str, presigned_url: str) -> bool:
|
|
67
|
+
"""上传文件到预签名URL"""
|
|
68
|
+
try:
|
|
69
|
+
with open(file_path, 'rb') as f:
|
|
70
|
+
content = f.read()
|
|
71
|
+
response = requests.put(presigned_url, data=content)
|
|
72
|
+
return response.status_code == 200
|
|
73
|
+
except Exception as e:
|
|
74
|
+
PrettyOutput.print(f"Failed to upload file: {e}", OutputType.ERROR)
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
def _get_file_info(self, file_data: Dict, name: str) -> Dict:
|
|
78
|
+
"""获取文件信息"""
|
|
79
|
+
url = "https://kimi.moonshot.cn/api/file"
|
|
80
|
+
payload = json.dumps({
|
|
81
|
+
"type": "file",
|
|
82
|
+
"name": name,
|
|
83
|
+
"object_name": file_data["object_name"],
|
|
84
|
+
"chat_id": self.chat_id,
|
|
85
|
+
"file_id": file_data.get("file_id", "")
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
headers = {
|
|
89
|
+
'Authorization': self.auth_header,
|
|
90
|
+
'Content-Type': 'application/json'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
response = requests.post(url, headers=headers, data=payload)
|
|
94
|
+
return response.json()
|
|
95
|
+
|
|
96
|
+
def _wait_for_parse(self, file_id: str) -> bool:
|
|
97
|
+
"""等待文件解析完成"""
|
|
98
|
+
url = "https://kimi.moonshot.cn/api/file/parse_process"
|
|
99
|
+
headers = {
|
|
100
|
+
'Authorization': self.auth_header,
|
|
101
|
+
'Content-Type': 'application/json'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
max_retries = 30
|
|
105
|
+
retry_count = 0
|
|
106
|
+
|
|
107
|
+
while retry_count < max_retries:
|
|
108
|
+
payload = json.dumps({"ids": [file_id]})
|
|
109
|
+
response = requests.post(url, headers=headers, data=payload, stream=True)
|
|
110
|
+
|
|
111
|
+
for line in response.iter_lines():
|
|
112
|
+
if not line:
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
line = line.decode('utf-8')
|
|
116
|
+
if not line.startswith("data: "):
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
data = json.loads(line[6:])
|
|
121
|
+
if data.get("event") == "resp":
|
|
122
|
+
status = data.get("file_info", {}).get("status")
|
|
123
|
+
if status == "parsed":
|
|
124
|
+
return True
|
|
125
|
+
elif status == "failed":
|
|
126
|
+
return False
|
|
127
|
+
except json.JSONDecodeError:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
retry_count += 1
|
|
131
|
+
time.sleep(1)
|
|
132
|
+
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
def upload_files(self, file_list: List[str]) -> List[Dict]:
|
|
136
|
+
"""上传文件列表并返回文件信息"""
|
|
137
|
+
if not file_list:
|
|
138
|
+
return []
|
|
139
|
+
|
|
140
|
+
PrettyOutput.print("开始处理文件上传...", OutputType.PROGRESS)
|
|
141
|
+
|
|
142
|
+
if not self.chat_id:
|
|
143
|
+
PrettyOutput.print("创建新的对话会话...", OutputType.PROGRESS)
|
|
144
|
+
if not self._create_chat():
|
|
145
|
+
raise Exception("Failed to create chat session")
|
|
146
|
+
|
|
147
|
+
uploaded_files = []
|
|
148
|
+
for index, file_path in enumerate(file_list, 1):
|
|
149
|
+
try:
|
|
150
|
+
PrettyOutput.print(f"处理文件 [{index}/{len(file_list)}]: {file_path}", OutputType.PROGRESS)
|
|
151
|
+
|
|
152
|
+
# 获取预签名URL
|
|
153
|
+
PrettyOutput.print("获取上传URL...", OutputType.PROGRESS)
|
|
154
|
+
presigned_data = self._get_presigned_url(file_path)
|
|
155
|
+
|
|
156
|
+
# 上传文件
|
|
157
|
+
PrettyOutput.print("上传文件内容...", OutputType.PROGRESS)
|
|
158
|
+
if self._upload_file(file_path, presigned_data["url"]):
|
|
159
|
+
# 获取文件信息
|
|
160
|
+
PrettyOutput.print("获取文件信息...", OutputType.PROGRESS)
|
|
161
|
+
file_info = self._get_file_info(presigned_data, os.path.basename(file_path))
|
|
162
|
+
|
|
163
|
+
# 等待文件解析
|
|
164
|
+
PrettyOutput.print("等待文件解析完成...", OutputType.PROGRESS)
|
|
165
|
+
if self._wait_for_parse(file_info["id"]):
|
|
166
|
+
uploaded_files.append(file_info)
|
|
167
|
+
PrettyOutput.print(f"✓ 文件处理成功: {file_path}", OutputType.SUCCESS)
|
|
168
|
+
else:
|
|
169
|
+
PrettyOutput.print(f"✗ 文件解析失败: {file_path}", OutputType.ERROR)
|
|
170
|
+
else:
|
|
171
|
+
PrettyOutput.print(f"✗ 文件上传失败: {file_path}", OutputType.ERROR)
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
PrettyOutput.print(f"✗ 处理文件出错 {file_path}: {str(e)}", OutputType.ERROR)
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
if uploaded_files:
|
|
178
|
+
PrettyOutput.print(f"成功处理 {len(uploaded_files)}/{len(file_list)} 个文件", OutputType.SUCCESS)
|
|
179
|
+
else:
|
|
180
|
+
PrettyOutput.print("没有文件成功处理", OutputType.WARNING)
|
|
181
|
+
|
|
182
|
+
self.uploaded_files = uploaded_files
|
|
183
|
+
return uploaded_files
|
|
184
|
+
|
|
185
|
+
def chat(self, message: str) -> str:
|
|
186
|
+
"""发送消息并获取响应"""
|
|
187
|
+
if not self.chat_id:
|
|
188
|
+
PrettyOutput.print("创建新的对话会话...", OutputType.PROGRESS)
|
|
189
|
+
if not self._create_chat():
|
|
190
|
+
raise Exception("Failed to create chat session")
|
|
191
|
+
|
|
192
|
+
url = f"https://kimi.moonshot.cn/api/chat/{self.chat_id}/completion/stream"
|
|
193
|
+
|
|
194
|
+
# 只在第一次对话时带上文件引用
|
|
195
|
+
refs = []
|
|
196
|
+
refs_file = []
|
|
197
|
+
if self.first_chat and self.uploaded_files:
|
|
198
|
+
PrettyOutput.print(f"首次对话,引用 {len(self.uploaded_files)} 个文件...", OutputType.PROGRESS)
|
|
199
|
+
refs = [f["id"] for f in self.uploaded_files]
|
|
200
|
+
refs_file = self.uploaded_files
|
|
201
|
+
self.first_chat = False
|
|
202
|
+
|
|
203
|
+
PrettyOutput.print("发送请求...", OutputType.PROGRESS)
|
|
204
|
+
payload = {
|
|
205
|
+
"messages": [{"role": "user", "content": message}],
|
|
206
|
+
"use_search": True,
|
|
207
|
+
"extend": {"sidebar": True},
|
|
208
|
+
"kimiplus_id": "kimi",
|
|
209
|
+
"use_research": False,
|
|
210
|
+
"use_math": False,
|
|
211
|
+
"refs": refs,
|
|
212
|
+
"refs_file": refs_file
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
headers = {
|
|
216
|
+
'Authorization': self.auth_header,
|
|
217
|
+
'Content-Type': 'application/json'
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
response = requests.post(url, headers=headers, json=payload, stream=True)
|
|
222
|
+
full_response = ""
|
|
223
|
+
|
|
224
|
+
PrettyOutput.print("接收响应...", OutputType.PROGRESS)
|
|
225
|
+
for line in response.iter_lines():
|
|
226
|
+
if not line:
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
line = line.decode('utf-8')
|
|
230
|
+
if not line.startswith("data: "):
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
data = json.loads(line[6:])
|
|
235
|
+
if data.get("event") == "cmpl":
|
|
236
|
+
text = data.get("text", "")
|
|
237
|
+
if text:
|
|
238
|
+
PrettyOutput.print_stream(text, OutputType.SYSTEM)
|
|
239
|
+
full_response += text
|
|
240
|
+
except json.JSONDecodeError:
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
PrettyOutput.print_stream_end()
|
|
244
|
+
return full_response
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
raise Exception(f"Chat failed: {str(e)}")
|
|
248
|
+
|
|
249
|
+
def reset(self):
|
|
250
|
+
"""重置对话"""
|
|
251
|
+
self.chat_id = ""
|
|
252
|
+
self.uploaded_files = []
|
|
253
|
+
self.first_chat = True # 重置first_chat标记
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
kimi = KimiModel()
|
|
258
|
+
print(kimi.chat([{"role": "user", "content": "ollama如何部署"}]))
|
jarvis/tools/__init__.py
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
from .base import Tool, ToolRegistry
|
|
2
2
|
from .file_ops import FileOperationTool
|
|
3
|
-
from .search import SearchTool
|
|
4
3
|
from .shell import ShellTool
|
|
5
|
-
from .webpage import WebpageTool
|
|
6
4
|
|
|
7
5
|
__all__ = [
|
|
8
6
|
'Tool',
|
|
9
7
|
'ToolRegistry',
|
|
10
8
|
'FileOperationTool',
|
|
11
|
-
'SearchTool',
|
|
12
9
|
'ShellTool',
|
|
13
|
-
'WebpageTool',
|
|
14
10
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
jarvis/tools/base.py
CHANGED
|
@@ -30,18 +30,12 @@ class ToolRegistry:
|
|
|
30
30
|
|
|
31
31
|
def _register_default_tools(self):
|
|
32
32
|
"""注册所有默认工具"""
|
|
33
|
-
from .search import SearchTool
|
|
34
33
|
from .shell import ShellTool
|
|
35
34
|
from .file_ops import FileOperationTool
|
|
36
|
-
from .webpage import WebpageTool
|
|
37
|
-
from .sub_agent import SubAgentTool
|
|
38
35
|
|
|
39
36
|
tools = [
|
|
40
|
-
SearchTool(self.model),
|
|
41
37
|
ShellTool(),
|
|
42
38
|
FileOperationTool(),
|
|
43
|
-
WebpageTool(),
|
|
44
|
-
SubAgentTool(self.model)
|
|
45
39
|
]
|
|
46
40
|
|
|
47
41
|
for tool in tools:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: jarvis-ai-assistant
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
4
4
|
Summary: Jarvis: An AI assistant that uses tools to interact with the system
|
|
5
5
|
Home-page: https://github.com/skyfireitdiy/Jarvis
|
|
6
6
|
Author: skyfire
|
|
@@ -17,13 +17,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
17
17
|
Requires-Python: >=3.8
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
Requires-Dist: requests>=2.25.1
|
|
20
|
-
Requires-Dist: beautifulsoup4>=4.9.3
|
|
21
|
-
Requires-Dist: duckduckgo-search>=3.0.0
|
|
22
20
|
Requires-Dist: pyyaml>=5.1
|
|
23
|
-
Requires-Dist: ollama>=0.1.6
|
|
24
21
|
Requires-Dist: colorama>=0.4.6
|
|
25
|
-
Requires-Dist: openai>=1.2.0
|
|
26
|
-
Requires-Dist: playwright>=1.41.1
|
|
27
22
|
Provides-Extra: dev
|
|
28
23
|
Requires-Dist: pytest; extra == "dev"
|
|
29
24
|
Requires-Dist: black; extra == "dev"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
jarvis/__init__.py,sha256=_aIzFZNgMY8TxeqpWoDZOcvaSQ7uK4SxZvtdW_HPIHw,50
|
|
2
|
+
jarvis/agent.py,sha256=AJCRmjrvlK19K-yj5zNwrFvVDbZ3mWEnzwHwxKCXTL8,8462
|
|
3
|
+
jarvis/main.py,sha256=_1hE0MZ1OsIJpmlipUwtqFszh9rm7VmQcnnWr80o3p4,4176
|
|
4
|
+
jarvis/utils.py,sha256=3hLtv-HcBL8Ngw69cowhARuIFrjcQ6yRP3Y1o9CvtsI,5992
|
|
5
|
+
jarvis/__pycache__/__init__.cpython-313.pyc,sha256=QtWKLHgbSECIa7pnYFe-pSmrHEzJUhP4Aorar8bxZqA,209
|
|
6
|
+
jarvis/__pycache__/agent.cpython-313.pyc,sha256=sLDZttVT9sOC6WKgs_SWhrEzfsN9vzlMhknMJkEqkVY,9746
|
|
7
|
+
jarvis/__pycache__/main.cpython-313.pyc,sha256=o80rvS95KbpeL4MSEoMC0rt0Y91-asDHbHPJU8M8kxo,5933
|
|
8
|
+
jarvis/__pycache__/models.cpython-313.pyc,sha256=uWuRIjGrY4YDB3dGW5PGDLWaS03et8g11O725TjY_eU,5960
|
|
9
|
+
jarvis/__pycache__/tools.cpython-313.pyc,sha256=lAD4LrnnWzNZQmHXGfZ_2l7oskOpr2_2OC-gdFhxQY8,33933
|
|
10
|
+
jarvis/__pycache__/utils.cpython-313.pyc,sha256=k4jyAlx4tlW0MKLMLzV7OOH9zsKrK0H955kY6M2SuFU,8772
|
|
11
|
+
jarvis/__pycache__/zte_llm.cpython-313.pyc,sha256=kMm9IGundGmOPqjsgrm9oIaWLDagYGCPRAaE3ipkc-0,5662
|
|
12
|
+
jarvis/models/__init__.py,sha256=B_IJFvKTaxdg19FAD1ea288tYp3-bRYRpkeGI0_OcBI,262
|
|
13
|
+
jarvis/models/base.py,sha256=GgVl5N0qDqn-yqRcX_PX3wHjogouE6GPFAWktG40cXg,403
|
|
14
|
+
jarvis/models/kimi.py,sha256=J_0QB6XY61tY-Oeu2cexWto58QbMU_liYj1f6PKbNRw,9763
|
|
15
|
+
jarvis/models/__pycache__/__init__.cpython-313.pyc,sha256=jAwySX4diR7EWM_alK75tiIb_J8bVfs4Bh_U3bdjDLo,534
|
|
16
|
+
jarvis/models/__pycache__/base.cpython-313.pyc,sha256=4I9KZlXHvTB7vENA9YWK4Fx0sns_KvIOtWqzE9y_-Co,1094
|
|
17
|
+
jarvis/models/__pycache__/kimi.cpython-313.pyc,sha256=doDXu1nPClQOVqnDFGGBl2p_3AMDuDaiFAgdsP-G1NM,12524
|
|
18
|
+
jarvis/tools/__init__.py,sha256=000SvX1Z0yzKtuVR9NS45r41DHUVpE6EiSEzzE99uR0,196
|
|
19
|
+
jarvis/tools/base.py,sha256=qm73kMOkqAWHjUisUWFye-x69LJuZdwm0U3Tct-mmMc,3890
|
|
20
|
+
jarvis/tools/file_ops.py,sha256=zTksx45NZm3iz9itN5iQGZ8DoxnSeTHdrnF08_ix7PU,3770
|
|
21
|
+
jarvis/tools/shell.py,sha256=7q52lA3slf0TdjBjP1bkwugoO5pB0eqh6cYjAzAXNtI,2547
|
|
22
|
+
jarvis/tools/__pycache__/__init__.cpython-313.pyc,sha256=6BQJ333a0Oh9uEDLlgh0HHeXS34I7NaPs9KFQI-sil8,347
|
|
23
|
+
jarvis/tools/__pycache__/base.cpython-313.pyc,sha256=ZfkdDuQDiCuA7V3qx69coGwrkchLPqNDF6tCGPXKflU,6197
|
|
24
|
+
jarvis/tools/__pycache__/bing_search.cpython-313.pyc,sha256=1G_wPbk5wcQYh7H0drLIS2Aw0XOG2ZM8ztgfQaqu3P8,2031
|
|
25
|
+
jarvis/tools/__pycache__/file_ops.cpython-313.pyc,sha256=LbOp31JUzoRp5XVazy1VBqCQhpFg0qQYmVftFVY90V4,3628
|
|
26
|
+
jarvis/tools/__pycache__/python_script.cpython-313.pyc,sha256=8JpryqTovEiTvBlWAK1KjZmPvHUuPc9GT9rTXBEQoJc,6693
|
|
27
|
+
jarvis/tools/__pycache__/rag.cpython-313.pyc,sha256=JH6-PSZRMKAvTZqCwlRXJGClxYXNMs-vetU0q7hBLz0,6064
|
|
28
|
+
jarvis/tools/__pycache__/search.cpython-313.pyc,sha256=wLMIkFwT-h4NGHgssytT4xme7sGO6ZhEnex7kjcy0-k,5990
|
|
29
|
+
jarvis/tools/__pycache__/shell.cpython-313.pyc,sha256=QMaLUc1MtZXWod3msv_x7iMq2IybwMwz1OoaKsbm6U4,3271
|
|
30
|
+
jarvis/tools/__pycache__/sub_agent.cpython-313.pyc,sha256=9spmVX8KSQ4qIH7FbXo5tmmZfNx8KfIGtjy2lxTSp4Y,3144
|
|
31
|
+
jarvis/tools/__pycache__/user_confirmation.cpython-313.pyc,sha256=wK3Ev10lHSUSRvoYmi7A0GzxYkzU-C4Wfhs5qW_HBqs,2271
|
|
32
|
+
jarvis/tools/__pycache__/user_input.cpython-313.pyc,sha256=JjTFOhObKsKF4Pn8KBRuKfV1_Ssj083fjU7Mfc_5z7c,2531
|
|
33
|
+
jarvis/tools/__pycache__/user_interaction.cpython-313.pyc,sha256=RuVZ-pmiPBDywY3efgXSfohMAciC1avMGPmBK5qlnew,3305
|
|
34
|
+
jarvis/tools/__pycache__/webpage.cpython-313.pyc,sha256=BjzSfnNzsKCrLETCcWjt32lNDLzwnjqcVGg4JfWd9OM,3008
|
|
35
|
+
jarvis_ai_assistant-0.1.11.dist-info/METADATA,sha256=PLBiD-OM5rCBVi3MWF93SXePf6piXNW0OI8I_D2tUP8,3585
|
|
36
|
+
jarvis_ai_assistant-0.1.11.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
37
|
+
jarvis_ai_assistant-0.1.11.dist-info/entry_points.txt,sha256=iKu7OMfew9dtfGhW71gIMTg4wvafuPqKb4wyQOnMAGU,44
|
|
38
|
+
jarvis_ai_assistant-0.1.11.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
|
|
39
|
+
jarvis_ai_assistant-0.1.11.dist-info/RECORD,,
|
jarvis/models.py
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import time
|
|
3
|
-
from typing import Dict, List, Optional, Tuple
|
|
4
|
-
from duckduckgo_search import DDGS
|
|
5
|
-
import ollama
|
|
6
|
-
from abc import ABC, abstractmethod
|
|
7
|
-
import yaml
|
|
8
|
-
import openai
|
|
9
|
-
|
|
10
|
-
from .utils import OutputType, PrettyOutput
|
|
11
|
-
|
|
12
|
-
class BaseModel(ABC):
|
|
13
|
-
"""大语言模型基类"""
|
|
14
|
-
|
|
15
|
-
@abstractmethod
|
|
16
|
-
def chat(self, messages: List[Dict]) -> str:
|
|
17
|
-
"""执行对话"""
|
|
18
|
-
pass
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class DDGSModel(BaseModel):
|
|
22
|
-
def __init__(self, model_name: str = "gpt-4o-mini"):
|
|
23
|
-
"""
|
|
24
|
-
[1]: gpt-4o-mini
|
|
25
|
-
[2]: claude-3-haiku
|
|
26
|
-
[3]: llama-3.1-70b
|
|
27
|
-
[4]: mixtral-8x7b
|
|
28
|
-
"""
|
|
29
|
-
self.model_name = model_name
|
|
30
|
-
|
|
31
|
-
def __make_prompt(self, messages: List[Dict]) -> str:
|
|
32
|
-
prompt = ""
|
|
33
|
-
for message in messages:
|
|
34
|
-
prompt += f"[{message['role']}]: {message['content']}\n"
|
|
35
|
-
return prompt
|
|
36
|
-
|
|
37
|
-
def chat(self, messages: List[Dict]) -> str:
|
|
38
|
-
ddgs = DDGS()
|
|
39
|
-
prompt = self.__make_prompt(messages)
|
|
40
|
-
content = ddgs.chat(prompt)
|
|
41
|
-
PrettyOutput.print(content, OutputType.SYSTEM)
|
|
42
|
-
return content
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class OllamaModel(BaseModel):
|
|
46
|
-
"""Ollama模型实现"""
|
|
47
|
-
|
|
48
|
-
def __init__(self, model_name: str = "qwen2.5:14b", api_base: str = "http://localhost:11434"):
|
|
49
|
-
self.model_name = model_name
|
|
50
|
-
self.api_base = api_base
|
|
51
|
-
self.client = ollama.Client(host=api_base)
|
|
52
|
-
|
|
53
|
-
def chat(self, messages: List[Dict]) -> str:
|
|
54
|
-
"""调用Ollama API获取响应"""
|
|
55
|
-
try:
|
|
56
|
-
# 使用流式调用
|
|
57
|
-
stream = self.client.chat(
|
|
58
|
-
model=self.model_name,
|
|
59
|
-
messages=messages,
|
|
60
|
-
stream=True
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
# 收集完整响应
|
|
64
|
-
content_parts = []
|
|
65
|
-
for chunk in stream:
|
|
66
|
-
if chunk.message.content:
|
|
67
|
-
content_parts.append(chunk.message.content)
|
|
68
|
-
# 实时打印内容
|
|
69
|
-
PrettyOutput.print_stream(chunk.message.content, OutputType.SYSTEM)
|
|
70
|
-
|
|
71
|
-
PrettyOutput.print_stream_end()
|
|
72
|
-
|
|
73
|
-
# 合并完整内容
|
|
74
|
-
return "".join(content_parts)
|
|
75
|
-
|
|
76
|
-
except Exception as e:
|
|
77
|
-
raise Exception(f"Ollama API调用失败: {str(e)}")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class OpenAIModel(BaseModel):
|
|
81
|
-
"""OpenAI模型实现"""
|
|
82
|
-
|
|
83
|
-
def __init__(self, model_name: str = "deepseek-chat", api_key: Optional[str] = None, api_base: Optional[str] = None):
|
|
84
|
-
"""
|
|
85
|
-
初始化OpenAI模型
|
|
86
|
-
Args:
|
|
87
|
-
model_name: 模型名称,默认为 deepseek-chat
|
|
88
|
-
api_key: OpenAI API密钥
|
|
89
|
-
api_base: 可选的API基础URL,用于自定义端点
|
|
90
|
-
"""
|
|
91
|
-
self.model_name = model_name
|
|
92
|
-
if api_key:
|
|
93
|
-
openai.api_key = api_key
|
|
94
|
-
if api_base:
|
|
95
|
-
openai.base_url = api_base
|
|
96
|
-
|
|
97
|
-
def chat(self, messages: List[Dict]) -> str:
|
|
98
|
-
"""调用OpenAI API获取响应"""
|
|
99
|
-
try:
|
|
100
|
-
# 使用流式调用
|
|
101
|
-
stream = openai.chat.completions.create(
|
|
102
|
-
model=self.model_name,
|
|
103
|
-
messages=messages,
|
|
104
|
-
stream=True
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
# 收集完整响应
|
|
108
|
-
content_parts = []
|
|
109
|
-
for chunk in stream:
|
|
110
|
-
if chunk.choices[0].delta.content:
|
|
111
|
-
content = chunk.choices[0].delta.content
|
|
112
|
-
content_parts.append(content)
|
|
113
|
-
# 实时打印内容
|
|
114
|
-
PrettyOutput.print_stream(content, OutputType.SYSTEM)
|
|
115
|
-
|
|
116
|
-
PrettyOutput.print_stream_end()
|
|
117
|
-
|
|
118
|
-
# 合并完整内容
|
|
119
|
-
return "".join(content_parts)
|
|
120
|
-
|
|
121
|
-
except Exception as e:
|
|
122
|
-
raise Exception(f"OpenAI API调用失败: {str(e)}")
|
jarvis/tools/bing_search.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from playwright.sync_api import sync_playwright
|
|
2
|
-
from urllib.parse import quote
|
|
3
|
-
|
|
4
|
-
def bing_search(query):
|
|
5
|
-
try:
|
|
6
|
-
with sync_playwright() as p:
|
|
7
|
-
browser = p.chromium.launch()
|
|
8
|
-
page = browser.new_page()
|
|
9
|
-
page.goto(
|
|
10
|
-
f"https://www.bing.com/search?form=QBRE&q={quote(query)}&cc=US"
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
page.wait_for_selector("#b_results", timeout=10000)
|
|
14
|
-
|
|
15
|
-
summaries = page.evaluate("""() => {
|
|
16
|
-
const liElements = Array.from(
|
|
17
|
-
document.querySelectorAll("#b_results > .b_algo")
|
|
18
|
-
);
|
|
19
|
-
return liElements.map((li) => {
|
|
20
|
-
const abstractElement = li.querySelector(".b_caption > p");
|
|
21
|
-
const linkElement = li.querySelector("a");
|
|
22
|
-
const href = linkElement.getAttribute("href");
|
|
23
|
-
const title = linkElement.textContent;
|
|
24
|
-
const abstract = abstractElement ? abstractElement.textContent : "";
|
|
25
|
-
return { href, title, abstract };
|
|
26
|
-
});
|
|
27
|
-
}""")
|
|
28
|
-
|
|
29
|
-
browser.close()
|
|
30
|
-
print(summaries)
|
|
31
|
-
return summaries
|
|
32
|
-
except Exception as error:
|
|
33
|
-
print("An error occurred:", error)
|
|
34
|
-
|
|
35
|
-
if __name__ == "__main__":
|
|
36
|
-
# results = bing_search("北京到西雅图的距离")
|
|
37
|
-
results = bing_search("北京到西雅图的距离")
|
|
38
|
-
print(results)
|
jarvis/tools/search.py
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import Dict, Any, List
|
|
3
|
-
from ..utils import PrettyOutput, OutputType
|
|
4
|
-
from .webpage import WebpageTool
|
|
5
|
-
from .bing_search import bing_search
|
|
6
|
-
|
|
7
|
-
class SearchTool:
|
|
8
|
-
name = "search"
|
|
9
|
-
description = "使用Bing搜索引擎搜索信息,并根据问题提取关键信息"
|
|
10
|
-
parameters = {
|
|
11
|
-
"type": "object",
|
|
12
|
-
"properties": {
|
|
13
|
-
"query": {
|
|
14
|
-
"type": "string",
|
|
15
|
-
"description": "搜索关键词"
|
|
16
|
-
},
|
|
17
|
-
"question": {
|
|
18
|
-
"type": "string",
|
|
19
|
-
"description": "需要回答的具体问题,用于从搜索结果中提取相关信息"
|
|
20
|
-
},
|
|
21
|
-
"max_results": {
|
|
22
|
-
"type": "integer",
|
|
23
|
-
"description": "最大搜索结果数量",
|
|
24
|
-
"default": 3
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"required": ["query", "question"]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
def __init__(self, model):
|
|
31
|
-
"""初始化搜索工具,需要传入语言模型用于信息提取"""
|
|
32
|
-
self.model = model
|
|
33
|
-
self.webpage_tool = WebpageTool()
|
|
34
|
-
|
|
35
|
-
def _search(self, query: str, max_results: int) -> List[Dict]:
|
|
36
|
-
"""执行搜索请求"""
|
|
37
|
-
try:
|
|
38
|
-
results = bing_search(query)
|
|
39
|
-
if not results:
|
|
40
|
-
return []
|
|
41
|
-
|
|
42
|
-
# 格式化搜索结果
|
|
43
|
-
formatted_results = []
|
|
44
|
-
for result in results[:max_results]:
|
|
45
|
-
formatted_results.append({
|
|
46
|
-
"title": result.get("title", ""),
|
|
47
|
-
"href": result.get("href", ""),
|
|
48
|
-
"body": result.get("abstract", "")
|
|
49
|
-
})
|
|
50
|
-
return formatted_results
|
|
51
|
-
except Exception as e:
|
|
52
|
-
PrettyOutput.print(f"搜索请求失败: {str(e)}", OutputType.ERROR)
|
|
53
|
-
return []
|
|
54
|
-
|
|
55
|
-
def _extract_info(self, contents: List[str], question: str) -> str:
|
|
56
|
-
"""使用语言模型从网页内容中提取关键信息"""
|
|
57
|
-
prompt = {
|
|
58
|
-
"role": "user",
|
|
59
|
-
"content": f"""请根据以下搜索结果内容,回答问题:{question}
|
|
60
|
-
|
|
61
|
-
搜索结果内容:
|
|
62
|
-
{'-' * 40}
|
|
63
|
-
{''.join(contents)}
|
|
64
|
-
{'-' * 40}
|
|
65
|
-
|
|
66
|
-
请提供一个简洁、准确的答案,重点关注与问题直接相关的信息。如果搜索结果中没有相关信息,请明确说明。
|
|
67
|
-
回答时注意:
|
|
68
|
-
1. 保持客观性,只基于搜索结果提供信息
|
|
69
|
-
2. 如果不同来源有冲突,请指出差异
|
|
70
|
-
3. 适当引用信息来源
|
|
71
|
-
4. 如果信息不完整或不确定,请说明"""
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
response = self.model.chat([prompt])
|
|
76
|
-
return response
|
|
77
|
-
except Exception as e:
|
|
78
|
-
return f"信息提取失败: {str(e)}"
|
|
79
|
-
|
|
80
|
-
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
81
|
-
"""执行搜索并提取信息"""
|
|
82
|
-
try:
|
|
83
|
-
query = args["query"]
|
|
84
|
-
question = args["question"]
|
|
85
|
-
max_results = args.get("max_results", 3)
|
|
86
|
-
|
|
87
|
-
# 打印搜索信息
|
|
88
|
-
PrettyOutput.print(f"搜索查询: {query}", OutputType.INFO)
|
|
89
|
-
PrettyOutput.print(f"相关问题: {question}", OutputType.INFO)
|
|
90
|
-
|
|
91
|
-
# 获取搜索结果
|
|
92
|
-
results = self._search(query, max_results)
|
|
93
|
-
if not results:
|
|
94
|
-
return {
|
|
95
|
-
"success": False,
|
|
96
|
-
"error": "未能获取任何搜索结果"
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
# 收集网页内容
|
|
100
|
-
contents = []
|
|
101
|
-
for i, result in enumerate(results, 1):
|
|
102
|
-
try:
|
|
103
|
-
PrettyOutput.print(f"正在读取第 {i}/{len(results)} 个结果... {result['title']} - {result['href']}", OutputType.PROGRESS)
|
|
104
|
-
webpage_result = self.webpage_tool.execute({"url": result["href"]})
|
|
105
|
-
if webpage_result["success"]:
|
|
106
|
-
contents.append(f"\n来源 {i}:{result['href']}\n")
|
|
107
|
-
contents.append(webpage_result["stdout"])
|
|
108
|
-
except Exception as e:
|
|
109
|
-
PrettyOutput.print(f"读取结果 {i} 失败: {str(e)}", OutputType.WARNING)
|
|
110
|
-
continue
|
|
111
|
-
|
|
112
|
-
if not contents:
|
|
113
|
-
return {
|
|
114
|
-
"success": False,
|
|
115
|
-
"error": "未能获取任何有效的搜索结果"
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
# 提取信息
|
|
119
|
-
PrettyOutput.print("正在分析搜索结果...", OutputType.PROGRESS)
|
|
120
|
-
analysis = self._extract_info(contents, question)
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
"success": True,
|
|
124
|
-
"stdout": f"搜索分析结果:\n\n{analysis}",
|
|
125
|
-
"stderr": ""
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
except Exception as e:
|
|
129
|
-
return {
|
|
130
|
-
"success": False,
|
|
131
|
-
"error": f"搜索失败: {str(e)}"
|
|
132
|
-
}
|