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.
- agents/__init__.py +0 -0
- agents/audit_agent.py +223 -0
- agents/backend_agent.py +179 -0
- agents/base_agent.py +406 -0
- agents/frontend_agent.py +148 -0
- agents/test_agent.py +155 -0
- coordinator/__init__.py +0 -0
- coordinator/coordinator.py +452 -0
- coordinator/dag.py +147 -0
- executor/__init__.py +0 -0
- executor/cb_integration.py +160 -0
- full_stack_coding_assistant_agent/__init__.py +6 -0
- full_stack_coding_assistant_agent/cli.py +10 -0
- full_stack_coding_assistant_agent/main.py +686 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/METADATA +849 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/RECORD +31 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/WHEEL +5 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/entry_points.txt +2 -0
- full_stack_coding_assistant_agent-0.1.0.dist-info/top_level.txt +7 -0
- model/__init__.py +0 -0
- model/config.py +62 -0
- model/model_router.py +150 -0
- storage/__init__.py +0 -0
- storage/context_db.py +274 -0
- utils/__init__.py +0 -0
- utils/agent_selector.py +243 -0
- utils/config_validator.py +143 -0
- utils/logger.py +95 -0
- utils/output_manager.py +1572 -0
- utils/pdf_reader.py +122 -0
- utils/version.py +188 -0
agents/base_agent.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent 基类 - 定义所有智能体的通用接口
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from model.model_router import ModelRouter
|
|
11
|
+
from storage.context_db import ContextDB
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseAgent(ABC):
|
|
15
|
+
"""智能体抽象基类"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
agent_type: str,
|
|
20
|
+
model_router: ModelRouter,
|
|
21
|
+
db: ContextDB,
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
初始化 Agent
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
agent_type: Agent 类型标识 (frontend/backend/test/audit)
|
|
28
|
+
model_router: 模型路由器实例
|
|
29
|
+
db: 数据库实例
|
|
30
|
+
"""
|
|
31
|
+
self.agent_type = agent_type
|
|
32
|
+
self.model_router = model_router
|
|
33
|
+
self.db = db
|
|
34
|
+
self.model = self.model_router.get_model_for_agent(agent_type)
|
|
35
|
+
self._output_dir: Optional[Path] = None # 输出目录,由 Coordinator 注入
|
|
36
|
+
self._usage_log: List[Dict] = [] # 每步 LLM 调用的 token 用量记录
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def get_system_prompt(self) -> str:
|
|
40
|
+
"""获取系统提示词 - 子类必须实现"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def execute(self, task_id: str, context: Dict) -> Dict:
|
|
45
|
+
"""
|
|
46
|
+
执行任务 - 子类必须实现
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
task_id: 任务 ID
|
|
50
|
+
context: 任务上下文(包含依赖的任务结果)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
执行结果字典
|
|
54
|
+
"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
def _call_llm(self, user_prompt: str, temperature: float = 0.7) -> str:
|
|
58
|
+
"""
|
|
59
|
+
调用 LLM 的通用方法
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
user_prompt: 用户提示词
|
|
63
|
+
temperature: 温度参数
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
模型生成的文本
|
|
67
|
+
"""
|
|
68
|
+
messages = [
|
|
69
|
+
{"role": "system", "content": self.get_system_prompt()},
|
|
70
|
+
{"role": "user", "content": user_prompt},
|
|
71
|
+
]
|
|
72
|
+
result = self.model_router.chat(
|
|
73
|
+
messages=messages,
|
|
74
|
+
model=self.model,
|
|
75
|
+
temperature=temperature,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# 记录 token 用量
|
|
79
|
+
usage = result.get("usage", {})
|
|
80
|
+
if usage:
|
|
81
|
+
self._usage_log.append(
|
|
82
|
+
{
|
|
83
|
+
"step": len(self._usage_log) + 1,
|
|
84
|
+
"model": result.get("model", self.model),
|
|
85
|
+
"prompt_tokens": usage.get("prompt_tokens", 0),
|
|
86
|
+
"completion_tokens": usage.get("completion_tokens", 0),
|
|
87
|
+
"total_tokens": usage.get("total_tokens", 0),
|
|
88
|
+
"latency_ms": result.get("latency_ms", 0),
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return result["content"]
|
|
93
|
+
|
|
94
|
+
def _check_dependencies(
|
|
95
|
+
self,
|
|
96
|
+
context: Dict,
|
|
97
|
+
required_keys: List[str],
|
|
98
|
+
) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
检查任务依赖是否满足
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
context: 任务上下文
|
|
104
|
+
required_keys: 必需的上下文键列表
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
是否满足所有依赖
|
|
108
|
+
"""
|
|
109
|
+
for key in required_keys:
|
|
110
|
+
if key not in context:
|
|
111
|
+
return False
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
def _log_change(
|
|
115
|
+
self,
|
|
116
|
+
task_id: str,
|
|
117
|
+
file_path: str,
|
|
118
|
+
change_type: str,
|
|
119
|
+
diff: Optional[str] = None,
|
|
120
|
+
):
|
|
121
|
+
"""记录代码变更到数据库"""
|
|
122
|
+
self.db.log_code_change(
|
|
123
|
+
task_id=task_id,
|
|
124
|
+
agent_type=self.agent_type,
|
|
125
|
+
file_path=file_path,
|
|
126
|
+
change_type=change_type,
|
|
127
|
+
diff=diff,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def _save_result(self, task_id: str, result: str):
|
|
131
|
+
"""保存任务结果到数据库"""
|
|
132
|
+
self.db.update_task_status(
|
|
133
|
+
task_id=task_id,
|
|
134
|
+
status="completed",
|
|
135
|
+
result=result,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------------------------ #
|
|
139
|
+
# 输出目录相关方法(由 Coordinator 注入 output_dir)
|
|
140
|
+
# ------------------------------------------------------------------ #
|
|
141
|
+
|
|
142
|
+
def _set_output_dir(self, output_dir: Path):
|
|
143
|
+
"""设置输出目录(由 Coordinator 在初始化后调用)"""
|
|
144
|
+
self._output_dir = output_dir
|
|
145
|
+
|
|
146
|
+
def _get_sub_dir(self) -> str:
|
|
147
|
+
"""
|
|
148
|
+
返回当前 Agent 对应的子目录名
|
|
149
|
+
子类可重写,默认使用 agent_type
|
|
150
|
+
"""
|
|
151
|
+
return self.agent_type
|
|
152
|
+
|
|
153
|
+
def _write_file(self, rel_path: str, content: str):
|
|
154
|
+
"""
|
|
155
|
+
将内容写入输出目录下的指定相对路径
|
|
156
|
+
|
|
157
|
+
仅在 _output_dir 已设置时实际写入文件系统;
|
|
158
|
+
否则仅记录日志(向后兼容)。
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
rel_path: 相对路径,如 "backend/main.py"
|
|
162
|
+
content: 文件内容
|
|
163
|
+
"""
|
|
164
|
+
if self._output_dir is None:
|
|
165
|
+
# 向后兼容:未设置输出目录时不写入文件
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
file_path = self._output_dir / rel_path
|
|
169
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
170
|
+
file_path.write_text(content, encoding="utf-8")
|
|
171
|
+
|
|
172
|
+
def _read_file(self, rel_path: str) -> str:
|
|
173
|
+
"""
|
|
174
|
+
读取输出目录下的指定文件
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
rel_path: 相对路径
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
文件内容;文件不存在时返回空字符串
|
|
181
|
+
"""
|
|
182
|
+
if self._output_dir is None:
|
|
183
|
+
return ""
|
|
184
|
+
|
|
185
|
+
file_path = self._output_dir / rel_path
|
|
186
|
+
if not file_path.exists():
|
|
187
|
+
return ""
|
|
188
|
+
return file_path.read_text(encoding="utf-8", errors="replace")
|
|
189
|
+
|
|
190
|
+
def _collect_existing_code(self) -> str:
|
|
191
|
+
"""
|
|
192
|
+
收集当前 Agent 对应子目录下所有已有代码,
|
|
193
|
+
用于构建迭代模式的 LLM prompt 上下文
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
格式化的代码块字符串,每个文件用 ### FILE: 标记
|
|
197
|
+
"""
|
|
198
|
+
if self._output_dir is None:
|
|
199
|
+
return ""
|
|
200
|
+
|
|
201
|
+
sub_dir = self._output_dir / self._get_sub_dir()
|
|
202
|
+
if not sub_dir.exists():
|
|
203
|
+
return ""
|
|
204
|
+
|
|
205
|
+
parts = []
|
|
206
|
+
for file_path in sorted(sub_dir.rglob("*")):
|
|
207
|
+
if file_path.is_file():
|
|
208
|
+
rel = file_path.relative_to(self._output_dir)
|
|
209
|
+
content = file_path.read_text(encoding="utf-8", errors="replace")
|
|
210
|
+
parts.append(f"### FILE: {rel}\n{content}\n")
|
|
211
|
+
return "\n".join(parts)
|
|
212
|
+
|
|
213
|
+
def _save_trace(
|
|
214
|
+
self,
|
|
215
|
+
task_id: str,
|
|
216
|
+
system_prompt: str,
|
|
217
|
+
user_prompt: str,
|
|
218
|
+
llm_response: str,
|
|
219
|
+
parsed_result: Dict,
|
|
220
|
+
):
|
|
221
|
+
"""
|
|
222
|
+
保存 Agent 执行 trace 到输出目录
|
|
223
|
+
|
|
224
|
+
记录完整的 LLM 输入输出以及 token 用量,以便审计和验证 Agent 的每一步行为。
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
task_id: 任务 ID
|
|
228
|
+
system_prompt: 系统提示词
|
|
229
|
+
user_prompt: 发送给 LLM 的完整用户提示词
|
|
230
|
+
llm_response: LLM 的原始返回
|
|
231
|
+
parsed_result: 解析后的结果(files, contracts 等)
|
|
232
|
+
"""
|
|
233
|
+
if self._output_dir is None:
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
from datetime import datetime
|
|
237
|
+
|
|
238
|
+
traces_dir = self._output_dir / "traces" / self.agent_type
|
|
239
|
+
traces_dir.mkdir(parents=True, exist_ok=True)
|
|
240
|
+
|
|
241
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
242
|
+
filename = f"{timestamp}_{task_id}.md"
|
|
243
|
+
file_path = traces_dir / filename
|
|
244
|
+
|
|
245
|
+
# 构建 token 用量部分
|
|
246
|
+
usage_lines = self._build_usage_section()
|
|
247
|
+
|
|
248
|
+
# 构建解析结果部分
|
|
249
|
+
parsed_lines = []
|
|
250
|
+
if parsed_result.get("files"):
|
|
251
|
+
parsed_lines.append("### 生成/修改的文件")
|
|
252
|
+
for f in parsed_result["files"]:
|
|
253
|
+
parsed_lines.append(f"- `{f}`")
|
|
254
|
+
parsed_lines.append("")
|
|
255
|
+
if parsed_result.get("contracts"):
|
|
256
|
+
parsed_lines.append("### 提取的 API 契约")
|
|
257
|
+
for c in parsed_result["contracts"]:
|
|
258
|
+
parsed_lines.append(
|
|
259
|
+
f"- **{c.get('method', '?')} {c.get('endpoint', '?')}**"
|
|
260
|
+
)
|
|
261
|
+
parsed_lines.append("")
|
|
262
|
+
if parsed_result.get("reports_count") is not None:
|
|
263
|
+
parsed_lines.append(
|
|
264
|
+
f"- 发现问题数: {parsed_result.get('reports_count', 0)}"
|
|
265
|
+
)
|
|
266
|
+
parsed_lines.append(f"- 高危问题: {parsed_result.get('high_severity', 0)}")
|
|
267
|
+
parsed_lines.append("")
|
|
268
|
+
if parsed_result.get("status"):
|
|
269
|
+
parsed_lines.append(f"- 状态: {parsed_result['status']}")
|
|
270
|
+
parsed_lines.append("")
|
|
271
|
+
|
|
272
|
+
content = f"""# Agent Trace: {self.agent_type}
|
|
273
|
+
|
|
274
|
+
- **时间**: {datetime.now().isoformat()}
|
|
275
|
+
- **任务ID**: {task_id}
|
|
276
|
+
- **Agent 类型**: {self.agent_type}
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
{usage_lines}
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 系统提示词 (System Prompt)
|
|
285
|
+
|
|
286
|
+
{system_prompt}
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 用户提示词 (User Prompt, sent to LLM)
|
|
291
|
+
|
|
292
|
+
{user_prompt}
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## LLM 原始输出 (Raw Response)
|
|
297
|
+
|
|
298
|
+
{llm_response}
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 解析结果 (Parsed Result)
|
|
303
|
+
|
|
304
|
+
{chr(10).join(parsed_lines) if parsed_lines else '(无解析结果)'}
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
"""
|
|
308
|
+
file_path.write_text(content, encoding="utf-8")
|
|
309
|
+
|
|
310
|
+
def _build_usage_section(self) -> str:
|
|
311
|
+
"""构建 token 用量报告 Markdown 段"""
|
|
312
|
+
if not self._usage_log:
|
|
313
|
+
return "## Token 用量\n\n(无用量数据)"
|
|
314
|
+
|
|
315
|
+
lines = [
|
|
316
|
+
"## Token 用量",
|
|
317
|
+
"",
|
|
318
|
+
"| Step | 模型 | Prompt Tokens | Completion Tokens | Total Tokens | 耗时(ms) |",
|
|
319
|
+
"|------|------|---------------|-------------------|--------------|----------|",
|
|
320
|
+
]
|
|
321
|
+
total_prompt = 0
|
|
322
|
+
total_completion = 0
|
|
323
|
+
total_tokens = 0
|
|
324
|
+
total_latency = 0.0
|
|
325
|
+
for entry in self._usage_log:
|
|
326
|
+
lines.append(
|
|
327
|
+
f"| {entry['step']} | {entry['model']} "
|
|
328
|
+
f"| {entry['prompt_tokens']:,} "
|
|
329
|
+
f"| {entry['completion_tokens']:,} "
|
|
330
|
+
f"| {entry['total_tokens']:,} "
|
|
331
|
+
f"| {entry['latency_ms']} |"
|
|
332
|
+
)
|
|
333
|
+
total_prompt += entry["prompt_tokens"]
|
|
334
|
+
total_completion += entry["completion_tokens"]
|
|
335
|
+
total_tokens += entry["total_tokens"]
|
|
336
|
+
total_latency += entry.get("latency_ms", 0)
|
|
337
|
+
|
|
338
|
+
lines.append(
|
|
339
|
+
f"| **合计** | — "
|
|
340
|
+
f"| **{total_prompt:,}** "
|
|
341
|
+
f"| **{total_completion:,}** "
|
|
342
|
+
f"| **{total_tokens:,}** "
|
|
343
|
+
f"| **{total_latency:.0f}** |"
|
|
344
|
+
)
|
|
345
|
+
return "\n".join(lines)
|
|
346
|
+
|
|
347
|
+
def get_usage_summary(self) -> Dict:
|
|
348
|
+
"""
|
|
349
|
+
获取当前 Agent 的 token 用量汇总
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
字典: {
|
|
353
|
+
"agent_type": str,
|
|
354
|
+
"steps": int,
|
|
355
|
+
"total_prompt_tokens": int,
|
|
356
|
+
"total_completion_tokens": int,
|
|
357
|
+
"total_tokens": int,
|
|
358
|
+
"total_latency_ms": float,
|
|
359
|
+
"details": List[Dict],
|
|
360
|
+
}
|
|
361
|
+
"""
|
|
362
|
+
total_prompt = sum(e["prompt_tokens"] for e in self._usage_log)
|
|
363
|
+
total_completion = sum(e["completion_tokens"] for e in self._usage_log)
|
|
364
|
+
total_tokens = sum(e["total_tokens"] for e in self._usage_log)
|
|
365
|
+
total_latency = sum(e.get("latency_ms", 0) for e in self._usage_log)
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
"agent_type": self.agent_type,
|
|
369
|
+
"steps": len(self._usage_log),
|
|
370
|
+
"total_prompt_tokens": total_prompt,
|
|
371
|
+
"total_completion_tokens": total_completion,
|
|
372
|
+
"total_tokens": total_tokens,
|
|
373
|
+
"total_latency_ms": round(total_latency, 1),
|
|
374
|
+
"details": list(self._usage_log),
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
def _parse_llm_code_output(self, text: str) -> Dict[str, str]:
|
|
378
|
+
"""
|
|
379
|
+
解析 LLM 返回内容中的文件块
|
|
380
|
+
|
|
381
|
+
期望格式:
|
|
382
|
+
### FILE: path/to/file.py
|
|
383
|
+
```python
|
|
384
|
+
# code here
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
也支持无代码块包裹的纯代码(直到下一个 ### FILE: 或文件结束)。
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
字典 {相对文件路径: 文件内容}
|
|
391
|
+
"""
|
|
392
|
+
result: Dict[str, str] = {}
|
|
393
|
+
pattern = r"### FILE:\s*(\S+)\n(.*?)(?=\n### FILE:|\Z)"
|
|
394
|
+
matches = re.findall(pattern, text, re.DOTALL)
|
|
395
|
+
|
|
396
|
+
if not matches:
|
|
397
|
+
# 无法解析出多文件,整段内容作为单个文件
|
|
398
|
+
return {"output.txt": text.strip()}
|
|
399
|
+
|
|
400
|
+
for file_path, content in matches:
|
|
401
|
+
# 去掉代码块标记(``` 包裹)
|
|
402
|
+
cleaned = re.sub(r"```[\w]*\n?", "", content)
|
|
403
|
+
cleaned = re.sub(r"```\s*$", "", cleaned)
|
|
404
|
+
result[file_path.strip()] = cleaned.strip()
|
|
405
|
+
|
|
406
|
+
return result
|
agents/frontend_agent.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
前端专家智能体
|
|
3
|
+
负责生成前端组件代码,并根据后端 API 契约调整接口调用
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict
|
|
7
|
+
|
|
8
|
+
from agents.base_agent import BaseAgent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FrontendAgent(BaseAgent):
|
|
12
|
+
"""前端专家 Agent"""
|
|
13
|
+
|
|
14
|
+
def get_system_prompt(self) -> str:
|
|
15
|
+
return """你是一位资深前端开发专家,精通 React/Vue/Angular 等主流框架。
|
|
16
|
+
你的职责是:
|
|
17
|
+
1. 根据 UI 需求生成高质量的前端组件代码
|
|
18
|
+
2. 编写类型安全的 TypeScript 代码
|
|
19
|
+
3. 实现响应式布局和交互逻辑
|
|
20
|
+
4. 根据后端 API 契约生成接口调用代码
|
|
21
|
+
|
|
22
|
+
输出格式要求:
|
|
23
|
+
- 每个文件用 ### FILE: 标记,格式如下:
|
|
24
|
+
### FILE: frontend/App.tsx
|
|
25
|
+
```tsx
|
|
26
|
+
// code here
|
|
27
|
+
```
|
|
28
|
+
- 使用函数式组件和 Hooks
|
|
29
|
+
- 包含完整的 TypeScript 类型定义
|
|
30
|
+
- 代码必须可直接运行
|
|
31
|
+
|
|
32
|
+
【重要】必须同时生成前端项目的工程配置文件,让项目可以 npm install && npm start 直接运行:
|
|
33
|
+
- frontend/package.json(含 react、react-dom、react-scripts 等必要依赖)
|
|
34
|
+
- frontend/tsconfig.json
|
|
35
|
+
- frontend/public/index.html(入口 HTML)
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def execute(self, task_id: str, context: Dict) -> Dict:
|
|
39
|
+
"""
|
|
40
|
+
执行前端开发任务
|
|
41
|
+
|
|
42
|
+
流程:
|
|
43
|
+
1. 获取后端 API 契约(如果有)
|
|
44
|
+
2. 如果是迭代模式,读取已有代码注入 prompt
|
|
45
|
+
3. 调用 LLM 生成前端代码
|
|
46
|
+
4. 解析多文件输出并写入文件系统
|
|
47
|
+
5. 记录代码变更
|
|
48
|
+
"""
|
|
49
|
+
# 1. 获取 API 契约
|
|
50
|
+
api_contracts = self.db.get_api_contracts(task_id)
|
|
51
|
+
contracts_str = ""
|
|
52
|
+
if api_contracts:
|
|
53
|
+
contracts_str = "\n## 后端 API 契约(必须严格按照此契约调用接口)\n"
|
|
54
|
+
for contract in api_contracts:
|
|
55
|
+
contracts_str += f"""
|
|
56
|
+
### {contract['method']} {contract['endpoint']}
|
|
57
|
+
- 请求 Schema: {contract.get('request_schema', '无')}
|
|
58
|
+
- 响应 Schema: {contract.get('response_schema', '无')}
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
description = context.get("description", "")
|
|
62
|
+
requirements = context.get("requirements", "")
|
|
63
|
+
is_iteration = context.get("is_iteration", False)
|
|
64
|
+
|
|
65
|
+
# 2. 构建 prompt
|
|
66
|
+
prompt = f"""请根据以下需求生成前端代码:
|
|
67
|
+
|
|
68
|
+
## 需求描述
|
|
69
|
+
{description}
|
|
70
|
+
|
|
71
|
+
## 详细要求
|
|
72
|
+
{requirements}
|
|
73
|
+
{contracts_str}
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# 迭代模式:注入已有代码
|
|
77
|
+
if is_iteration:
|
|
78
|
+
existing_code = self._collect_existing_code()
|
|
79
|
+
if existing_code:
|
|
80
|
+
prompt += f"""
|
|
81
|
+
## 已有代码(请基于以下代码进行修改,不要重复生成未修改的文件)
|
|
82
|
+
{existing_code}
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
prompt += """
|
|
86
|
+
请生成完整的前端代码,每个文件用 ### FILE: 标记,格式如下:
|
|
87
|
+
|
|
88
|
+
### FILE: frontend/App.tsx
|
|
89
|
+
```tsx
|
|
90
|
+
// code here
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### FILE: frontend/components/Login.tsx
|
|
94
|
+
```tsx
|
|
95
|
+
// code here
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
代码要求:
|
|
99
|
+
- 使用函数式组件和 Hooks
|
|
100
|
+
- 完整的 TypeScript 类型定义
|
|
101
|
+
- 包含错误处理和加载状态
|
|
102
|
+
- 使用 Tailwind CSS 做样式
|
|
103
|
+
|
|
104
|
+
【必须生成以下工程配置文件,否则项目无法运行】:
|
|
105
|
+
### FILE: frontend/package.json
|
|
106
|
+
包含完整的 dependencies(react, react-dom, react-scripts 等)和 scripts(start, build, test)
|
|
107
|
+
|
|
108
|
+
### FILE: frontend/tsconfig.json
|
|
109
|
+
TypeScript 配置,target 为 ES2020,jsx 为 react-jsx,strict 模式
|
|
110
|
+
|
|
111
|
+
### FILE: frontend/public/index.html
|
|
112
|
+
React 入口 HTML 文件
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# 3. 调用 LLM 生成代码
|
|
116
|
+
system_prompt = self.get_system_prompt()
|
|
117
|
+
code_result = self._call_llm(prompt, temperature=0.3)
|
|
118
|
+
|
|
119
|
+
# 4. 解析多文件输出并写入文件系统
|
|
120
|
+
files = self._parse_llm_code_output(code_result)
|
|
121
|
+
change_type = "update" if is_iteration else "create"
|
|
122
|
+
for file_path, content in files.items():
|
|
123
|
+
self._write_file(file_path, content)
|
|
124
|
+
self._log_change(
|
|
125
|
+
task_id=task_id,
|
|
126
|
+
file_path=file_path,
|
|
127
|
+
change_type=change_type,
|
|
128
|
+
diff=content,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# 5. 保存结果
|
|
132
|
+
result = {
|
|
133
|
+
"status": "success",
|
|
134
|
+
"files": list(files.keys()),
|
|
135
|
+
"api_contracts_used": len(api_contracts),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# 6. 保存 Agent 执行 trace(完整 I/O 审计)
|
|
139
|
+
self._save_trace(
|
|
140
|
+
task_id=task_id,
|
|
141
|
+
system_prompt=system_prompt,
|
|
142
|
+
user_prompt=prompt,
|
|
143
|
+
llm_response=code_result,
|
|
144
|
+
parsed_result=result,
|
|
145
|
+
)
|
|
146
|
+
self._save_result(task_id, str(result))
|
|
147
|
+
|
|
148
|
+
return result
|
agents/test_agent.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
测试专家智能体
|
|
3
|
+
负责生成单元测试、集成测试代码
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Dict
|
|
7
|
+
|
|
8
|
+
from agents.base_agent import BaseAgent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestAgent(BaseAgent):
|
|
12
|
+
"""测试专家 Agent"""
|
|
13
|
+
|
|
14
|
+
def get_system_prompt(self) -> str:
|
|
15
|
+
return """你是一位资深测试开发专家,精通单元测试、集成测试和 E2E 测试。
|
|
16
|
+
你的职责是:
|
|
17
|
+
1. 为前端组件生成单元测试(Jest + React Testing Library)
|
|
18
|
+
2. 为后端 API 生成集成测试
|
|
19
|
+
3. 编写测试用例覆盖正常和异常场景
|
|
20
|
+
4. 确保测试代码的可维护性和可读性
|
|
21
|
+
|
|
22
|
+
输出格式要求:
|
|
23
|
+
- 每个文件用 ### FILE: 标记,格式如下:
|
|
24
|
+
### FILE: tests/test_api.py
|
|
25
|
+
```python
|
|
26
|
+
# code here
|
|
27
|
+
```
|
|
28
|
+
- 测试用例必须完整覆盖主要逻辑分支
|
|
29
|
+
- 包含 Mock 数据和方法
|
|
30
|
+
- 测试代码必须可直接运行
|
|
31
|
+
|
|
32
|
+
【重要】如果生成了前端测试(.tsx/.ts 文件),也必须生成测试运行所需的配置文件:
|
|
33
|
+
- frontend/jest.config.js(或 package.json 中的 jest 配置节)
|
|
34
|
+
- frontend/src/setupTests.ts(Jest setup 文件,导入 @testing-library/jest-dom)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def execute(self, task_id: str, context: Dict) -> Dict:
|
|
38
|
+
"""
|
|
39
|
+
执行测试生成任务
|
|
40
|
+
|
|
41
|
+
流程:
|
|
42
|
+
1. 获取前后端代码
|
|
43
|
+
2. 如果是迭代模式,读取已有测试代码注入 prompt
|
|
44
|
+
3. 调用 LLM 生成测试代码
|
|
45
|
+
4. 解析多文件输出并写入文件系统
|
|
46
|
+
5. 记录代码变更
|
|
47
|
+
"""
|
|
48
|
+
# 1. 获取前后端代码
|
|
49
|
+
frontend_code = context.get("frontend_code", "")
|
|
50
|
+
backend_code = context.get("backend_code", "")
|
|
51
|
+
is_iteration = context.get("is_iteration", False)
|
|
52
|
+
|
|
53
|
+
# 2. 构建 prompt
|
|
54
|
+
prompt = f"""请为以下代码生成完整的测试:
|
|
55
|
+
|
|
56
|
+
## 前端代码
|
|
57
|
+
```tsx
|
|
58
|
+
{frontend_code}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 后端代码
|
|
62
|
+
```python
|
|
63
|
+
{backend_code}
|
|
64
|
+
```
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# 迭代模式:注入已有测试代码
|
|
68
|
+
if is_iteration:
|
|
69
|
+
existing_code = self._collect_existing_code()
|
|
70
|
+
if existing_code:
|
|
71
|
+
prompt += f"""
|
|
72
|
+
## 已有测试代码(请基于以下代码进行补充或修改)
|
|
73
|
+
{existing_code}
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
prompt += """
|
|
77
|
+
请生成:
|
|
78
|
+
1. 前端单元测试(使用 Jest + React Testing Library)——注意放在 frontend/src/ 下
|
|
79
|
+
2. 后端 API 集成测试(使用 pytest)
|
|
80
|
+
3. Mock 数据和辅助函数
|
|
81
|
+
4. 测试运行所需的配置文件(如 jest.config.js、setupTests.ts)
|
|
82
|
+
|
|
83
|
+
每个文件用 ### FILE: 标记,格式如下:
|
|
84
|
+
|
|
85
|
+
后端测试(放在 tests/ 目录):
|
|
86
|
+
### FILE: tests/test_api.py
|
|
87
|
+
```python
|
|
88
|
+
# code here
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
前端测试(放在 frontend/src/ 目录,文件名以 .test.tsx 结尾以匹配 CRA/Jest 约定):
|
|
92
|
+
### FILE: frontend/src/__tests__/ComponentName.test.tsx
|
|
93
|
+
```tsx
|
|
94
|
+
// code here
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
或直接放在 frontend/src/ 下:
|
|
98
|
+
### FILE: frontend/src/components.test.tsx
|
|
99
|
+
```tsx
|
|
100
|
+
// code here
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
【如果生成了前端测试,必须同时生成以下配置文件】:
|
|
104
|
+
### FILE: frontend/jest.config.js
|
|
105
|
+
```js
|
|
106
|
+
module.exports = {
|
|
107
|
+
testEnvironment: 'jsdom',
|
|
108
|
+
setupFilesAfterSetup: ['<rootDir>/src/setupTests.ts'],
|
|
109
|
+
};
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### FILE: frontend/src/setupTests.ts
|
|
113
|
+
```ts
|
|
114
|
+
import '@testing-library/jest-dom';
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
测试要求:
|
|
118
|
+
- 覆盖正常流程和异常流程
|
|
119
|
+
- 包含边界条件测试
|
|
120
|
+
- 测试代码可直接运行
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
# 3. 调用 LLM 生成测试
|
|
124
|
+
system_prompt = self.get_system_prompt()
|
|
125
|
+
test_code = self._call_llm(prompt, temperature=0.3)
|
|
126
|
+
|
|
127
|
+
# 4. 解析多文件输出并写入文件系统
|
|
128
|
+
files = self._parse_llm_code_output(test_code)
|
|
129
|
+
change_type = "update" if is_iteration else "create"
|
|
130
|
+
for file_path, content in files.items():
|
|
131
|
+
self._write_file(file_path, content)
|
|
132
|
+
self._log_change(
|
|
133
|
+
task_id=task_id,
|
|
134
|
+
file_path=file_path,
|
|
135
|
+
change_type=change_type,
|
|
136
|
+
diff=content,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# 5. 保存结果
|
|
140
|
+
result = {
|
|
141
|
+
"status": "success",
|
|
142
|
+
"files": list(files.keys()),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# 6. 保存 Agent 执行 trace(完整 I/O 审计)
|
|
146
|
+
self._save_trace(
|
|
147
|
+
task_id=task_id,
|
|
148
|
+
system_prompt=system_prompt,
|
|
149
|
+
user_prompt=prompt,
|
|
150
|
+
llm_response=test_code,
|
|
151
|
+
parsed_result=result,
|
|
152
|
+
)
|
|
153
|
+
self._save_result(task_id, str(result))
|
|
154
|
+
|
|
155
|
+
return result
|
coordinator/__init__.py
ADDED
|
File without changes
|