jarvis-ai-assistant 0.1.148__py3-none-any.whl → 0.1.150__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/jarvis_agent/jarvis.py +7 -5
- jarvis/jarvis_agent/main.py +0 -1
- jarvis/jarvis_agent/patch.py +42 -7
- jarvis/jarvis_code_analysis/code_review.py +0 -1
- jarvis/jarvis_git_utils/git_commiter.py +0 -1
- jarvis/jarvis_lsp/base.py +1 -1
- jarvis/jarvis_lsp/cpp.py +1 -1
- jarvis/jarvis_lsp/go.py +1 -1
- jarvis/jarvis_lsp/python.py +1 -1
- jarvis/jarvis_lsp/registry.py +2 -1
- jarvis/jarvis_lsp/rust.py +1 -1
- jarvis/jarvis_mcp/__init__.py +36 -0
- jarvis/jarvis_mcp/local_mcp_client.py +241 -0
- jarvis/jarvis_mcp/remote_mcp_client.py +230 -0
- jarvis/jarvis_platform/base.py +1 -1
- jarvis/jarvis_platform/kimi.py +3 -3
- jarvis/jarvis_platform/registry.py +2 -4
- jarvis/jarvis_platform/yuanbao.py +4 -4
- jarvis/jarvis_tools/code_plan.py +0 -1
- jarvis/jarvis_tools/file_analyzer.py +1 -1
- jarvis/jarvis_tools/file_operation.py +7 -26
- jarvis/jarvis_tools/methodology.py +2 -1
- jarvis/jarvis_tools/read_code.py +0 -1
- jarvis/jarvis_tools/registry.py +108 -3
- jarvis/jarvis_tools/search_web.py +0 -1
- jarvis/jarvis_utils/config.py +14 -8
- jarvis/jarvis_utils/embedding.py +4 -8
- jarvis/jarvis_utils/file_processors.py +0 -262
- jarvis/jarvis_utils/git_utils.py +19 -8
- jarvis/jarvis_utils/input.py +7 -1
- jarvis/jarvis_utils/methodology.py +2 -2
- jarvis/jarvis_utils/output.py +0 -1
- jarvis/jarvis_utils/utils.py +23 -221
- {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/METADATA +31 -38
- {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/RECORD +40 -38
- jarvis/jarvis_platform_manager/openai_test.py +0 -138
- {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.148.dist-info → jarvis_ai_assistant-0.1.150.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
import requests
|
|
3
|
+
import sseclient
|
|
4
|
+
from urllib.parse import urljoin
|
|
5
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
6
|
+
from . import McpClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RemoteMcpClient(McpClient):
|
|
10
|
+
"""远程MCP客户端实现
|
|
11
|
+
|
|
12
|
+
参数:
|
|
13
|
+
config: 配置字典,包含以下字段:
|
|
14
|
+
- base_url: str - MCP服务器的基础URL
|
|
15
|
+
- auth_token: str - 认证令牌(可选)
|
|
16
|
+
- headers: Dict[str, str] - 额外的HTTP头(可选)
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, config: Dict[str, Any]):
|
|
19
|
+
self.config = config
|
|
20
|
+
self.base_url = config.get('base_url', '')
|
|
21
|
+
if not self.base_url:
|
|
22
|
+
raise ValueError('No base_url specified in config')
|
|
23
|
+
|
|
24
|
+
# 设置HTTP客户端
|
|
25
|
+
self.session = requests.Session()
|
|
26
|
+
self.session.headers.update({
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'Accept': 'application/json',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
# 添加认证令牌(如果提供)
|
|
32
|
+
auth_token = config.get('auth_token')
|
|
33
|
+
if auth_token:
|
|
34
|
+
self.session.headers['Authorization'] = f'Bearer {auth_token}'
|
|
35
|
+
|
|
36
|
+
# 添加额外的HTTP头
|
|
37
|
+
extra_headers = config.get('headers', {})
|
|
38
|
+
self.session.headers.update(extra_headers)
|
|
39
|
+
|
|
40
|
+
# 初始化SSE连接
|
|
41
|
+
self.sse_client = None
|
|
42
|
+
self._initialize()
|
|
43
|
+
|
|
44
|
+
def _initialize(self) -> None:
|
|
45
|
+
"""初始化MCP连接"""
|
|
46
|
+
try:
|
|
47
|
+
# 发送初始化请求
|
|
48
|
+
response = self._send_request('initialize', {
|
|
49
|
+
'processId': None, # 远程客户端不需要进程ID
|
|
50
|
+
'clientInfo': {
|
|
51
|
+
'name': 'jarvis',
|
|
52
|
+
'version': '1.0.0'
|
|
53
|
+
},
|
|
54
|
+
'capabilities': {},
|
|
55
|
+
'protocolVersion': "2025-03-26"
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
# 验证服务器响应
|
|
59
|
+
if 'result' not in response:
|
|
60
|
+
raise RuntimeError(f"初始化失败: {response.get('error', 'Unknown error')}")
|
|
61
|
+
|
|
62
|
+
result = response['result']
|
|
63
|
+
|
|
64
|
+
# 发送initialized通知 - 使用正确的方法名格式
|
|
65
|
+
self._send_notification('notifications/initialized', {})
|
|
66
|
+
|
|
67
|
+
# 建立SSE连接
|
|
68
|
+
sse_url = urljoin(self.base_url, 'events')
|
|
69
|
+
response = self.session.get(sse_url, stream=True)
|
|
70
|
+
self.sse_client = sseclient.SSEClient(response)
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
PrettyOutput.print(f"MCP初始化失败: {str(e)}", OutputType.ERROR)
|
|
74
|
+
raise
|
|
75
|
+
|
|
76
|
+
def _send_request(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
77
|
+
"""发送请求到MCP服务器
|
|
78
|
+
|
|
79
|
+
参数:
|
|
80
|
+
method: 请求方法
|
|
81
|
+
params: 请求参数
|
|
82
|
+
|
|
83
|
+
返回:
|
|
84
|
+
Dict[str, Any]: 响应结果
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
# 构建请求
|
|
88
|
+
request = {
|
|
89
|
+
'jsonrpc': '2.0',
|
|
90
|
+
'method': method,
|
|
91
|
+
'params': params,
|
|
92
|
+
'id': 1
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# 发送请求
|
|
96
|
+
response = self.session.post(
|
|
97
|
+
urljoin(self.base_url, 'rpc'),
|
|
98
|
+
json=request
|
|
99
|
+
)
|
|
100
|
+
response.raise_for_status()
|
|
101
|
+
return response.json()
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
PrettyOutput.print(f"发送请求失败: {str(e)}", OutputType.ERROR)
|
|
105
|
+
raise
|
|
106
|
+
|
|
107
|
+
def _send_notification(self, method: str, params: Dict[str, Any]) -> None:
|
|
108
|
+
"""发送通知到MCP服务器(不需要响应)
|
|
109
|
+
|
|
110
|
+
参数:
|
|
111
|
+
method: 通知方法
|
|
112
|
+
params: 通知参数
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
# 构建通知
|
|
116
|
+
notification = {
|
|
117
|
+
'jsonrpc': '2.0',
|
|
118
|
+
'method': method,
|
|
119
|
+
'params': params
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# 发送通知
|
|
123
|
+
response = self.session.post(
|
|
124
|
+
urljoin(self.base_url, 'rpc'),
|
|
125
|
+
json=notification
|
|
126
|
+
)
|
|
127
|
+
response.raise_for_status()
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
PrettyOutput.print(f"发送通知失败: {str(e)}", OutputType.ERROR)
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
def get_tool_list(self) -> List[Dict[str, Any]]:
|
|
134
|
+
"""获取工具列表
|
|
135
|
+
|
|
136
|
+
返回:
|
|
137
|
+
List[Dict[str, Any]]: 工具列表,每个工具包含以下字段:
|
|
138
|
+
- name: str - 工具名称
|
|
139
|
+
- description: str - 工具描述
|
|
140
|
+
- parameters: Dict - 工具参数
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
response = self._send_request('tools/list', {})
|
|
144
|
+
if 'result' in response and 'tools' in response['result']:
|
|
145
|
+
# 注意这里: 响应结构是 response['result']['tools']
|
|
146
|
+
tools = response['result']['tools']
|
|
147
|
+
# 将MCP协议字段转换为内部格式
|
|
148
|
+
formatted_tools = []
|
|
149
|
+
for tool in tools:
|
|
150
|
+
# 从inputSchema中提取参数定义
|
|
151
|
+
input_schema = tool.get('inputSchema', {})
|
|
152
|
+
parameters = {}
|
|
153
|
+
if 'properties' in input_schema:
|
|
154
|
+
parameters = input_schema['properties']
|
|
155
|
+
|
|
156
|
+
formatted_tools.append({
|
|
157
|
+
'name': tool.get('name', ''),
|
|
158
|
+
'description': tool.get('description', ''),
|
|
159
|
+
'parameters': parameters
|
|
160
|
+
})
|
|
161
|
+
return formatted_tools
|
|
162
|
+
else:
|
|
163
|
+
error_msg = "获取工具列表失败"
|
|
164
|
+
if 'error' in response:
|
|
165
|
+
error_msg += f": {response['error']}"
|
|
166
|
+
elif 'result' in response:
|
|
167
|
+
error_msg += f": 响应格式不正确 - {response['result']}"
|
|
168
|
+
else:
|
|
169
|
+
error_msg += ": 未知错误"
|
|
170
|
+
|
|
171
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
172
|
+
return []
|
|
173
|
+
except Exception as e:
|
|
174
|
+
PrettyOutput.print(f"获取工具列表失败: {str(e)}", OutputType.ERROR)
|
|
175
|
+
return []
|
|
176
|
+
|
|
177
|
+
def execute(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
178
|
+
"""执行工具
|
|
179
|
+
|
|
180
|
+
参数:
|
|
181
|
+
tool_name: 工具名称
|
|
182
|
+
arguments: 参数字典,包含工具执行所需的参数
|
|
183
|
+
|
|
184
|
+
返回:
|
|
185
|
+
Dict[str, Any]: 执行结果,包含以下字段:
|
|
186
|
+
- success: bool - 是否执行成功
|
|
187
|
+
- stdout: str - 标准输出
|
|
188
|
+
- stderr: str - 标准错误
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
response = self._send_request('tools/call', {
|
|
192
|
+
'name': tool_name,
|
|
193
|
+
'arguments': arguments
|
|
194
|
+
})
|
|
195
|
+
if 'result' in response:
|
|
196
|
+
result = response['result']
|
|
197
|
+
# 从content中提取输出信息
|
|
198
|
+
stdout = ''
|
|
199
|
+
stderr = ''
|
|
200
|
+
for content in result.get('content', []):
|
|
201
|
+
if content.get('type') == 'text':
|
|
202
|
+
stdout += content.get('text', '')
|
|
203
|
+
elif content.get('type') == 'error':
|
|
204
|
+
stderr += content.get('text', '')
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
'success': True,
|
|
208
|
+
'stdout': stdout,
|
|
209
|
+
'stderr': stderr
|
|
210
|
+
}
|
|
211
|
+
else:
|
|
212
|
+
return {
|
|
213
|
+
'success': False,
|
|
214
|
+
'stdout': '',
|
|
215
|
+
'stderr': response.get('error', 'Unknown error')
|
|
216
|
+
}
|
|
217
|
+
except Exception as e:
|
|
218
|
+
PrettyOutput.print(f"执行工具失败: {str(e)}", OutputType.ERROR)
|
|
219
|
+
return {
|
|
220
|
+
'success': False,
|
|
221
|
+
'stdout': '',
|
|
222
|
+
'stderr': str(e)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
def __del__(self):
|
|
226
|
+
"""清理资源"""
|
|
227
|
+
if self.sse_client and hasattr(self.sse_client, 'resp'):
|
|
228
|
+
self.sse_client.resp.close()
|
|
229
|
+
if self.session:
|
|
230
|
+
self.session.close()
|
jarvis/jarvis_platform/base.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
import re
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import List, Tuple
|
|
4
4
|
from jarvis.jarvis_utils.globals import clear_read_file_record
|
|
5
5
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
6
6
|
from jarvis.jarvis_utils.utils import ct, ot, get_context_token_count, while_success, while_true
|
jarvis/jarvis_platform/kimi.py
CHANGED
|
@@ -7,7 +7,7 @@ import time
|
|
|
7
7
|
from jarvis.jarvis_platform.base import BasePlatform
|
|
8
8
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
9
|
from jarvis.jarvis_utils.utils import while_success
|
|
10
|
-
from
|
|
10
|
+
from jarvis.jarvis_utils.config import get_data_dir
|
|
11
11
|
|
|
12
12
|
class KimiModel(BasePlatform):
|
|
13
13
|
"""Kimi model implementation"""
|
|
@@ -40,8 +40,8 @@ class KimiModel(BasePlatform):
|
|
|
40
40
|
" • 在请求中找到 Authorization 头\n"
|
|
41
41
|
" • 复制 token 值(去掉 'Bearer ' 前缀)\n"
|
|
42
42
|
"2. 设置环境变量:\n"
|
|
43
|
-
" • 方法 1:
|
|
44
|
-
" echo 'KIMI_API_KEY=your_key_here' >
|
|
43
|
+
" • 方法 1: 创建或编辑配置文件:\n"
|
|
44
|
+
f" echo 'KIMI_API_KEY=your_key_here' > {get_data_dir()}/env\n"
|
|
45
45
|
" • 方法 2: 直接设置环境变量:\n"
|
|
46
46
|
" export KIMI_API_KEY=your_key_here\n"
|
|
47
47
|
"设置后,重新运行 Jarvis。"
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
import sys
|
|
5
5
|
from typing import Dict, Type, Optional, List
|
|
6
6
|
from jarvis.jarvis_platform.base import BasePlatform
|
|
7
|
-
from jarvis.jarvis_utils.config import get_normal_model_name, get_normal_platform_name, get_thinking_model_name, get_thinking_platform_name
|
|
7
|
+
from jarvis.jarvis_utils.config import get_normal_model_name, get_normal_platform_name, get_thinking_model_name, get_thinking_platform_name, get_data_dir
|
|
8
8
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
9
|
|
|
10
10
|
REQUIRED_METHODS = [
|
|
@@ -26,15 +26,13 @@ class PlatformRegistry:
|
|
|
26
26
|
|
|
27
27
|
@staticmethod
|
|
28
28
|
def get_platform_dir() -> str:
|
|
29
|
-
user_platform_dir = os.path.
|
|
29
|
+
user_platform_dir = os.path.join(get_data_dir(), "models")
|
|
30
30
|
if not os.path.exists(user_platform_dir):
|
|
31
31
|
try:
|
|
32
32
|
os.makedirs(user_platform_dir)
|
|
33
33
|
# 创建 __init__.py 使其成为 Python 包
|
|
34
34
|
with open(os.path.join(user_platform_dir, "__init__.py"), "w", errors="ignore") as f:
|
|
35
35
|
pass
|
|
36
|
-
|
|
37
|
-
pass
|
|
38
36
|
except Exception as e:
|
|
39
37
|
PrettyOutput.print(f"创建平台目录失败: {str(e)}", OutputType.ERROR)
|
|
40
38
|
return ""
|
|
@@ -6,7 +6,6 @@ import hmac
|
|
|
6
6
|
import hashlib
|
|
7
7
|
import time
|
|
8
8
|
import urllib.parse
|
|
9
|
-
from pathlib import Path
|
|
10
9
|
from PIL import Image
|
|
11
10
|
from yaspin import yaspin
|
|
12
11
|
from yaspin.spinners import Spinners
|
|
@@ -14,6 +13,7 @@ from yaspin.api import Yaspin
|
|
|
14
13
|
from jarvis.jarvis_platform.base import BasePlatform
|
|
15
14
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
16
15
|
from jarvis.jarvis_utils.utils import while_success
|
|
16
|
+
from jarvis.jarvis_utils.config import get_data_dir
|
|
17
17
|
|
|
18
18
|
class YuanbaoPlatform(BasePlatform):
|
|
19
19
|
"""Hunyuan模型实现"""
|
|
@@ -46,9 +46,9 @@ class YuanbaoPlatform(BasePlatform):
|
|
|
46
46
|
" • 发送任意消息\n"
|
|
47
47
|
" • 在请求中找到 X-Uskey 和 T-UserID 头部值\n"
|
|
48
48
|
"2. 设置环境变量:\n"
|
|
49
|
-
" • 方法 1:
|
|
50
|
-
" echo 'YUANBAO_COOKIES=your_cookies_here' >>
|
|
51
|
-
" echo 'YUANBAO_AGENT_ID=your_agent_id_here' >>
|
|
49
|
+
" • 方法 1: 创建或编辑配置文件:\n"
|
|
50
|
+
f" echo 'YUANBAO_COOKIES=your_cookies_here' >> {get_data_dir()}/env\n"
|
|
51
|
+
f" echo 'YUANBAO_AGENT_ID=your_agent_id_here' >> {get_data_dir()}/env\n"
|
|
52
52
|
" • 方法 2: 直接设置环境变量:\n"
|
|
53
53
|
" export YUANBAO_COOKIES=your_cookies_here\n"
|
|
54
54
|
" export YUANBAO_AGENT_ID=your_agent_id_here\n"
|
jarvis/jarvis_tools/code_plan.py
CHANGED
|
@@ -15,7 +15,6 @@ from jarvis.jarvis_agent import Agent
|
|
|
15
15
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
16
16
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
17
17
|
from jarvis.jarvis_utils.git_utils import find_git_root
|
|
18
|
-
from jarvis.jarvis_utils.utils import init_env
|
|
19
18
|
|
|
20
19
|
class CodePlanTool:
|
|
21
20
|
"""用于代码修改规划和需求分析的工具
|
|
@@ -8,15 +8,14 @@ from jarvis.jarvis_utils.globals import add_read_file_record
|
|
|
8
8
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
9
9
|
# 导入文件处理器
|
|
10
10
|
from jarvis.jarvis_utils.file_processors import (
|
|
11
|
-
TextFileProcessor
|
|
12
|
-
PPTProcessor, ExcelProcessor
|
|
11
|
+
TextFileProcessor
|
|
13
12
|
)
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class FileOperationTool:
|
|
18
17
|
name = "file_operation"
|
|
19
|
-
description = "
|
|
18
|
+
description = "文件批量操作工具,可批量读写多个文件,仅支持文本文件,适用于需要同时处理多个文件的场景(读取配置文件、保存生成内容等)"
|
|
20
19
|
parameters = {
|
|
21
20
|
"type": "object",
|
|
22
21
|
"properties": {
|
|
@@ -44,10 +43,6 @@ class FileOperationTool:
|
|
|
44
43
|
def _get_file_processor(self, file_path: str):
|
|
45
44
|
"""获取适合处理指定文件的处理器"""
|
|
46
45
|
processors = [
|
|
47
|
-
PDFProcessor, # PDF文件处理器
|
|
48
|
-
DocxProcessor, # Word文档处理器
|
|
49
|
-
PPTProcessor, # PowerPoint演示文稿处理器
|
|
50
|
-
ExcelProcessor, # Excel表格处理器
|
|
51
46
|
TextFileProcessor # 文本文件处理器(放在最后作为兜底)
|
|
52
47
|
]
|
|
53
48
|
|
|
@@ -126,25 +121,11 @@ class FileOperationTool:
|
|
|
126
121
|
"stderr": f"读取文本文件失败: {str(e)}"
|
|
127
122
|
}
|
|
128
123
|
else:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
file_type_names = {
|
|
135
|
-
PDFProcessor: "PDF文档",
|
|
136
|
-
DocxProcessor: "Word文档",
|
|
137
|
-
PPTProcessor: "PowerPoint演示文稿",
|
|
138
|
-
ExcelProcessor: "Excel表格"
|
|
139
|
-
}
|
|
140
|
-
file_type = file_type_names.get(processor, file_extension)
|
|
141
|
-
file_info = f"\n文件: {abs_path} ({file_type})"
|
|
142
|
-
except Exception as e:
|
|
143
|
-
return {
|
|
144
|
-
"success": False,
|
|
145
|
-
"stdout": "",
|
|
146
|
-
"stderr": f"提取 {file_extension} 文件内容失败: {str(e)}"
|
|
147
|
-
}
|
|
124
|
+
return {
|
|
125
|
+
"success": False,
|
|
126
|
+
"stdout": "",
|
|
127
|
+
"stderr": f"不支持的文件类型: {file_extension}"
|
|
128
|
+
}
|
|
148
129
|
|
|
149
130
|
# 构建输出信息
|
|
150
131
|
output = f"{file_info}\n{content}" + "\n\n"
|
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import hashlib
|
|
4
4
|
from typing import Dict, Any
|
|
5
5
|
|
|
6
|
+
from jarvis.jarvis_utils.config import get_data_dir
|
|
6
7
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
7
8
|
|
|
8
9
|
|
|
@@ -35,7 +36,7 @@ class MethodologyTool:
|
|
|
35
36
|
|
|
36
37
|
def __init__(self):
|
|
37
38
|
"""初始化经验管理工具"""
|
|
38
|
-
self.methodology_dir = os.path.
|
|
39
|
+
self.methodology_dir = os.path.join(get_data_dir(), "methodologies")
|
|
39
40
|
self._ensure_dir_exists()
|
|
40
41
|
|
|
41
42
|
def _ensure_dir_exists(self):
|
jarvis/jarvis_tools/read_code.py
CHANGED
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -10,10 +10,12 @@ import yaml
|
|
|
10
10
|
from jarvis.jarvis_agent.output_handler import OutputHandler
|
|
11
11
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
12
12
|
from jarvis.jarvis_tools.base import Tool
|
|
13
|
-
from jarvis.jarvis_utils.config import INPUT_WINDOW_REVERSE_SIZE, get_max_input_token_count
|
|
13
|
+
from jarvis.jarvis_utils.config import INPUT_WINDOW_REVERSE_SIZE, get_max_input_token_count, get_data_dir
|
|
14
14
|
from jarvis.jarvis_utils.embedding import get_context_token_count
|
|
15
15
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
16
16
|
from jarvis.jarvis_utils.utils import ct, ot, init_env
|
|
17
|
+
from jarvis.jarvis_mcp.local_mcp_client import LocalMcpClient
|
|
18
|
+
from jarvis.jarvis_mcp.remote_mcp_client import RemoteMcpClient
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
|
|
@@ -131,6 +133,7 @@ class ToolRegistry(OutputHandler):
|
|
|
131
133
|
# 加载内置工具和外部工具
|
|
132
134
|
self._load_builtin_tools()
|
|
133
135
|
self._load_external_tools()
|
|
136
|
+
self._load_mcp_tools()
|
|
134
137
|
self.max_input_token_count = get_max_input_token_count() - INPUT_WINDOW_REVERSE_SIZE
|
|
135
138
|
|
|
136
139
|
def use_tools(self, name: List[str]) -> None:
|
|
@@ -152,6 +155,16 @@ class ToolRegistry(OutputHandler):
|
|
|
152
155
|
"""
|
|
153
156
|
self.tools = {name: tool for name, tool in self.tools.items() if name not in names}
|
|
154
157
|
|
|
158
|
+
def _load_mcp_tools(self) -> None:
|
|
159
|
+
"""从jarvis_data/tools/mcp加载工具"""
|
|
160
|
+
mcp_tools_dir = Path(get_data_dir()) / 'mcp'
|
|
161
|
+
if not mcp_tools_dir.exists():
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
# 遍历目录中的所有.yaml文件
|
|
165
|
+
for file_path in mcp_tools_dir.glob("*.yaml"):
|
|
166
|
+
self.register_mcp_tool_by_file(str(file_path))
|
|
167
|
+
|
|
155
168
|
def _load_builtin_tools(self) -> None:
|
|
156
169
|
"""从内置工具目录加载工具"""
|
|
157
170
|
tools_dir = Path(__file__).parent
|
|
@@ -165,8 +178,8 @@ class ToolRegistry(OutputHandler):
|
|
|
165
178
|
self.register_tool_by_file(str(file_path))
|
|
166
179
|
|
|
167
180
|
def _load_external_tools(self) -> None:
|
|
168
|
-
"""
|
|
169
|
-
external_tools_dir = Path
|
|
181
|
+
"""从jarvis_data/tools加载外部工具"""
|
|
182
|
+
external_tools_dir = Path(get_data_dir()) / 'tools'
|
|
170
183
|
if not external_tools_dir.exists():
|
|
171
184
|
return
|
|
172
185
|
|
|
@@ -178,6 +191,98 @@ class ToolRegistry(OutputHandler):
|
|
|
178
191
|
|
|
179
192
|
self.register_tool_by_file(str(file_path))
|
|
180
193
|
|
|
194
|
+
def register_mcp_tool_by_file(self, file_path: str) -> bool:
|
|
195
|
+
"""从指定文件加载并注册工具
|
|
196
|
+
|
|
197
|
+
参数:
|
|
198
|
+
file_path: 工具文件的路径
|
|
199
|
+
|
|
200
|
+
返回:
|
|
201
|
+
bool: 工具是否加载成功
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
config = yaml.safe_load(open(file_path, 'r', encoding='utf-8'))
|
|
205
|
+
if 'type' not in config:
|
|
206
|
+
PrettyOutput.print(f"文件 {file_path} 缺少type字段", OutputType.WARNING)
|
|
207
|
+
return False
|
|
208
|
+
if config['type'] == 'local':
|
|
209
|
+
if 'command' not in config:
|
|
210
|
+
PrettyOutput.print(f"文件 {file_path} 缺少command字段", OutputType.WARNING)
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
# 创建本地MCP客户端
|
|
214
|
+
mcp_client = LocalMcpClient(config)
|
|
215
|
+
|
|
216
|
+
# 获取工具信息
|
|
217
|
+
tools = mcp_client.get_tool_list()
|
|
218
|
+
if not tools:
|
|
219
|
+
PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
# 注册每个工具
|
|
223
|
+
for tool in tools:
|
|
224
|
+
def create_local_execute_func(tool_name: str, client: LocalMcpClient):
|
|
225
|
+
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
226
|
+
args = arguments.copy()
|
|
227
|
+
args.pop('agent', None)
|
|
228
|
+
args.pop('want', None)
|
|
229
|
+
ret = client.execute(tool_name, args)
|
|
230
|
+
PrettyOutput.print(f"MCP {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
|
|
231
|
+
return ret
|
|
232
|
+
return execute
|
|
233
|
+
|
|
234
|
+
# 注册工具
|
|
235
|
+
self.register_tool(
|
|
236
|
+
name=tool['name'],
|
|
237
|
+
description=tool['description'],
|
|
238
|
+
parameters=tool['parameters'],
|
|
239
|
+
func=create_local_execute_func(tool['name'], mcp_client)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
elif config['type'] == 'remote':
|
|
245
|
+
if 'base_url' not in config:
|
|
246
|
+
PrettyOutput.print(f"文件 {file_path} 缺少base_url字段", OutputType.WARNING)
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
# 创建远程MCP客户端
|
|
250
|
+
mcp_client = RemoteMcpClient(config)
|
|
251
|
+
|
|
252
|
+
# 获取工具信息
|
|
253
|
+
tools = mcp_client.get_tool_list()
|
|
254
|
+
if not tools:
|
|
255
|
+
PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
# 注册每个工具
|
|
259
|
+
for tool in tools:
|
|
260
|
+
def create_remote_execute_func(tool_name: str, client: RemoteMcpClient):
|
|
261
|
+
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
262
|
+
args = arguments.copy()
|
|
263
|
+
args.pop('agent', None)
|
|
264
|
+
args.pop('want', None)
|
|
265
|
+
ret = client.execute(tool_name, args)
|
|
266
|
+
PrettyOutput.print(f"MCP {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
|
|
267
|
+
return ret
|
|
268
|
+
return execute
|
|
269
|
+
|
|
270
|
+
# 注册工具
|
|
271
|
+
self.register_tool(
|
|
272
|
+
name=tool['name'],
|
|
273
|
+
description=tool['description'],
|
|
274
|
+
parameters=tool['parameters'],
|
|
275
|
+
func=create_remote_execute_func(tool['name'], mcp_client)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return True
|
|
279
|
+
else:
|
|
280
|
+
PrettyOutput.print(f"文件 {file_path} 类型错误: {config['type']}", OutputType.WARNING)
|
|
281
|
+
return False
|
|
282
|
+
except Exception as e:
|
|
283
|
+
PrettyOutput.print(f"文件 {file_path} 加载失败: {str(e)}", OutputType.WARNING)
|
|
284
|
+
return False
|
|
285
|
+
|
|
181
286
|
def register_tool_by_file(self, file_path: str) -> bool:
|
|
182
287
|
"""从指定文件加载并注册工具
|
|
183
288
|
|
jarvis/jarvis_utils/config.py
CHANGED
|
@@ -31,14 +31,6 @@ def get_max_input_token_count() -> int:
|
|
|
31
31
|
"""
|
|
32
32
|
return int(os.getenv('JARVIS_MAX_INPUT_TOKEN_COUNT', '32000'))
|
|
33
33
|
|
|
34
|
-
def get_thread_count() -> int:
|
|
35
|
-
"""
|
|
36
|
-
获取用于并行处理的线程数。
|
|
37
|
-
|
|
38
|
-
返回:
|
|
39
|
-
int: 线程数,默认为1
|
|
40
|
-
"""
|
|
41
|
-
return int(os.getenv('JARVIS_THREAD_COUNT', '1'))
|
|
42
34
|
|
|
43
35
|
def is_auto_complete() -> bool:
|
|
44
36
|
"""
|
|
@@ -118,3 +110,17 @@ def get_max_tool_call_count() -> int:
|
|
|
118
110
|
int: 最大连续工具调用次数,默认为20
|
|
119
111
|
"""
|
|
120
112
|
return int(os.getenv('JARVIS_MAX_TOOL_CALL_COUNT', '20'))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_data_dir() -> str:
|
|
116
|
+
"""
|
|
117
|
+
获取Jarvis数据存储目录路径。
|
|
118
|
+
|
|
119
|
+
返回:
|
|
120
|
+
str: 数据目录路径,优先从JARVIS_DATA_PATH环境变量获取,
|
|
121
|
+
如果未设置或为空,则使用~/.jarvis作为默认值
|
|
122
|
+
"""
|
|
123
|
+
data_path = os.getenv('JARVIS_DATA_PATH', '').strip()
|
|
124
|
+
if not data_path:
|
|
125
|
+
return os.path.expanduser('~/.jarvis')
|
|
126
|
+
return data_path
|
jarvis/jarvis_utils/embedding.py
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
from sentence_transformers import SentenceTransformer
|
|
5
|
-
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
|
6
|
-
from typing import List, Any, Optional, Tuple
|
|
2
|
+
from transformers import AutoTokenizer
|
|
3
|
+
from typing import List
|
|
7
4
|
import functools
|
|
8
5
|
|
|
9
|
-
from yaspin.api import Yaspin
|
|
10
6
|
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
7
|
+
from jarvis.jarvis_utils.config import get_data_dir
|
|
11
8
|
|
|
12
9
|
# 全局缓存,避免重复加载模型
|
|
13
|
-
_global_models = {}
|
|
14
10
|
_global_tokenizers = {}
|
|
15
11
|
|
|
16
12
|
def get_context_token_count(text: str) -> int:
|
|
@@ -159,7 +155,7 @@ def load_tokenizer() -> AutoTokenizer:
|
|
|
159
155
|
AutoTokenizer: 加载的分词器
|
|
160
156
|
"""
|
|
161
157
|
model_name = "gpt2"
|
|
162
|
-
cache_dir = os.path.
|
|
158
|
+
cache_dir = os.path.join(get_data_dir(), "huggingface", "hub")
|
|
163
159
|
|
|
164
160
|
# 检查全局缓存
|
|
165
161
|
if model_name in _global_tokenizers:
|