feat: 技能检索LLM语义匹配+SDLC能力声明功能菜单(项目/Bug/测试/迭代)

This commit is contained in:
ymq 2026-08-16 17:19:48 +08:00
parent 488ba4daa4
commit 50c9220d2d
2 changed files with 56 additions and 9 deletions

View File

@ -357,6 +357,35 @@ class AgentExecutor:
except Exception:
return self.config.skills.base_dir
async def _llm_select_skills(self, user_input: str, catalog, max_skills: int) -> List[str]:
"""用 LLM 从技能目录选最相关的 N 个技能(语义匹配,非关键词硬编码)。"""
if not catalog:
return []
catalog_text = "\n".join(f"- {name}: {desc}" for name, desc in catalog)
prompt = (
f"以下是可用技能目录(技能名: 描述):\n{catalog_text}\n\n"
f"用户需求:{user_input}\n\n"
f"请从上述目录中选出与用户需求最相关的至多 {max_skills} 个技能,"
f"只返回技能名的 JSON 数组,如 [\"a\", \"b\"]。没有相关技能返回 []。"
)
try:
from pipeline_service.llm_bridge import llm_call_msgs
import json as _json
import re as _re
content = await llm_call_msgs(
[{"role": "user", "content": prompt}],
model=self.model_name,
temperature=0,
)
m = _re.search(r"\[[^\]]*\]", content or "")
if m:
names = _json.loads(m.group(0))
return [n for n in names if isinstance(n, str)]
return []
except Exception as e:
logger.error(f"_llm_select_skills failed: {e}")
return []
async def _build_system_prompt(self, user_input: str) -> str:
"""组装完整 system prompt = 基础 prompt + 记忆 + 技能 + 工具列表"""
prompt = self.config.system_prompt
@ -385,15 +414,27 @@ class AgentExecutor:
# 注入技能六级隔离global→org→pipeline→role→project→user
if self.config.skills.enabled and self._skill_loader:
skill_block = self._skill_loader.build_prompt_block(
user_input=user_input,
pipeline_id=self.pipeline_id if self.config.skills.enable_pipeline else "",
role=self.role if self.config.skills.enable_role else "",
project_id=self.project_id if self.config.skills.enable_project else "",
org_id=self.org_id if self.config.skills.enable_org else "",
user_id=self.user_id if self.config.skills.enable_user else "",
max_skills=self.config.skills.max_skills_per_turn,
)
pipeline_id = self.pipeline_id if self.config.skills.enable_pipeline else ""
role = self.role if self.config.skills.enable_role else ""
project_id = self.project_id if self.config.skills.enable_project else ""
org_id = self.org_id if self.config.skills.enable_org else ""
user_id = self.user_id if self.config.skills.enable_user else ""
# 技能检索必须 LLM 做(语义匹配,非关键词硬编码)
catalog = self._skill_loader.get_skill_catalog(
pipeline_id=pipeline_id, role=role, project_id=project_id,
org_id=org_id, user_id=user_id)
names = await self._llm_select_skills(
user_input, catalog, self.config.skills.max_skills_per_turn)
if names:
skill_block = self._skill_loader.build_prompt_block_by_names(
names, pipeline_id=pipeline_id, role=role, project_id=project_id,
org_id=org_id, user_id=user_id)
else:
# LLM 无结果兜底:按 scope+名字列出前 N 个(不传 user_input走 fallback 分支)
skill_block = self._skill_loader.build_prompt_block(
pipeline_id=pipeline_id, role=role, project_id=project_id,
org_id=org_id, user_id=user_id,
max_skills=self.config.skills.max_skills_per_turn)
if skill_block:
prompt += f"\n\n{skill_block}"

View File

@ -610,6 +610,12 @@ def register_sdlc_ability():
system_prompt=SDL_PROMPT,
handlers=SDL_HANDLERS,
roles=SDL_ROLES,
menus=[
{"label": "📁 项目", "icon": "", "url": "/pipeline-sdlc/sd_projects/index.ui", "type": "popup"},
{"label": "🐛 Bug", "icon": "", "url": "/pipeline-sdlc/sd_bugs/index.ui", "type": "popup"},
{"label": "🧪 测试用例", "icon": "", "url": "/pipeline-sdlc/sd_test_cases/index.ui", "type": "popup"},
{"label": "🔄 迭代", "icon": "", "url": "/pipeline-sdlc/sd_iterations/index.ui", "type": "popup"},
],
)
register_ability(ability)
return ability