jarvis-ai-assistant 0.1.9__py3-none-any.whl → 0.1.10__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/__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 +72 -76
- jarvis/main.py +8 -87
- 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 +15 -0
- jarvis/models/kimi.py +135 -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.10.dist-info}/METADATA +1 -6
- jarvis_ai_assistant-0.1.10.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.10.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.9.dist-info → jarvis_ai_assistant-0.1.10.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.9.dist-info → jarvis_ai_assistant-0.1.10.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
jarvis/agent.py
CHANGED
|
@@ -24,8 +24,69 @@ class Agent:
|
|
|
24
24
|
self.tool_registry = tool_registry or ToolRegistry(model)
|
|
25
25
|
self.name = name
|
|
26
26
|
self.is_sub_agent = is_sub_agent
|
|
27
|
+
self.messages = []
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def extract_tool_calls(content: str) -> List[Dict]:
|
|
32
|
+
"""从内容中提取工具调用,如果检测到多个工具调用则抛出异常,并返回工具调用之前的内容和工具调用"""
|
|
33
|
+
# 分割内容为行
|
|
34
|
+
lines = content.split('\n')
|
|
35
|
+
tool_call_lines = []
|
|
36
|
+
in_tool_call = False
|
|
37
|
+
|
|
38
|
+
# 逐行处理
|
|
39
|
+
for line in lines:
|
|
40
|
+
if '<START_TOOL_CALL>' in line:
|
|
41
|
+
in_tool_call = True
|
|
42
|
+
continue
|
|
43
|
+
elif '<END_TOOL_CALL>' in line:
|
|
44
|
+
if in_tool_call and tool_call_lines:
|
|
45
|
+
try:
|
|
46
|
+
# 直接解析YAML
|
|
47
|
+
tool_call_text = '\n'.join(tool_call_lines)
|
|
48
|
+
tool_call_data = yaml.safe_load(tool_call_text)
|
|
49
|
+
|
|
50
|
+
# 验证必要的字段
|
|
51
|
+
if "name" in tool_call_data and "arguments" in tool_call_data:
|
|
52
|
+
# 返回工具调用之前的内容和工具调用
|
|
53
|
+
return [{
|
|
54
|
+
"name": tool_call_data["name"],
|
|
55
|
+
"arguments": tool_call_data["arguments"]
|
|
56
|
+
}]
|
|
57
|
+
else:
|
|
58
|
+
PrettyOutput.print("工具调用缺少必要字段", OutputType.ERROR)
|
|
59
|
+
raise '工具调用缺少必要字段'
|
|
60
|
+
except yaml.YAMLError as e:
|
|
61
|
+
PrettyOutput.print(f"YAML解析错误: {str(e)}", OutputType.ERROR)
|
|
62
|
+
raise 'YAML解析错误'
|
|
63
|
+
except Exception as e:
|
|
64
|
+
PrettyOutput.print(f"处理工具调用时发生错误: {str(e)}", OutputType.ERROR)
|
|
65
|
+
raise '处理工具调用时发生错误'
|
|
66
|
+
in_tool_call = False
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
if in_tool_call:
|
|
70
|
+
tool_call_lines.append(line)
|
|
71
|
+
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
def _call_model(self, messages: List[Dict]) -> Dict:
|
|
75
|
+
"""调用模型获取响应"""
|
|
76
|
+
try:
|
|
77
|
+
return self.model.chat(
|
|
78
|
+
messages=messages,
|
|
79
|
+
)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise Exception(f"{self.name}: 模型调用失败: {str(e)}")
|
|
82
|
+
|
|
83
|
+
def run(self, user_input: str) -> str:
|
|
84
|
+
"""处理用户输入并返回响应,返回任务总结报告"""
|
|
85
|
+
self.clear_history()
|
|
86
|
+
|
|
87
|
+
# 显示任务开始
|
|
88
|
+
PrettyOutput.section(f"开始新任务: {self.name}", OutputType.PLANNING)
|
|
27
89
|
|
|
28
|
-
# 构建工具说明
|
|
29
90
|
tools_prompt = "可用工具:\n"
|
|
30
91
|
for tool in self.tool_registry.get_all_tools():
|
|
31
92
|
tools_prompt += f"- 名称: {tool['name']}\n"
|
|
@@ -34,8 +95,8 @@ class Agent:
|
|
|
34
95
|
|
|
35
96
|
self.messages = [
|
|
36
97
|
{
|
|
37
|
-
"role": "
|
|
38
|
-
"content": f"""你是 {name},一个严格遵循 ReAct 框架进行逐步推理和行动的 AI 助手。
|
|
98
|
+
"role": "user",
|
|
99
|
+
"content": f"""你是 {self.name},一个严格遵循 ReAct 框架进行逐步推理和行动的 AI 助手。
|
|
39
100
|
|
|
40
101
|
{tools_prompt}
|
|
41
102
|
|
|
@@ -125,78 +186,12 @@ arguments:
|
|
|
125
186
|
- 不要虚构对话
|
|
126
187
|
- 每个动作后停止
|
|
127
188
|
- 只在有实际用户输入时继续
|
|
189
|
+
|
|
190
|
+
任务:
|
|
191
|
+
{user_input}
|
|
128
192
|
"""
|
|
129
193
|
}
|
|
130
194
|
]
|
|
131
|
-
|
|
132
|
-
@staticmethod
|
|
133
|
-
def extract_tool_calls(content: str) -> Tuple[str, List[Dict]]:
|
|
134
|
-
"""从内容中提取工具调用,如果检测到多个工具调用则抛出异常,并返回工具调用之前的内容和工具调用"""
|
|
135
|
-
# 分割内容为行
|
|
136
|
-
lines = content.split('\n')
|
|
137
|
-
tool_call_lines = []
|
|
138
|
-
content_lines = [] # 存储工具调用之前的内容
|
|
139
|
-
in_tool_call = False
|
|
140
|
-
|
|
141
|
-
# 逐行处理
|
|
142
|
-
for line in lines:
|
|
143
|
-
content_lines.append(line) # 所有内容都添加到 content_lines
|
|
144
|
-
|
|
145
|
-
if '<START_TOOL_CALL>' in line:
|
|
146
|
-
in_tool_call = True
|
|
147
|
-
continue
|
|
148
|
-
elif '<END_TOOL_CALL>' in line:
|
|
149
|
-
if in_tool_call and tool_call_lines:
|
|
150
|
-
try:
|
|
151
|
-
# 直接解析YAML
|
|
152
|
-
tool_call_text = '\n'.join(tool_call_lines)
|
|
153
|
-
tool_call_data = yaml.safe_load(tool_call_text)
|
|
154
|
-
|
|
155
|
-
# 验证必要的字段
|
|
156
|
-
if "name" in tool_call_data and "arguments" in tool_call_data:
|
|
157
|
-
# 返回工具调用之前的内容和工具调用
|
|
158
|
-
return '\n'.join(content_lines), [{
|
|
159
|
-
"name": tool_call_data["name"],
|
|
160
|
-
"arguments": tool_call_data["arguments"]
|
|
161
|
-
}]
|
|
162
|
-
else:
|
|
163
|
-
PrettyOutput.print("工具调用缺少必要字段", OutputType.ERROR)
|
|
164
|
-
raise '工具调用缺少必要字段'
|
|
165
|
-
except yaml.YAMLError as e:
|
|
166
|
-
PrettyOutput.print(f"YAML解析错误: {str(e)}", OutputType.ERROR)
|
|
167
|
-
raise 'YAML解析错误'
|
|
168
|
-
except Exception as e:
|
|
169
|
-
PrettyOutput.print(f"处理工具调用时发生错误: {str(e)}", OutputType.ERROR)
|
|
170
|
-
raise '处理工具调用时发生错误'
|
|
171
|
-
in_tool_call = False
|
|
172
|
-
continue
|
|
173
|
-
|
|
174
|
-
if in_tool_call:
|
|
175
|
-
tool_call_lines.append(line)
|
|
176
|
-
|
|
177
|
-
# 如果没有找到有效的工具调用,返回原始内容
|
|
178
|
-
return '\n'.join(content_lines), []
|
|
179
|
-
|
|
180
|
-
def _call_model(self, messages: List[Dict]) -> Dict:
|
|
181
|
-
"""调用模型获取响应"""
|
|
182
|
-
try:
|
|
183
|
-
return self.model.chat(
|
|
184
|
-
messages=messages,
|
|
185
|
-
)
|
|
186
|
-
except Exception as e:
|
|
187
|
-
raise Exception(f"{self.name}: 模型调用失败: {str(e)}")
|
|
188
|
-
|
|
189
|
-
def run(self, user_input: str) -> str:
|
|
190
|
-
"""处理用户输入并返回响应,返回任务总结报告"""
|
|
191
|
-
self.clear_history()
|
|
192
|
-
|
|
193
|
-
# 显示任务开始
|
|
194
|
-
PrettyOutput.section(f"开始新任务: {self.name}", OutputType.PLANNING)
|
|
195
|
-
|
|
196
|
-
self.messages.append({
|
|
197
|
-
"role": "user",
|
|
198
|
-
"content": user_input
|
|
199
|
-
})
|
|
200
195
|
|
|
201
196
|
while True:
|
|
202
197
|
try:
|
|
@@ -218,15 +213,15 @@ arguments:
|
|
|
218
213
|
|
|
219
214
|
self.messages.append({
|
|
220
215
|
"role": "assistant",
|
|
221
|
-
"content":
|
|
216
|
+
"content": current_response
|
|
222
217
|
})
|
|
223
218
|
|
|
224
219
|
|
|
225
|
-
if len(result
|
|
220
|
+
if len(result) > 0:
|
|
226
221
|
try:
|
|
227
222
|
# 显示工具调用
|
|
228
223
|
PrettyOutput.print("执行工具调用...", OutputType.PROGRESS)
|
|
229
|
-
tool_result = self.tool_registry.handle_tool_calls(result
|
|
224
|
+
tool_result = self.tool_registry.handle_tool_calls(result)
|
|
230
225
|
PrettyOutput.print(tool_result, OutputType.RESULT)
|
|
231
226
|
except Exception as e:
|
|
232
227
|
PrettyOutput.print(str(e), OutputType.ERROR)
|
|
@@ -295,4 +290,5 @@ arguments:
|
|
|
295
290
|
|
|
296
291
|
def clear_history(self):
|
|
297
292
|
"""清除对话历史,只保留系统提示"""
|
|
298
|
-
self.messages = [
|
|
293
|
+
self.messages = []
|
|
294
|
+
self.model.reset()
|
jarvis/main.py
CHANGED
|
@@ -12,33 +12,9 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
12
12
|
|
|
13
13
|
from jarvis.agent import Agent
|
|
14
14
|
from jarvis.tools import ToolRegistry
|
|
15
|
-
from jarvis.models import
|
|
15
|
+
from jarvis.models import KimiModel
|
|
16
16
|
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, load_env_from_file
|
|
17
|
-
from jarvis.zte_llm import create_zte_llm
|
|
18
17
|
|
|
19
|
-
# 定义支持的平台和模型
|
|
20
|
-
SUPPORTED_PLATFORMS = {
|
|
21
|
-
"ollama": {
|
|
22
|
-
"models": ["qwen2.5:14b", "qwq"],
|
|
23
|
-
"default": "qwen2.5:14b",
|
|
24
|
-
"allow_custom": True
|
|
25
|
-
},
|
|
26
|
-
"ddgs": {
|
|
27
|
-
"models": ["gpt-4o-mini", "claude-3-haiku", "llama-3.1-70b", "mixtral-8x7b"],
|
|
28
|
-
"default": "gpt-4o-mini",
|
|
29
|
-
"allow_custom": False
|
|
30
|
-
},
|
|
31
|
-
"zte": {
|
|
32
|
-
"models": ["NebulaBiz", "nebulacoder", "NTele-72B"],
|
|
33
|
-
"default": "NebulaBiz",
|
|
34
|
-
"allow_custom": False
|
|
35
|
-
},
|
|
36
|
-
"openai": {
|
|
37
|
-
"models": ["deepseek-chat"],
|
|
38
|
-
"default": "deepseek-chat",
|
|
39
|
-
"allow_custom": True
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
18
|
|
|
43
19
|
def load_tasks() -> dict:
|
|
44
20
|
"""Load tasks from .jarvis file if it exists."""
|
|
@@ -98,75 +74,20 @@ def main():
|
|
|
98
74
|
"""Main entry point for Jarvis."""
|
|
99
75
|
|
|
100
76
|
load_env_from_file()
|
|
101
|
-
|
|
102
|
-
parser = argparse.ArgumentParser(description="Jarvis AI Assistant")
|
|
103
|
-
|
|
104
|
-
# 添加平台选择参数
|
|
105
|
-
parser.add_argument(
|
|
106
|
-
"--platform",
|
|
107
|
-
choices=list(SUPPORTED_PLATFORMS.keys()),
|
|
108
|
-
default=os.getenv("JARVIS_PLATFORM") or "ddgs",
|
|
109
|
-
help="选择运行平台 (默认: ollama)"
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
# 添加模型选择参数
|
|
113
|
-
parser.add_argument(
|
|
114
|
-
"--model",
|
|
115
|
-
help="选择模型 (默认: 根据平台自动选择)"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
# 添加API基础URL参数
|
|
119
|
-
parser.add_argument(
|
|
120
|
-
"--api-base",
|
|
121
|
-
default=os.getenv("JARVIS_OLLAMA_API_BASE") or "http://localhost:11434",
|
|
122
|
-
help="Ollama API基础URL (仅用于Ollama平台, 默认: http://localhost:11434)"
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
args = parser.parse_args()
|
|
126
|
-
|
|
127
|
-
args.model = args.model or os.getenv("JARVIS_MODEL")
|
|
128
77
|
|
|
129
|
-
# 修改模型验证逻辑
|
|
130
|
-
if args.model:
|
|
131
|
-
if (args.model not in SUPPORTED_PLATFORMS[args.platform]["models"] and
|
|
132
|
-
not SUPPORTED_PLATFORMS[args.platform]["allow_custom"]):
|
|
133
|
-
supported_models = ", ".join(SUPPORTED_PLATFORMS[args.platform]["models"])
|
|
134
|
-
PrettyOutput.print(
|
|
135
|
-
f"错误: 平台 {args.platform} 不支持模型 {args.model}\n"
|
|
136
|
-
f"支持的模型: {supported_models}",
|
|
137
|
-
OutputType.ERROR
|
|
138
|
-
)
|
|
139
|
-
return 1
|
|
140
|
-
else:
|
|
141
|
-
args.model = SUPPORTED_PLATFORMS[args.platform]["default"]
|
|
142
|
-
|
|
143
78
|
try:
|
|
144
|
-
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
platform_name = f"Ollama ({args.model})"
|
|
151
|
-
elif args.platform == "ddgs": # ddgs
|
|
152
|
-
model = DDGSModel(model_name=args.model)
|
|
153
|
-
platform_name = f"DuckDuckGo Search ({args.model})"
|
|
154
|
-
elif args.platform == "zte": # zte
|
|
155
|
-
model = create_zte_llm(model_name=args.model)
|
|
156
|
-
platform_name = f"ZTE ({args.model})"
|
|
157
|
-
elif args.platform == "openai":
|
|
158
|
-
model = OpenAIModel(
|
|
159
|
-
model_name=args.model,
|
|
160
|
-
api_key=os.getenv("OPENAI_API_KEY"),
|
|
161
|
-
api_base=os.getenv("OPENAI_API_BASE")
|
|
162
|
-
)
|
|
163
|
-
platform_name = f"OpenAI ({args.model})"
|
|
79
|
+
kimi_api_key = os.getenv("KIMI_API_KEY")
|
|
80
|
+
if not kimi_api_key:
|
|
81
|
+
PrettyOutput.print("Kimi API key 未设置", OutputType.ERROR)
|
|
82
|
+
return 1
|
|
83
|
+
|
|
84
|
+
model = KimiModel(kimi_api_key)
|
|
164
85
|
|
|
165
86
|
tool_registry = ToolRegistry(model)
|
|
166
87
|
agent = Agent(model, tool_registry)
|
|
167
88
|
|
|
168
89
|
# 欢迎信息
|
|
169
|
-
PrettyOutput.print(f"Jarvis 已初始化 -
|
|
90
|
+
PrettyOutput.print(f"Jarvis 已初始化 - With Kimi", OutputType.SYSTEM)
|
|
170
91
|
|
|
171
92
|
# 加载预定义任务
|
|
172
93
|
tasks = load_tasks()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from typing import Dict, List, Optional, Tuple
|
|
2
|
+
from duckduckgo_search import DDGS
|
|
3
|
+
import ollama
|
|
4
|
+
import yaml
|
|
5
|
+
import openai
|
|
6
|
+
|
|
7
|
+
from ..utils import OutputType, PrettyOutput
|
|
8
|
+
from .base import BaseModel
|
|
9
|
+
from .kimi import KimiModel
|
|
10
|
+
|
|
11
|
+
__all__ = ['BaseModel', 'KimiModel']
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
jarvis/models/base.py
ADDED
jarvis/models/kimi.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from .base import BaseModel
|
|
6
|
+
from ..utils import PrettyOutput, OutputType
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
class KimiModel(BaseModel):
|
|
10
|
+
"""Kimi模型实现"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, api_key: str = None):
|
|
13
|
+
"""
|
|
14
|
+
初始化Kimi模型
|
|
15
|
+
Args:
|
|
16
|
+
api_key: Kimi API密钥,如果不提供则从环境变量获取
|
|
17
|
+
"""
|
|
18
|
+
self.api_key = api_key or os.getenv("KIMI_API_KEY")
|
|
19
|
+
if not self.api_key:
|
|
20
|
+
raise Exception("KIMI_API_KEY is not set")
|
|
21
|
+
self.auth_header = f"Bearer {self.api_key}"
|
|
22
|
+
self.chat_id = ""
|
|
23
|
+
|
|
24
|
+
def _create_chat(self) -> bool:
|
|
25
|
+
"""创建新的对话会话"""
|
|
26
|
+
url = "https://kimi.moonshot.cn/api/chat"
|
|
27
|
+
payload = json.dumps({
|
|
28
|
+
"name": "未命名会话",
|
|
29
|
+
"is_example": False,
|
|
30
|
+
"kimiplus_id": "kimi"
|
|
31
|
+
})
|
|
32
|
+
headers = {
|
|
33
|
+
'Authorization': self.auth_header,
|
|
34
|
+
'Content-Type': 'application/json'
|
|
35
|
+
}
|
|
36
|
+
try:
|
|
37
|
+
response = requests.request("POST", url, headers=headers, data=payload)
|
|
38
|
+
self.chat_id = response.json()["id"]
|
|
39
|
+
return True
|
|
40
|
+
except Exception as e:
|
|
41
|
+
PrettyOutput.print(f"Failed to create chat: {e}", OutputType.ERROR)
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def chat(self, messages: List[Dict]) -> str:
|
|
45
|
+
"""
|
|
46
|
+
实现BaseModel的chat方法
|
|
47
|
+
Args:
|
|
48
|
+
messages: 对话消息列表
|
|
49
|
+
Returns:
|
|
50
|
+
str: 模型的回复内容
|
|
51
|
+
"""
|
|
52
|
+
if not self.chat_id:
|
|
53
|
+
if not self._create_chat():
|
|
54
|
+
raise Exception("Failed to create chat")
|
|
55
|
+
|
|
56
|
+
url = f"https://kimi.moonshot.cn/api/chat/{self.chat_id}/completion/stream"
|
|
57
|
+
payload = json.dumps({
|
|
58
|
+
"messages": [messages[-1]],
|
|
59
|
+
"use_search": True,
|
|
60
|
+
"extend": {
|
|
61
|
+
"sidebar": True
|
|
62
|
+
},
|
|
63
|
+
"kimiplus_id": "kimi",
|
|
64
|
+
"use_research": False,
|
|
65
|
+
"use_math": False,
|
|
66
|
+
"refs": [],
|
|
67
|
+
"refs_file": []
|
|
68
|
+
})
|
|
69
|
+
headers = {
|
|
70
|
+
'Authorization': self.auth_header,
|
|
71
|
+
'Content-Type': 'application/json'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
response = requests.request("POST", url, headers=headers, data=payload, stream=True)
|
|
76
|
+
full_response = ""
|
|
77
|
+
references = []
|
|
78
|
+
|
|
79
|
+
for line in response.iter_lines():
|
|
80
|
+
if not line:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
line = line.decode('utf-8')
|
|
84
|
+
if not line.startswith("data: "):
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
data = json.loads(line[6:]) # 去掉 "data: " 前缀
|
|
89
|
+
|
|
90
|
+
# 处理不同类型的事件
|
|
91
|
+
if data.get("event") == "cmpl":
|
|
92
|
+
if "text" in data:
|
|
93
|
+
text = data["text"]
|
|
94
|
+
PrettyOutput.print_stream(text, OutputType.SYSTEM) # 使用统一的流式输出
|
|
95
|
+
full_response += text
|
|
96
|
+
elif data.get("event") == "ref_docs":
|
|
97
|
+
if "ref_cards" in data:
|
|
98
|
+
for ref in data["ref_cards"]:
|
|
99
|
+
reference = {
|
|
100
|
+
"title": ref.get("title", ""),
|
|
101
|
+
"url": ref.get("url", ""),
|
|
102
|
+
"abstract": ref.get("abstract", "")
|
|
103
|
+
}
|
|
104
|
+
references.append(reference)
|
|
105
|
+
PrettyOutput.print(f"\n参考来源: {reference['title']} - {reference['url']}",
|
|
106
|
+
OutputType.INFO)
|
|
107
|
+
elif data.get("event") == "error":
|
|
108
|
+
error_msg = data.get("error", "Unknown error")
|
|
109
|
+
raise Exception(f"Chat error: {error_msg}")
|
|
110
|
+
elif data.get("event") == "all_done":
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
except json.JSONDecodeError:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# 如果有参考文献,在回答后面添加引用信息
|
|
117
|
+
if references:
|
|
118
|
+
full_response += "\n\n参考来源:\n"
|
|
119
|
+
for i, ref in enumerate(references, 1):
|
|
120
|
+
full_response += f"{i}. {ref['title']} - {ref['url']}\n"
|
|
121
|
+
|
|
122
|
+
PrettyOutput.print_stream_end() # 结束流式输出
|
|
123
|
+
return full_response
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
raise Exception(f"Kimi API调用失败: {str(e)}")
|
|
127
|
+
|
|
128
|
+
def reset(self):
|
|
129
|
+
"""重置模型"""
|
|
130
|
+
self.chat_id = ""
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
kimi = KimiModel()
|
|
135
|
+
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.10
|
|
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=PifeyN0tR6cMlun7jfNuqaJFzH6pHXIbZ8zE-okSuR4,50
|
|
2
|
+
jarvis/agent.py,sha256=t_J8-Hn3S-fLdetrnb7n8ZdP0mdVFqV4YDzYw6_YUq0,10645
|
|
3
|
+
jarvis/main.py,sha256=m641nuFgg-EHGfupyhj0Z_KuNkzZ509U3H7cB7XcsrM,3938
|
|
4
|
+
jarvis/utils.py,sha256=3hLtv-HcBL8Ngw69cowhARuIFrjcQ6yRP3Y1o9CvtsI,5992
|
|
5
|
+
jarvis/__pycache__/__init__.cpython-313.pyc,sha256=SLcRG4m_RNF7YaqL8LpYiOd4FgneCJ_X3E_J9ZLlM4g,208
|
|
6
|
+
jarvis/__pycache__/agent.cpython-313.pyc,sha256=IMD8Ng23CqurRq4P-JtzH89unYxJDDkbKiTfvyt5zh4,11392
|
|
7
|
+
jarvis/__pycache__/main.cpython-313.pyc,sha256=61803cexmA3duJzFajeyLqR_4k3-FOwtUp9QSr8c0Ys,5558
|
|
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=hBEcHaKzB8KASH93k4XNYr6OO6rKLH4z3tj1hwVQdFU,306
|
|
14
|
+
jarvis/models/kimi.py,sha256=UlKvZfsfW5Hspk2gYR9CuRmi6v-P0vtmIf0VRsmhYB4,4925
|
|
15
|
+
jarvis/models/__pycache__/__init__.cpython-313.pyc,sha256=jAwySX4diR7EWM_alK75tiIb_J8bVfs4Bh_U3bdjDLo,534
|
|
16
|
+
jarvis/models/__pycache__/base.cpython-313.pyc,sha256=HzjCIJMvmapnFIMtASkUuSnKjJsL5iIwl5Rane3fq8I,901
|
|
17
|
+
jarvis/models/__pycache__/kimi.cpython-313.pyc,sha256=k35PQUjCtlm5nh3FnJtgi1ZejgO9xMvRhGMwkthYfJ8,5857
|
|
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.10.dist-info/METADATA,sha256=qlQS-5cO8H9Unc3698Ny3Kzptexy3IMQNwvvgKORPRk,3585
|
|
36
|
+
jarvis_ai_assistant-0.1.10.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
37
|
+
jarvis_ai_assistant-0.1.10.dist-info/entry_points.txt,sha256=iKu7OMfew9dtfGhW71gIMTg4wvafuPqKb4wyQOnMAGU,44
|
|
38
|
+
jarvis_ai_assistant-0.1.10.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
|
|
39
|
+
jarvis_ai_assistant-0.1.10.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
|
-
}
|
jarvis/tools/sub_agent.py
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
2
|
-
from ..agent import Agent
|
|
3
|
-
from ..models import BaseModel
|
|
4
|
-
from ..utils import PrettyOutput, OutputType
|
|
5
|
-
from .base import ToolRegistry
|
|
6
|
-
|
|
7
|
-
class SubAgentTool:
|
|
8
|
-
name = "create_sub_agent"
|
|
9
|
-
description = """创建一个子代理来处理独立任务。(重要:子代理启动时没有任何上下文!必须提供完整的步骤和上下文。)"""
|
|
10
|
-
|
|
11
|
-
parameters = {
|
|
12
|
-
"type": "object",
|
|
13
|
-
"properties": {
|
|
14
|
-
"name": {
|
|
15
|
-
"type": "string",
|
|
16
|
-
"description": "子代理名称(例如:'文件分析器')"
|
|
17
|
-
},
|
|
18
|
-
"task": {
|
|
19
|
-
"type": "string",
|
|
20
|
-
"description": "需要明确步骤和目标的任务"
|
|
21
|
-
},
|
|
22
|
-
"context": {
|
|
23
|
-
"type": "string",
|
|
24
|
-
"description": "必填:背景信息、执行步骤和预期结果",
|
|
25
|
-
"default": ""
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"required": ["name", "task", "context"]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
def __init__(self, model: BaseModel):
|
|
32
|
-
"""Initialize with the same model as parent agent"""
|
|
33
|
-
self.model = model
|
|
34
|
-
|
|
35
|
-
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
36
|
-
"""Create and run a sub-agent for the specified task"""
|
|
37
|
-
try:
|
|
38
|
-
name = args["name"]
|
|
39
|
-
task = args["task"]
|
|
40
|
-
context = args.get("context")
|
|
41
|
-
|
|
42
|
-
if not context:
|
|
43
|
-
return {
|
|
44
|
-
"success": False,
|
|
45
|
-
"error": "必须提供上下文信息,包括完整的背景和执行步骤。"
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
PrettyOutput.print(f"正在创建子代理 '{name}'...", OutputType.INFO)
|
|
49
|
-
|
|
50
|
-
# Create a new tool registry for the sub-agent
|
|
51
|
-
tool_registry = ToolRegistry(self.model)
|
|
52
|
-
|
|
53
|
-
# Create the sub-agent with the specified name
|
|
54
|
-
sub_agent = Agent(self.model, tool_registry, name=name, is_sub_agent=True)
|
|
55
|
-
|
|
56
|
-
# Prepare the task with context
|
|
57
|
-
full_task = f"""背景和步骤:
|
|
58
|
-
{context}
|
|
59
|
-
|
|
60
|
-
主要任务:
|
|
61
|
-
{task}
|
|
62
|
-
|
|
63
|
-
要求:
|
|
64
|
-
1. 严格按照提供的步骤执行
|
|
65
|
-
2. 每完成一个步骤都要报告进度
|
|
66
|
-
3. 突出显示任何问题或不明确的点
|
|
67
|
-
4. 提供符合预期输出的详细结果"""
|
|
68
|
-
|
|
69
|
-
PrettyOutput.print(f"子代理 '{name}' 正在执行任务...", OutputType.INFO)
|
|
70
|
-
|
|
71
|
-
# Execute the task and get the summary
|
|
72
|
-
summary = sub_agent.run(full_task)
|
|
73
|
-
return {
|
|
74
|
-
"success": True,
|
|
75
|
-
"stdout": f"子代理 '{name}' 已完成。\n\n结果:\n{summary}",
|
|
76
|
-
"stderr": ""
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
except Exception as e:
|
|
80
|
-
return {
|
|
81
|
-
"success": False,
|
|
82
|
-
"error": f"子代理执行失败:{str(e)}"
|
|
83
|
-
}
|
jarvis/tools/webpage.py
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Any
|
|
2
|
-
import requests
|
|
3
|
-
from bs4 import BeautifulSoup
|
|
4
|
-
from ..utils import PrettyOutput, OutputType
|
|
5
|
-
|
|
6
|
-
class WebpageTool:
|
|
7
|
-
name = "read_webpage"
|
|
8
|
-
description = "读取网页内容,提取标题和正文文本"
|
|
9
|
-
parameters = {
|
|
10
|
-
"type": "object",
|
|
11
|
-
"properties": {
|
|
12
|
-
"url": {
|
|
13
|
-
"type": "string",
|
|
14
|
-
"description": "需要读取的网页URL"
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"required": ["url"]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
def execute(self, args: Dict) -> Dict[str, Any]:
|
|
21
|
-
"""读取网页内容"""
|
|
22
|
-
try:
|
|
23
|
-
url = args["url"]
|
|
24
|
-
|
|
25
|
-
# 设置请求头
|
|
26
|
-
headers = {
|
|
27
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
# 发送请求
|
|
31
|
-
PrettyOutput.print(f"正在读取网页: {url}", OutputType.INFO)
|
|
32
|
-
response = requests.get(url, headers=headers, timeout=10)
|
|
33
|
-
response.raise_for_status()
|
|
34
|
-
|
|
35
|
-
# 使用正确的编码
|
|
36
|
-
response.encoding = response.apparent_encoding
|
|
37
|
-
|
|
38
|
-
# 解析HTML
|
|
39
|
-
soup = BeautifulSoup(response.text, 'html.parser')
|
|
40
|
-
|
|
41
|
-
# 移除script和style标签
|
|
42
|
-
for script in soup(["script", "style"]):
|
|
43
|
-
script.decompose()
|
|
44
|
-
|
|
45
|
-
# 提取标题
|
|
46
|
-
title = soup.title.string if soup.title else ""
|
|
47
|
-
title = title.strip() if title else "无标题"
|
|
48
|
-
|
|
49
|
-
# 提取正文
|
|
50
|
-
text = soup.get_text(separator='\n', strip=True)
|
|
51
|
-
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
|
52
|
-
|
|
53
|
-
# 构建输出
|
|
54
|
-
output = [
|
|
55
|
-
f"标题: {title}",
|
|
56
|
-
"",
|
|
57
|
-
"正文内容:",
|
|
58
|
-
"\n".join(lines)
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
"success": True,
|
|
63
|
-
"stdout": "\n".join(output),
|
|
64
|
-
"stderr": ""
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
except requests.RequestException as e:
|
|
68
|
-
return {
|
|
69
|
-
"success": False,
|
|
70
|
-
"error": f"网页请求失败: {str(e)}"
|
|
71
|
-
}
|
|
72
|
-
except Exception as e:
|
|
73
|
-
return {
|
|
74
|
-
"success": False,
|
|
75
|
-
"error": f"解析网页失败: {str(e)}"
|
|
76
|
-
}
|
jarvis/zte_llm.py
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Dict, Any, List, Optional
|
|
6
|
-
|
|
7
|
-
from jarvis.utils import OutputType, PrettyOutput
|
|
8
|
-
from .models import BaseModel
|
|
9
|
-
|
|
10
|
-
class ZteLLM(BaseModel):
|
|
11
|
-
"""ZTE Nebula LLM implementation"""
|
|
12
|
-
|
|
13
|
-
def __init__(self,
|
|
14
|
-
app_id: str,
|
|
15
|
-
app_key: str,
|
|
16
|
-
emp_no: str,
|
|
17
|
-
auth_value: str,
|
|
18
|
-
model: str = "nebulacoder",
|
|
19
|
-
):
|
|
20
|
-
"""Initialize ZTE LLM with required credentials"""
|
|
21
|
-
self.app_id = str(app_id)
|
|
22
|
-
self.app_key = str(app_key)
|
|
23
|
-
self.emp_no = str(emp_no)
|
|
24
|
-
self.auth_value = str(auth_value)
|
|
25
|
-
self.model = model
|
|
26
|
-
self.base_url = "https://studio.zte.com.cn/zte-studio-ai-platform/openapi/v1"
|
|
27
|
-
|
|
28
|
-
def _make_request(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
29
|
-
"""Make request to ZTE API"""
|
|
30
|
-
headers = {
|
|
31
|
-
'Content-Type': 'application/json',
|
|
32
|
-
'Authorization': f'Bearer {self.app_id}-{self.app_key}',
|
|
33
|
-
'X-Emp-No': self.emp_no,
|
|
34
|
-
'X-Auth-Value': self.auth_value
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
response = requests.post(
|
|
38
|
-
f"{self.base_url}/{endpoint}",
|
|
39
|
-
headers=headers,
|
|
40
|
-
json=data,
|
|
41
|
-
stream=True # 启用流式传输
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
response.raise_for_status()
|
|
45
|
-
|
|
46
|
-
full_content = []
|
|
47
|
-
for line in response.iter_lines():
|
|
48
|
-
if line:
|
|
49
|
-
# 解析 SSE 数据
|
|
50
|
-
line = line.decode('utf-8')
|
|
51
|
-
if line.startswith('data: '):
|
|
52
|
-
try:
|
|
53
|
-
data = json.loads(line[6:]) # 跳过 "data: " 前缀
|
|
54
|
-
if "result" in data:
|
|
55
|
-
result = data["result"]
|
|
56
|
-
if result: # 只处理非空结果
|
|
57
|
-
full_content.append(result)
|
|
58
|
-
PrettyOutput.print_stream(result, OutputType.SYSTEM)
|
|
59
|
-
if data.get("finishReason") == "stop":
|
|
60
|
-
break
|
|
61
|
-
except json.JSONDecodeError:
|
|
62
|
-
continue
|
|
63
|
-
PrettyOutput.print_stream_end()
|
|
64
|
-
|
|
65
|
-
return "".join(full_content)
|
|
66
|
-
|
|
67
|
-
def chat(self, messages: List[Dict[str, Any]]) -> str:
|
|
68
|
-
"""Chat with ZTE LLM"""
|
|
69
|
-
# Convert messages to prompt
|
|
70
|
-
prompt = self._convert_messages_to_prompt(messages)
|
|
71
|
-
|
|
72
|
-
# Prepare data for API call
|
|
73
|
-
data = {
|
|
74
|
-
"chatUuid": "",
|
|
75
|
-
"chatName": "",
|
|
76
|
-
"stream": True, # 启用流式响应
|
|
77
|
-
"keep": False,
|
|
78
|
-
"text": prompt,
|
|
79
|
-
"model": self.model
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
return self._make_request("chat", data)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
except Exception as e:
|
|
87
|
-
raise Exception(f"ZTE LLM chat failed: {str(e)}")
|
|
88
|
-
|
|
89
|
-
def _convert_messages_to_prompt(self, messages: List[Dict[str, Any]]) -> str:
|
|
90
|
-
"""Convert message list to a single prompt string"""
|
|
91
|
-
prompt_parts = []
|
|
92
|
-
|
|
93
|
-
for message in messages:
|
|
94
|
-
role = message["role"]
|
|
95
|
-
content = message.get("content", "")
|
|
96
|
-
|
|
97
|
-
if role == "system":
|
|
98
|
-
prompt_parts.append(f"System: {content}")
|
|
99
|
-
elif role == "user":
|
|
100
|
-
prompt_parts.append(f"User: {content}")
|
|
101
|
-
elif role == "assistant":
|
|
102
|
-
prompt_parts.append(f"Assistant: {content}")
|
|
103
|
-
elif role == "tool":
|
|
104
|
-
prompt_parts.append(f"Tool Result: {content}")
|
|
105
|
-
|
|
106
|
-
return "\n\n".join(prompt_parts)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def create_zte_llm(model_name: str = "NebulaBiz") -> ZteLLM:
|
|
110
|
-
"""Create ZTE LLM instance with provided parameters"""
|
|
111
|
-
# Load environment variables from file
|
|
112
|
-
|
|
113
|
-
# Get credentials from parameters, env file, or system environment variables
|
|
114
|
-
app_id = os.getenv('ZTE_APP_ID')
|
|
115
|
-
app_key = os.getenv('ZTE_APP_KEY')
|
|
116
|
-
emp_no = os.getenv('ZTE_EMP_NO')
|
|
117
|
-
auth_value = os.getenv('ZTE_AUTH_VALUE')
|
|
118
|
-
|
|
119
|
-
# Validate required credentials
|
|
120
|
-
if not all([app_id, app_key, emp_no, auth_value]):
|
|
121
|
-
raise ValueError(
|
|
122
|
-
"Missing required credentials. Please provide through either:\n"
|
|
123
|
-
"1. Function parameters\n"
|
|
124
|
-
"2. ~/.jarvis_env file\n"
|
|
125
|
-
"3. System environment variables\n\n"
|
|
126
|
-
"Required variables: ZTE_APP_ID, ZTE_APP_KEY, ZTE_EMP_NO, ZTE_AUTH_VALUE"
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
return ZteLLM(
|
|
130
|
-
app_id=app_id,
|
|
131
|
-
app_key=app_key,
|
|
132
|
-
emp_no=emp_no,
|
|
133
|
-
auth_value=auth_value,
|
|
134
|
-
model=model_name
|
|
135
|
-
)
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
jarvis/__init__.py,sha256=-pqI_MJPVrCN3BBcW2YIg8KbW5B7vfbpb-ESMyNKpVg,49
|
|
2
|
-
jarvis/agent.py,sha256=ePgdOCWdkw35KITvZOqcrJOGl-F4wzvrltHTw7N4p88,11014
|
|
3
|
-
jarvis/main.py,sha256=4IqebMKGoBGJMSNFE6E6GQ_Ay-uuOT-BLsQPnQVW47k,6705
|
|
4
|
-
jarvis/models.py,sha256=G9QkUqoyOyUx6eSqwbn3hCSwHguN0bUm7Nbd6Ql0IQs,3771
|
|
5
|
-
jarvis/utils.py,sha256=3hLtv-HcBL8Ngw69cowhARuIFrjcQ6yRP3Y1o9CvtsI,5992
|
|
6
|
-
jarvis/zte_llm.py,sha256=Us5D6zMdZT6SoSnUgKWQbxthGadXkulLs_oSmW5MPDo,4666
|
|
7
|
-
jarvis/__pycache__/__init__.cpython-313.pyc,sha256=GF-b9JNTGgyKROLNqZojLYFIrgPShHKlZADEFHP_YfE,208
|
|
8
|
-
jarvis/__pycache__/agent.cpython-313.pyc,sha256=itbFLk6qJ9FTjbiyj6DsQhu5NbdxVaP4g-SygRguvSc,11625
|
|
9
|
-
jarvis/__pycache__/main.cpython-313.pyc,sha256=6hWRW-RG_bZrqwhSQXyhKaJBXexgLqKa-_SZzAk6_eo,8479
|
|
10
|
-
jarvis/__pycache__/models.cpython-313.pyc,sha256=uWuRIjGrY4YDB3dGW5PGDLWaS03et8g11O725TjY_eU,5960
|
|
11
|
-
jarvis/__pycache__/tools.cpython-313.pyc,sha256=lAD4LrnnWzNZQmHXGfZ_2l7oskOpr2_2OC-gdFhxQY8,33933
|
|
12
|
-
jarvis/__pycache__/utils.cpython-313.pyc,sha256=k4jyAlx4tlW0MKLMLzV7OOH9zsKrK0H955kY6M2SuFU,8772
|
|
13
|
-
jarvis/__pycache__/zte_llm.cpython-313.pyc,sha256=R_HT_Gc6OQelxRLRQ-yB8Tf0sYHcb18dh9E4RF6ivbE,5662
|
|
14
|
-
jarvis/tools/__init__.py,sha256=tH6YaApKpqs1YSjEllRzOZZUd-OTFKSZ5oA0zs7HdSE,297
|
|
15
|
-
jarvis/tools/base.py,sha256=zK-JteBwj9d2BD_7BSAp-zD7wgJbVBppAWyfFxaPObI,4114
|
|
16
|
-
jarvis/tools/bing_search.py,sha256=SvYeXM83eP9qREDcWguh0_r0m0npbcwXIyzEwwfreKU,1444
|
|
17
|
-
jarvis/tools/file_ops.py,sha256=zTksx45NZm3iz9itN5iQGZ8DoxnSeTHdrnF08_ix7PU,3770
|
|
18
|
-
jarvis/tools/search.py,sha256=P4vEbwcXTP6gpc4nwLEuERX0vVszGaer9xvaLfFI0Uc,4836
|
|
19
|
-
jarvis/tools/shell.py,sha256=7q52lA3slf0TdjBjP1bkwugoO5pB0eqh6cYjAzAXNtI,2547
|
|
20
|
-
jarvis/tools/sub_agent.py,sha256=QJYWdil1goZfo95CQkuhkjZBl-Rx6UvyeFMiGnDyjOA,2832
|
|
21
|
-
jarvis/tools/webpage.py,sha256=DBnh9gye6oL2nVjzU2SU4Jupsck8x9g9On-rbjV6d8Y,2386
|
|
22
|
-
jarvis/tools/__pycache__/__init__.cpython-313.pyc,sha256=1d1eUI0FlaydGB8TFBln6s7cBKd6gcPdER2_DIfX2Aw,443
|
|
23
|
-
jarvis/tools/__pycache__/base.cpython-313.pyc,sha256=2OJWYNsGJjKJs6Vdj9NcN_Snjd2bA_OLlB-zr2j4wfs,6462
|
|
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=qzNLnQxqFZoAiYOPH4GrCGxaQVs-QWfTLspW5a1Xa6c,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.9.dist-info/METADATA,sha256=d5LRsaxuZB6V0m8klCq07sxghlARFo7K8VM36TyCnvk,3753
|
|
36
|
-
jarvis_ai_assistant-0.1.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
37
|
-
jarvis_ai_assistant-0.1.9.dist-info/entry_points.txt,sha256=iKu7OMfew9dtfGhW71gIMTg4wvafuPqKb4wyQOnMAGU,44
|
|
38
|
-
jarvis_ai_assistant-0.1.9.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
|
|
39
|
-
jarvis_ai_assistant-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
{jarvis_ai_assistant-0.1.9.dist-info → jarvis_ai_assistant-0.1.10.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|