From 2dcf463b42a48e433d8ca753837dc88f1b1c4846 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 31 Aug 2026 16:24:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(skills):=20load=5Fskill=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E5=90=8D=E5=BD=92=E4=B8=80=E5=8C=96=E2=80=94=E2=80=94=E5=AE=B9?= =?UTF-8?q?=E5=BF=8DLLM=E4=BC=A0=E5=85=A5[=E4=BA=A7=E7=BA=BF]=E5=89=8D?= =?UTF-8?q?=E7=BC=80/=E5=8A=A0=E7=B2=97/=E6=8F=8F=E8=BF=B0=E5=B0=BE?= =?UTF-8?q?=E5=B7=B4=E7=AD=89=E7=9B=AE=E5=BD=95=E5=B1=82=E8=A3=85=E9=A5=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_core/skill_loader.py | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pipeline_core/skill_loader.py b/pipeline_core/skill_loader.py index 6dc77d8..168ad33 100644 --- a/pipeline_core/skill_loader.py +++ b/pipeline_core/skill_loader.py @@ -189,6 +189,50 @@ def _parse_frontmatter(content: str) -> Optional[dict]: return result +# ── LLM 技能名归一化(目录层装饰容错)── + +def normalize_skill_name(name: str) -> str: + """清洗 LLM 传入技能名时携带的目录层装饰噪音: + '[产线] bid-workflow' / '**[产线] bid-workflow**: 总纲' / 'bid-workflow(总纲)' → 'bid-workflow'。 + 技能名只含字母/数字/连字符/下划线,其余皆是装饰。""" + n = (name or '').strip().replace('**', '').replace('`', '') + n = re.sub(r'^\s*(\[[^\]]*\]\s*)+', '', n) # scope 标签:[产线]/[通用]/[角色](可能多个) + n = re.split(r'[::]', n, 1)[0] # 冒号后的描述尾巴 + n = re.split(r'\s+[—–-]\s+', n, 1)[0] # 破折号描述(两侧须有空白,不伤 bid-workflow 自带连字符) + n = re.sub(r'\s*[((][^))]*[))]\s*$', '', n) # 尾部括号注释 + return n.strip() + + +def resolve_skill(merged: Dict[str, "Skill"], name: str) -> Optional["Skill"]: + """按名称在 merged 中查技能,容忍 LLM 装饰。 + 匹配顺序:精确 → 清洗后精确 → 忽略大小写 → 子串唯一命中。""" + if not name: + return None + if name in merged: + return merged[name] + cleaned = normalize_skill_name(name) + if cleaned in merged: + return merged[cleaned] + lower_map = {k.lower(): k for k in merged} + if cleaned.lower() in lower_map: + return merged[lower_map[cleaned.lower()]] + if len(cleaned) >= 4: + hits = [k for k in merged if k in cleaned or cleaned in k] + if len(hits) == 1: + return merged[hits[0]] + return None + + +def format_skill_names(merged: Dict[str, "Skill"], limit: int = 60) -> str: + """FAIL 提示用技能名列表:高优先级 scope(role/pipeline/project/org)排前,超出截断。""" + ordered = sorted(merged.values(), key=lambda s: (-SCOPE_PRIORITY.get(s.scope, 0), s.name)) + names = [s.name for s in ordered[:limit]] + text = ", ".join(names) + if len(merged) > len(names): + text += f" …(还有 {len(merged) - len(names)} 个)" + return text or "(无可用技能)" + + # ── 六级技能加载器 ── class SkillLoader: