jarvis-ai-assistant 0.2.8__py3-none-any.whl → 0.3.1__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/jarvis_agent/__init__.py +277 -242
- jarvis/jarvis_agent/agent_manager.py +85 -0
- jarvis/jarvis_agent/config_editor.py +53 -0
- jarvis/jarvis_agent/file_methodology_manager.py +105 -0
- jarvis/jarvis_agent/jarvis.py +30 -619
- jarvis/jarvis_agent/memory_manager.py +127 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompts.py +18 -3
- jarvis/jarvis_agent/share_manager.py +176 -0
- jarvis/jarvis_agent/task_analyzer.py +126 -0
- jarvis/jarvis_agent/task_manager.py +111 -0
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_code_agent/code_agent.py +26 -20
- jarvis/jarvis_data/config_schema.json +37 -4
- jarvis/jarvis_platform/ai8.py +13 -1
- jarvis/jarvis_platform/base.py +20 -5
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +10 -0
- jarvis/jarvis_platform/openai.py +20 -0
- jarvis/jarvis_platform/tongyi.py +14 -9
- jarvis/jarvis_platform/yuanbao.py +10 -0
- jarvis/jarvis_platform_manager/main.py +12 -12
- jarvis/jarvis_platform_manager/service.py +9 -4
- jarvis/jarvis_tools/registry.py +32 -0
- jarvis/jarvis_tools/retrieve_memory.py +36 -8
- jarvis/jarvis_tools/search_web.py +1 -1
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +64 -0
- jarvis/jarvis_utils/git_utils.py +17 -7
- jarvis/jarvis_utils/globals.py +18 -12
- jarvis/jarvis_utils/input.py +118 -16
- jarvis/jarvis_utils/methodology.py +48 -5
- jarvis/jarvis_utils/utils.py +169 -105
- {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.1.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.1.dist-info}/RECORD +40 -30
- {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.1.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.1.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
记忆管理器模块
|
4
|
+
负责处理Agent的记忆保存和检索功能
|
5
|
+
"""
|
6
|
+
from typing import Optional, Dict, List, Any
|
7
|
+
|
8
|
+
from jarvis.jarvis_utils.globals import get_all_memory_tags
|
9
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
10
|
+
|
11
|
+
|
12
|
+
class MemoryManager:
|
13
|
+
"""记忆管理器,负责处理记忆相关的功能"""
|
14
|
+
|
15
|
+
def __init__(self, agent):
|
16
|
+
"""
|
17
|
+
初始化记忆管理器
|
18
|
+
|
19
|
+
参数:
|
20
|
+
agent: Agent实例
|
21
|
+
"""
|
22
|
+
self.agent = agent
|
23
|
+
|
24
|
+
def prepare_memory_tags_prompt(self) -> str:
|
25
|
+
"""准备记忆标签提示"""
|
26
|
+
memory_tags = get_all_memory_tags()
|
27
|
+
memory_tags_prompt = ""
|
28
|
+
|
29
|
+
# 检查是否有save_memory工具
|
30
|
+
if self._has_save_memory_tool():
|
31
|
+
memory_tags_prompt = "\n\n💡 提示:在分析任务之前,建议使用 save_memory 工具将关键信息记录下来,便于后续检索和复用。"
|
32
|
+
|
33
|
+
# 构建记忆标签列表
|
34
|
+
if any(tags for tags in memory_tags.values()):
|
35
|
+
memory_tags_prompt += self._format_memory_tags(memory_tags)
|
36
|
+
|
37
|
+
return memory_tags_prompt
|
38
|
+
|
39
|
+
def _has_save_memory_tool(self) -> bool:
|
40
|
+
"""检查是否有save_memory工具"""
|
41
|
+
tool_registry = self.agent.get_tool_registry()
|
42
|
+
if tool_registry:
|
43
|
+
tool_names = [tool.name for tool in tool_registry.tools.values()]
|
44
|
+
return "save_memory" in tool_names
|
45
|
+
return False
|
46
|
+
|
47
|
+
def _format_memory_tags(self, memory_tags: dict) -> str:
|
48
|
+
"""格式化记忆标签"""
|
49
|
+
prompt = (
|
50
|
+
"\n\n系统中存在以下记忆标签,你可以使用 retrieve_memory 工具检索相关记忆:"
|
51
|
+
)
|
52
|
+
|
53
|
+
type_names = {
|
54
|
+
"short_term": "短期记忆",
|
55
|
+
"project_long_term": "项目长期记忆",
|
56
|
+
"global_long_term": "全局长期记忆",
|
57
|
+
}
|
58
|
+
|
59
|
+
for memory_type, tags in memory_tags.items():
|
60
|
+
if tags:
|
61
|
+
type_name = type_names.get(memory_type, memory_type)
|
62
|
+
prompt += f"\n- {type_name}: {', '.join(tags)}"
|
63
|
+
|
64
|
+
return prompt
|
65
|
+
|
66
|
+
def prompt_memory_save(self):
|
67
|
+
"""让大模型自动判断并保存值得记忆的信息"""
|
68
|
+
# 检查是否有记忆相关工具
|
69
|
+
tool_registry = self.agent.get_tool_registry()
|
70
|
+
if not tool_registry:
|
71
|
+
return
|
72
|
+
|
73
|
+
tool_names = [tool.name for tool in tool_registry.tools.values()]
|
74
|
+
if "save_memory" not in tool_names:
|
75
|
+
return
|
76
|
+
|
77
|
+
print("🔍 正在分析是否有值得记忆的信息...")
|
78
|
+
|
79
|
+
# 构建提示词,让大模型自己判断并保存记忆
|
80
|
+
prompt = """请回顾本次任务的整个过程,判断是否有值得长期记忆或项目记忆的信息。
|
81
|
+
|
82
|
+
如果有以下类型的信息,请使用 save_memory 工具保存:
|
83
|
+
1. 解决问题的新方法或技巧(适合保存为 global_long_term)
|
84
|
+
2. 项目相关的重要发现或配置(适合保存为 project_long_term)
|
85
|
+
3. 用户的偏好或习惯(适合保存为 global_long_term)
|
86
|
+
4. 重要的技术知识或经验(适合保存为 global_long_term)
|
87
|
+
5. 项目特定的实现细节或约定(适合保存为 project_long_term)
|
88
|
+
|
89
|
+
请分析并保存有价值的信息,选择合适的记忆类型和标签。如果没有值得记忆的信息,请直接说明。"""
|
90
|
+
|
91
|
+
# 处理记忆保存
|
92
|
+
try:
|
93
|
+
response = self.agent.model.chat_until_success(prompt) # type: ignore
|
94
|
+
|
95
|
+
# 执行工具调用(如果有)
|
96
|
+
need_return, result = self.agent._call_tools(response)
|
97
|
+
|
98
|
+
# 根据响应判断是否保存了记忆
|
99
|
+
if "save_memory" in response:
|
100
|
+
print("✅ 已自动保存有价值的信息到记忆系统")
|
101
|
+
else:
|
102
|
+
print("📝 本次任务没有特别需要记忆的信息")
|
103
|
+
|
104
|
+
except Exception as e:
|
105
|
+
print(f"❌ 记忆分析失败: {str(e)}")
|
106
|
+
|
107
|
+
def add_memory_prompts_to_addon(self, addon_prompt: str, tool_registry) -> str:
|
108
|
+
"""在附加提示中添加记忆相关提示"""
|
109
|
+
memory_prompts = ""
|
110
|
+
|
111
|
+
if tool_registry:
|
112
|
+
tool_names = [tool.name for tool in tool_registry.tools.values()]
|
113
|
+
|
114
|
+
# 如果有save_memory工具,添加相关提示
|
115
|
+
if "save_memory" in tool_names:
|
116
|
+
memory_prompts += (
|
117
|
+
"\n - 如果有关键信息需要记忆,请调用save_memory工具进行记忆:"
|
118
|
+
)
|
119
|
+
memory_prompts += "\n * project_long_term: 保存与当前项目相关的长期信息(如:架构决策、关键配置、特定实现)"
|
120
|
+
memory_prompts += "\n * global_long_term: 保存通用的信息、用户喜好、知识、方法等(如:常用命令、个人偏好、解决方案)"
|
121
|
+
memory_prompts += "\n * short_term: 保存当前任务相关的临时信息(如:当前处理的文件、用户中间需求)"
|
122
|
+
|
123
|
+
# 如果有retrieve_memory工具,添加相关提示
|
124
|
+
if "retrieve_memory" in tool_names:
|
125
|
+
memory_prompts += "\n - 如果需要获取上下文或寻找解决方案,请调用retrieve_memory工具检索相关记忆"
|
126
|
+
|
127
|
+
return memory_prompts
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""方法论分享管理模块"""
|
3
|
+
import os
|
4
|
+
import glob
|
5
|
+
import json
|
6
|
+
import shutil
|
7
|
+
from typing import List, Dict, Any
|
8
|
+
|
9
|
+
import typer
|
10
|
+
|
11
|
+
from jarvis.jarvis_agent import OutputType, PrettyOutput, user_confirm
|
12
|
+
from jarvis.jarvis_agent.share_manager import ShareManager
|
13
|
+
from jarvis.jarvis_utils.config import (
|
14
|
+
get_central_methodology_repo,
|
15
|
+
get_methodology_dirs,
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
class MethodologyShareManager(ShareManager):
|
20
|
+
"""方法论分享管理器"""
|
21
|
+
|
22
|
+
def __init__(self):
|
23
|
+
central_repo = get_central_methodology_repo()
|
24
|
+
if not central_repo:
|
25
|
+
PrettyOutput.print(
|
26
|
+
"错误:未配置中心方法论仓库(JARVIS_CENTRAL_METHODOLOGY_REPO)",
|
27
|
+
OutputType.ERROR,
|
28
|
+
)
|
29
|
+
PrettyOutput.print(
|
30
|
+
"请在配置文件中设置中心方法论仓库的Git地址", OutputType.INFO
|
31
|
+
)
|
32
|
+
raise typer.Exit(code=1)
|
33
|
+
|
34
|
+
super().__init__(central_repo, "central_methodology_repo")
|
35
|
+
|
36
|
+
def get_resource_type(self) -> str:
|
37
|
+
"""获取资源类型名称"""
|
38
|
+
return "方法论"
|
39
|
+
|
40
|
+
def format_resource_display(self, resource: Dict[str, Any]) -> str:
|
41
|
+
"""格式化资源显示"""
|
42
|
+
dir_name = os.path.basename(resource["directory"])
|
43
|
+
return f"{resource['problem_type']} (来自: {dir_name})"
|
44
|
+
|
45
|
+
def get_existing_resources(self) -> Dict[str, str]:
|
46
|
+
"""获取中心仓库中已有的方法论"""
|
47
|
+
existing_methodologies = {} # 存储 problem_type -> content 的映射
|
48
|
+
for filepath in glob.glob(os.path.join(self.repo_path, "*.json")):
|
49
|
+
try:
|
50
|
+
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
51
|
+
methodology = json.load(f)
|
52
|
+
problem_type = methodology.get("problem_type", "")
|
53
|
+
content = methodology.get("content", "")
|
54
|
+
if problem_type and content:
|
55
|
+
existing_methodologies[problem_type] = content
|
56
|
+
except Exception:
|
57
|
+
pass
|
58
|
+
return existing_methodologies
|
59
|
+
|
60
|
+
def get_local_resources(self) -> List[Dict[str, Any]]:
|
61
|
+
"""获取本地方法论"""
|
62
|
+
# 获取中心仓库中已有的方法论
|
63
|
+
existing_methodologies = self.get_existing_resources()
|
64
|
+
|
65
|
+
# 获取所有方法论目录
|
66
|
+
from jarvis.jarvis_utils.methodology import _get_methodology_directory
|
67
|
+
|
68
|
+
methodology_dirs = [_get_methodology_directory()] + get_methodology_dirs()
|
69
|
+
|
70
|
+
# 收集所有方法论文件(排除中心仓库目录和已存在的方法论)
|
71
|
+
methodology_files = []
|
72
|
+
seen_problem_types = set() # 用于去重
|
73
|
+
|
74
|
+
for directory in set(methodology_dirs):
|
75
|
+
# 跳过中心仓库目录
|
76
|
+
if os.path.abspath(directory) == os.path.abspath(self.repo_path):
|
77
|
+
continue
|
78
|
+
|
79
|
+
if not os.path.isdir(directory):
|
80
|
+
continue
|
81
|
+
|
82
|
+
for filepath in glob.glob(os.path.join(directory, "*.json")):
|
83
|
+
try:
|
84
|
+
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
85
|
+
methodology = json.load(f)
|
86
|
+
problem_type = methodology.get("problem_type", "")
|
87
|
+
content = methodology.get("content", "")
|
88
|
+
|
89
|
+
# 基于内容判断是否已存在于中心仓库
|
90
|
+
is_duplicate = False
|
91
|
+
if problem_type in existing_methodologies:
|
92
|
+
# 如果problem_type相同,比较内容
|
93
|
+
if (
|
94
|
+
content.strip()
|
95
|
+
== existing_methodologies[problem_type].strip()
|
96
|
+
):
|
97
|
+
is_duplicate = True
|
98
|
+
|
99
|
+
# 排除已存在于中心仓库的方法论(基于内容),以及本地重复的方法论
|
100
|
+
if (
|
101
|
+
problem_type
|
102
|
+
and content
|
103
|
+
and not is_duplicate
|
104
|
+
and problem_type not in seen_problem_types
|
105
|
+
):
|
106
|
+
methodology_files.append(
|
107
|
+
{
|
108
|
+
"path": filepath,
|
109
|
+
"problem_type": problem_type,
|
110
|
+
"directory": directory,
|
111
|
+
"methodology": methodology,
|
112
|
+
}
|
113
|
+
)
|
114
|
+
seen_problem_types.add(problem_type)
|
115
|
+
except Exception:
|
116
|
+
pass
|
117
|
+
|
118
|
+
return methodology_files
|
119
|
+
|
120
|
+
def share_resources(self, resources: List[Dict[str, Any]]) -> List[str]:
|
121
|
+
"""分享方法论到中心仓库"""
|
122
|
+
# 确认操作
|
123
|
+
share_list = ["\n将要分享以下方法论到中心仓库:"]
|
124
|
+
for meth in resources:
|
125
|
+
share_list.append(f"- {meth['problem_type']}")
|
126
|
+
PrettyOutput.print("\n".join(share_list), OutputType.INFO)
|
127
|
+
|
128
|
+
if not user_confirm("确认分享这些方法论吗?"):
|
129
|
+
return []
|
130
|
+
|
131
|
+
# 复制选中的方法论到中心仓库
|
132
|
+
copied_list = []
|
133
|
+
for meth in resources:
|
134
|
+
src_file = meth["path"]
|
135
|
+
dst_file = os.path.join(self.repo_path, os.path.basename(src_file))
|
136
|
+
shutil.copy2(src_file, dst_file)
|
137
|
+
copied_list.append(f"已复制: {meth['problem_type']}")
|
138
|
+
|
139
|
+
return copied_list
|
140
|
+
|
141
|
+
def run(self) -> None:
|
142
|
+
"""执行方法论分享流程"""
|
143
|
+
try:
|
144
|
+
# 更新中心仓库
|
145
|
+
self.update_central_repo()
|
146
|
+
|
147
|
+
# 获取本地资源
|
148
|
+
local_resources = self.get_local_resources()
|
149
|
+
if not local_resources:
|
150
|
+
PrettyOutput.print(
|
151
|
+
"没有找到新的方法论文件(所有方法论可能已存在于中心仓库)",
|
152
|
+
OutputType.WARNING,
|
153
|
+
)
|
154
|
+
raise typer.Exit(code=0)
|
155
|
+
|
156
|
+
# 选择要分享的资源
|
157
|
+
selected_resources = self.select_resources(local_resources)
|
158
|
+
if not selected_resources:
|
159
|
+
raise typer.Exit(code=0)
|
160
|
+
|
161
|
+
# 分享资源
|
162
|
+
copied_list = self.share_resources(selected_resources)
|
163
|
+
if copied_list:
|
164
|
+
# 一次性显示所有复制结果
|
165
|
+
PrettyOutput.print("\n".join(copied_list), OutputType.SUCCESS)
|
166
|
+
|
167
|
+
# 提交并推送
|
168
|
+
self.commit_and_push(len(selected_resources))
|
169
|
+
|
170
|
+
PrettyOutput.print("\n方法论已成功分享到中心仓库!", OutputType.SUCCESS)
|
171
|
+
|
172
|
+
except Exception as e:
|
173
|
+
PrettyOutput.print(f"分享方法论时出错: {str(e)}", OutputType.ERROR)
|
174
|
+
raise typer.Exit(code=1)
|
jarvis/jarvis_agent/prompts.py
CHANGED
@@ -43,13 +43,28 @@ SUMMARY_REQUEST_PROMPT = """<summary_request>
|
|
43
43
|
|
44
44
|
TASK_ANALYSIS_PROMPT = f"""<task_analysis>
|
45
45
|
<request>
|
46
|
-
|
47
|
-
|
46
|
+
当前任务已结束,请按以下步骤分析该任务:
|
47
|
+
|
48
|
+
第一步:记忆值得保存的信息
|
49
|
+
1. 识别任务中的关键信息和知识点
|
50
|
+
2. 评估是否有值得保存的项目长期记忆或全局长期记忆
|
51
|
+
3. 使用 save_memory 工具保存有价值的信息:
|
52
|
+
- project_long_term: 保存与当前项目相关的长期信息(如项目配置、架构决策、开发规范等)
|
53
|
+
- global_long_term: 保存通用的信息、用户偏好、知识或方法(如技术知识、最佳实践、用户习惯等)
|
54
|
+
|
55
|
+
第二步:分析任务解决方案
|
56
|
+
1. 检查现有工具或方法论是否已经可以完成该任务,如果可以,直接说明即可,无需生成新内容
|
48
57
|
2. 如果现有工具/方法论不足,评估当前任务是否可以通过编写新工具来自动化解决
|
49
58
|
3. 如果可以通过工具解决,请设计并提供工具代码
|
50
59
|
4. 如果无法通过编写通用工具完成,评估当前的执行流程是否可以总结为通用方法论
|
51
60
|
5. 如果以上都不可行,给出详细理由
|
52
|
-
|
61
|
+
|
62
|
+
请根据分析结果采取相应行动。
|
63
|
+
|
64
|
+
重要提示:每次只能执行一个操作!
|
65
|
+
- 如果有记忆需要保存,可以调用一次 save_memory 批量保存多条记忆
|
66
|
+
- 保存完所有记忆后,再进行工具/方法论的创建或说明
|
67
|
+
- 不要在一次响应中同时调用多个工具(如同时保存记忆和创建工具/方法论)
|
53
68
|
</request>
|
54
69
|
<evaluation_criteria>
|
55
70
|
现有资源评估:
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""分享管理模块,负责工具和方法论的分享功能"""
|
3
|
+
import os
|
4
|
+
import subprocess
|
5
|
+
from typing import List, Dict, Any
|
6
|
+
from abc import ABC, abstractmethod
|
7
|
+
|
8
|
+
from prompt_toolkit import prompt
|
9
|
+
|
10
|
+
from jarvis.jarvis_agent import OutputType, PrettyOutput, user_confirm
|
11
|
+
from jarvis.jarvis_utils.config import get_data_dir
|
12
|
+
|
13
|
+
|
14
|
+
def parse_selection(selection_str: str, max_value: int) -> List[int]:
|
15
|
+
"""解析用户输入的选择字符串,支持逗号分隔和范围选择
|
16
|
+
|
17
|
+
例如: "1,2,3,4-9,20" -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 20]
|
18
|
+
"""
|
19
|
+
selected: set[int] = set()
|
20
|
+
parts = selection_str.split(",")
|
21
|
+
|
22
|
+
for part in parts:
|
23
|
+
part = part.strip()
|
24
|
+
if "-" in part:
|
25
|
+
# 处理范围选择
|
26
|
+
try:
|
27
|
+
start_str, end_str = part.split("-")
|
28
|
+
start_num = int(start_str.strip())
|
29
|
+
end_num = int(end_str.strip())
|
30
|
+
if 1 <= start_num <= max_value and 1 <= end_num <= max_value:
|
31
|
+
selected.update(range(start_num, end_num + 1))
|
32
|
+
except ValueError:
|
33
|
+
continue
|
34
|
+
else:
|
35
|
+
# 处理单个数字
|
36
|
+
try:
|
37
|
+
num = int(part)
|
38
|
+
if 1 <= num <= max_value:
|
39
|
+
selected.add(num)
|
40
|
+
except ValueError:
|
41
|
+
continue
|
42
|
+
|
43
|
+
return sorted(list(selected))
|
44
|
+
|
45
|
+
|
46
|
+
class ShareManager(ABC):
|
47
|
+
"""分享管理器基类"""
|
48
|
+
|
49
|
+
def __init__(self, central_repo_url: str, repo_name: str):
|
50
|
+
self.central_repo_url = central_repo_url
|
51
|
+
self.repo_name = repo_name
|
52
|
+
self.repo_path = os.path.join(get_data_dir(), repo_name)
|
53
|
+
|
54
|
+
def update_central_repo(self) -> None:
|
55
|
+
"""克隆或更新中心仓库"""
|
56
|
+
if not os.path.exists(self.repo_path):
|
57
|
+
PrettyOutput.print(
|
58
|
+
f"正在克隆中心{self.get_resource_type()}仓库...", OutputType.INFO
|
59
|
+
)
|
60
|
+
subprocess.run(
|
61
|
+
["git", "clone", self.central_repo_url, self.repo_path], check=True
|
62
|
+
)
|
63
|
+
else:
|
64
|
+
PrettyOutput.print(
|
65
|
+
f"正在更新中心{self.get_resource_type()}仓库...", OutputType.INFO
|
66
|
+
)
|
67
|
+
# 检查是否是空仓库
|
68
|
+
try:
|
69
|
+
# 先尝试获取远程分支信息
|
70
|
+
result = subprocess.run(
|
71
|
+
["git", "ls-remote", "--heads", "origin"],
|
72
|
+
cwd=self.repo_path,
|
73
|
+
capture_output=True,
|
74
|
+
text=True,
|
75
|
+
check=True,
|
76
|
+
)
|
77
|
+
# 如果有远程分支,执行pull
|
78
|
+
if result.stdout.strip():
|
79
|
+
subprocess.run(["git", "pull"], cwd=self.repo_path, check=True)
|
80
|
+
else:
|
81
|
+
PrettyOutput.print(
|
82
|
+
f"中心{self.get_resource_type()}仓库是空的,将初始化为新仓库",
|
83
|
+
OutputType.INFO,
|
84
|
+
)
|
85
|
+
except subprocess.CalledProcessError:
|
86
|
+
# 如果命令失败,可能是网络问题或其他错误
|
87
|
+
PrettyOutput.print("无法连接到远程仓库,将跳过更新", OutputType.WARNING)
|
88
|
+
|
89
|
+
def commit_and_push(self, count: int) -> None:
|
90
|
+
"""提交并推送更改"""
|
91
|
+
PrettyOutput.print("\n正在提交更改...", OutputType.INFO)
|
92
|
+
subprocess.run(["git", "add", "."], cwd=self.repo_path, check=True)
|
93
|
+
|
94
|
+
commit_msg = f"Add {count} {self.get_resource_type()}(s) from local collection"
|
95
|
+
subprocess.run(
|
96
|
+
["git", "commit", "-m", commit_msg], cwd=self.repo_path, check=True
|
97
|
+
)
|
98
|
+
|
99
|
+
PrettyOutput.print("正在推送到远程仓库...", OutputType.INFO)
|
100
|
+
# 检查是否需要设置上游分支(空仓库的情况)
|
101
|
+
try:
|
102
|
+
# 先尝试普通推送
|
103
|
+
subprocess.run(["git", "push"], cwd=self.repo_path, check=True)
|
104
|
+
except subprocess.CalledProcessError:
|
105
|
+
# 如果失败,可能是空仓库,尝试设置上游分支
|
106
|
+
try:
|
107
|
+
subprocess.run(
|
108
|
+
["git", "push", "-u", "origin", "main"],
|
109
|
+
cwd=self.repo_path,
|
110
|
+
check=True,
|
111
|
+
)
|
112
|
+
except subprocess.CalledProcessError:
|
113
|
+
# 如果main分支不存在,尝试master分支
|
114
|
+
subprocess.run(
|
115
|
+
["git", "push", "-u", "origin", "master"],
|
116
|
+
cwd=self.repo_path,
|
117
|
+
check=True,
|
118
|
+
)
|
119
|
+
|
120
|
+
def select_resources(self, resources: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
121
|
+
"""让用户选择要分享的资源"""
|
122
|
+
# 显示可选的资源
|
123
|
+
resource_list = [
|
124
|
+
f"\n可分享的{self.get_resource_type()}(已排除中心仓库中已有的):"
|
125
|
+
]
|
126
|
+
for i, resource in enumerate(resources, 1):
|
127
|
+
resource_list.append(f"[{i}] {self.format_resource_display(resource)}")
|
128
|
+
|
129
|
+
# 一次性打印所有资源
|
130
|
+
PrettyOutput.print("\n".join(resource_list), OutputType.INFO)
|
131
|
+
|
132
|
+
# 让用户选择
|
133
|
+
while True:
|
134
|
+
try:
|
135
|
+
choice_str = prompt(
|
136
|
+
f"\n请选择要分享的{self.get_resource_type()}编号(支持格式: 1,2,3,4-9,20 或 all):"
|
137
|
+
).strip()
|
138
|
+
if choice_str == "0":
|
139
|
+
return []
|
140
|
+
|
141
|
+
if choice_str.lower() == "all":
|
142
|
+
return resources
|
143
|
+
else:
|
144
|
+
selected_indices = parse_selection(choice_str, len(resources))
|
145
|
+
if not selected_indices:
|
146
|
+
PrettyOutput.print("无效的选择", OutputType.WARNING)
|
147
|
+
continue
|
148
|
+
return [resources[i - 1] for i in selected_indices]
|
149
|
+
|
150
|
+
except ValueError:
|
151
|
+
PrettyOutput.print("请输入有效的数字", OutputType.WARNING)
|
152
|
+
|
153
|
+
@abstractmethod
|
154
|
+
def get_resource_type(self) -> str:
|
155
|
+
"""获取资源类型名称"""
|
156
|
+
pass
|
157
|
+
|
158
|
+
@abstractmethod
|
159
|
+
def format_resource_display(self, resource: Dict[str, Any]) -> str:
|
160
|
+
"""格式化资源显示"""
|
161
|
+
pass
|
162
|
+
|
163
|
+
@abstractmethod
|
164
|
+
def get_existing_resources(self) -> Any:
|
165
|
+
"""获取中心仓库中已有的资源"""
|
166
|
+
pass
|
167
|
+
|
168
|
+
@abstractmethod
|
169
|
+
def get_local_resources(self) -> List[Dict[str, Any]]:
|
170
|
+
"""获取本地资源"""
|
171
|
+
pass
|
172
|
+
|
173
|
+
@abstractmethod
|
174
|
+
def share_resources(self, resources: List[Dict[str, Any]]) -> List[str]:
|
175
|
+
"""分享资源到中心仓库"""
|
176
|
+
pass
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
"""
|
3
|
+
任务分析器模块
|
4
|
+
负责处理任务分析和方法论生成功能
|
5
|
+
"""
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
from jarvis.jarvis_utils.globals import get_interrupt, set_interrupt
|
9
|
+
from jarvis.jarvis_utils.input import user_confirm
|
10
|
+
from jarvis.jarvis_agent.prompts import TASK_ANALYSIS_PROMPT
|
11
|
+
|
12
|
+
|
13
|
+
class TaskAnalyzer:
|
14
|
+
"""任务分析器,负责任务分析和满意度反馈处理"""
|
15
|
+
|
16
|
+
def __init__(self, agent):
|
17
|
+
"""
|
18
|
+
初始化任务分析器
|
19
|
+
|
20
|
+
参数:
|
21
|
+
agent: Agent实例
|
22
|
+
"""
|
23
|
+
self.agent = agent
|
24
|
+
|
25
|
+
def analysis_task(self, satisfaction_feedback: str = ""):
|
26
|
+
"""分析任务并生成方法论"""
|
27
|
+
print("🔍 正在分析任务...")
|
28
|
+
try:
|
29
|
+
# 准备分析提示
|
30
|
+
self.agent.session.prompt = self._prepare_analysis_prompt(
|
31
|
+
satisfaction_feedback
|
32
|
+
)
|
33
|
+
|
34
|
+
if not self.agent.model:
|
35
|
+
raise RuntimeError("Model not initialized")
|
36
|
+
|
37
|
+
# 循环处理工具调用,直到没有工具调用为止
|
38
|
+
self._process_analysis_loop()
|
39
|
+
|
40
|
+
print("✅ 分析完成")
|
41
|
+
except Exception as e:
|
42
|
+
print("❌ 分析失败")
|
43
|
+
|
44
|
+
def _prepare_analysis_prompt(self, satisfaction_feedback: str) -> str:
|
45
|
+
"""准备分析提示"""
|
46
|
+
analysis_prompt = TASK_ANALYSIS_PROMPT
|
47
|
+
if satisfaction_feedback:
|
48
|
+
analysis_prompt += satisfaction_feedback
|
49
|
+
return analysis_prompt
|
50
|
+
|
51
|
+
def _process_analysis_loop(self):
|
52
|
+
"""处理分析循环"""
|
53
|
+
while True:
|
54
|
+
response = self.agent.model.chat_until_success(self.agent.session.prompt) # type: ignore
|
55
|
+
self.agent.session.prompt = ""
|
56
|
+
|
57
|
+
# 处理用户中断
|
58
|
+
if get_interrupt():
|
59
|
+
if not self._handle_analysis_interrupt(response):
|
60
|
+
break
|
61
|
+
|
62
|
+
# 执行工具调用
|
63
|
+
need_return, self.agent.session.prompt = self.agent._call_tools(response)
|
64
|
+
|
65
|
+
# 如果没有工具调用或者没有新的提示,退出循环
|
66
|
+
if not self.agent.session.prompt:
|
67
|
+
break
|
68
|
+
|
69
|
+
def _handle_analysis_interrupt(self, response: str) -> bool:
|
70
|
+
"""处理分析过程中的用户中断
|
71
|
+
|
72
|
+
返回:
|
73
|
+
bool: True 继续分析,False 退出分析
|
74
|
+
"""
|
75
|
+
set_interrupt(False)
|
76
|
+
user_input = self.agent.multiline_inputer(
|
77
|
+
f"分析任务期间被中断,请输入用户干预信息:"
|
78
|
+
)
|
79
|
+
|
80
|
+
if not user_input:
|
81
|
+
# 用户输入为空,退出分析
|
82
|
+
return False
|
83
|
+
|
84
|
+
if self._has_tool_calls(response):
|
85
|
+
self.agent.session.prompt = self._handle_interrupt_with_tool_calls(
|
86
|
+
user_input
|
87
|
+
)
|
88
|
+
else:
|
89
|
+
self.agent.session.prompt = f"被用户中断,用户补充信息为:{user_input}"
|
90
|
+
|
91
|
+
return True
|
92
|
+
|
93
|
+
def _has_tool_calls(self, response: str) -> bool:
|
94
|
+
"""检查响应中是否有工具调用"""
|
95
|
+
return any(
|
96
|
+
handler.can_handle(response) for handler in self.agent.output_handler
|
97
|
+
)
|
98
|
+
|
99
|
+
def _handle_interrupt_with_tool_calls(self, user_input: str) -> str:
|
100
|
+
"""处理有工具调用时的中断"""
|
101
|
+
if user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
|
102
|
+
return f"被用户中断,用户补充信息为:{user_input}\n\n用户同意继续工具调用。"
|
103
|
+
else:
|
104
|
+
return f"被用户中断,用户补充信息为:{user_input}\n\n检测到有工具调用,但被用户拒绝执行。请根据用户的补充信息重新考虑下一步操作。"
|
105
|
+
|
106
|
+
def collect_satisfaction_feedback(self, auto_completed: bool) -> str:
|
107
|
+
"""收集满意度反馈"""
|
108
|
+
satisfaction_feedback = ""
|
109
|
+
|
110
|
+
if not auto_completed and self.agent.use_analysis:
|
111
|
+
if user_confirm("您对本次任务的完成是否满意?", True):
|
112
|
+
satisfaction_feedback = "\n\n用户对本次任务的完成表示满意。"
|
113
|
+
else:
|
114
|
+
feedback = self.agent.multiline_inputer(
|
115
|
+
"请提供您的反馈意见(可留空直接回车):"
|
116
|
+
)
|
117
|
+
if feedback:
|
118
|
+
satisfaction_feedback = (
|
119
|
+
f"\n\n用户对本次任务的完成不满意,反馈意见如下:\n{feedback}"
|
120
|
+
)
|
121
|
+
else:
|
122
|
+
satisfaction_feedback = (
|
123
|
+
"\n\n用户对本次任务的完成不满意,未提供具体反馈意见。"
|
124
|
+
)
|
125
|
+
|
126
|
+
return satisfaction_feedback
|