auto-coder 0.1.361__py3-none-any.whl → 0.1.363__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 auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/METADATA +2 -1
- {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/RECORD +57 -29
- autocoder/agent/auto_learn.py +249 -262
- autocoder/agent/base_agentic/__init__.py +0 -0
- autocoder/agent/base_agentic/agent_hub.py +169 -0
- autocoder/agent/base_agentic/agentic_lang.py +112 -0
- autocoder/agent/base_agentic/agentic_tool_display.py +180 -0
- autocoder/agent/base_agentic/base_agent.py +1582 -0
- autocoder/agent/base_agentic/default_tools.py +683 -0
- autocoder/agent/base_agentic/test_base_agent.py +82 -0
- autocoder/agent/base_agentic/tool_registry.py +425 -0
- autocoder/agent/base_agentic/tools/__init__.py +12 -0
- autocoder/agent/base_agentic/tools/ask_followup_question_tool_resolver.py +72 -0
- autocoder/agent/base_agentic/tools/attempt_completion_tool_resolver.py +37 -0
- autocoder/agent/base_agentic/tools/base_tool_resolver.py +35 -0
- autocoder/agent/base_agentic/tools/example_tool_resolver.py +46 -0
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +72 -0
- autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +110 -0
- autocoder/agent/base_agentic/tools/plan_mode_respond_tool_resolver.py +35 -0
- autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +54 -0
- autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +156 -0
- autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +134 -0
- autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +96 -0
- autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +79 -0
- autocoder/agent/base_agentic/tools/use_mcp_tool_resolver.py +44 -0
- autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +58 -0
- autocoder/agent/base_agentic/types.py +189 -0
- autocoder/agent/base_agentic/utils.py +100 -0
- autocoder/auto_coder.py +1 -1
- autocoder/auto_coder_runner.py +36 -14
- autocoder/chat/conf_command.py +11 -10
- autocoder/commands/auto_command.py +227 -159
- autocoder/common/__init__.py +2 -2
- autocoder/common/ignorefiles/ignore_file_utils.py +12 -8
- autocoder/common/result_manager.py +10 -2
- autocoder/common/rulefiles/autocoderrules_utils.py +169 -0
- autocoder/common/save_formatted_log.py +1 -1
- autocoder/common/v2/agent/agentic_edit.py +53 -41
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +15 -12
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +73 -1
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +132 -4
- autocoder/common/v2/agent/agentic_edit_types.py +1 -2
- autocoder/common/v2/agent/agentic_tool_display.py +2 -3
- autocoder/common/v2/code_auto_generate_editblock.py +3 -1
- autocoder/index/index.py +14 -8
- autocoder/privacy/model_filter.py +297 -35
- autocoder/rag/long_context_rag.py +424 -397
- autocoder/rag/test_doc_filter.py +393 -0
- autocoder/rag/test_long_context_rag.py +473 -0
- autocoder/rag/test_token_limiter.py +342 -0
- autocoder/shadows/shadow_manager.py +1 -3
- autocoder/utils/_markitdown.py +22 -3
- autocoder/version.py +1 -1
- {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.361.dist-info → auto_coder-0.1.363.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BaseAgent类和byzerllm.prompt装饰器的单元测试
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
import shutil
|
|
8
|
+
from unittest.mock import patch, MagicMock
|
|
9
|
+
import byzerllm
|
|
10
|
+
|
|
11
|
+
# 导入被测类
|
|
12
|
+
from autocoder.agent.base_agentic import BaseAgent
|
|
13
|
+
from autocoder.agent.base_agentic.types import BaseTool, AgentRequest, ToolResult
|
|
14
|
+
from autocoder.common import AutoCoderArgs, SourceCodeList, SourceCode
|
|
15
|
+
|
|
16
|
+
# 测试环境辅助函数
|
|
17
|
+
def create_test_environment(base_dir, structure):
|
|
18
|
+
"""创建测试所需的文件/目录结构"""
|
|
19
|
+
for path, content in structure.items():
|
|
20
|
+
full_path = os.path.join(base_dir, path)
|
|
21
|
+
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
|
22
|
+
with open(full_path, 'w', encoding='utf-8') as f:
|
|
23
|
+
f.write(content)
|
|
24
|
+
|
|
25
|
+
# Pytest Fixture: 临时目录
|
|
26
|
+
@pytest.fixture(scope="function")
|
|
27
|
+
def temp_test_dir():
|
|
28
|
+
"""提供一个临时的、测试后自动清理的目录"""
|
|
29
|
+
temp_dir = tempfile.mkdtemp()
|
|
30
|
+
print(f"Created temp dir for test: {temp_dir}")
|
|
31
|
+
yield temp_dir
|
|
32
|
+
print(f"Cleaning up temp dir: {temp_dir}")
|
|
33
|
+
shutil.rmtree(temp_dir)
|
|
34
|
+
|
|
35
|
+
# Pytest Fixture: 重置FileMonitor
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def setup_file_monitor(temp_test_dir):
|
|
38
|
+
"""初始化FileMonitor,必须最先执行"""
|
|
39
|
+
try:
|
|
40
|
+
from autocoder.common.file_monitor.monitor import FileMonitor
|
|
41
|
+
# 重置FileMonitor实例,确保测试隔离性
|
|
42
|
+
monitor = FileMonitor(temp_test_dir)
|
|
43
|
+
monitor.reset_instance()
|
|
44
|
+
if not monitor.is_running():
|
|
45
|
+
monitor.start()
|
|
46
|
+
print(f"文件监控已启动: {temp_test_dir}")
|
|
47
|
+
else:
|
|
48
|
+
print(f"文件监控已在运行中: {monitor.root_dir}")
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"初始化文件监控出错: {e}")
|
|
51
|
+
|
|
52
|
+
# 加载规则文件
|
|
53
|
+
try:
|
|
54
|
+
from autocoder.common.rulefiles.autocoderrules_utils import get_rules, reset_rules_manager
|
|
55
|
+
reset_rules_manager() # 重置规则管理器,确保测试隔离性
|
|
56
|
+
rules = get_rules(temp_test_dir)
|
|
57
|
+
print(f"已加载规则: {len(rules)} 条")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"加载规则出错: {e}")
|
|
60
|
+
|
|
61
|
+
return temp_test_dir
|
|
62
|
+
|
|
63
|
+
# Pytest Fixture: 加载tokenizer
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def load_tokenizer_fixture(setup_file_monitor):
|
|
66
|
+
"""加载tokenizer,必须在FileMonitor和rules初始化之后"""
|
|
67
|
+
from autocoder.auto_coder_runner import load_tokenizer
|
|
68
|
+
load_tokenizer()
|
|
69
|
+
print("Tokenizer加载完成")
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
# Pytest Fixture: mock的LLM
|
|
73
|
+
@pytest.fixture
|
|
74
|
+
def mock_llm():
|
|
75
|
+
"""创建模拟的LLM对象"""
|
|
76
|
+
mock = MagicMock()
|
|
77
|
+
# 模拟chat_oai方法返回结果
|
|
78
|
+
mock.chat_oai.return_value = [{"role": "assistant", "content": "这是模拟的LLM回答"}]
|
|
79
|
+
# 设置必要的属性
|
|
80
|
+
mock.default_model_name = "mock_model"
|
|
81
|
+
return mock
|
|
82
|
+
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
from typing import Dict, Type, ClassVar, Optional, List
|
|
2
|
+
import logging
|
|
3
|
+
import inspect
|
|
4
|
+
from .types import BaseTool, ToolDescription, ToolExample
|
|
5
|
+
from .tools.base_tool_resolver import BaseToolResolver
|
|
6
|
+
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolRegistry:
|
|
11
|
+
"""
|
|
12
|
+
工具注册表,用于管理工具和对应的解析器
|
|
13
|
+
"""
|
|
14
|
+
# 类变量,存储工具和解析器的映射关系
|
|
15
|
+
_tool_resolver_map: ClassVar[Dict[Type[BaseTool], Type[BaseToolResolver]]] = {}
|
|
16
|
+
_tag_model_map: ClassVar[Dict[str, Type[BaseTool]]] = {}
|
|
17
|
+
|
|
18
|
+
# 存储工具描述和示例
|
|
19
|
+
_tool_descriptions: ClassVar[Dict[str, ToolDescription]] = {}
|
|
20
|
+
_tool_examples: ClassVar[Dict[str, ToolExample]] = {}
|
|
21
|
+
|
|
22
|
+
# 存储工具使用指南
|
|
23
|
+
_tool_use_guidelines: ClassVar[Dict[str, str]] = {}
|
|
24
|
+
|
|
25
|
+
# 存储工具用例文档
|
|
26
|
+
_tools_case_doc: ClassVar[Dict[str, Dict[str, object]]] = {}
|
|
27
|
+
|
|
28
|
+
# 存储角色描述和通用描述
|
|
29
|
+
_role_descriptions: ClassVar[Dict[str, str]] = {}
|
|
30
|
+
|
|
31
|
+
# 存储默认工具集
|
|
32
|
+
_default_tools: ClassVar[Dict[str, Type[BaseTool]]] = {}
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def register_tool(cls, tool_tag: str, tool_cls: Type[BaseTool], resolver_cls: Type[BaseToolResolver],
|
|
36
|
+
description: ToolDescription, example: ToolExample, use_guideline: str = "") -> None:
|
|
37
|
+
"""
|
|
38
|
+
注册工具和对应的解析器
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
tool_tag: XML标签名称
|
|
42
|
+
tool_cls: 工具类
|
|
43
|
+
resolver_cls: 解析器类
|
|
44
|
+
description: 工具描述,包含title和body,将用于生成提示
|
|
45
|
+
example: 工具使用示例,包含title和body,将用于生成提示
|
|
46
|
+
use_guideline: 工具使用指南,提供给LLM关于如何正确使用该工具的具体指导
|
|
47
|
+
"""
|
|
48
|
+
if tool_cls in cls._tool_resolver_map:
|
|
49
|
+
logger.warning(f"工具 {tool_cls.__name__} 已经注册过,将被覆盖")
|
|
50
|
+
|
|
51
|
+
cls._tool_resolver_map[tool_cls] = resolver_cls
|
|
52
|
+
cls._tag_model_map[tool_tag] = tool_cls
|
|
53
|
+
|
|
54
|
+
# 注册描述和示例
|
|
55
|
+
cls._tool_descriptions[tool_tag] = description
|
|
56
|
+
cls._tool_examples[tool_tag] = example
|
|
57
|
+
|
|
58
|
+
# 注册使用指南
|
|
59
|
+
if use_guideline:
|
|
60
|
+
cls._tool_use_guidelines[tool_tag] = use_guideline
|
|
61
|
+
|
|
62
|
+
logger.info(f"成功注册工具: {tool_tag} -> {tool_cls.__name__} 使用解析器 {resolver_cls.__name__}")
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def register_default_tool(cls, tool_tag: str, tool_cls: Type[BaseTool], resolver_cls: Type[BaseToolResolver],
|
|
66
|
+
description: ToolDescription, example: ToolExample, use_guideline: str = "") -> None:
|
|
67
|
+
"""
|
|
68
|
+
注册默认工具和对应的解析器,同时记录在默认工具集中
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
tool_tag: XML标签名称
|
|
72
|
+
tool_cls: 工具类
|
|
73
|
+
resolver_cls: 解析器类
|
|
74
|
+
description: 工具描述,包含title和body,将用于生成提示
|
|
75
|
+
example: 工具使用示例,包含title和body,将用于生成提示
|
|
76
|
+
use_guideline: 工具使用指南,提供给LLM关于如何正确使用该工具的具体指导
|
|
77
|
+
"""
|
|
78
|
+
# 注册工具
|
|
79
|
+
cls.register_tool(tool_tag, tool_cls, resolver_cls, description, example, use_guideline)
|
|
80
|
+
|
|
81
|
+
# 添加到默认工具集
|
|
82
|
+
cls._default_tools[tool_tag] = tool_cls
|
|
83
|
+
logger.info(f"已将工具 {tool_tag} 添加到默认工具集")
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def register_tools_case_doc(cls, case_name: str, tools: List[str], doc: str) -> None:
|
|
87
|
+
"""
|
|
88
|
+
注册工具用例文档
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
case_name: 用例名称
|
|
92
|
+
tools: 工具名称列表
|
|
93
|
+
doc: 用例文档说明
|
|
94
|
+
"""
|
|
95
|
+
cls._tools_case_doc[case_name] = {
|
|
96
|
+
"tools": tools,
|
|
97
|
+
"doc": doc
|
|
98
|
+
}
|
|
99
|
+
logger.info(f"成功注册工具用例文档: {case_name}, 包含工具: {', '.join(tools)}")
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def register_unified_tool(cls, tool_tag: str, tool_info: Dict) -> None:
|
|
103
|
+
"""
|
|
104
|
+
统一注册工具的所有相关信息
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
tool_tag: 工具标签
|
|
108
|
+
tool_info: 工具信息字典,包含以下字段:
|
|
109
|
+
- tool_cls: 工具类
|
|
110
|
+
- resolver_cls: 解析器类
|
|
111
|
+
- description: 工具描述对象
|
|
112
|
+
- example: 工具示例对象
|
|
113
|
+
- use_guideline: 工具使用指南(可选)
|
|
114
|
+
- category: 工具分类(可选)
|
|
115
|
+
- is_default: 是否为默认工具(可选,默认为False)
|
|
116
|
+
- case_docs: 关联的用例文档列表(可选)
|
|
117
|
+
"""
|
|
118
|
+
# 提取工具信息
|
|
119
|
+
tool_cls = tool_info.get("tool_cls")
|
|
120
|
+
resolver_cls = tool_info.get("resolver_cls")
|
|
121
|
+
description = tool_info.get("description")
|
|
122
|
+
example = tool_info.get("example")
|
|
123
|
+
use_guideline = tool_info.get("use_guideline", "")
|
|
124
|
+
is_default = tool_info.get("is_default", False)
|
|
125
|
+
case_docs = tool_info.get("case_docs", [])
|
|
126
|
+
|
|
127
|
+
# 验证必要字段
|
|
128
|
+
if not all([tool_cls, resolver_cls, description]):
|
|
129
|
+
logger.error(f"注册工具 {tool_tag} 失败:缺少必要信息")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
# 注册工具
|
|
133
|
+
if is_default:
|
|
134
|
+
cls.register_default_tool(
|
|
135
|
+
tool_tag=tool_tag,
|
|
136
|
+
tool_cls=tool_cls,
|
|
137
|
+
resolver_cls=resolver_cls,
|
|
138
|
+
description=description,
|
|
139
|
+
example=example,
|
|
140
|
+
use_guideline=use_guideline
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
cls.register_tool(
|
|
144
|
+
tool_tag=tool_tag,
|
|
145
|
+
tool_cls=tool_cls,
|
|
146
|
+
resolver_cls=resolver_cls,
|
|
147
|
+
description=description,
|
|
148
|
+
example=example,
|
|
149
|
+
use_guideline=use_guideline
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# 关联用例文档
|
|
153
|
+
for case_doc in case_docs:
|
|
154
|
+
if case_doc in cls._tools_case_doc:
|
|
155
|
+
if tool_tag not in cls._tools_case_doc[case_doc]["tools"]:
|
|
156
|
+
cls._tools_case_doc[case_doc]["tools"].append(tool_tag)
|
|
157
|
+
logger.info(f"将工具 {tool_tag} 添加到用例文档 {case_doc}")
|
|
158
|
+
|
|
159
|
+
logger.info(f"成功统一注册工具: {tool_tag}")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def unregister_tool(cls, tool_tag: str) -> bool:
|
|
164
|
+
"""
|
|
165
|
+
卸载一个已注册的工具
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
tool_tag: 工具标签
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
是否成功卸载
|
|
172
|
+
"""
|
|
173
|
+
tool_cls = cls._tag_model_map.get(tool_tag)
|
|
174
|
+
if tool_cls is None:
|
|
175
|
+
logger.warning(f"找不到要卸载的工具: {tool_tag}")
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
# 清除工具关联数据
|
|
179
|
+
if tool_cls in cls._tool_resolver_map:
|
|
180
|
+
del cls._tool_resolver_map[tool_cls]
|
|
181
|
+
if tool_tag in cls._tag_model_map:
|
|
182
|
+
del cls._tag_model_map[tool_tag]
|
|
183
|
+
if tool_tag in cls._tool_descriptions:
|
|
184
|
+
del cls._tool_descriptions[tool_tag]
|
|
185
|
+
if tool_tag in cls._tool_examples:
|
|
186
|
+
del cls._tool_examples[tool_tag]
|
|
187
|
+
if tool_tag in cls._tool_use_guidelines:
|
|
188
|
+
del cls._tool_use_guidelines[tool_tag]
|
|
189
|
+
if tool_tag in cls._default_tools:
|
|
190
|
+
del cls._default_tools[tool_tag]
|
|
191
|
+
|
|
192
|
+
# 清除工具在用例文档中的引用
|
|
193
|
+
for case_name, case_data in list(cls._tools_case_doc.items()):
|
|
194
|
+
if tool_tag in case_data["tools"]:
|
|
195
|
+
# 如果该工具是用例中唯一的工具,删除整个用例
|
|
196
|
+
if len(case_data["tools"]) == 1:
|
|
197
|
+
del cls._tools_case_doc[case_name]
|
|
198
|
+
logger.info(f"已删除依赖于工具 {tool_tag} 的用例文档: {case_name}")
|
|
199
|
+
else:
|
|
200
|
+
# 否则只从工具列表中移除该工具
|
|
201
|
+
case_data["tools"].remove(tool_tag)
|
|
202
|
+
logger.info(f"已从用例 {case_name} 中移除工具 {tool_tag}")
|
|
203
|
+
|
|
204
|
+
logger.info(f"成功卸载工具: {tool_tag}")
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def reset_to_default_tools(cls) -> None:
|
|
209
|
+
"""
|
|
210
|
+
重置为仅包含默认工具的状态,移除所有非默认工具
|
|
211
|
+
"""
|
|
212
|
+
# 获取需要保留的工具标签
|
|
213
|
+
default_tags = set(cls._default_tools.keys())
|
|
214
|
+
# 获取所有当前工具标签
|
|
215
|
+
all_tags = set(cls._tag_model_map.keys())
|
|
216
|
+
# 计算要删除的标签
|
|
217
|
+
tags_to_remove = all_tags - default_tags
|
|
218
|
+
|
|
219
|
+
# 移除非默认工具
|
|
220
|
+
for tag in tags_to_remove:
|
|
221
|
+
cls.unregister_tool(tag)
|
|
222
|
+
|
|
223
|
+
logger.info(f"工具注册表已重置为默认工具集,保留了 {len(default_tags)} 个默认工具")
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def is_default_tool(cls, tool_tag: str) -> bool:
|
|
227
|
+
"""
|
|
228
|
+
检查工具是否为默认工具
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
tool_tag: 工具标签
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
是否为默认工具
|
|
235
|
+
"""
|
|
236
|
+
return tool_tag in cls._default_tools
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def get_default_tools(cls) -> List[str]:
|
|
240
|
+
"""
|
|
241
|
+
获取所有默认工具标签
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
默认工具标签列表
|
|
245
|
+
"""
|
|
246
|
+
return list(cls._default_tools.keys())
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def register_role_description(cls, agent_type: str, description: str) -> None:
|
|
250
|
+
"""
|
|
251
|
+
注册代理角色描述
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
agent_type: 代理类型标识
|
|
255
|
+
description: 角色描述文本
|
|
256
|
+
"""
|
|
257
|
+
cls._role_descriptions[agent_type] = description
|
|
258
|
+
logger.info(f"成功注册角色描述: {agent_type}")
|
|
259
|
+
|
|
260
|
+
@classmethod
|
|
261
|
+
def get_role_description(cls, agent_type: str) -> Optional[str]:
|
|
262
|
+
"""
|
|
263
|
+
获取代理角色描述
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
agent_type: 代理类型标识
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
角色描述文本,如果未找到则返回None
|
|
270
|
+
"""
|
|
271
|
+
return cls._role_descriptions.get(agent_type)
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def get_tool_description(cls, tool_tag: str) -> Optional[ToolDescription]:
|
|
275
|
+
"""
|
|
276
|
+
获取工具描述
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
tool_tag: 工具标签
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
工具描述对象,如果未找到则返回None
|
|
283
|
+
"""
|
|
284
|
+
return cls._tool_descriptions.get(tool_tag)
|
|
285
|
+
|
|
286
|
+
@classmethod
|
|
287
|
+
def get_tool_example(cls, tool_tag: str) -> Optional[ToolExample]:
|
|
288
|
+
"""
|
|
289
|
+
获取工具示例
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
tool_tag: 工具标签
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
工具示例对象,如果未找到则返回None
|
|
296
|
+
"""
|
|
297
|
+
return cls._tool_examples.get(tool_tag)
|
|
298
|
+
|
|
299
|
+
@classmethod
|
|
300
|
+
def get_tool_use_guideline(cls, tool_tag: str) -> Optional[str]:
|
|
301
|
+
"""
|
|
302
|
+
获取工具使用指南
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
tool_tag: 工具标签
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
工具使用指南,如果未找到则返回None
|
|
309
|
+
"""
|
|
310
|
+
return cls._tool_use_guidelines.get(tool_tag)
|
|
311
|
+
|
|
312
|
+
@classmethod
|
|
313
|
+
def get_tools_case_doc(cls, case_name: str) -> Optional[Dict[str, object]]:
|
|
314
|
+
"""
|
|
315
|
+
获取工具用例文档
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
case_name: 用例名称
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
工具用例文档,包含tools和doc字段,如果未找到则返回None
|
|
322
|
+
"""
|
|
323
|
+
return cls._tools_case_doc.get(case_name)
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
def get_all_tool_descriptions(cls) -> Dict[str, ToolDescription]:
|
|
327
|
+
"""
|
|
328
|
+
获取所有工具描述
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
工具描述字典,key为工具标签,value为描述对象
|
|
332
|
+
"""
|
|
333
|
+
return cls._tool_descriptions.copy()
|
|
334
|
+
|
|
335
|
+
@classmethod
|
|
336
|
+
def get_all_tool_examples(cls) -> Dict[str, ToolExample]:
|
|
337
|
+
"""
|
|
338
|
+
获取所有工具示例
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
工具示例字典,key为工具标签,value为示例对象
|
|
342
|
+
"""
|
|
343
|
+
return cls._tool_examples.copy()
|
|
344
|
+
|
|
345
|
+
@classmethod
|
|
346
|
+
def get_all_tool_use_guidelines(cls) -> Dict[str, str]:
|
|
347
|
+
"""
|
|
348
|
+
获取所有工具使用指南
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
工具使用指南字典,key为工具标签,value为使用指南文本
|
|
352
|
+
"""
|
|
353
|
+
return cls._tool_use_guidelines.copy()
|
|
354
|
+
|
|
355
|
+
@classmethod
|
|
356
|
+
def get_all_tools_case_docs(cls) -> Dict[str, Dict[str, object]]:
|
|
357
|
+
"""
|
|
358
|
+
获取所有工具用例文档
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
工具用例文档字典,key为用例名称,value为包含tools和doc字段的字典
|
|
362
|
+
"""
|
|
363
|
+
return cls._tools_case_doc.copy()
|
|
364
|
+
|
|
365
|
+
@classmethod
|
|
366
|
+
def get_resolver_for_tool(cls, tool_cls_or_instance) -> Type[BaseToolResolver]:
|
|
367
|
+
"""
|
|
368
|
+
获取工具对应的解析器类
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
tool_cls_or_instance: 工具类或工具实例
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
解析器类
|
|
375
|
+
"""
|
|
376
|
+
# 如果传入的是实例,获取其类
|
|
377
|
+
if not inspect.isclass(tool_cls_or_instance):
|
|
378
|
+
tool_cls = type(tool_cls_or_instance)
|
|
379
|
+
else:
|
|
380
|
+
tool_cls = tool_cls_or_instance
|
|
381
|
+
|
|
382
|
+
return cls._tool_resolver_map.get(tool_cls)
|
|
383
|
+
|
|
384
|
+
@classmethod
|
|
385
|
+
def get_model_for_tag(cls, tool_tag: str) -> Type[BaseTool]:
|
|
386
|
+
"""
|
|
387
|
+
根据标签获取对应的工具类
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
tool_tag: 工具标签
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
工具类
|
|
394
|
+
"""
|
|
395
|
+
return cls._tag_model_map.get(tool_tag)
|
|
396
|
+
|
|
397
|
+
@classmethod
|
|
398
|
+
def get_tool_resolver_map(cls) -> Dict[Type[BaseTool], Type[BaseToolResolver]]:
|
|
399
|
+
"""
|
|
400
|
+
获取工具和解析器的映射关系
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
工具和解析器的映射字典
|
|
404
|
+
"""
|
|
405
|
+
return cls._tool_resolver_map.copy()
|
|
406
|
+
|
|
407
|
+
@classmethod
|
|
408
|
+
def get_tag_model_map(cls) -> Dict[str, Type[BaseTool]]:
|
|
409
|
+
"""
|
|
410
|
+
获取标签和工具的映射关系
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
标签和工具的映射字典
|
|
414
|
+
"""
|
|
415
|
+
return cls._tag_model_map.copy()
|
|
416
|
+
|
|
417
|
+
@classmethod
|
|
418
|
+
def get_all_registered_tools(cls) -> List[str]:
|
|
419
|
+
"""
|
|
420
|
+
获取所有已注册的工具标签
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
工具标签列表
|
|
424
|
+
"""
|
|
425
|
+
return list(cls._tag_model_map.keys())
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
基础工具包,包含基础工具解析器和注册工具
|
|
3
|
+
"""
|
|
4
|
+
from .base_tool_resolver import BaseToolResolver
|
|
5
|
+
from .talk_to_tool_resolver import TalkToToolResolver
|
|
6
|
+
from .talk_to_group_tool_resolver import TalkToGroupToolResolver
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"BaseToolResolver",
|
|
10
|
+
"TalkToToolResolver",
|
|
11
|
+
"TalkToGroupToolResolver"
|
|
12
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from autocoder.agent.base_agentic.tools.base_tool_resolver import BaseToolResolver
|
|
3
|
+
from autocoder.agent.base_agentic.types import AskFollowupQuestionTool, ToolResult # Import ToolResult from types
|
|
4
|
+
from loguru import logger
|
|
5
|
+
import typing
|
|
6
|
+
from autocoder.common import AutoCoderArgs
|
|
7
|
+
from autocoder.run_context import get_run_context
|
|
8
|
+
from autocoder.events.event_manager_singleton import get_event_manager
|
|
9
|
+
from autocoder.events import event_content as EventContentCreator
|
|
10
|
+
from prompt_toolkit import PromptSession
|
|
11
|
+
from prompt_toolkit.styles import Style
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.text import Text
|
|
15
|
+
from autocoder.common.result_manager import ResultManager
|
|
16
|
+
|
|
17
|
+
if typing.TYPE_CHECKING:
|
|
18
|
+
from ..base_agent import BaseAgent
|
|
19
|
+
|
|
20
|
+
class AskFollowupQuestionToolResolver(BaseToolResolver):
|
|
21
|
+
def __init__(self, agent: Optional['BaseAgent'], tool: AskFollowupQuestionTool, args: AutoCoderArgs):
|
|
22
|
+
super().__init__(agent, tool, args)
|
|
23
|
+
self.tool: AskFollowupQuestionTool = tool # For type hinting
|
|
24
|
+
self.result_manager = ResultManager()
|
|
25
|
+
|
|
26
|
+
def resolve(self) -> ToolResult:
|
|
27
|
+
"""
|
|
28
|
+
Packages the question and options to be handled by the main loop/UI.
|
|
29
|
+
This resolver doesn't directly ask the user but prepares the data for it.
|
|
30
|
+
"""
|
|
31
|
+
question = self.tool.question
|
|
32
|
+
options = self.tool.options or []
|
|
33
|
+
options_text = "\n".join([f"{i+1}. {option}" for i, option in enumerate(options)])
|
|
34
|
+
if get_run_context().is_web():
|
|
35
|
+
answer = get_event_manager(
|
|
36
|
+
self.args.event_file).ask_user(prompt=question)
|
|
37
|
+
self.result_manager.append(content=answer + ("\n" + options_text if options_text else ""), meta={
|
|
38
|
+
"action": "ask_user",
|
|
39
|
+
"input": {
|
|
40
|
+
"question": question
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
return ToolResult(success=True, message="Follow-up question prepared.", content=answer)
|
|
44
|
+
|
|
45
|
+
console = Console()
|
|
46
|
+
|
|
47
|
+
# 创建一个醒目的问题面板
|
|
48
|
+
question_text = Text(question, style="bold cyan")
|
|
49
|
+
question_panel = Panel(
|
|
50
|
+
question_text,
|
|
51
|
+
title="[bold yellow]auto-coder.chat's Question[/bold yellow]",
|
|
52
|
+
border_style="blue",
|
|
53
|
+
expand=False
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# 显示问题面板
|
|
57
|
+
console.print(question_panel)
|
|
58
|
+
|
|
59
|
+
session = PromptSession(
|
|
60
|
+
message=self.printer.get_message_from_key('tool_ask_user'))
|
|
61
|
+
try:
|
|
62
|
+
answer = session.prompt()
|
|
63
|
+
except KeyboardInterrupt:
|
|
64
|
+
answer = ""
|
|
65
|
+
|
|
66
|
+
# The actual asking logic resides outside the resolver, typically in the agent's main loop
|
|
67
|
+
# or UI interaction layer. The resolver's job is to validate and package the request.
|
|
68
|
+
if not answer:
|
|
69
|
+
return ToolResult(success=False, message="Error: Question not answered.")
|
|
70
|
+
|
|
71
|
+
# Indicate success in preparing the question data
|
|
72
|
+
return ToolResult(success=True, message="Follow-up question prepared.", content=answer)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from autocoder.agent.base_agentic.tools.base_tool_resolver import BaseToolResolver
|
|
3
|
+
from autocoder.agent.base_agentic.types import AttemptCompletionTool, ToolResult # Import ToolResult from types
|
|
4
|
+
from loguru import logger
|
|
5
|
+
import typing
|
|
6
|
+
from autocoder.common import AutoCoderArgs
|
|
7
|
+
import os
|
|
8
|
+
import tempfile
|
|
9
|
+
|
|
10
|
+
if typing.TYPE_CHECKING:
|
|
11
|
+
from ..base_agent import BaseAgent
|
|
12
|
+
|
|
13
|
+
class AttemptCompletionToolResolver(BaseToolResolver):
|
|
14
|
+
def __init__(self, agent: Optional['BaseAgent'], tool: AttemptCompletionTool, args: AutoCoderArgs):
|
|
15
|
+
super().__init__(agent, tool, args)
|
|
16
|
+
self.tool: AttemptCompletionTool = tool # For type hinting
|
|
17
|
+
|
|
18
|
+
def resolve(self) -> ToolResult:
|
|
19
|
+
"""
|
|
20
|
+
Packages the completion result and optional command to signal task completion.
|
|
21
|
+
"""
|
|
22
|
+
result_text = self.tool.result
|
|
23
|
+
command = self.tool.command
|
|
24
|
+
|
|
25
|
+
logger.info(f"Resolving AttemptCompletionTool: Result='{result_text[:100]}...', Command='{command}'")
|
|
26
|
+
|
|
27
|
+
if not result_text:
|
|
28
|
+
return ToolResult(success=False, message="Error: Completion result cannot be empty.")
|
|
29
|
+
|
|
30
|
+
# The actual presentation of the result happens outside the resolver.
|
|
31
|
+
result_content = {
|
|
32
|
+
"result": result_text,
|
|
33
|
+
"command": command
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Indicate success in preparing the completion data
|
|
37
|
+
return ToolResult(success=True, message="Task completion attempted.", content=result_content)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, Optional, TYPE_CHECKING
|
|
3
|
+
from autocoder.common import AutoCoderArgs
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from autocoder.agent.base_agentic.base_agent import BaseAgent
|
|
7
|
+
from autocoder.agent.base_agentic.types import BaseTool, ToolResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseToolResolver(ABC):
|
|
11
|
+
"""
|
|
12
|
+
工具解析器的基类,所有工具解析器都应该继承此类
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, agent: Optional['BaseAgent'], tool: 'BaseTool', args: AutoCoderArgs):
|
|
15
|
+
"""
|
|
16
|
+
初始化解析器
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
agent: 代理实例,可能为None
|
|
20
|
+
tool: 工具实例
|
|
21
|
+
args: 其他需要的参数,如项目目录等
|
|
22
|
+
"""
|
|
23
|
+
self.agent = agent
|
|
24
|
+
self.tool = tool
|
|
25
|
+
self.args = args
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def resolve(self) -> 'ToolResult':
|
|
29
|
+
"""
|
|
30
|
+
执行工具逻辑,返回结果
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
ToolResult对象,表示成功或失败以及消息
|
|
34
|
+
"""
|
|
35
|
+
pass
|