2026-03-05 09:24:49 +00:00
|
|
|
|
# core/requirement_analyzer.py - 需求分解、模块分类、函数签名生成
|
2026-03-05 05:38:26 +00:00
|
|
|
|
import json
|
|
|
|
|
|
from typing import List, Optional, Callable
|
2026-03-06 14:25:18 +00:00
|
|
|
|
import config
|
2026-03-04 18:09:45 +00:00
|
|
|
|
from core.llm_client import LLMClient
|
2026-03-05 09:24:49 +00:00
|
|
|
|
from database.models import FunctionalRequirement, ChangeHistory
|
|
|
|
|
|
from database.db_manager import DBManager
|
|
|
|
|
|
|
|
|
|
|
|
db = DBManager()
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RequirementAnalyzer:
|
2026-03-05 05:38:26 +00:00
|
|
|
|
"""负责需求分解、模块分类、函数签名生成"""
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
2026-03-05 05:38:26 +00:00
|
|
|
|
def __init__(self, llm: LLMClient):
|
|
|
|
|
|
self.llm = llm
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
# ══════════════════════════════════════════════════
|
|
|
|
|
|
# 需求分解
|
|
|
|
|
|
# ══════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
def decompose(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
self,
|
|
|
|
|
|
raw_requirement: str,
|
|
|
|
|
|
project_id: int,
|
|
|
|
|
|
raw_req_id: int,
|
|
|
|
|
|
knowledge: str = "",
|
2026-03-04 18:09:45 +00:00
|
|
|
|
) -> List[FunctionalRequirement]:
|
|
|
|
|
|
"""
|
2026-03-05 05:38:26 +00:00
|
|
|
|
将原始需求文本分解为功能需求列表(含模块分类)。
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
raw_requirement: 原始需求文本
|
|
|
|
|
|
project_id: 所属项目 ID
|
|
|
|
|
|
raw_req_id: 原始需求记录 ID
|
|
|
|
|
|
knowledge: 知识库文本(可选)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
FunctionalRequirement 对象列表(未持久化,id=None)
|
|
|
|
|
|
"""
|
2026-03-05 05:38:26 +00:00
|
|
|
|
knowledge_section = (
|
|
|
|
|
|
f"【参考知识库】\n{knowledge}\n" if knowledge else ""
|
2026-03-04 18:09:45 +00:00
|
|
|
|
)
|
2026-03-05 05:38:26 +00:00
|
|
|
|
prompt = config.DECOMPOSE_PROMPT_TEMPLATE.format(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
raw_requirement=raw_requirement,
|
|
|
|
|
|
knowledge_section=knowledge_section,
|
2026-03-04 18:09:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-05 05:38:26 +00:00
|
|
|
|
try:
|
|
|
|
|
|
items = self.llm.chat_json(prompt)
|
|
|
|
|
|
if not isinstance(items, list):
|
|
|
|
|
|
raise ValueError("LLM 返回结果不是数组")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
raise RuntimeError(f"需求分解失败: {e}")
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
2026-03-05 05:38:26 +00:00
|
|
|
|
reqs = []
|
|
|
|
|
|
for i, item in enumerate(items, 1):
|
2026-03-04 18:09:45 +00:00
|
|
|
|
req = FunctionalRequirement(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
project_id=project_id,
|
|
|
|
|
|
raw_req_id=raw_req_id,
|
|
|
|
|
|
index_no=i,
|
|
|
|
|
|
title=item.get("title", f"功能{i}"),
|
|
|
|
|
|
description=item.get("description", ""),
|
|
|
|
|
|
function_name=item.get("function_name", f"function_{i}"),
|
|
|
|
|
|
priority=item.get("priority", "medium"),
|
|
|
|
|
|
module=item.get("module", config.DEFAULT_MODULE),
|
|
|
|
|
|
status="pending",
|
|
|
|
|
|
is_custom=False,
|
2026-03-04 18:09:45 +00:00
|
|
|
|
)
|
2026-03-05 05:38:26 +00:00
|
|
|
|
reqs.append(req)
|
|
|
|
|
|
return reqs
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
2026-03-05 05:38:26 +00:00
|
|
|
|
# ══════════════════════════════════════════════════
|
|
|
|
|
|
# 模块分类(独立步骤,可对已有需求列表重新分类)
|
|
|
|
|
|
# ══════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
def classify_modules(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
self,
|
|
|
|
|
|
func_reqs: List[FunctionalRequirement],
|
|
|
|
|
|
knowledge: str = "",
|
2026-03-05 05:38:26 +00:00
|
|
|
|
) -> List[dict]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
对功能需求列表进行模块分类,返回 {function_name: module} 映射列表。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
func_reqs: 功能需求列表
|
|
|
|
|
|
knowledge: 知识库文本(可选)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
[{"function_name": "...", "module": "..."}, ...]
|
|
|
|
|
|
"""
|
|
|
|
|
|
req_list = [
|
|
|
|
|
|
{
|
2026-03-05 09:24:49 +00:00
|
|
|
|
"index_no": r.index_no,
|
|
|
|
|
|
"title": r.title,
|
|
|
|
|
|
"description": r.description,
|
2026-03-05 05:38:26 +00:00
|
|
|
|
"function_name": r.function_name,
|
|
|
|
|
|
}
|
|
|
|
|
|
for r in func_reqs
|
|
|
|
|
|
]
|
|
|
|
|
|
knowledge_section = f"【参考知识库】\n{knowledge}\n" if knowledge else ""
|
|
|
|
|
|
prompt = config.MODULE_CLASSIFY_PROMPT_TEMPLATE.format(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
requirements_json=json.dumps(req_list, ensure_ascii=False, indent=2),
|
|
|
|
|
|
knowledge_section=knowledge_section,
|
2026-03-05 05:38:26 +00:00
|
|
|
|
)
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = self.llm.chat_json(prompt)
|
|
|
|
|
|
if not isinstance(result, list):
|
|
|
|
|
|
raise ValueError("LLM 返回结果不是数组")
|
|
|
|
|
|
return result
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
raise RuntimeError(f"模块分类失败: {e}")
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
# ══════════════════════════════════════════════════
|
2026-03-05 05:38:26 +00:00
|
|
|
|
# 函数签名生成
|
2026-03-04 18:09:45 +00:00
|
|
|
|
# ══════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
def build_function_signature(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
self,
|
|
|
|
|
|
func_req: FunctionalRequirement,
|
|
|
|
|
|
requirement_id: str = "",
|
|
|
|
|
|
knowledge: str = "",
|
2026-03-04 18:09:45 +00:00
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""
|
2026-03-05 05:38:26 +00:00
|
|
|
|
为单个功能需求生成函数签名 dict。
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
Args:
|
2026-03-05 05:38:26 +00:00
|
|
|
|
func_req: 功能需求对象
|
|
|
|
|
|
requirement_id: 需求编号字符串(如 "REQ.01")
|
|
|
|
|
|
knowledge: 知识库文本
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
Returns:
|
2026-03-05 05:38:26 +00:00
|
|
|
|
函数签名 dict
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
Raises:
|
2026-03-05 05:38:26 +00:00
|
|
|
|
RuntimeError: LLM 调用或解析失败
|
2026-03-04 18:09:45 +00:00
|
|
|
|
"""
|
2026-03-05 05:38:26 +00:00
|
|
|
|
knowledge_section = f"【参考知识库】\n{knowledge}\n" if knowledge else ""
|
2026-03-04 18:09:45 +00:00
|
|
|
|
prompt = config.FUNC_SIGNATURE_PROMPT_TEMPLATE.format(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
requirement_id=requirement_id or f"REQ.{func_req.index_no:02d}",
|
|
|
|
|
|
title=func_req.title,
|
|
|
|
|
|
description=func_req.description,
|
|
|
|
|
|
function_name=func_req.function_name,
|
|
|
|
|
|
module=func_req.module or config.DEFAULT_MODULE,
|
|
|
|
|
|
knowledge_section=knowledge_section,
|
2026-03-04 18:09:45 +00:00
|
|
|
|
)
|
2026-03-05 05:38:26 +00:00
|
|
|
|
try:
|
|
|
|
|
|
sig = self.llm.chat_json(prompt)
|
|
|
|
|
|
if not isinstance(sig, dict):
|
|
|
|
|
|
raise ValueError("LLM 返回结果不是 dict")
|
|
|
|
|
|
# 确保 module 字段存在
|
|
|
|
|
|
if "module" not in sig:
|
|
|
|
|
|
sig["module"] = func_req.module or config.DEFAULT_MODULE
|
|
|
|
|
|
return sig
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
raise RuntimeError(f"签名生成失败 [{func_req.function_name}]: {e}")
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
def build_function_signatures_batch(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
self,
|
|
|
|
|
|
func_reqs: List[FunctionalRequirement],
|
|
|
|
|
|
knowledge: str = "",
|
|
|
|
|
|
on_progress: Optional[Callable] = None,
|
2026-03-04 18:09:45 +00:00
|
|
|
|
) -> List[dict]:
|
|
|
|
|
|
"""
|
2026-03-05 05:38:26 +00:00
|
|
|
|
批量生成函数签名,失败时使用降级结构。
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
func_reqs: 功能需求列表
|
2026-03-05 05:38:26 +00:00
|
|
|
|
knowledge: 知识库文本
|
|
|
|
|
|
on_progress: 进度回调 fn(index, total, req, signature, error)
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
Returns:
|
2026-03-05 05:38:26 +00:00
|
|
|
|
与 func_reqs 等长的签名 dict 列表(索引一一对应)
|
2026-03-04 18:09:45 +00:00
|
|
|
|
"""
|
2026-03-05 05:38:26 +00:00
|
|
|
|
signatures = []
|
2026-03-05 09:24:49 +00:00
|
|
|
|
total = len(func_reqs)
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
2026-03-05 05:38:26 +00:00
|
|
|
|
for i, req in enumerate(func_reqs, 1):
|
|
|
|
|
|
req_id = f"REQ.{req.index_no:02d}"
|
2026-03-04 18:09:45 +00:00
|
|
|
|
try:
|
2026-03-05 09:24:49 +00:00
|
|
|
|
sig = self.build_function_signature(req, req_id, knowledge)
|
2026-03-05 05:38:26 +00:00
|
|
|
|
error = None
|
2026-03-04 18:09:45 +00:00
|
|
|
|
except Exception as e:
|
2026-03-05 09:24:49 +00:00
|
|
|
|
sig = self._fallback_signature(req, req_id)
|
2026-03-05 05:38:26 +00:00
|
|
|
|
error = e
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
2026-03-05 05:38:26 +00:00
|
|
|
|
signatures.append(sig)
|
|
|
|
|
|
if on_progress:
|
|
|
|
|
|
on_progress(i, total, req, sig, error)
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
2026-03-05 05:38:26 +00:00
|
|
|
|
return signatures
|
2026-03-04 18:09:45 +00:00
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2026-03-05 05:38:26 +00:00
|
|
|
|
def _fallback_signature(
|
2026-03-05 09:24:49 +00:00
|
|
|
|
req: FunctionalRequirement,
|
|
|
|
|
|
requirement_id: str,
|
2026-03-05 05:38:26 +00:00
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""生成降级签名结构(LLM 失败时使用)"""
|
2026-03-04 18:09:45 +00:00
|
|
|
|
return {
|
2026-03-05 09:24:49 +00:00
|
|
|
|
"name": req.function_name,
|
2026-03-05 05:38:26 +00:00
|
|
|
|
"requirement_id": requirement_id,
|
2026-03-05 09:24:49 +00:00
|
|
|
|
"description": req.description,
|
|
|
|
|
|
"type": "function",
|
|
|
|
|
|
"module": req.module or config.DEFAULT_MODULE,
|
|
|
|
|
|
"parameters": {},
|
2026-03-05 05:38:26 +00:00
|
|
|
|
"return": {
|
2026-03-05 09:24:49 +00:00
|
|
|
|
"type": "any",
|
2026-03-05 05:38:26 +00:00
|
|
|
|
"on_success": {"value": "...", "description": "成功时返回值"},
|
|
|
|
|
|
"on_failure": {"value": "None", "description": "失败时返回 None"},
|
2026-03-04 18:09:45 +00:00
|
|
|
|
},
|
2026-03-05 09:24:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ══════════════════════════════════════════════════
|
|
|
|
|
|
# 记录变更
|
|
|
|
|
|
# ══════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
def log_change(self, project_id: int, changes: str) -> None:
|
|
|
|
|
|
"""记录需求变更到数据库"""
|
|
|
|
|
|
change = ChangeHistory(project_id=project_id, changes=changes)
|
|
|
|
|
|
db.create_change_history(change)
|
|
|
|
|
|
|
|
|
|
|
|
def get_change_history(self, project_id: int) -> List[ChangeHistory]:
|
|
|
|
|
|
"""查询项目变更历史"""
|
|
|
|
|
|
return db.list_change_history(project_id)
|
|
|
|
|
|
|
|
|
|
|
|
def analyze_changes(self, old_reqs: List[FunctionalRequirement], new_reqs: List[FunctionalRequirement]) -> List[
|
|
|
|
|
|
str]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
分析需求变更,返回需要变更的代码文件列表。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
old_reqs: 旧的功能需求列表
|
|
|
|
|
|
new_reqs: 新的功能需求列表
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
需要变更的文件列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
changed_files = []
|
|
|
|
|
|
old_func_names = {req.function_name: req for req in old_reqs}
|
|
|
|
|
|
for new_req in new_reqs:
|
|
|
|
|
|
old_req = old_func_names.get(new_req.function_name)
|
|
|
|
|
|
if not old_req or old_req.description != new_req.description or old_req.module != new_req.module:
|
|
|
|
|
|
changed_files.append(new_req.function_name)
|
|
|
|
|
|
return changed_files
|