3. 自动检测机制
3.1 检测流程
┌─────────────────────────────────────────────────────────────────┐
│ Skill Detection Pipeline │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. User Input │
│ "Deploy the app to production" │
│ │
│ 2. Extract Intent │
│ ├── Keywords: [deploy, production] │
│ ├── Action: deployment │
│ └── Context: production environment │
│ │
│ 3. Match Skills │
│ ├── Keyword Match │
│ │ deployment-skill: triggers=[deploy, production] ✓ │
│ │ testing-skill: triggers=[test, qa] ✗ │
│ │ │
│ ├── Semantic Match │
│ │ deployment-skill: similarity=0.92 ✓ │
│ │ monitoring-skill: similarity=0.45 ✗ │
│ │ │
│ └── Context Match │
│ deployment-skill: context=production ✓ │
│ │
│ 4. Rank Candidates │
│ 1. deployment-skill (score: 0.95) │
│ 2. docker-skill (score: 0.72) │
│ 3. k8s-skill (score: 0.68) │
│ │
│ 5. Load Top Skill │
│ Load: deployment-skill │
│ │
│ 6. Inject into Context │
│ System Prompt += deployment-skill content │
│ │
└─────────────────────────────────────────────────────────────────┘3.2 实现代码
typescript
class SkillDetector {
private skills: Map<string, SkillMetadata> = new Map();
private embeddings: Map<string, number[]> = new Map();
// 初始化:扫描并加载所有 skill 元数据
async initialize(skillsDir: string) {
const skillDirs = await fs.readdir(skillsDir);
for (const dir of skillDirs) {
const skillPath = path.join(skillsDir, dir, 'SKILL.md');
if (await fs.exists(skillPath)) {
const metadata = await this.parseSkillMetadata(skillPath);
this.skills.set(metadata.name, metadata);
// 预计算嵌入向量
const embedding = await this.embed(
`${metadata.name}: ${metadata.description}`
);
this.embeddings.set(metadata.name, embedding);
}
}
console.log(`Loaded ${this.skills.size} skills`);
}
// 检测相关 skills
async detect(userInput: string, limit = 3): Promise<Skill[]> {
const candidates: SkillCandidate[] = [];
for (const [name, metadata] of this.skills) {
const score = await this.calculateScore(userInput, metadata);
if (score > 0.5) { // 阈值
candidates.push({ name, metadata, score });
}
}
// 排序并返回 top N
const topSkills = candidates
.sort((a, b) => b.score - a.score)
.slice(0, limit);
// 加载完整 skill 内容
return await Promise.all(
topSkills.map(c => this.loadSkill(c.name))
);
}
// 计算匹配分数
private async calculateScore(
userInput: string,
metadata: SkillMetadata
): Promise<number> {
let score = 0;
// 1. 关键词匹配 (40%)
const keywordScore = this.keywordMatch(userInput, metadata.triggers);
score += keywordScore * 0.4;
// 2. 语义匹配 (40%)
const semanticScore = await this.semanticMatch(userInput, metadata.name);
score += semanticScore * 0.4;
// 3. 上下文匹配 (20%)
const contextScore = this.contextMatch(metadata);
score += contextScore * 0.2;
return score;
}
// 关键词匹配
private keywordMatch(input: string, triggers: string[]): number {
const inputLower = input.toLowerCase();
const matches = triggers.filter(t =>
inputLower.includes(t.toLowerCase())
);
return matches.length / triggers.length;
}
// 语义匹配
private async semanticMatch(input: string, skillName: string): Promise<number> {
const inputEmbedding = await this.embed(input);
const skillEmbedding = this.embeddings.get(skillName)!;
return this.cosineSimilarity(inputEmbedding, skillEmbedding);
}
// 上下文匹配
private contextMatch(metadata: SkillMetadata): number {
// 检查当前项目是否匹配 skill 的上下文
// 例如:检查是否有 Dockerfile, k8s 配置等
let score = 0;
if (metadata.name === 'deployment-skill') {
if (fs.existsSync('Dockerfile')) score += 0.5;
if (fs.existsSync('k8s/')) score += 0.5;
}
return score;
}
// 解析 SKILL.md frontmatter
private async parseSkillMetadata(skillPath: string): Promise<SkillMetadata> {
const content = await fs.readFile(skillPath, 'utf-8');
// 提取 YAML frontmatter
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) {
throw new Error(`Invalid SKILL.md: ${skillPath}`);
}
const frontmatter = yaml.parse(match[1]);
return {
name: frontmatter.name,
description: frontmatter.description,
triggers: frontmatter.triggers || [],
version: frontmatter.version,
author: frontmatter.author,
tags: frontmatter.tags || [],
path: skillPath
};
}
// 加载完整 skill
private async loadSkill(name: string): Promise<Skill> {
const metadata = this.skills.get(name)!;
const content = await fs.readFile(metadata.path, 'utf-8');
// 移除 frontmatter,只保留 markdown 内容
const body = content.replace(/^---\n[\s\S]*?\n---\n/, '');
return {
...metadata,
content: body
};
}
}