full-stack-coding-assistant-agent 0.1.0__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.
@@ -0,0 +1,243 @@
1
+ """
2
+ Agent 智能选择器 - 基于 LLM 分析用户需求,判断需要运行哪些 Agent
3
+ """
4
+
5
+ import json
6
+ import re
7
+ from typing import Dict, Optional
8
+
9
+ from model.model_router import ModelRouter
10
+ from utils.logger import error, info, warning
11
+
12
+
13
+ class AgentSelector:
14
+ """
15
+ 智能选择需要运行的 Agent
16
+
17
+ 使用轻量模型分析用户需求,输出需要运行的 Agent 列表。
18
+ 同时处理依赖修正(如选中 frontend 时必须也选中 backend)。
19
+ """
20
+
21
+ # 所有可用的 Agent 类型
22
+ ALL_AGENTS = ["backend", "frontend", "test", "audit"]
23
+
24
+ # Agent 中文描述(用于 prompt)
25
+ AGENT_DESCRIPTIONS = {
26
+ "backend": "后端开发(生成/修改 API 接口、数据库、业务逻辑)",
27
+ "frontend": "前端开发(生成/修改 UI 组件、页面、样式)",
28
+ "test": "测试(生成/修改单元测试、集成测试、E2E 测试)",
29
+ "audit": "代码审计(检查代码质量、安全漏洞、性能问题)",
30
+ }
31
+
32
+ def __init__(self, model_router: ModelRouter):
33
+ """
34
+ 初始化选择器
35
+
36
+ Args:
37
+ model_router: 模型路由器实例(使用轻量模型以节省成本)
38
+ """
39
+ self.model_router = model_router
40
+ # 使用轻量模型进行 Agent 选择
41
+ self.model = model_router.get_model_for_agent("frontend") # frontend 用轻量模型
42
+
43
+ def select_agents(
44
+ self,
45
+ user_input: str,
46
+ existing_code_summary: str = "",
47
+ ) -> Dict:
48
+ """
49
+ 分析用户需求,返回需要运行的 Agent 列表
50
+
51
+ Args:
52
+ user_input: 用户输入的需求描述
53
+ existing_code_summary: 已有代码的摘要(可选)
54
+
55
+ Returns:
56
+ 字典 {
57
+ "agents": ["backend", "frontend"],
58
+ "reason": "需要修改登录接口和登录页面"
59
+ }
60
+ """
61
+ prompt = self._build_prompt(user_input, existing_code_summary)
62
+
63
+ try:
64
+ messages = [
65
+ {"role": "system", "content": self._get_system_prompt()},
66
+ {"role": "user", "content": prompt},
67
+ ]
68
+ response = self.model_router.chat(
69
+ messages=messages,
70
+ model=self.model,
71
+ temperature=0.1, # 低温度,确保输出稳定
72
+ max_tokens=512,
73
+ )
74
+
75
+ result = self._parse_response(response["content"])
76
+ result = self._fix_dependencies(result)
77
+ return result
78
+
79
+ except Exception as e:
80
+ error(f"Agent 选择失败,使用默认全部 Agent: {e}")
81
+ return {
82
+ "agents": self.ALL_AGENTS,
83
+ "reason": f"LLM 调用失败,默认运行全部 Agent: {e}",
84
+ }
85
+
86
+ def _get_system_prompt(self) -> str:
87
+ """获取系统提示词"""
88
+ return """你是一个智能任务分析器。你的任务是分析用户的需求描述,判断需要运行哪些智能体(Agent)来完成任务。
89
+
90
+ 请以严格的 JSON 格式输出结果,不要输出任何 JSON 以外的内容。"""
91
+
92
+ def _build_prompt(self, user_input: str, existing_code_summary: str) -> str:
93
+ """构建用户提示词"""
94
+ agent_list = "\n".join(
95
+ f"- {name}: {desc}" for name, desc in self.AGENT_DESCRIPTIONS.items()
96
+ )
97
+
98
+ prompt = f"""分析以下用户需求,判断需要运行哪些智能体(Agent):
99
+
100
+ ## 用户需求
101
+ {user_input}
102
+
103
+ ## 可选 Agent
104
+ {agent_list}
105
+
106
+ ## 输出要求
107
+ 请以以下 JSON 格式输出(只输出 JSON,不要有任何其他文字):
108
+ ```json
109
+ {{
110
+ "agents": ["backend", "frontend"],
111
+ "reason": "需要修改登录接口和登录页面"
112
+ }}
113
+ ```
114
+
115
+ ## 选择规则
116
+ 1. 如果需求涉及后端逻辑、API、数据库,选择 backend
117
+ 2. 如果需求涉及前端页面、UI、交互,选择 frontend
118
+ 3. 如果需求明确提到测试、测试用例,选择 test
119
+ 4. 如果需求提到代码审查、安全检查、性能优化,选择 audit
120
+ 5. 如果不确定,选择全部 Agent
121
+ 6. frontend 依赖 backend 的输出(API 契约),如果选中 frontend 建议也选中 backend
122
+ 7. test 依赖 backend 和 frontend 的代码,如果选中 test 建议也选中 backend 和 frontend
123
+ """
124
+
125
+ if existing_code_summary:
126
+ prompt += f"\n## 已有代码摘要\n{existing_code_summary}\n"
127
+
128
+ prompt += "\n请只输出 JSON,不要有任何其他文字:\n"
129
+ return prompt
130
+
131
+ def _parse_response(self, response: str) -> Dict:
132
+ """
133
+ 解析 LLM 返回的 JSON
134
+
135
+ Args:
136
+ response: LLM 返回的文本
137
+
138
+ Returns:
139
+ 解析后的字典,失败时返回默认全部 Agent
140
+ """
141
+ if not response:
142
+ warning("Agent 选择器收到空响应,使用默认全部 Agent")
143
+ return {
144
+ "agents": self.ALL_AGENTS,
145
+ "reason": "LLM 返回空响应",
146
+ }
147
+
148
+ # 尝试提取 JSON(可能被 ```json ``` 包裹)
149
+ json_match = re.search(r"\{.*\}", response, re.DOTALL)
150
+ if not json_match:
151
+ warning(f"无法从响应中解析 JSON: {response[:200]}")
152
+ return {
153
+ "agents": self.ALL_AGENTS,
154
+ "reason": "无法解析 LLM 响应,默认运行全部 Agent",
155
+ }
156
+
157
+ try:
158
+ data = json.loads(json_match.group())
159
+
160
+ # 验证 agents 字段
161
+ agents = data.get("agents", [])
162
+ if not isinstance(agents, list) or len(agents) == 0:
163
+ warning(f"agents 字段无效: {agents}")
164
+ agents = self.ALL_AGENTS
165
+
166
+ # 过滤掉未知的 Agent 类型
167
+ agents = [a for a in agents if a in self.ALL_AGENTS]
168
+ if not agents:
169
+ agents = self.ALL_AGENTS
170
+
171
+ return {
172
+ "agents": agents,
173
+ "reason": data.get("reason", "用户需求涉及多个方面"),
174
+ }
175
+
176
+ except json.JSONDecodeError as e:
177
+ warning(f"JSON 解析失败: {e}, 响应: {response[:200]}")
178
+ return {
179
+ "agents": self.ALL_AGENTS,
180
+ "reason": "JSON 解析失败,默认运行全部 Agent",
181
+ }
182
+
183
+ def _fix_dependencies(self, result: Dict) -> Dict:
184
+ """
185
+ 修正依赖关系:
186
+ - 如果选中 frontend 但未选中 backend,自动添加 backend
187
+ - 如果选中 test 但未选中 backend/frontend,自动添加
188
+ - 如果选中 audit 但未选中其他,也添加(audit 需要代码来审计)
189
+ """
190
+ agents = set(result["agents"])
191
+
192
+ # frontend 依赖 backend(需要 API 契约)
193
+ if "frontend" in agents and "backend" not in agents:
194
+ info("依赖修正: frontend 需要 backend,自动添加 backend")
195
+ agents.add("backend")
196
+
197
+ # test 依赖 backend 和 frontend
198
+ if "test" in agents:
199
+ if "backend" not in agents:
200
+ info("依赖修正: test 需要 backend,自动添加 backend")
201
+ agents.add("backend")
202
+ if "frontend" not in agents:
203
+ info("依赖修正: test 需要 frontend,自动添加 frontend")
204
+ agents.add("frontend")
205
+
206
+ # audit 需要代码来审计
207
+ if "audit" in agents and len(agents) == 1:
208
+ info("依赖修正: audit 需要代码来审计,自动添加 backend 和 frontend")
209
+ agents.add("backend")
210
+ agents.add("frontend")
211
+
212
+ result["agents"] = sorted(agents)
213
+ return result
214
+
215
+ @staticmethod
216
+ def format_existing_code_summary(output_dir: Optional[str]) -> str:
217
+ """
218
+ 生成已有代码的简短摘要(用于注入到选择器的 prompt)
219
+
220
+ Args:
221
+ output_dir: 输出目录路径
222
+
223
+ Returns:
224
+ 代码摘要字符串(文件名列表)
225
+ """
226
+ if not output_dir:
227
+ return ""
228
+
229
+ from pathlib import Path
230
+
231
+ path = Path(output_dir)
232
+ if not path.exists():
233
+ return ""
234
+
235
+ parts = []
236
+ for sub_dir in ["backend", "frontend", "tests"]:
237
+ target = path / sub_dir
238
+ if target.exists():
239
+ files = [f.name for f in target.iterdir() if f.is_file()]
240
+ if files:
241
+ parts.append(f"{sub_dir}: {', '.join(files[:5])}")
242
+
243
+ return "\n".join(parts)
@@ -0,0 +1,143 @@
1
+ """
2
+ 配置验证工具 - 检查必需的环境变量和配置
3
+ 支持 Python 3.13+ 的类型检查
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ from typing import List, Tuple
9
+
10
+ from utils.logger import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class ConfigValidator:
16
+ """配置验证器"""
17
+
18
+ # 必需的环境变量
19
+ REQUIRED_ENVS = [
20
+ "TENCENT_API_KEY",
21
+ ]
22
+
23
+ # 可选的环境变量及其默认值
24
+ OPTIONAL_ENVS = {
25
+ "TENCENT_API_BASE": "https://api.hunyuan.cloud.tencent.com/hyllm/v1",
26
+ "SQLITE_DB_PATH": "./context.db",
27
+ "CODEBUDDY_CLI_PATH": "codebuddy",
28
+ "CODEBUDDY_TIMEOUT": "60",
29
+ "LOG_LEVEL": "INFO",
30
+ "DEBUG": "false",
31
+ }
32
+
33
+ # API Key 格式验证
34
+ API_KEY_PATTERNS = {
35
+ "TENCENT_API_KEY": r"^sk-", # 腾讯混元 API Key 通常以 sk- 开头
36
+ }
37
+
38
+ @classmethod
39
+ def validate_envs(cls, exit_on_error: bool = True) -> Tuple[bool, List[str]]:
40
+ """
41
+ 验证环境变量配置
42
+
43
+ Args:
44
+ exit_on_error: 验证失败时是否退出程序
45
+
46
+ Returns:
47
+ (是否通过验证, 错误信息列表)
48
+ """
49
+ errors = []
50
+ warnings = []
51
+
52
+ # 检查必需的环境变量
53
+ for env_name in cls.REQUIRED_ENVS:
54
+ value = os.getenv(env_name)
55
+ if not value or value == f"YOUR_{env_name}_HERE":
56
+ errors.append(f"缺少必需的环境变量: {env_name}")
57
+ else:
58
+ # 验证 API Key 格式
59
+ if env_name in cls.API_KEY_PATTERNS:
60
+ import re
61
+
62
+ pattern = cls.API_KEY_PATTERNS[env_name]
63
+ if not re.match(pattern, value):
64
+ warnings.append(f"{env_name} 格式可能不正确(期望以 sk- 开头)")
65
+
66
+ # 检查可选的环境变量
67
+ for env_name, default_value in cls.OPTIONAL_ENVS.items():
68
+ value = os.getenv(env_name)
69
+ if not value:
70
+ warnings.append(
71
+ f"可选环境变量 {env_name} 未设置,将使用默认值: {default_value}"
72
+ )
73
+
74
+ # 输出结果
75
+ if warnings:
76
+ for warning in warnings:
77
+ logger.warning(warning)
78
+
79
+ if errors:
80
+ for error in errors:
81
+ logger.error(error)
82
+
83
+ if exit_on_error:
84
+ logger.error("配置验证失败,程序退出")
85
+ sys.exit(1)
86
+ return False, errors
87
+
88
+ logger.info("✅ 配置验证通过")
89
+ return True, []
90
+
91
+ @classmethod
92
+ def print_config_summary(cls):
93
+ """打印配置摘要"""
94
+ print("=" * 60)
95
+ print("配置摘要")
96
+ print("=" * 60)
97
+
98
+ # 必需的环境变量
99
+ print("\n【必需配置】")
100
+ for env_name in cls.REQUIRED_ENVS:
101
+ value = os.getenv(env_name)
102
+ if value and value != f"YOUR_{env_name}_HERE":
103
+ # 隐藏 API Key 的大部分内容
104
+ if "API_KEY" in env_name:
105
+ masked_value = value[:10] + "*" * (len(value) - 10)
106
+ print(f" {env_name}: {masked_value}")
107
+ else:
108
+ print(f" {env_name}: {value}")
109
+ else:
110
+ print(f" {env_name}: ❌ 未配置")
111
+
112
+ # 可选的环境变量
113
+ print("\n【可选配置】")
114
+ for env_name, default_value in cls.OPTIONAL_ENVS.items():
115
+ value = os.getenv(env_name, default_value)
116
+ print(f" {env_name}: {value}")
117
+
118
+ print("=" * 60)
119
+
120
+
121
+ def validate_config(exit_on_error: bool = True) -> bool:
122
+ """
123
+ 便捷函数:验证配置
124
+
125
+ Args:
126
+ exit_on_error: 验证失败时是否退出程序
127
+
128
+ Returns:
129
+ 是否通过验证
130
+ """
131
+ return ConfigValidator.validate_envs(exit_on_error)[0]
132
+
133
+
134
+ def print_config():
135
+ """便捷函数:打印配置摘要"""
136
+ ConfigValidator.print_config_summary()
137
+
138
+
139
+ if __name__ == "__main__":
140
+ # 单独运行此脚本时,打印配置摘要
141
+ print_config()
142
+ print("\n" + "=" * 60)
143
+ validate_config(exit_on_error=False)
utils/logger.py ADDED
@@ -0,0 +1,95 @@
1
+ """
2
+ 日志工具 - 提供统一的日志记录功能
3
+ 支持 Python 3.13+ 的最新日志特性
4
+ """
5
+
6
+ import logging
7
+ import os
8
+ from typing import Optional
9
+
10
+
11
+ def setup_logger(
12
+ name: str = "coding_agent",
13
+ log_level: Optional[str] = None,
14
+ log_file: Optional[str] = None,
15
+ ) -> logging.Logger:
16
+ """
17
+ 设置日志记录器
18
+
19
+ Args:
20
+ name: 日志记录器名称
21
+ log_level: 日志级别 (DEBUG/INFO/WARNING/ERROR)
22
+ log_file: 日志文件路径
23
+
24
+ Returns:
25
+ 配置好的 Logger 实例
26
+ """
27
+ # 从环境变量读取配置
28
+ if log_level is None:
29
+ log_level = os.getenv("LOG_LEVEL", "INFO")
30
+
31
+ if log_file is None:
32
+ log_file = os.getenv("LOG_FILE", "logs/app.log")
33
+
34
+ # 创建日志记录器
35
+ logger = logging.getLogger(name)
36
+ logger.setLevel(getattr(logging, log_level.upper()))
37
+
38
+ # 避免重复添加 handler
39
+ if logger.handlers:
40
+ return logger
41
+
42
+ # 创建日志目录
43
+ log_dir = os.path.dirname(log_file)
44
+ if log_dir and not os.path.exists(log_dir):
45
+ os.makedirs(log_dir, exist_ok=True)
46
+
47
+ # 日志格式(Python 3.13+ 优化)
48
+ formatter = logging.Formatter(
49
+ fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
50
+ datefmt="%Y-%m-%d %H:%M:%S",
51
+ )
52
+
53
+ # 文件 Handler(带轮转)
54
+ file_handler = logging.FileHandler(log_file, encoding="utf-8")
55
+ file_handler.setLevel(getattr(logging, log_level.upper()))
56
+ file_handler.setFormatter(formatter)
57
+ logger.addHandler(file_handler)
58
+
59
+ # 控制台 Handler
60
+ console_handler = logging.StreamHandler()
61
+ console_handler.setLevel(getattr(logging, log_level.upper()))
62
+ console_handler.setFormatter(formatter)
63
+ logger.addHandler(console_handler)
64
+
65
+ return logger
66
+
67
+
68
+ # 全局日志记录器
69
+ logger = setup_logger()
70
+
71
+
72
+ def get_logger(name: str) -> logging.Logger:
73
+ """获取指定名称的日志记录器"""
74
+ return setup_logger(name)
75
+
76
+
77
+ # 便捷函数
78
+ def debug(msg: str, *args, **kwargs):
79
+ """记录 DEBUG 日志"""
80
+ logger.debug(msg, *args, **kwargs)
81
+
82
+
83
+ def info(msg: str, *args, **kwargs):
84
+ """记录 INFO 日志"""
85
+ logger.info(msg, *args, **kwargs)
86
+
87
+
88
+ def warning(msg: str, *args, **kwargs):
89
+ """记录 WARNING 日志"""
90
+ logger.warning(msg, *args, **kwargs)
91
+
92
+
93
+ def error(msg: str, *args, **kwargs):
94
+ """记录 ERROR 日志"""
95
+ logger.error(msg, *args, **kwargs)