diff --git a/pipeline_service/agent_loop_v2.py b/pipeline_service/agent_loop_v2.py index 2308f8b..0831014 100644 --- a/pipeline_service/agent_loop_v2.py +++ b/pipeline_service/agent_loop_v2.py @@ -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}" diff --git a/pipeline_service/sdlc_ability.py b/pipeline_service/sdlc_ability.py index 239f8dc..1b1aac2 100644 --- a/pipeline_service/sdlc_ability.py +++ b/pipeline_service/sdlc_ability.py @@ -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