rule_based_engine.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. #!/usr/bin/env python3
  2. """
  3. AceFlow v2.0 基于规则的轻量级决策引擎
  4. 专为Agent工具集成设计,无需外部LLM
  5. """
  6. import json
  7. import yaml
  8. import re
  9. from datetime import datetime
  10. from typing import Dict, List, Any, Optional, Tuple
  11. from pathlib import Path
  12. from dataclasses import dataclass, asdict
  13. from enum import Enum
  14. # 任务类型枚举
  15. class TaskType(Enum):
  16. FEATURE_DEVELOPMENT = "feature_development"
  17. BUG_FIX = "bug_fix"
  18. REFACTORING = "refactoring"
  19. TESTING = "testing"
  20. DOCUMENTATION = "documentation"
  21. RESEARCH = "research"
  22. ARCHITECTURE = "architecture"
  23. DEPLOYMENT = "deployment"
  24. MAINTENANCE = "maintenance"
  25. # 项目复杂度枚举
  26. class ProjectComplexity(Enum):
  27. SIMPLE = "simple"
  28. MODERATE = "moderate"
  29. COMPLEX = "complex"
  30. ENTERPRISE = "enterprise"
  31. # 流程模式枚举
  32. class FlowMode(Enum):
  33. MINIMAL = "minimal"
  34. STANDARD = "standard"
  35. COMPLETE = "complete"
  36. @dataclass
  37. class ProjectProfile:
  38. """项目特征画像"""
  39. project_type: str = "unknown"
  40. team_size: int = 1
  41. complexity: ProjectComplexity = ProjectComplexity.SIMPLE
  42. tech_stack: List[str] = None
  43. has_tests: bool = False
  44. has_ci_cd: bool = False
  45. has_documentation: bool = False
  46. git_activity: str = "low" # low, medium, high
  47. file_count: int = 0
  48. def __post_init__(self):
  49. if self.tech_stack is None:
  50. self.tech_stack = []
  51. @dataclass
  52. class DecisionResult:
  53. """决策结果"""
  54. recommended_flow: str
  55. confidence: float
  56. reasoning: str
  57. steps: List[str]
  58. estimated_hours: int
  59. alternatives: List[Dict[str, Any]]
  60. metadata: Dict[str, Any]
  61. def to_dict(self) -> Dict[str, Any]:
  62. return asdict(self)
  63. class PatternMatcher:
  64. """任务模式匹配器"""
  65. def __init__(self):
  66. self.patterns = {
  67. TaskType.BUG_FIX: [
  68. r'(fix|bug|error|issue|problem|crash|fail)',
  69. r'(修复|错误|问题|故障|崩溃|失败)',
  70. r'(debug|debugging)',
  71. r'(not working|broken|incorrect)',
  72. r'(无法|不能|无效|失效)'
  73. ],
  74. TaskType.FEATURE_DEVELOPMENT: [
  75. r'(add|new|create|implement|build|develop)',
  76. r'(feature|functionality|capability|module|component)',
  77. r'(新增|添加|创建|实现|开发|构建)',
  78. r'(功能|特性|模块|组件|接口)',
  79. r'(enhancement|improvement|upgrade)',
  80. r'(增强|改进|升级|扩展)'
  81. ],
  82. TaskType.REFACTORING: [
  83. r'(refactor|refactoring|restructure|reorganize)',
  84. r'(optimize|optimization|improve|clean)',
  85. r'(重构|重组|优化|改进|整理)',
  86. r'(code quality|performance|maintainability)',
  87. r'(代码质量|性能|可维护性)'
  88. ],
  89. TaskType.TESTING: [
  90. r'(test|testing|unit test|integration test)',
  91. r'(测试|单元测试|集成测试|测试用例)',
  92. r'(coverage|test coverage|qa)',
  93. r'(覆盖率|测试覆盖率|质量保证)',
  94. r'(verify|validation|check)',
  95. r'(验证|校验|检查)'
  96. ],
  97. TaskType.DOCUMENTATION: [
  98. r'(doc|docs|documentation|readme|guide)',
  99. r'(文档|说明|指南|手册)',
  100. r'(comment|comments|api doc)',
  101. r'(注释|API文档|接口文档)',
  102. r'(tutorial|example|demo)',
  103. r'(教程|示例|演示)'
  104. ],
  105. TaskType.RESEARCH: [
  106. r'(research|investigate|explore|study)',
  107. r'(调研|研究|探索|分析)',
  108. r'(poc|proof of concept|spike)',
  109. r'(原型|概念验证|技术验证)',
  110. r'(evaluation|comparison|analysis)',
  111. r'(评估|比较|分析)'
  112. ],
  113. TaskType.ARCHITECTURE: [
  114. r'(architecture|design|structure|framework)',
  115. r'(架构|设计|结构|框架)',
  116. r'(system design|technical design)',
  117. r'(系统设计|技术设计)',
  118. r'(blueprint|plan|specification)',
  119. r'(蓝图|规划|规范)'
  120. ],
  121. TaskType.DEPLOYMENT: [
  122. r'(deploy|deployment|release|publish)',
  123. r'(部署|发布|上线|发版)',
  124. r'(ci/cd|pipeline|build)',
  125. r'(环境|生产环境|服务器)',
  126. r'(docker|kubernetes|container)',
  127. r'(容器|容器化|编排)'
  128. ],
  129. TaskType.MAINTENANCE: [
  130. r'(maintain|maintenance|update|upgrade)',
  131. r'(维护|更新|升级|迁移)',
  132. r'(security|vulnerability|patch)',
  133. r'(安全|漏洞|补丁)',
  134. r'(dependency|library|framework)',
  135. r'(依赖|库|框架)'
  136. ]
  137. }
  138. def classify_task(self, task_description: str) -> TaskType:
  139. """基于模式匹配分类任务"""
  140. if not task_description:
  141. return TaskType.FEATURE_DEVELOPMENT
  142. task_lower = task_description.lower()
  143. scores = {}
  144. for task_type, patterns in self.patterns.items():
  145. score = 0
  146. for pattern in patterns:
  147. if re.search(pattern, task_lower):
  148. score += 1
  149. scores[task_type] = score
  150. # 返回得分最高的任务类型
  151. if scores:
  152. best_type = max(scores, key=scores.get)
  153. if scores[best_type] > 0:
  154. return best_type
  155. return TaskType.FEATURE_DEVELOPMENT
  156. class ProjectAnalyzer:
  157. """项目分析器"""
  158. def __init__(self, project_root: Path = None):
  159. self.project_root = project_root or Path.cwd()
  160. self.aceflow_dir = self.project_root / ".aceflow"
  161. def analyze_project(self) -> ProjectProfile:
  162. """分析项目特征"""
  163. profile = ProjectProfile()
  164. # 分析项目类型
  165. profile.project_type = self._detect_project_type()
  166. # 分析团队规模
  167. profile.team_size = self._estimate_team_size()
  168. # 分析复杂度
  169. profile.complexity = self._assess_complexity()
  170. # 分析技术栈
  171. profile.tech_stack = self._detect_tech_stack()
  172. # 分析项目特征
  173. profile.has_tests = self._has_tests()
  174. profile.has_ci_cd = self._has_ci_cd()
  175. profile.has_documentation = self._has_documentation()
  176. profile.git_activity = self._assess_git_activity()
  177. profile.file_count = self._count_files()
  178. return profile
  179. def _detect_project_type(self) -> str:
  180. """检测项目类型"""
  181. # 检查配置文件
  182. config_file = self.aceflow_dir / "config.yaml"
  183. if config_file.exists():
  184. try:
  185. with open(config_file, 'r', encoding='utf-8') as f:
  186. config = yaml.safe_load(f)
  187. if 'project' in config and 'project_type' in config['project']:
  188. return config['project']['project_type']
  189. except:
  190. pass
  191. # 基于文件检测
  192. if (self.project_root / "package.json").exists():
  193. return "web"
  194. elif (self.project_root / "requirements.txt").exists() or (self.project_root / "pyproject.toml").exists():
  195. return "python"
  196. elif (self.project_root / "pom.xml").exists():
  197. return "java"
  198. elif (self.project_root / "Cargo.toml").exists():
  199. return "rust"
  200. elif (self.project_root / "go.mod").exists():
  201. return "go"
  202. elif (self.project_root / "pubspec.yaml").exists():
  203. return "flutter"
  204. else:
  205. return "unknown"
  206. def _estimate_team_size(self) -> int:
  207. """估算团队规模"""
  208. # 从配置文件读取
  209. config_file = self.aceflow_dir / "config.yaml"
  210. if config_file.exists():
  211. try:
  212. with open(config_file, 'r', encoding='utf-8') as f:
  213. config = yaml.safe_load(f)
  214. if 'project' in config and 'team_size' in config['project']:
  215. team_size_str = config['project']['team_size']
  216. if isinstance(team_size_str, int):
  217. return team_size_str
  218. elif isinstance(team_size_str, str):
  219. # 解析 "1-5人" 格式
  220. import re
  221. match = re.search(r'(\d+)', team_size_str)
  222. if match:
  223. return int(match.group(1))
  224. except:
  225. pass
  226. # 基于Git提交者数量估算
  227. try:
  228. import subprocess
  229. result = subprocess.run(
  230. ["git", "log", "--pretty=format:%ae", "--since=3 months ago"],
  231. capture_output=True, text=True, cwd=self.project_root
  232. )
  233. if result.returncode == 0:
  234. contributors = set(result.stdout.strip().split('\n'))
  235. return max(1, len(contributors))
  236. except:
  237. pass
  238. return 1
  239. def _assess_complexity(self) -> ProjectComplexity:
  240. """评估项目复杂度"""
  241. score = 0
  242. # 文件数量
  243. file_count = self._count_files()
  244. if file_count > 100:
  245. score += 2
  246. elif file_count > 50:
  247. score += 1
  248. # 目录深度
  249. max_depth = self._get_max_directory_depth()
  250. if max_depth > 5:
  251. score += 2
  252. elif max_depth > 3:
  253. score += 1
  254. # 技术栈复杂度
  255. tech_stack = self._detect_tech_stack()
  256. if len(tech_stack) > 5:
  257. score += 2
  258. elif len(tech_stack) > 3:
  259. score += 1
  260. # 配置文件数量
  261. config_files = self._count_config_files()
  262. if config_files > 10:
  263. score += 2
  264. elif config_files > 5:
  265. score += 1
  266. # 依赖数量
  267. dependencies = self._count_dependencies()
  268. if dependencies > 50:
  269. score += 2
  270. elif dependencies > 20:
  271. score += 1
  272. # 根据得分确定复杂度
  273. if score >= 7:
  274. return ProjectComplexity.ENTERPRISE
  275. elif score >= 5:
  276. return ProjectComplexity.COMPLEX
  277. elif score >= 3:
  278. return ProjectComplexity.MODERATE
  279. else:
  280. return ProjectComplexity.SIMPLE
  281. def _detect_tech_stack(self) -> List[str]:
  282. """检测技术栈"""
  283. tech_stack = []
  284. # 检查常见文件
  285. tech_indicators = {
  286. "package.json": ["JavaScript", "Node.js"],
  287. "requirements.txt": ["Python"],
  288. "pyproject.toml": ["Python"],
  289. "pom.xml": ["Java", "Maven"],
  290. "build.gradle": ["Java", "Gradle"],
  291. "Cargo.toml": ["Rust"],
  292. "go.mod": ["Go"],
  293. "pubspec.yaml": ["Flutter", "Dart"],
  294. "composer.json": ["PHP"],
  295. "Gemfile": ["Ruby"],
  296. "mix.exs": ["Elixir"],
  297. "project.clj": ["Clojure"],
  298. "*.csproj": ["C#", ".NET"],
  299. "Dockerfile": ["Docker"],
  300. "docker-compose.yml": ["Docker Compose"],
  301. "k8s": ["Kubernetes"],
  302. ".github/workflows": ["GitHub Actions"],
  303. ".gitlab-ci.yml": ["GitLab CI"],
  304. "terraform": ["Terraform"],
  305. "ansible": ["Ansible"]
  306. }
  307. for file_pattern, technologies in tech_indicators.items():
  308. if '*' in file_pattern:
  309. # 通配符匹配
  310. pattern = file_pattern.replace('*', '**/*')
  311. if list(self.project_root.glob(pattern)):
  312. tech_stack.extend(technologies)
  313. else:
  314. # 精确匹配
  315. if (self.project_root / file_pattern).exists():
  316. tech_stack.extend(technologies)
  317. return list(set(tech_stack))
  318. def _count_files(self) -> int:
  319. """统计文件数量(排除隐藏文件和常见忽略目录)"""
  320. ignore_dirs = {'.git', '.aceflow', 'node_modules', '__pycache__', '.pytest_cache', 'target', 'build', 'dist'}
  321. ignore_files = {'.gitignore', '.DS_Store', 'Thumbs.db'}
  322. count = 0
  323. for file_path in self.project_root.rglob('*'):
  324. if file_path.is_file():
  325. # 检查是否在忽略目录中
  326. if any(ignore_dir in file_path.parts for ignore_dir in ignore_dirs):
  327. continue
  328. # 检查是否是忽略文件
  329. if file_path.name in ignore_files:
  330. continue
  331. count += 1
  332. return count
  333. def _get_max_directory_depth(self) -> int:
  334. """获取目录最大深度"""
  335. max_depth = 0
  336. for path in self.project_root.rglob('*'):
  337. if path.is_dir():
  338. depth = len(path.relative_to(self.project_root).parts)
  339. max_depth = max(max_depth, depth)
  340. return max_depth
  341. def _count_config_files(self) -> int:
  342. """统计配置文件数量"""
  343. config_patterns = [
  344. "*.json", "*.yaml", "*.yml", "*.toml", "*.ini", "*.cfg", "*.config",
  345. "*.properties", "*.env", "Dockerfile", "docker-compose*"
  346. ]
  347. count = 0
  348. for pattern in config_patterns:
  349. count += len(list(self.project_root.glob(pattern)))
  350. return count
  351. def _count_dependencies(self) -> int:
  352. """统计依赖数量"""
  353. count = 0
  354. # package.json
  355. package_json = self.project_root / "package.json"
  356. if package_json.exists():
  357. try:
  358. with open(package_json, 'r') as f:
  359. data = json.load(f)
  360. count += len(data.get('dependencies', {}))
  361. count += len(data.get('devDependencies', {}))
  362. except:
  363. pass
  364. # requirements.txt
  365. requirements = self.project_root / "requirements.txt"
  366. if requirements.exists():
  367. try:
  368. with open(requirements, 'r') as f:
  369. lines = f.readlines()
  370. count += len([line for line in lines if line.strip() and not line.startswith('#')])
  371. except:
  372. pass
  373. # pyproject.toml
  374. pyproject = self.project_root / "pyproject.toml"
  375. if pyproject.exists():
  376. try:
  377. import toml
  378. with open(pyproject, 'r') as f:
  379. data = toml.load(f)
  380. if 'tool' in data and 'poetry' in data['tool'] and 'dependencies' in data['tool']['poetry']:
  381. count += len(data['tool']['poetry']['dependencies'])
  382. except:
  383. pass
  384. return count
  385. def _has_tests(self) -> bool:
  386. """检查是否有测试文件"""
  387. test_patterns = [
  388. "**/test_*.py", "**/tests/*.py", "**/*_test.py",
  389. "**/test*.js", "**/tests/*.js", "**/*.test.js", "**/*.spec.js",
  390. "**/test*.java", "**/tests/*.java", "**/*Test.java",
  391. "**/test*.go", "**/*_test.go"
  392. ]
  393. for pattern in test_patterns:
  394. if list(self.project_root.glob(pattern)):
  395. return True
  396. return False
  397. def _has_ci_cd(self) -> bool:
  398. """检查是否有CI/CD配置"""
  399. ci_cd_files = [
  400. ".github/workflows",
  401. ".gitlab-ci.yml",
  402. ".travis.yml",
  403. "circle.yml",
  404. "appveyor.yml",
  405. "jenkins.yml",
  406. "Jenkinsfile"
  407. ]
  408. for file_path in ci_cd_files:
  409. if (self.project_root / file_path).exists():
  410. return True
  411. return False
  412. def _has_documentation(self) -> bool:
  413. """检查是否有文档"""
  414. doc_files = [
  415. "README.md", "README.rst", "README.txt",
  416. "docs", "doc", "documentation",
  417. "CHANGELOG.md", "CHANGELOG.rst",
  418. "API.md", "api.md"
  419. ]
  420. for file_path in doc_files:
  421. if (self.project_root / file_path).exists():
  422. return True
  423. return False
  424. def _assess_git_activity(self) -> str:
  425. """评估Git活动水平"""
  426. try:
  427. import subprocess
  428. from datetime import datetime, timedelta
  429. # 最近3个月的提交数
  430. three_months_ago = (datetime.now() - timedelta(days=90)).strftime('%Y-%m-%d')
  431. result = subprocess.run(
  432. ["git", "log", "--oneline", f"--since={three_months_ago}"],
  433. capture_output=True, text=True, cwd=self.project_root
  434. )
  435. if result.returncode == 0:
  436. commit_count = len(result.stdout.strip().split('\n'))
  437. if commit_count > 100:
  438. return "high"
  439. elif commit_count > 20:
  440. return "medium"
  441. else:
  442. return "low"
  443. except:
  444. pass
  445. return "low"
  446. class RuleEngine:
  447. """规则引擎"""
  448. def __init__(self):
  449. self.rules = self._load_rules()
  450. def _load_rules(self) -> Dict[str, Any]:
  451. """加载决策规则"""
  452. return {
  453. "flow_rules": {
  454. "minimal": {
  455. "conditions": [
  456. {"field": "task_type", "operator": "in", "value": ["BUG_FIX", "MAINTENANCE", "DOCUMENTATION"]},
  457. {"field": "team_size", "operator": "<=", "value": 3},
  458. {"field": "complexity", "operator": "==", "value": "SIMPLE"},
  459. {"field": "urgency", "operator": "==", "value": "high"}
  460. ],
  461. "weight": 1.0
  462. },
  463. "standard": {
  464. "conditions": [
  465. {"field": "task_type", "operator": "in", "value": ["FEATURE_DEVELOPMENT", "TESTING", "REFACTORING"]},
  466. {"field": "team_size", "operator": "between", "value": [3, 8]},
  467. {"field": "complexity", "operator": "in", "value": ["MODERATE", "COMPLEX"]},
  468. {"field": "has_tests", "operator": "==", "value": True}
  469. ],
  470. "weight": 1.0
  471. },
  472. "complete": {
  473. "conditions": [
  474. {"field": "task_type", "operator": "in", "value": ["ARCHITECTURE", "RESEARCH", "DEPLOYMENT"]},
  475. {"field": "team_size", "operator": ">", "value": 8},
  476. {"field": "complexity", "operator": "==", "value": "ENTERPRISE"},
  477. {"field": "has_ci_cd", "operator": "==", "value": True}
  478. ],
  479. "weight": 1.0
  480. }
  481. },
  482. "estimation_rules": {
  483. "BUG_FIX": {"base_hours": 4, "complexity_factor": 1.2},
  484. "FEATURE_DEVELOPMENT": {"base_hours": 16, "complexity_factor": 1.5},
  485. "REFACTORING": {"base_hours": 8, "complexity_factor": 1.3},
  486. "TESTING": {"base_hours": 6, "complexity_factor": 1.1},
  487. "DOCUMENTATION": {"base_hours": 4, "complexity_factor": 1.0},
  488. "RESEARCH": {"base_hours": 12, "complexity_factor": 1.4},
  489. "ARCHITECTURE": {"base_hours": 24, "complexity_factor": 1.6},
  490. "DEPLOYMENT": {"base_hours": 8, "complexity_factor": 1.2},
  491. "MAINTENANCE": {"base_hours": 6, "complexity_factor": 1.1}
  492. }
  493. }
  494. def evaluate_flow_rules(self, task_type: TaskType, project_profile: ProjectProfile,
  495. urgency: str = "medium") -> Dict[str, float]:
  496. """评估流程规则,返回各流程的匹配分数"""
  497. scores = {}
  498. # 构建评估上下文
  499. context = {
  500. "task_type": task_type.value.upper(),
  501. "team_size": project_profile.team_size,
  502. "complexity": project_profile.complexity.value.upper(),
  503. "has_tests": project_profile.has_tests,
  504. "has_ci_cd": project_profile.has_ci_cd,
  505. "urgency": urgency
  506. }
  507. # 评估每个流程
  508. for flow_name, flow_rule in self.rules["flow_rules"].items():
  509. score = self._evaluate_conditions(flow_rule["conditions"], context)
  510. scores[flow_name] = score * flow_rule["weight"]
  511. return scores
  512. def _evaluate_conditions(self, conditions: List[Dict], context: Dict) -> float:
  513. """评估条件集合,返回匹配分数"""
  514. if not conditions:
  515. return 0.0
  516. matched_count = 0
  517. total_count = len(conditions)
  518. for condition in conditions:
  519. if self._evaluate_single_condition(condition, context):
  520. matched_count += 1
  521. return matched_count / total_count
  522. def _evaluate_single_condition(self, condition: Dict, context: Dict) -> bool:
  523. """评估单个条件"""
  524. field = condition["field"]
  525. operator = condition["operator"]
  526. value = condition["value"]
  527. if field not in context:
  528. return False
  529. context_value = context[field]
  530. if operator == "==":
  531. return context_value == value
  532. elif operator == "!=":
  533. return context_value != value
  534. elif operator == "<":
  535. return context_value < value
  536. elif operator == "<=":
  537. return context_value <= value
  538. elif operator == ">":
  539. return context_value > value
  540. elif operator == ">=":
  541. return context_value >= value
  542. elif operator == "in":
  543. return context_value in value
  544. elif operator == "not_in":
  545. return context_value not in value
  546. elif operator == "between":
  547. return value[0] <= context_value <= value[1]
  548. else:
  549. return False
  550. def estimate_duration(self, task_type: TaskType, project_profile: ProjectProfile) -> int:
  551. """估算任务持续时间"""
  552. task_key = task_type.value.upper()
  553. if task_key not in self.rules["estimation_rules"]:
  554. return 8 # 默认8小时
  555. rule = self.rules["estimation_rules"][task_key]
  556. base_hours = rule["base_hours"]
  557. complexity_factor = rule["complexity_factor"]
  558. # 应用复杂度因子
  559. complexity_multiplier = {
  560. ProjectComplexity.SIMPLE: 1.0,
  561. ProjectComplexity.MODERATE: 1.2,
  562. ProjectComplexity.COMPLEX: 1.5,
  563. ProjectComplexity.ENTERPRISE: 2.0
  564. }
  565. hours = base_hours * complexity_factor * complexity_multiplier[project_profile.complexity]
  566. # 应用团队规模因子
  567. if project_profile.team_size > 5:
  568. hours *= 1.2 # 大团队协调成本
  569. elif project_profile.team_size == 1:
  570. hours *= 0.9 # 单人项目效率
  571. return int(hours)
  572. class RuleBasedDecisionEngine:
  573. """基于规则的决策引擎"""
  574. def __init__(self, project_root: Path = None):
  575. self.project_root = project_root or Path.cwd()
  576. self.pattern_matcher = PatternMatcher()
  577. self.project_analyzer = ProjectAnalyzer(self.project_root)
  578. self.rule_engine = RuleEngine()
  579. def make_decision(self, task_input: str, context: Dict[str, Any] = None) -> DecisionResult:
  580. """做出决策"""
  581. if context is None:
  582. context = {}
  583. # 1. 任务分类
  584. task_type = self.pattern_matcher.classify_task(task_input)
  585. # 2. 项目分析
  586. project_profile = self.project_analyzer.analyze_project()
  587. # 3. 应用上下文覆盖
  588. self._apply_context_overrides(project_profile, context)
  589. # 4. 流程推荐
  590. flow_scores = self.rule_engine.evaluate_flow_rules(
  591. task_type, project_profile, context.get("urgency", "medium")
  592. )
  593. # 5. 选择最佳流程
  594. recommended_flow = max(flow_scores, key=flow_scores.get)
  595. confidence = flow_scores[recommended_flow]
  596. # 6. 生成步骤
  597. steps = self._generate_steps(recommended_flow)
  598. # 7. 估算时间
  599. estimated_hours = self.rule_engine.estimate_duration(task_type, project_profile)
  600. # 8. 生成推理解释
  601. reasoning = self._generate_reasoning(task_type, project_profile, recommended_flow, confidence)
  602. # 9. 提供替代方案
  603. alternatives = self._generate_alternatives(flow_scores, recommended_flow)
  604. # 10. 元数据
  605. metadata = {
  606. "task_type": task_type.value,
  607. "project_profile": asdict(project_profile),
  608. "flow_scores": flow_scores,
  609. "timestamp": datetime.now().isoformat()
  610. }
  611. return DecisionResult(
  612. recommended_flow=recommended_flow,
  613. confidence=confidence,
  614. reasoning=reasoning,
  615. steps=steps,
  616. estimated_hours=estimated_hours,
  617. alternatives=alternatives,
  618. metadata=metadata
  619. )
  620. def _apply_context_overrides(self, project_profile: ProjectProfile, context: Dict[str, Any]):
  621. """应用上下文覆盖"""
  622. if "team_size" in context:
  623. project_profile.team_size = context["team_size"]
  624. if "project_type" in context:
  625. project_profile.project_type = context["project_type"]
  626. if "complexity" in context:
  627. if isinstance(context["complexity"], str):
  628. project_profile.complexity = ProjectComplexity(context["complexity"])
  629. def _generate_steps(self, flow_mode: str) -> List[str]:
  630. """生成流程步骤"""
  631. flow_steps = {
  632. "minimal": [
  633. "Planning (P): 需求分析和任务规划",
  634. "Development (D): 开发实现",
  635. "Review (R): 代码审查和测试验证"
  636. ],
  637. "standard": [
  638. "Planning 1 (P1): 需求分析和架构设计",
  639. "Planning 2 (P2): 详细设计和任务分解",
  640. "Development 1 (D1): 核心功能开发",
  641. "Development 2 (D2): 集成和优化",
  642. "Review 1 (R1): 测试和质量验证"
  643. ],
  644. "complete": [
  645. "Strategy (S1): 项目策略和目标制定",
  646. "Analysis (S2): 需求分析和可行性研究",
  647. "Design (S3): 系统设计和架构规划",
  648. "Planning (S4): 详细计划和资源分配",
  649. "Development (S5): 开发实现",
  650. "Integration (S6): 系统集成和联调",
  651. "Testing (S7): 全面测试和质量保证",
  652. "Deployment (S8): 部署和上线"
  653. ]
  654. }
  655. return flow_steps.get(flow_mode, flow_steps["standard"])
  656. def _generate_reasoning(self, task_type: TaskType, project_profile: ProjectProfile,
  657. recommended_flow: str, confidence: float) -> str:
  658. """生成推理解释"""
  659. reasons = []
  660. # 任务类型相关推理
  661. if task_type == TaskType.BUG_FIX:
  662. reasons.append("这是一个bug修复任务,通常需要快速响应")
  663. elif task_type == TaskType.FEATURE_DEVELOPMENT:
  664. reasons.append("这是功能开发任务,需要完整的开发流程")
  665. elif task_type == TaskType.ARCHITECTURE:
  666. reasons.append("这是架构设计任务,需要严格的规划和审查")
  667. # 项目特征相关推理
  668. if project_profile.team_size <= 3:
  669. reasons.append("小团队适合轻量级流程")
  670. elif project_profile.team_size > 8:
  671. reasons.append("大团队需要更严格的协调流程")
  672. if project_profile.complexity == ProjectComplexity.SIMPLE:
  673. reasons.append("项目复杂度较低,可以简化流程")
  674. elif project_profile.complexity == ProjectComplexity.ENTERPRISE:
  675. reasons.append("企业级项目需要完整的质量保证流程")
  676. # 技术特征相关推理
  677. if project_profile.has_tests:
  678. reasons.append("项目有测试基础,支持标准化流程")
  679. if project_profile.has_ci_cd:
  680. reasons.append("项目有CI/CD支持,适合自动化流程")
  681. # 置信度解释
  682. if confidence > 0.8:
  683. confidence_text = "高置信度推荐"
  684. elif confidence > 0.6:
  685. confidence_text = "中等置信度推荐"
  686. else:
  687. confidence_text = "低置信度推荐,请根据实际情况调整"
  688. reasoning = f"推荐使用{recommended_flow}流程,{confidence_text}。"
  689. if reasons:
  690. reasoning += "主要原因:" + ",".join(reasons) + "。"
  691. return reasoning
  692. def _generate_alternatives(self, flow_scores: Dict[str, float],
  693. recommended_flow: str) -> List[Dict[str, Any]]:
  694. """生成替代方案"""
  695. alternatives = []
  696. # 排序并排除推荐流程
  697. sorted_flows = sorted(
  698. [(flow, score) for flow, score in flow_scores.items() if flow != recommended_flow],
  699. key=lambda x: x[1], reverse=True
  700. )
  701. for flow, score in sorted_flows[:2]: # 最多2个替代方案
  702. reason = self._get_alternative_reason(flow, score)
  703. alternatives.append({
  704. "flow": flow,
  705. "confidence": score,
  706. "reason": reason
  707. })
  708. return alternatives
  709. def _get_alternative_reason(self, flow: str, score: float) -> str:
  710. """获取替代方案的推荐理由"""
  711. reasons = {
  712. "minimal": "如果时间紧迫或团队经验丰富,可以选择轻量级流程",
  713. "standard": "如果需要平衡效率和质量,可以选择标准流程",
  714. "complete": "如果质量要求很高或团队较大,可以选择完整流程"
  715. }
  716. base_reason = reasons.get(flow, "可以考虑此流程")
  717. if score > 0.5:
  718. return f"{base_reason}(匹配度:{score:.1%})"
  719. else:
  720. return f"{base_reason}(匹配度较低:{score:.1%})"
  721. # 全局引擎实例
  722. _engine_instance = None
  723. def get_decision_engine(project_root: Path = None) -> RuleBasedDecisionEngine:
  724. """获取决策引擎单例"""
  725. global _engine_instance
  726. if _engine_instance is None:
  727. _engine_instance = RuleBasedDecisionEngine(project_root)
  728. return _engine_instance
  729. def main():
  730. """测试主函数"""
  731. engine = get_decision_engine()
  732. # 测试用例
  733. test_cases = [
  734. "修复用户登录页面的显示错误",
  735. "为移动应用添加推送通知功能",
  736. "重构支付模块的代码结构",
  737. "编写API接口的单元测试",
  738. "更新项目的技术文档",
  739. "调研新的前端框架",
  740. "设计微服务架构",
  741. "部署应用到生产环境"
  742. ]
  743. print("🤖 AceFlow基于规则的决策引擎测试")
  744. print("=" * 50)
  745. for i, task in enumerate(test_cases, 1):
  746. print(f"\n测试用例 {i}: {task}")
  747. print("-" * 30)
  748. result = engine.make_decision(task)
  749. print(f"推荐流程: {result.recommended_flow}")
  750. print(f"置信度: {result.confidence:.1%}")
  751. print(f"推理: {result.reasoning}")
  752. print(f"预估时间: {result.estimated_hours}小时")
  753. print(f"流程步骤: {len(result.steps)}个步骤")
  754. if result.alternatives:
  755. print("替代方案:")
  756. for alt in result.alternatives:
  757. print(f" - {alt['flow']}: {alt['reason']}")
  758. if __name__ == "__main__":
  759. main()