AIDeveloper-PC/requirements_generator/core/requirement_analyzer.py

209 lines
7.8 KiB
Python
Raw Normal View History

2026-03-04 18:09:45 +00:00
# core/requirement_analyzer.py - 需求分解 & 函数签名生成
2026-03-05 05:38:26 +00:00
import json
from typing import List, Optional, Callable
2026-03-04 18:09:45 +00:00
import config
from core.llm_client import LLMClient
from database.models import FunctionalRequirement
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(
self,
raw_requirement: str,
2026-03-05 05:38:26 +00:00
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(
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 05:38:26 +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(
self,
func_reqs: List[FunctionalRequirement],
knowledge: str = "",
) -> List[dict]:
"""
对功能需求列表进行模块分类返回 {function_name: module} 映射列表
Args:
func_reqs: 功能需求列表
knowledge: 知识库文本可选
Returns:
[{"function_name": "...", "module": "..."}, ...]
"""
req_list = [
{
"index_no": r.index_no,
"title": r.title,
"description": r.description,
"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(
requirements_json = json.dumps(req_list, ensure_ascii=False, indent=2),
knowledge_section = knowledge_section,
)
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(
self,
2026-03-05 05:38:26 +00:00
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 05:38:26 +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(
self,
2026-03-05 05:38:26 +00:00
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 = []
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 05:38:26 +00:00
sig = self.build_function_signature(req, req_id, knowledge)
error = None
2026-03-04 18:09:45 +00:00
except Exception as e:
2026-03-05 05:38:26 +00:00
sig = self._fallback_signature(req, req_id)
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(
req: FunctionalRequirement,
requirement_id: str,
) -> dict:
"""生成降级签名结构LLM 失败时使用)"""
2026-03-04 18:09:45 +00:00
return {
2026-03-05 05:38:26 +00:00
"name": req.function_name,
"requirement_id": requirement_id,
"description": req.description,
2026-03-04 18:09:45 +00:00
"type": "function",
2026-03-05 05:38:26 +00:00
"module": req.module or config.DEFAULT_MODULE,
2026-03-04 18:09:45 +00:00
"parameters": {},
2026-03-05 05:38:26 +00:00
"return": {
"type": "any",
"on_success": {"value": "...", "description": "成功时返回值"},
"on_failure": {"value": "None", "description": "失败时返回 None"},
2026-03-04 18:09:45 +00:00
},
}