完全采用交互式使用方式;

This commit is contained in:
sonto.lau 2026-03-06 23:41:59 +08:00
parent ef9a846b51
commit cb479138af
2 changed files with 957 additions and 915 deletions

View File

@ -1,6 +1,7 @@
# core/requirement_analyzer.py - 需求分解、模块分类、函数签名生成 # core/requirement_analyzer.py - 需求分解、模块分类、函数签名生成
import json import json
from typing import List, Optional, Callable from typing import List, Optional, Callable
import config import config
from core.llm_client import LLMClient from core.llm_client import LLMClient
from database.models import FunctionalRequirement, ChangeHistory from database.models import FunctionalRequirement, ChangeHistory
@ -20,11 +21,12 @@ class RequirementAnalyzer:
# ══════════════════════════════════════════════════ # ══════════════════════════════════════════════════
def decompose( def decompose(
self, self,
raw_requirement: str, raw_requirement: str,
project_id: int, project_id: int,
raw_req_id: int, raw_req_id: int,
knowledge: str = "", knowledge: str = "",
start_index: int = 1,
) -> List[FunctionalRequirement]: ) -> List[FunctionalRequirement]:
""" """
将原始需求文本分解为功能需求列表含模块分类 将原始需求文本分解为功能需求列表含模块分类
@ -34,6 +36,7 @@ class RequirementAnalyzer:
project_id: 所属项目 ID project_id: 所属项目 ID
raw_req_id: 原始需求记录 ID raw_req_id: 原始需求记录 ID
knowledge: 知识库文本可选 knowledge: 知识库文本可选
start_index: 功能需求序号起始值新增需求时传入当前最大序号+1
Returns: Returns:
FunctionalRequirement 对象列表未持久化id=None FunctionalRequirement 对象列表未持久化id=None
@ -42,8 +45,8 @@ class RequirementAnalyzer:
f"【参考知识库】\n{knowledge}\n" if knowledge else "" f"【参考知识库】\n{knowledge}\n" if knowledge else ""
) )
prompt = config.DECOMPOSE_PROMPT_TEMPLATE.format( prompt = config.DECOMPOSE_PROMPT_TEMPLATE.format(
raw_requirement=raw_requirement, raw_requirement = raw_requirement,
knowledge_section=knowledge_section, knowledge_section = knowledge_section,
) )
try: try:
@ -54,30 +57,65 @@ class RequirementAnalyzer:
raise RuntimeError(f"需求分解失败: {e}") raise RuntimeError(f"需求分解失败: {e}")
reqs = [] reqs = []
for i, item in enumerate(items, 1): for i, item in enumerate(items, start_index):
req = FunctionalRequirement( req = FunctionalRequirement(
project_id=project_id, project_id = project_id,
raw_req_id=raw_req_id, raw_req_id = raw_req_id,
index_no=i, index_no = i,
title=item.get("title", f"功能{i}"), title = item.get("title", f"功能{i}"),
description=item.get("description", ""), description = item.get("description", ""),
function_name=item.get("function_name", f"function_{i}"), function_name = item.get("function_name", f"function_{i}"),
priority=item.get("priority", "medium"), priority = item.get("priority", "medium"),
module=item.get("module", config.DEFAULT_MODULE), module = item.get("module", config.DEFAULT_MODULE),
status="pending", status = "pending",
is_custom=False, is_custom = False,
) )
reqs.append(req) reqs.append(req)
return reqs return reqs
# ══════════════════════════════════════════════════
# 新增需求(追加到已有功能需求列表)
# ══════════════════════════════════════════════════
def add_new_requirements(
self,
new_requirement_text: str,
project_id: int,
raw_req_id: int,
existing_reqs: List[FunctionalRequirement],
knowledge: str = "",
) -> List[FunctionalRequirement]:
"""
将新需求文本分解为功能需求序号接续已有需求返回新增部分列表未持久化
Args:
new_requirement_text: 新增需求描述文本
project_id: 所属项目 ID
raw_req_id: 原始需求记录 ID
existing_reqs: 已有功能需求列表用于计算起始序号
knowledge: 知识库文本可选
Returns:
新增的 FunctionalRequirement 列表未持久化
"""
start_index = max((r.index_no for r in existing_reqs), default=0) + 1
new_reqs = self.decompose(
raw_requirement = new_requirement_text,
project_id = project_id,
raw_req_id = raw_req_id,
knowledge = knowledge,
start_index = start_index,
)
return new_reqs
# ══════════════════════════════════════════════════ # ══════════════════════════════════════════════════
# 模块分类(独立步骤,可对已有需求列表重新分类) # 模块分类(独立步骤,可对已有需求列表重新分类)
# ══════════════════════════════════════════════════ # ══════════════════════════════════════════════════
def classify_modules( def classify_modules(
self, self,
func_reqs: List[FunctionalRequirement], func_reqs: List[FunctionalRequirement],
knowledge: str = "", knowledge: str = "",
) -> List[dict]: ) -> List[dict]:
""" """
对功能需求列表进行模块分类返回 {function_name: module} 映射列表 对功能需求列表进行模块分类返回 {function_name: module} 映射列表
@ -91,17 +129,17 @@ class RequirementAnalyzer:
""" """
req_list = [ req_list = [
{ {
"index_no": r.index_no, "index_no": r.index_no,
"title": r.title, "title": r.title,
"description": r.description, "description": r.description,
"function_name": r.function_name, "function_name": r.function_name,
} }
for r in func_reqs for r in func_reqs
] ]
knowledge_section = f"【参考知识库】\n{knowledge}\n" if knowledge else "" knowledge_section = f"【参考知识库】\n{knowledge}\n" if knowledge else ""
prompt = config.MODULE_CLASSIFY_PROMPT_TEMPLATE.format( prompt = config.MODULE_CLASSIFY_PROMPT_TEMPLATE.format(
requirements_json=json.dumps(req_list, ensure_ascii=False, indent=2), requirements_json = json.dumps(req_list, ensure_ascii=False, indent=2),
knowledge_section=knowledge_section, knowledge_section = knowledge_section,
) )
try: try:
result = self.llm.chat_json(prompt) result = self.llm.chat_json(prompt)
@ -116,10 +154,10 @@ class RequirementAnalyzer:
# ══════════════════════════════════════════════════ # ══════════════════════════════════════════════════
def build_function_signature( def build_function_signature(
self, self,
func_req: FunctionalRequirement, func_req: FunctionalRequirement,
requirement_id: str = "", requirement_id: str = "",
knowledge: str = "", knowledge: str = "",
) -> dict: ) -> dict:
""" """
为单个功能需求生成函数签名 dict 为单个功能需求生成函数签名 dict
@ -137,18 +175,17 @@ class RequirementAnalyzer:
""" """
knowledge_section = f"【参考知识库】\n{knowledge}\n" if knowledge else "" knowledge_section = f"【参考知识库】\n{knowledge}\n" if knowledge else ""
prompt = config.FUNC_SIGNATURE_PROMPT_TEMPLATE.format( prompt = config.FUNC_SIGNATURE_PROMPT_TEMPLATE.format(
requirement_id=requirement_id or f"REQ.{func_req.index_no:02d}", requirement_id = requirement_id or f"REQ.{func_req.index_no:02d}",
title=func_req.title, title = func_req.title,
description=func_req.description, description = func_req.description,
function_name=func_req.function_name, function_name = func_req.function_name,
module=func_req.module or config.DEFAULT_MODULE, module = func_req.module or config.DEFAULT_MODULE,
knowledge_section=knowledge_section, knowledge_section = knowledge_section,
) )
try: try:
sig = self.llm.chat_json(prompt) sig = self.llm.chat_json(prompt)
if not isinstance(sig, dict): if not isinstance(sig, dict):
raise ValueError("LLM 返回结果不是 dict") raise ValueError("LLM 返回结果不是 dict")
# 确保 module 字段存在
if "module" not in sig: if "module" not in sig:
sig["module"] = func_req.module or config.DEFAULT_MODULE sig["module"] = func_req.module or config.DEFAULT_MODULE
return sig return sig
@ -156,10 +193,10 @@ class RequirementAnalyzer:
raise RuntimeError(f"签名生成失败 [{func_req.function_name}]: {e}") raise RuntimeError(f"签名生成失败 [{func_req.function_name}]: {e}")
def build_function_signatures_batch( def build_function_signatures_batch(
self, self,
func_reqs: List[FunctionalRequirement], func_reqs: List[FunctionalRequirement],
knowledge: str = "", knowledge: str = "",
on_progress: Optional[Callable] = None, on_progress: Optional[Callable] = None,
) -> List[dict]: ) -> List[dict]:
""" """
批量生成函数签名失败时使用降级结构 批量生成函数签名失败时使用降级结构
@ -173,15 +210,15 @@ class RequirementAnalyzer:
func_reqs 等长的签名 dict 列表索引一一对应 func_reqs 等长的签名 dict 列表索引一一对应
""" """
signatures = [] signatures = []
total = len(func_reqs) total = len(func_reqs)
for i, req in enumerate(func_reqs, 1): for i, req in enumerate(func_reqs, 1):
req_id = f"REQ.{req.index_no:02d}" req_id = f"REQ.{req.index_no:02d}"
try: try:
sig = self.build_function_signature(req, req_id, knowledge) sig = self.build_function_signature(req, req_id, knowledge)
error = None error = None
except Exception as e: except Exception as e:
sig = self._fallback_signature(req, req_id) sig = self._fallback_signature(req, req_id)
error = e error = e
signatures.append(sig) signatures.append(sig)
@ -192,26 +229,26 @@ class RequirementAnalyzer:
@staticmethod @staticmethod
def _fallback_signature( def _fallback_signature(
req: FunctionalRequirement, req: FunctionalRequirement,
requirement_id: str, requirement_id: str,
) -> dict: ) -> dict:
"""生成降级签名结构LLM 失败时使用)""" """生成降级签名结构LLM 失败时使用)"""
return { return {
"name": req.function_name, "name": req.function_name,
"requirement_id": requirement_id, "requirement_id": requirement_id,
"description": req.description, "description": req.description,
"type": "function", "type": "function",
"module": req.module or config.DEFAULT_MODULE, "module": req.module or config.DEFAULT_MODULE,
"parameters": {}, "parameters": {},
"return": { "return": {
"type": "any", "type": "any",
"on_success": {"value": "...", "description": "成功时返回值"}, "on_success": {"value": "...", "description": "成功时返回值"},
"on_failure": {"value": "None", "description": "失败时返回 None"}, "on_failure": {"value": "None", "description": "失败时返回 None"},
}, },
} }
# ══════════════════════════════════════════════════ # ══════════════════════════════════════════════════
# 记录变更 # 变更记录与分析
# ══════════════════════════════════════════════════ # ══════════════════════════════════════════════════
def log_change(self, project_id: int, changes: str) -> None: def log_change(self, project_id: int, changes: str) -> None:
@ -223,22 +260,32 @@ class RequirementAnalyzer:
"""查询项目变更历史""" """查询项目变更历史"""
return db.list_change_history(project_id) return db.list_change_history(project_id)
def analyze_changes(self, old_reqs: List[FunctionalRequirement], new_reqs: List[FunctionalRequirement]) -> List[ def analyze_changes(
str]: self,
old_reqs: List[FunctionalRequirement],
new_reqs: List[FunctionalRequirement],
) -> List[str]:
""" """
分析需求变更返回需要变更的代码文件列表 分析需求变更返回受影响的函数名列表
Args: Args:
old_reqs: 旧的功能需求列表 old_reqs: 旧的功能需求列表
new_reqs: 新的功能需求列表 new_reqs: 变更/增后的功能需求列表
Returns: Returns:
需要变更的文件列表 受影响的 function_name 列表
""" """
changed_files = [] changed_funcs = []
old_func_names = {req.function_name: req for req in old_reqs} old_func_map = {req.function_name: req for req in old_reqs}
for new_req in new_reqs: for new_req in new_reqs:
old_req = old_func_names.get(new_req.function_name) old_req = old_func_map.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) if (
return changed_files not old_req
or old_req.description != new_req.description
or old_req.module != new_req.module
):
changed_funcs.append(new_req.function_name)
return changed_funcs

File diff suppressed because it is too large Load Diff