
Sparksatchel
- 5 installs
- 4 repo stars
- Updated March 8, 2026
- gccszs/spark-satchel
Recommend the most appropriate Claude Code skill for a request using semantic search, intent analysis, and confidence tiers that decide whether to auto-recommend or offer alternatives.
About
A meta-skill that retrieves and recommends skills via bilingual embeddings, three-tier confidence scoring, and historical learning, plus health checks and cache cleanup. A developer uses it when a request could match multiple skills and selection needs intelligent analysis.
- Three confidence tiers drive auto-recommend versus primary-plus-alternatives
- Learns from usage history and performs automatic health and cache management
Sparksatchel by the numbers
- 5 all-time installs (skills.sh)
- Ranked #568 of 782 Skill Development skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gccszs/spark-satchel --skill sparksatchelAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 4 |
| Last updated | March 8, 2026 |
| Repository | gccszs/spark-satchel ↗ |
What it does
Recommend the most appropriate Claude Code skill for a request using semantic search, intent analysis, and confidence tiers that decide whether to auto-recommend or offer alternatives.
Files
SparkSatchel 灵犀妙计
A Meta-Skill that provides intelligent skill retrieval and recommendation for Claude Code.
Core Philosophy
"Think twice before acting, keep the user burden-free"
Quick Start
from src.retriever import SparkSatchel
sparksatchel = SparkSatchel()
result = sparksatchel.retrieve("process this PDF")Decision Mechanism
The system evaluates confidence and responds accordingly:
| Confidence Level | Threshold | Action |
|---|---|---|
| High | >70% | Auto-recommend with reasoning |
| Medium | 40-70% | Recommend primary + alternatives |
| Low | <40% | Present candidates and ask user |
Key Features
1. Semantic Retrieval
- Bilingual embeddings: Supports Chinese and English via paraphrase-multilingual-MiniLM-L12-v2
- Sharded storage: Skills organized by category for efficient retrieval
- Vector similarity: Matches user intent to skill descriptions
2. Intent Analysis
Extracts from user requests:
- Primary intent
- Keywords
- Entities (filenames, formats, etc.)
3. Historical Learning
- Tracks all skill calls
- Records success/failure feedback
- Calculates skill success rates
- Optimizes recommendation ranking
4. Health Checking
- Detects missing skills
- Identifies corrupted skills
- Handles version mismatches
- Provides fallback strategies
5. Cache Management
- Monitors database size
- Tracks record count
- Suggests cleanup when needed
- Supports auto/manual cleanup
Usage Examples
High Confidence (Auto-recommend)
User: "Process this PDF"
SparkSatchel: "I recommend pdf-skill because it specializes in PDF documents (92% historical success rate)"Medium Confidence (With alternatives)
User: "Create a document"
SparkSatchel: "I suggest docx-skill. pdf-skill is also available. Want me to compare them?"Low Confidence (Ask user)
User: "Process data"
SparkSatchel: "Found several matching skills. Which one fits best?
- xlsx-skill: Excel spreadsheet processing
- pandas-skill: Data analysis with Python
- csv-skill: CSV file handling"Embedding Models
Pre-installed Model (Ready to Use)
SparkSatchel comes with a pre-downloaded bilingual embedding model:
- Model:
paraphrase-multilingual-MiniLM-L12-v2 - Size: ~470MB
- Languages: 50+ including Chinese and English
- Dimension: 384
- Status: ✅ Pre-downloaded, ready to use out-of-the-box
- Location:
~/.cache/huggingface/hub/
The default model provides good balance between:
- ✅ Bilingual support (Chinese + English)
- ✅ Lightweight size
- ✅ Fast inference
- ✅ Offline capability
Model Selection Guide
Choose the right model based on your scenario:
Model Comparison
| Model | Size | Languages | Speed | Accuracy | Best For |
|---|---|---|---|---|---|
| paraphrase-multilingual-MiniLM-L12-v2 | 470MB | 50+ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Default choice - Balanced performance |
| shibing624/text2vec-base-chinese | 110MB | Chinese | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Chinese-only - Faster & more accurate |
| intfloat/multilingual-e5-large | 1.3GB | 100+ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | High accuracy - Best for complex queries |
| BAAI/bge-large-zh-v1.5 | 390MB | Chinese | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Chinese advanced - State-of-the-art |
| all-MiniLM-L6-v2 | 23MB | English | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | English only - Ultra lightweight |
Scenario Recommendations
Quick Download with Script
Use the provided download script for convenience:
# List available models
python scripts/download_model.py --list
# Download default model (already downloaded ✅)
python scripts/download_model.py default
# Download Chinese-optimized model
python scripts/download_model.py chinese
# Download high-accuracy multilingual model
python scripts/download_model.py large
# Download ultra-lightweight English model
python scripts/download_model.py englishScenario 1: Chinese-dominant environment
# Option A: Use download script
python scripts/download_model.py chinese
# Option B: Manual download
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('shibing624/text2vec-base-chinese')"Scenario 2: English-only (fastest)
# Option A: Use download script
python scripts/download_model.py english
# Option B: Manual download
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')"Scenario 3: Maximum accuracy (multilingual)
# Download high-accuracy model
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('intfloat/multilingual-e5-large')"Scenario 4: Cloud-based (no local storage)
# Use OpenAI API (requires API key)
pip install openaiHow to Switch Models
Option 1: Modify code (permanent)
Edit src/models/embedding.py:
class EmbeddingModel:
# Change default model
DEFAULT_MODEL = "shibing624/text2vec-base-chinese" # Your choiceOption 2: Pass model name (temporary)
from src.models.embedding import EmbeddingModel
from src.retriever import SparkSatchel
# Use custom model
custom_model = EmbeddingModel(
model_name="shibing624/text2vec-base-chinese",
device="cpu" # or "cuda" for GPU acceleration
)
# Pass to SparkSatchel
sparksatchel = SparkSatchel(embedding_model=custom_model)Option 3: Use OpenAI API
import openai
def openai_embedding(text: str) -> list:
response = openai.Embedding.create(
model="text-embedding-3-small",
input=text
)
return response['data'][0]['embedding']Model Performance Tips
1. GPU Acceleration: If you have NVIDIA GPU, use device="cuda" for 5-10x speedup 2. Batch Processing: Process multiple texts at once for better throughput 3. Caching: Models are cached after first download, no re-downloading needed 4. Quantization: For memory-constrained environments, use 8-bit quantized models
Project Structure
SparkSatchel/
├── SKILL.md # This file
├── requirements.txt # Dependencies
├── src/
│ ├── retriever.py # Main entry point
│ ├── models/ # Embedding models
│ ├── storage/ # Vector DB + history
│ ├── analysis/ # Intent + confidence
│ └── maintenance/ # Health + lifecycle + cache
└── data/ # Data storage
├── collections/ # Vector databases
└── history.db # Call historyAPI Reference
Main Interface
class SparkSatchel:
def retrieve(self, user_request: str) -> RetrievalResult:
"""Search and recommend skills"""
def feedback(self, skill_name: str, success: bool, feedback: str = ""):
"""Record user feedback"""
def check_health(self) -> Dict:
"""Check system health"""
def cleanup(self, strategy: dict = None):
"""Execute cache cleanup"""Retrieval Result
@dataclass
class RetrievalResult:
confidence: float # 0-1
recommended_skill: str # Skill name
reasoning: str # Explanation
alternative_skills: List[str] # For medium confidence
candidate_skills: List[Dict] # For low confidence
requires_confirmation: bool # Needs user input?Maintenance
Check Health
health = sparksatchel.check_health()
if health["cache"]["needs_cleanup"]:
print(health["suggestion"])Cleanup Cache
from src.maintenance.cache import CleanupStrategy
# By age (delete records older than 30 days)
sparksatchel.cleanup(CleanupStrategy.by_age(days=30))
# By count (keep recent 1000 records)
sparksatchel.cleanup(CleanupStrategy.by_count(keep=1000))Tech Stack
- Python: 3.10+
- Vector DB: ChromaDB
- Embedding: sentence-transformers
- History: SQLite
Performance
| Metric | Target |
|---|---|
| Retrieval latency | <500ms (100k skills) |
| Memory usage | <500MB |
| Startup time | <3s |
| Accuracy | >85% |
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
venv/
ENV/
env/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Data directories (don't upload vector databases and history)
data/collections/
data/cache/
data/*.db
data/*.sqlite
# Model cache (large embedding models)
data/cache/models/
*.bin
*.pt
*.pth
*.onnx
# Logs
*.log
# OS
.DS_Store
Thumbs.db
# Temporary files
*.tmp
*.bak
*.swp
.cache/
# Test coverage
.coverage
htmlcov/
.pytest_cache/
# MyPy
.mypy_cache/
.dmypy.json
dmypy.json
# Non-core documentation files (keep only essential skill files)
DESIGN.md
GITHUB_DESCRIPTION.md
MIT License
Copyright (c) 2026 SparkSatchel Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Embedding Model Download Guide
This guide helps you download and configure different embedding models for SparkSatchel.
Quick Download Commands
Default Model (Already Downloaded ✅)
# paraphrase-multilingual-MiniLM-L12-v2 (470MB)
# Status: Pre-downloaded, ready to use
# Location: ~/.cache/huggingface/hub/Chinese-Optimized Model
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('shibing624/text2vec-base-chinese')"High-Accuracy Multilingual Model
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('intfloat/multilingual-e5-large')"Ultra-Lightweight English Model
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')"How to Switch Models
Method 1: Edit Configuration (Permanent)
Edit src/models/embedding.py:
class EmbeddingModel:
DEFAULT_MODEL = "your-chosen-model-name"Method 2: Runtime Configuration (Temporary)
from src.models.embedding import EmbeddingModel
from src.retriever import SparkSatchel
custom_model = EmbeddingModel(model_name="your-model")
sparksatchel = SparkSatchel(embedding_model=custom_model)Model Storage Location
Models are cached at:
- Linux/Mac:
~/.cache/huggingface/hub/ - Windows:
C:\Users\<username>\.cache\huggingface\hub\
GPU Acceleration
If you have NVIDIA GPU with CUDA:
model = EmbeddingModel(
model_name="paraphrase-multilingual-MiniLM-L12-v2",
device="cuda" # Use GPU instead of CPU
)Performance improvement: 5-10x faster inference.
{
"name": "sparksatchel",
"version": "1.0.0",
"description": "灵犀妙计 SparkSatchel - Intelligent skill retrieval system for AI IDEs",
"bin": {
"sparksatchel": "src/retriever.py"
},
"scripts": {
"start": "python src/retriever.py",
"install-deps": "pip install -r requirements.txt"
},
"keywords": [
"skill-retrieval",
"semantic-search",
"claude-code",
"cursor",
"ai-ide",
"embeddings",
"bilingual"
],
"author": "Codestyle Team",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/gccszs/Spark-Satchel.git"
},
"engines": {
"node": ">=14.0.0"
}
}
<div align="center">
🧠 SparkSatchel
Intelligent Skill Retrieval & Recommendation System
  
English | 中文
🌈 🧠 🌈
"身无彩凤双飞翼,心有灵犀一点通"
Without colorful phoenix wings to fly to you, our hearts connect at a single point.
SparkSatchel helps you and your Agent find the perfect SKILL from thousands by inferring the best match for your current task.
A different kind of spark! ⚡
Compatible with Claude Code, Cursor, Windsurf, Trae, and all AI IDEs
</div>
---
📖 Table of Contents
---
🎯 Overview
SparkSatchel is an intelligent skill retrieval and recommendation system designed for all AI IDEs (Claude Code, Cursor, Windsurf, Trae, etc.).
Through semantic analysis, intent inference, and historical learning, it helps users quickly find the most suitable skill from thousands.
Why SparkSatchel?
As the AI skill ecosystem grows, users may install hundreds of skills. When completing a task:
❌ Traditional: Manually search skill names, read descriptions one by one
✅ SparkSatchel: Describe your need, automatically get the best skill recommendation---
🚀 Quick Start
Method 1: npx Installation (Recommended)
# Quick install with npx
npx skills add gccszs/Spark-Satchel
# Ready to use immediately after installationMethod 2: Git Clone
# Clone repository
git clone https://github.com/gccszs/Spark-Satchel.git
cd Spark-Satchel
# Install dependencies
pip install -r requirements.txt✨ Ready to Use
Great news! The embedding model is pre-downloaded (~470MB), no waiting required:
- ✅ Pre-installed: paraphrase-multilingual-MiniLM-L12-v2
- ✅ Bilingual: Supports 50+ languages
- ✅ Offline: No internet connection needed
- ✅ Plug & Play: Use immediately after installing dependencies
Basic Usage
from src.retriever import SparkSatchel
# Initialize (model pre-installed)
sparksatchel = SparkSatchel()
# Retrieve skills
result = sparksatchel.retrieve("process this PDF")
# Respond based on confidence
if result.confidence > 0.7:
# High confidence - direct recommendation
print(f"✅ Recommend: {result.recommended_skill}")
print(result.reasoning)
elif result.confidence > 0.4:
# Medium confidence - provide alternatives
print(f"💡 Recommend: {result.recommended_skill}")
print(f"Alternatives: {', '.join(result.alternative_skills)}")
else:
# Low confidence - ask user
print("❓ Please choose from:")
for skill in result.candidate_skills:
print(f" - {skill['skill_name']}: {skill['description']}")
# Record feedback (helps system learn)
sparksatchel.feedback(result.recommended_skill, success=True)---
✨ Key Features
🧠 Smart Inference
- Understands natural language descriptions of user intent
- Semantic similarity matching based on embeddings
- Supports 50+ languages with bilingual optimization (Chinese/English)
⚖️ Prudent Decision
Responds intelligently based on confidence:
| Confidence | Action | Example |
|---|---|---|
| High (>70%) | Auto-recommend with reasoning | "Use pdf-skill, 92% success rate" |
| Medium (40-70%) | Recommend + alternatives | "Use docx-skill, alternative: pdf-skill" |
| Low (<40%) | Present candidates + ask user | "Choose: xlsx-skill, pandas-skill..." |
📚 Continuous Learning
- Tracks every skill call
- Records success/failure feedback
- Calculates skill success rates
- Dynamically optimizes recommendation ranking
🔧 Easy Maintenance
- Automatic health checks (monitors skill status)
- Smart cache cleanup (frees storage space)
- Lifecycle management (version migration, fallback strategies)
---
📊 Decision Mechanism
User Request → Intent Analysis → Vector Search → Confidence → Decision
↓
┌─────────────────────────────────┐
│ Confidence │
├─────────────────────────────────┤
│ High (>70%) │ Med (40-70%) │ Low│
├─────────────────────────────────┤
│ Auto-rec │ Rec+Alt │ Ask│
└─────────────────────────────────┘Usage Examples
Example 1: High Confidence
User: "Process this PDF"
SparkSatchel: "I recommend pdf-skill because it specializes in PDF documents (92% historical success rate)"Example 2: Medium Confidence
User: "Create a document"
SparkSatchel: "I suggest docx-skill. pdf-skill is also available. Want me to compare?"Example 3: Low Confidence
User: "Process data"
SparkSatchel: "Found 3 matching skills:
- xlsx-skill: Excel spreadsheet processing
- pandas-skill: Python data analysis
- csv-skill: CSV file handling
Please choose the most suitable one."---
🛠️ Features
1. Semantic Retrieval
| Feature | Description |
|---|---|
| Sharded Storage | Skills organized by category for efficient retrieval |
| Vector Similarity | Semantic matching based on embeddings |
| Bilingual | Default model supports 50+ languages |
2. Intent Analysis
Extracts from user requests:
- Primary intent (e.g., document processing, project creation)
- Keywords (e.g., PDF, Word, Excel)
- Entities (e.g., filenames, formats)
3. Confidence Evaluation
Multi-dimensional scoring:
- Similarity (50%): Semantic matching degree
- History (30%): Success rate and call count
- Relevance (15%): Keyword matching
- Freshness (5%): Recent usage bonus
4. Historical Learning
# Get skill statistics
stats = sparksatchel.history.get_skill_stats("pdf-skill")
print(f"Success rate: {stats.success_rate:.0%}")
print(f"Total calls: {stats.total_calls}")
print(f"Last called: {stats.last_called}")5. Health Checking
# Check system health
health = sparksatchel.check_health()
if health["cache"]["needs_cleanup"]:
print(f"⚠️ {health['suggestion']}")
if health["skills"]["unhealthy_count"] > 0:
print(f"⚠️ Found {health['skills']['unhealthy_count']} unhealthy skills")6. Cache Management
from src.maintenance.cache import CleanupStrategy
# Cleanup by age (delete records older than 30 days)
sparksatchel.cleanup(CleanupStrategy.by_age(days=30))
# Cleanup by count (keep recent 1000 records)
sparksatchel.cleanup(CleanupStrategy.by_count(keep=1000))
# Auto cleanup (if needed)
sparksatchel.cache_manager.auto_cleanup_if_needed()---
🔧 Tech Stack
| Component | Technology | Description |
|---|---|---|
| Language | Python 3.10+ | Main development language |
| Vector DB | ChromaDB | Local vector storage |
| Embedding | sentence-transformers | Bilingual support |
| History | SQLite | Lightweight database |
| Vector Math | NumPy | Efficient numerical computation |
Project Structure
SparkSatchel/
├── SKILL.md # Meta-skill definition
├── README.md # Chinese documentation
├── README_EN.md # This file (English)
├── MODELS.md # Model selection guide
├── requirements.txt # Dependencies
├── package.json # npx configuration
│
├── scripts/ # Utility scripts
│ └── download_model.py # Model download script
│
├── src/ # Source code
│ ├── retriever.py # Main entry point
│ ├── models/ # Embedding wrapper
│ ├── storage/ # Vector DB + history
│ ├── analysis/ # Intent + confidence
│ └── maintenance/ # Health + lifecycle + cache
│
└── data/ # Data directory
├── collections/ # Vector databases (sharded)
├── history.db # Call history
└── cache/ # Cache directory---
📚 Documentation
| Document | Description |
|---|---|
| MODELS.md | Embedding model selection and download guide |
| SKILL.md | Meta-skill definition |
---
🎨 Design Philosophy
Spark (灵犀)
"身无彩凤双飞翼,心有灵犀一点通"
- Spark of understanding user intent
- Semantic similarity matching
- Bilingual support
Satchel (妙计)
"锦囊妙计,随需随取"
- Bag full of skills
- Prudent decision mechanism
- Continuous learning optimization
---
🤝 Contributing
Contributions, issues, and feature requests are welcome!
1. Fork the repository 2. Create your feature branch (git checkout -b feature/AmazingFeature) 3. Commit your changes (git commit -m 'Add some AmazingFeature') 4. Push to the branch (git push origin feature/AmazingFeature) 5. Open a Pull Request
---
📄 License
This project is licensed under the MIT License.
---
🙏 Acknowledgments
- ChromaDB - Excellent vector database
- sentence-transformers - Powerful text embeddings
- All contributors and users
---
<div align="center">
Making every skill call precise ⚡
Compatible with Claude Code, Cursor, Windsurf, Trae, and all AI IDEs
🐝 Made with ❤️ by <a href="https://github.com/codestyle-mafeng">Codestyle Team</a>
</div>
<div align="center">
🧠 SparkSatchel 灵犀妙计
智能技能检索与推荐系统
  
English | 中文
🌈 🧠 🌈
"身无彩凤双飞翼,心有灵犀一点通"
灵犀妙计帮助你和你的 Agent 从万千技能中推断出你最适合当前任务的技能。
不一样的花火!⚡
适用于 Claude Code、Cursor、Windsurf、Trae 等所有 AI IDE
</div>
---
📖 目录
---
🎯 简介
SparkSatchel (灵犀妙计) 是一个智能技能检索与推荐系统,适用于所有 AI IDE(Claude Code、Cursor、Windsurf、Trae 等)。
它通过语义分析、意图推断和历史学习,帮助用户从海量技能中快速找到最合适的那个。
为什么需要灵犀妙计?
随着 AI 技能生态系统的发展,用户可能会安装数百个技能。当需要完成某个任务时:
❌ 传统方式:手动搜索技能名称,逐个查看描述
✅ 灵犀妙计:描述你的需求,自动推荐最合适的技能---
🚀 快速开始
方式一:npx 安装(推荐)
# 使用 npx 快速安装技能
npx skills add gccszs/Spark-Satchel
# 安装完成即可使用,无需额外配置方式二:Git 克隆
# 克隆仓库
git clone https://github.com/gccszs/Spark-Satchel.git
cd Spark-Satchel
# 安装依赖
pip install -r requirements.txt✨ 开袋即用
好消息! Embedding 模型已预下载 (~470MB),无需等待:
- ✅ 预装模型: paraphrase-multilingual-MiniLM-L12-v2
- ✅ 中英双语: 支持 50+ 语言
- ✅ 离线可用: 无需联网
- ✅ 即装即用: 安装依赖后立即使用
基本使用
from src.retriever import SparkSatchel
# 初始化(模型已预装)
sparksatchel = SparkSatchel()
# 检索技能
result = sparksatchel.retrieve("处理这个PDF")
# 根据置信度响应
if result.confidence > 0.7:
# 高置信度 - 直接推荐
print(f"✅ 建议使用 {result.recommended_skill}")
print(result.reasoning)
elif result.confidence > 0.4:
# 中置信度 - 提供备选
print(f"💡 建议使用 {result.recommended_skill}")
print(f"备选:{', '.join(result.alternative_skills)}")
else:
# 低置信度 - 询问用户
print("❓ 请从以下技能中选择:")
for skill in result.candidate_skills:
print(f" - {skill['skill_name']}: {skill['description']}")
# 记录反馈(帮助系统学习)
sparksatchel.feedback(result.recommended_skill, success=True)---
✨ 核心特点
🧠 智能推断
- 理解用户意图的自然语言描述
- 基于 embedding 的语义相似度匹配
- 支持 50+ 语言,中英双语优化
⚖️ 审慎决策
根据置信度智能响应:
| 置信度 | 行为 | 示例 |
|---|---|---|
| 高 (>70%) | 自动推荐 + 说明理由 | "建议用 pdf-skill,成功率 92%" |
| 中 (40-70%) | 推荐 + 备选方案 | "建议用 docx-skill,备选:pdf-skill" |
| 低 (<40%) | 展示候选 + 询问用户 | "请选择:xlsx-skill、pandas-skill..." |
📚 持续学习
- 记录每次技能调用
- 追踪成功/失败反馈
- 计算技能成功率
- 动态优化推荐排序
🔧 易于维护
- 自动健康检查(检测技能状态)
- 智能缓存清理(释放存储空间)
- 生命周期管理(版本迁移、降级策略)
---
📊 决策机制
用户请求 → 意图推断 → 向量检索 → 置信度评估 → 决策
↓
┌─────────────────────────────────┐
│ 置信度 │
├─────────────────────────────────┤
│ 高 (>70%) │ 中 (40-70%) │ 低 │
├─────────────────────────────────┤
│ 自动推荐 │ 推荐+备选 │ 询问 │
└─────────────────────────────────┘使用示例
示例 1:高置信度
用户: "处理这个PDF"
灵犀妙计: "建议用 pdf-skill,因为它专门处理 PDF 文档(历史成功率 92%)"示例 2:中置信度
用户: "创建文档"
灵犀妙计: "建议用 docx-skill。pdf-skill 也可以,要我详细对比吗?"示例 3:低置信度
用户: "处理数据"
灵犀妙计: "找到 3 个可能符合的技能:
- xlsx-skill: Excel 电子表格处理
- pandas-skill: Python 数据分析
- csv-skill: CSV 文件处理
请选择最合适的一个。"---
🛠️ 功能详解
1. 语义检索
| 特性 | 说明 |
|---|---|
| 分库存储 | 技能按类别分库,提升检索效率 |
| 向量相似度 | 基于 embedding 的语义匹配 |
| 中英双语 | 默认模型支持 50+ 语言 |
2. 意图推断
从用户请求中提取:
- 主要意图(如:文档处理、项目创建)
- 关键词(如:PDF、Word、Excel)
- 实体(如:文件名、格式)
3. 置信度评估
多维度综合评分:
- 相似度 (50%): 语义匹配程度
- 历史表现 (30%): 成功率和调用次数
- 相关性 (15%): 关键词匹配
- 新鲜度 (5%): 最近使用加成
4. 历史学习
# 获取技能统计
stats = sparksatchel.history.get_skill_stats("pdf-skill")
print(f"成功率: {stats.success_rate:.0%}")
print(f"调用次数: {stats.total_calls}")
print(f"最后调用: {stats.last_called}")5. 健康检查
# 检查系统健康
health = sparksatchel.check_health()
if health["cache"]["needs_cleanup"]:
print(f"⚠️ {health['suggestion']}")
if health["skills"]["unhealthy_count"] > 0:
print(f"⚠️ 发现 {health['skills']['unhealthy_count']} 个异常技能")6. 缓存管理
from src.maintenance.cache import CleanupStrategy
# 按时间清理(删除 30 天前的记录)
sparksatchel.cleanup(CleanupStrategy.by_age(days=30))
# 按数量清理(保留最近 1000 条)
sparksatchel.cleanup(CleanupStrategy.by_count(keep=1000))
# 自动清理(如果需要)
sparksatchel.cache_manager.auto_cleanup_if_needed()---
🔧 技术架构
技术栈
| 组件 | 技术 | 说明 |
|---|---|---|
| 编程语言 | Python 3.10+ | 主要开发语言 |
| 向量数据库 | ChromaDB | 本地向量存储 |
| Embedding | sentence-transformers | 中英双语支持 |
| 历史记录 | SQLite | 轻量级数据库 |
| 向量计算 | NumPy | 高效数值计算 |
项目结构
SparkSatchel/
├── SKILL.md # Meta-skill 定义
├── README.md # 本文档(中文)
├── README_EN.md # 英文文档
├── MODELS.md # 模型选择指南
├── requirements.txt # 依赖列表
├── package.json # npx 配置
│
├── scripts/ # 工具脚本
│ └── download_model.py # 模型下载脚本
│
├── src/ # 源代码
│ ├── retriever.py # 主入口
│ ├── models/ # Embedding 封装
│ ├── storage/ # 向量库 + 历史
│ ├── analysis/ # 意图 + 置信度
│ └── maintenance/ # 健康 + 生命周期 + 缓存
│
└── data/ # 数据目录
├── collections/ # 向量数据库(分库)
├── history.db # 历史记录
└── cache/ # 缓存目录---
📚 文档
| 文档 | 描述 |
|---|---|
| MODELS.md | Embedding 模型选择和下载指南 |
| SKILL.md | Meta-skill 定义(英文) |
---
🎨 设计理念
灵犀 (Spark)
"身无彩凤双飞翼,心有灵犀一点通"
- 理解用户意图的火花
- 语义相似度匹配
- 中英双语支持
妙计 (Satchel)
"锦囊妙计,随需随取"
- 装满技能的锦囊
- 审慎决策机制
- 持续学习优化
---
🤝 贡献
欢迎贡献代码、报告问题或提出建议!
1. Fork 本项目 2. 创建特性分支 (git checkout -b feature/AmazingFeature) 3. 提交更改 (git commit -m 'Add some AmazingFeature') 4. 推送到分支 (git push origin feature/AmazingFeature) 5. 开启 Pull Request
---
📄 许可证
本项目采用 MIT License 开源协议。
---
🙏 致谢
- ChromaDB - 优秀的向量数据库
- sentence-transformers - 强大的文本 Embedding
- 所有贡献者和使用者
---
<div align="center">
让每一次技能调用都精准到位 ⚡
适用于 Claude Code、Cursor、Windsurf、Trae 等所有 AI IDE
🐝 Made with ❤️ by <a href="https://github.com/codestyle-mafeng">Codestyle Team</a>
</div>
# SparkSatchel 灵犀妙计 - 依赖列表
# Meta-Skill for intelligent skill retrieval and recommendation
# 核心依赖
chromadb>=0.5.0 # 向量数据库
sentence-transformers>=2.7.0 # Embedding 模型
numpy>=1.24.0 # 向量运算
# 可选依赖(增强功能)
tiktoken>=0.5.0 # 文本分词(可选)
openai>=1.0.0 # OpenAI API(可选,用于更精准的 embedding)
# 开发依赖
pytest>=7.4.0 # 测试框架
black>=23.0.0 # 代码格式化
mypy>=1.0.0 # 类型检查
"""
Embedding Model Download Script
Download and setup embedding models for SparkSatchel.
Models will be downloaded to the project's data/cache/models directory.
"""
import os
import sys
from pathlib import Path
# Available models
MODELS = {
"default": {
"name": "paraphrase-multilingual-MiniLM-L12-v2",
"size_mb": 470,
"description": "Bilingual (Chinese/English), 50+ languages, balanced performance",
"languages": ["Chinese", "English", "50+ others"],
"dimension": 384
},
"chinese": {
"name": "shibing624/text2vec-base-chinese",
"size_mb": 110,
"description": "Chinese-optimized, faster and more accurate for Chinese",
"languages": ["Chinese"],
"dimension": 768
},
"large": {
"name": "intfloat/multilingual-e5-large",
"size_mb": 1300,
"description": "High accuracy multilingual, best for complex queries",
"languages": ["100+ languages"],
"dimension": 1024
},
"english": {
"name": "all-MiniLM-L6-v2",
"size_mb": 23,
"description": "English only, ultra lightweight",
"languages": ["English"],
"dimension": 384
}
}
def print_models():
"""Print available models"""
print("\n📦 Available Embedding Models")
print("=" * 60)
for key, model in MODELS.items():
print(f"\n[{key}] {model['name']}")
print(f" Size: {model['size_mb']}MB")
print(f" Languages: {', '.join(model['languages'])}")
print(f" Dimension: {model['dimension']}")
print(f" Description: {model['description']}")
def download_model(model_key: str = "default"):
"""Download specified model
Args:
model_key: Key from MODELS dict
"""
if model_key not in MODELS:
print(f"❌ Unknown model: {model_key}")
print_models()
sys.exit(1)
model = MODELS[model_key]
print(f"\n📥 Downloading model: {model['name']}")
print(f" Size: ~{model['size_mb']}MB")
print(f" This may take a few minutes...\n")
try:
from sentence_transformers import SentenceTransformer
import torch
# Check GPU availability
device = "cuda" if torch.cuda.is_available() else "cpu"
if device == "cuda":
print(f"🚀 Using GPU for faster inference")
# Download model
model_obj = SentenceTransformer(model['name'], device=device)
# Get cache location
cache_path = Path(model_obj._cache_dir) if hasattr(model_obj, '_cache_dir') else Path.home() / ".cache" / "huggingface"
print(f"\n✅ Model downloaded successfully!")
print(f" Cache location: {cache_path}")
print(f" Dimension: {model_obj.get_sentence_embedding_dimension()}")
# Test encoding
test_text = "Hello, 世界!"
embedding = model_obj.encode(test_text)
print(f" Test: Encoded '{test_text}' -> vector shape {embedding.shape}")
print(f"\n💡 To use this model, modify src/models/embedding.py:")
print(f" DEFAULT_MODEL = \"{model['name']}\"")
except ImportError:
print("\n❌ sentence-transformers not installed")
print(" Run: pip install sentence-transformers")
sys.exit(1)
except Exception as e:
print(f"\n❌ Download failed: {e}")
sys.exit(1)
def main():
"""Main entry point"""
import argparse
parser = argparse.ArgumentParser(description="Download embedding models for SparkSatchel")
parser.add_argument(
"model",
nargs="?",
default="default",
choices=list(MODELS.keys()),
help="Model to download (default: default)"
)
parser.add_argument(
"--list",
action="store_true",
help="List available models"
)
args = parser.parse_args()
if args.list:
print_models()
sys.exit(0)
download_model(args.model)
if __name__ == "__main__":
main()
"""
置信度评估模块
评估技能推荐的置信度,综合考虑多种因素
"""
from typing import List, Optional
from dataclasses import dataclass
from src.storage.vector_db import SearchResult
from src.storage.history import SkillStats
@dataclass
class ConfidenceBreakdown:
"""置信度分解"""
total: float # 总置信度
similarity: float # 语义相似度
historical: float # 历史成功率
relevance: float # 相关性分数
freshness: float # 最近使用加成
class ConfidenceEvaluator:
"""置信度评估器"""
# 权重配置
WEIGHTS = {
"similarity": 0.5, # 语义相似度权重
"historical": 0.3, # 历史成功率权重
"relevance": 0.15, # 相关性权重
"freshness": 0.05 # 最近使用加成
}
# 置信度阈值
THRESHOLDS = {
"high": 0.70, # 高置信度阈值
"medium": 0.40, # 中置信度阈值
"low": 0.0 # 低置信度阈值
}
def __init__(self, weights: dict = None):
"""初始化评估器
Args:
weights: 自定义权重
"""
if weights:
self.WEIGHTS.update(weights)
def evaluate(
self,
search_result: SearchResult,
stats: Optional[SkillStats] = None,
num_candidates: int = 1
) -> ConfidenceBreakdown:
"""评估单个结果的置信度
Args:
search_result: 搜索结果
stats: 技能统计信息
num_candidates: 候选技能数量
Returns:
置信度分解
"""
# 计算各维度分数
similarity = self._similarity_score(search_result.similarity)
historical = self._historical_score(stats)
relevance = self._relevance_score(search_result, num_candidates)
freshness = self._freshness_score(stats)
# 加权求和
total = (
similarity * self.WEIGHTS["similarity"] +
historical * self.WEIGHTS["historical"] +
relevance * self.WEIGHTS["relevance"] +
freshness * self.WEIGHTS["freshness"]
)
return ConfidenceBreakdown(
total=min(total, 1.0),
similarity=similarity,
historical=historical,
relevance=relevance,
freshness=freshness
)
def evaluate_batch(
self,
search_results: List[SearchResult],
all_stats: dict
) -> List[ConfidenceBreakdown]:
"""批量评估
Args:
search_results: 搜索结果列表
all_stats: 所有技能的统计信息映射
Returns:
置信度分解列表
"""
return [
self.evaluate(
result,
all_stats.get(result.skill_name),
len(search_results)
)
for result in search_results
]
def _similarity_score(self, similarity: float) -> float:
"""计算相似度分数
Args:
similarity: 原始相似度
Returns:
归一化后的分数 [0, 1]
"""
# 相似度已经归一化,但可以应用非线性变换
# 使得高相似度更突出
return similarity ** 0.8
def _historical_score(self, stats: Optional[SkillStats]) -> float:
"""计算历史表现分数
Args:
stats: 技能统计
Returns:
历史分数 [0, 1]
"""
if stats is None or stats.total_calls == 0:
# 没有历史记录,给予中性分数
return 0.5
# 主要考虑成功率
success_rate = stats.success_rate
# 调用次数也影响可信度
call_confidence = min(stats.total_calls / 50, 1.0) # 50次调用达到最高可信度
# 综合评分
return success_rate * 0.8 + call_confidence * 0.2
def _relevance_score(self, result: SearchResult, num_candidates: int) -> float:
"""计算相关性分数
Args:
result: 搜索结果
num_candidates: 候选数量
Returns:
相关性分数 [0, 1]
"""
score = 1.0
# 关键词匹配加成
# TODO: 实现更精确的关键词匹配
# 候选数量影响(候选越少,说明越明确)
if num_candidates == 1:
score *= 1.2
elif num_candidates <= 3:
score *= 1.1
elif num_candidates > 10:
score *= 0.9
return min(score, 1.0)
def _freshness_score(self, stats: Optional[SkillStats]) -> float:
"""计算新鲜度分数(最近使用加成)
Args:
stats: 技能统计
Returns:
新鲜度分数 [0, 1]
"""
if stats is None or not stats.last_called:
return 0.5
# TODO: 实现基于时间的衰减
# 最近使用的技能有轻微加成
return 0.5
def get_confidence_level(self, confidence: float) -> str:
"""获取置信度等级
Args:
confidence: 置信度分数
Returns:
等级: "high", "medium", "low"
"""
if confidence >= self.THRESHOLDS["high"]:
return "high"
elif confidence >= self.THRESHOLDS["medium"]:
return "medium"
else:
return "low"
def should_ask_user(self, confidence: float) -> bool:
"""判断是否需要询问用户
Args:
confidence: 置信度分数
Returns:
是否需要询问
"""
return confidence < self.THRESHOLDS["high"]
"""
意图分析模块
从用户请求中提取意图,支持关键词匹配和语义分析
"""
import re
from typing import List, Optional, Dict, Tuple
from dataclasses import dataclass
@dataclass
class Intent:
"""用户意图"""
primary: str # 主要意图
keywords: List[str] # 提取的关键词
entities: List[str] # 实体(如文件名、格式等)
confidence: float # 意图识别置信度
class IntentAnalyzer:
"""意图分析器"""
# 常见意图模式
INTENT_PATTERNS = {
"document_process": [
r"处理.*文档", r"处理.*文件", r"转换.*格式",
r"处理.*PDF", r"处理.*Word", r"处理.*Excel",
r"extract.*document", r"process.*file"
],
"project_create": [
r"创建.*项目", r"初始化.*项目", r"新建.*项目",
r"based on.*create", r"template.*project"
],
"data_analysis": [
r"分析.*数据", r"处理.*数据", r"统计.*",
r"analyze.*data", r"process.*data"
],
"search_skill": [
r"查找.*技能", r"搜索.*技能", r"什么.*技能",
r"find.*skill", r"search.*skill"
],
"disk_clean": [
r"清理.*磁盘", r"清理.*空间", r"删除.*缓存",
r"clean.*disk", r"free.*space"
],
"ai_collaborate": [
r"多.*AI.*协作", r"AI.*协同", r"Agent.*协作",
r"multi.*agent", r"AI.*collaborate"
]
}
# 实体提取模式
ENTITY_PATTERNS = {
"file_format": [
r"\.(PDF|pdf|DOCX|docx|PPT|ppt|XLSX|xlsx|CSV|csv)",
r"(PDF|Word|Excel|PowerPoint|文档|表格)"
],
"programming_language": [
r"\b(Python|Java|JavaScript|TypeScript|Go|Rust|C\+\+)\b"
]
}
def __init__(self):
"""初始化意图分析器"""
self._compile_patterns()
def _compile_patterns(self):
"""编译正则表达式"""
self.compiled_intents = {}
for intent, patterns in self.INTENT_PATTERNS.items():
self.compiled_intents[intent] = [
re.compile(p, re.IGNORECASE) for p in patterns
]
self.compiled_entities = {}
for entity_type, patterns in self.ENTITY_PATTERNS.items():
self.compiled_entities[entity_type] = [
re.compile(p, re.IGNORECASE) for p in patterns
]
def analyze(self, user_request: str) -> Intent:
"""分析用户请求的意图
Args:
user_request: 用户请求文本
Returns:
意图对象
"""
keywords = self._extract_keywords(user_request)
entities = self._extract_entities(user_request)
primary_intent = self._match_intent(user_request)
confidence = self._calculate_confidence(user_request, primary_intent)
return Intent(
primary=primary_intent,
keywords=keywords,
entities=entities,
confidence=confidence
)
def _extract_keywords(self, text: str) -> List[str]:
"""提取关键词
Args:
text: 输入文本
Returns:
关键词列表
"""
# 简单实现:提取中文词汇和英文单词
# 实际应该使用 jieba 或其他分词工具
# 移除标点符号
text = re.sub(r'[^\w\s\u4e00-\u9fff]', ' ', text)
# 提取中文词汇(2-4字)
chinese_words = re.findall(r'[\u4e00-\u9fff]{2,4}', text)
# 提取英文单词
english_words = re.findall(r'\b[a-zA-Z]{3,}\b', text)
# 过滤常见词
stop_words = {'这个', '那个', '可以', '需要', 'the', 'a', 'an', 'is', 'are'}
keywords = [w for w in chinese_words + english_words if w.lower() not in stop_words]
return list(set(keywords))
def _extract_entities(self, text: str) -> List[str]:
"""提取实体
Args:
text: 输入文本
Returns:
实体列表
"""
entities = []
for entity_type, patterns in self.compiled_entities.items():
for pattern in patterns:
matches = pattern.findall(text)
entities.extend(matches)
return list(set(entities))
def _match_intent(self, text: str) -> str:
"""匹配意图
Args:
text: 输入文本
Returns:
意图名称
"""
best_intent = "general"
best_score = 0
for intent, patterns in self.compiled_intents.items():
score = sum(1 for p in patterns if p.search(text))
if score > best_score:
best_score = score
best_intent = intent
return best_intent
def _calculate_confidence(self, text: str, intent: str) -> float:
"""计算意图识别置信度
Args:
text: 输入文本
intent: 匹配的意图
Returns:
置信度 [0, 1]
"""
# 基于匹配模式数量
patterns = self.compiled_intents.get(intent, [])
match_count = sum(1 for p in patterns if p.search(text))
if match_count == 0:
return 0.3 # 默认置信度
# 匹配越多,置信度越高
confidence = min(0.5 + match_count * 0.15, 1.0)
return confidence
def extract_skill_hints(self, user_request: str) -> List[str]:
"""从请求中提取技能提示
Args:
user_request: 用户请求
Returns:
可能的技能名称列表
"""
hints = []
# 直接提到的技能名
skill_names = re.findall(
r'(\w+-?\w*\s*skill)',
user_request,
re.IGNORECASE
)
hints.extend([s.strip().lower() for s in skill_names])
# 常见功能映射
function_mapping = {
"pdf": ["pdf-skill"],
"文档": ["pdf-skill", "docx-skill"],
"word": ["docx-skill"],
"excel": ["xlsx-skill"],
"表格": ["xlsx-skill"],
"ppt": ["pptx-skill"],
"演示": ["pptx-skill"],
"清理": ["disk-cleaner"],
"磁盘": ["disk-cleaner"],
"协作": ["agent-call"],
"项目": ["project-start-skill"]
}
for keyword, skills in function_mapping.items():
if keyword.lower() in user_request.lower():
hints.extend(skills)
return list(set(hints))
"""
决策引擎
根据置信度和候选技能,做出推荐决策
"""
from typing import List, Optional, Dict, Any
from dataclasses import dataclass, field
from enum import Enum
from src.storage.vector_db import SearchResult, SkillMetadata
from src.storage.history import SkillStats
from src.analysis.confidence import ConfidenceEvaluator, ConfidenceBreakdown
class DecisionLevel(Enum):
"""决策等级"""
AUTO_RECOMMEND = "auto" # 自动推荐
WITH_ALTERNATIVES = "alt" # 带备选的推荐
ASK_USER = "ask" # 询问用户
@dataclass
class DecisionResult:
"""决策结果"""
level: DecisionLevel # 决策等级
recommended_skill: str # 推荐技能
confidence: float # 置信度
breakdown: ConfidenceBreakdown # 置信度分解
# 中低置信度时提供
alternative_skills: List[str] = field(default_factory=list)
candidate_skills: List[Dict[str, Any]] = field(default_factory=list)
# 推荐理由
reasoning: str = ""
# 是否需要用户确认
requires_confirmation: bool = False
class DecisionEngine:
"""决策引擎"""
def __init__(
self,
confidence_evaluator: ConfidenceEvaluator = None
):
"""初始化决策引擎
Args:
confidence_evaluator: 置信度评估器
"""
self.evaluator = confidence_evaluator or ConfidenceEvaluator()
def decide(
self,
search_results: List[SearchResult],
stats_map: Dict[str, SkillStats]
) -> DecisionResult:
"""做出决策
Args:
search_results: 搜索结果列表
stats_map: 技能统计映射
Returns:
决策结果
"""
if not search_results:
return self._no_match_result()
# 评估所有结果
evaluations = []
for result in search_results:
stats = stats_map.get(result.skill_name)
breakdown = self.evaluator.evaluate(result, stats, len(search_results))
evaluations.append((result, breakdown))
# 按置信度排序
evaluations.sort(key=lambda x: x[1].total, reverse=True)
# 获取最佳结果
best_result, best_breakdown = evaluations[0]
# 根据置信度决定
level = self.evaluator.get_confidence_level(best_breakdown.total)
if level == "high":
return self._auto_recommend(best_result, best_breakdown)
elif level == "medium":
alternatives = [e[0].skill_name for e in evaluations[1:3]]
return self._recommend_with_alternatives(
best_result, best_breakdown, alternatives
)
else:
candidates = [
{
"skill_name": r.skill_name,
"similarity": r.similarity,
"description": r.metadata.description
}
for r, _ in evaluations[:5]
]
return self._ask_user(best_result, best_breakdown, candidates)
def _auto_recommend(
self,
result: SearchResult,
breakdown: ConfidenceBreakdown
) -> DecisionResult:
"""自动推荐
Args:
result: 最佳搜索结果
breakdown: 置信度分解
Returns:
决策结果
"""
reasoning = self._generate_reasoning(result, breakdown)
return DecisionResult(
level=DecisionLevel.AUTO_RECOMMEND,
recommended_skill=result.skill_name,
confidence=breakdown.total,
breakdown=breakdown,
reasoning=reasoning,
requires_confirmation=False
)
def _recommend_with_alternatives(
self,
result: SearchResult,
breakdown: ConfidenceBreakdown,
alternatives: List[str]
) -> DecisionResult:
"""推荐带备选
Args:
result: 最佳搜索结果
breakdown: 置信度分解
alternatives: 备选技能列表
Returns:
决策结果
"""
reasoning = self._generate_reasoning(result, breakdown, alternatives)
return DecisionResult(
level=DecisionLevel.WITH_ALTERNATIVES,
recommended_skill=result.skill_name,
confidence=breakdown.total,
breakdown=breakdown,
alternative_skills=alternatives,
reasoning=reasoning,
requires_confirmation=False
)
def _ask_user(
self,
result: SearchResult,
breakdown: ConfidenceBreakdown,
candidates: List[Dict[str, Any]]
) -> DecisionResult:
"""询问用户
Args:
result: 最佳搜索结果
breakdown: 置信度分解
candidates: 候选技能列表
Returns:
决策结果
"""
reasoning = (
f"找到 {len(candidates)} 个可能符合的技能,"
f"需要你帮忙选择最合适的一个。"
)
return DecisionResult(
level=DecisionLevel.ASK_USER,
recommended_skill=result.skill_name, # 仍然给出推荐
confidence=breakdown.total,
breakdown=breakdown,
candidate_skills=candidates,
reasoning=reasoning,
requires_confirmation=True
)
def _no_match_result(self) -> DecisionResult:
"""无匹配结果
Returns:
决策结果
"""
return DecisionResult(
level=DecisionLevel.ASK_USER,
recommended_skill="",
confidence=0.0,
breakdown=ConfidenceBreakdown(0, 0, 0, 0, 0),
reasoning="未找到匹配的技能",
requires_confirmation=True
)
def _generate_reasoning(
self,
result: SearchResult,
breakdown: ConfidenceBreakdown,
alternatives: List[str] = None
) -> str:
"""生成推荐理由
Args:
result: 搜索结果
breakdown: 置信度分解
alternatives: 备选技能
Returns:
推荐理由文本
"""
parts = []
# 主要理由
parts.append(f"建议用 **{result.skill_name}**")
# 描述
if result.metadata.description:
parts.append(f"- {result.metadata.description}")
# 相似度
if breakdown.similarity > 0.7:
parts.append(f"- 与你的请求高度匹配({breakdown.similarity:.0%})")
# 历史表现
if breakdown.historical > 0.6:
parts.append(f"- 历史使用效果良好({breakdown.historical:.0%})")
# 备选
if alternatives:
alt_list = "、".join(alternatives[:3])
parts.append(f"- 备选:{alt_list}")
return "\n".join(parts)
"""
缓存管理模块
监控数据库大小,提供清理策略
"""
import os
from pathlib import Path
from typing import Optional, Callable, Dict, Any
from datetime import datetime, timedelta
from dataclasses import dataclass
from enum import Enum
from src.storage.history import HistoryTracker
class CleanupTrigger(Enum):
"""清理触发条件"""
SIZE_LIMIT = "size" # 大小超限
COUNT_LIMIT = "count" # 数量超限
OLD_RECORDS = "old" # 旧记录过多
MANUAL = "manual" # 手动触发
class CleanupStrategy:
"""清理策略"""
@staticmethod
def by_age(days: int = 30) -> dict:
"""按时间清理:删除 N 天前的记录
Args:
days: 保留最近 N 天的记录
Returns:
策略配置
"""
return {
"type": "age",
"days": days,
"description": f"删除 {days} 天前的记录"
}
@staticmethod
def by_count(keep: int = 1000) -> dict:
"""按数量清理:保留最近 N 条
Args:
keep: 保留最近 N 条记录
Returns:
策略配置
"""
return {
"type": "count",
"keep": keep,
"description": f"保留最近 {keep} 条记录"
}
@staticmethod
def by_success_rate(min_rate: float = 0.5) -> dict:
"""按成功率清理:删除低成功率记录
Args:
min_rate: 最低保留成功率
Returns:
策略配置
"""
return {
"type": "success_rate",
"min_rate": min_rate,
"description": f"删除成功率低于 {min_rate:.0%} 的记录"
}
@staticmethod
def by_size(max_size_mb: int = 100) -> dict:
"""按大小清理:限制数据库大小
Args:
max_size_mb: 最大大小(MB)
Returns:
策略配置
"""
return {
"type": "size",
"max_size_mb": max_size_mb,
"description": f"限制数据库大小为 {max_size_mb}MB"
}
@dataclass
class CleanupReport:
"""清理报告"""
trigger: CleanupTrigger
strategy_used: dict
records_before: int
records_after: int
size_before_mb: float
size_after_mb: float
records_deleted: int
@dataclass
class HealthStatus:
"""缓存健康状态"""
needs_cleanup: bool
reason: str = ""
suggestion: str = ""
can_auto_cleanup: bool = True
current_size_mb: float = 0
record_count: int = 0
class CacheManager:
"""缓存管理器"""
# 清理阈值
THRESHOLDS = {
"max_size_mb": 500, # 最大数据库大小(MB)
"max_records": 10000, # 最大记录数
"old_days": 30, # 旧记录阈值(天)
"old_ratio": 0.3 # 旧记录比例阈值
}
def __init__(
self,
history_tracker: HistoryTracker = None,
data_dir: str = None
):
"""初始化缓存管理器
Args:
history_tracker: 历史记录追踪器
data_dir: 数据目录
"""
self.history = history_tracker or HistoryTracker()
if data_dir is None:
data_dir = os.path.join(
os.path.dirname(__file__),
"..", "..", "data"
)
self.data_dir = Path(data_dir)
def check_health(self) -> HealthStatus:
"""检查缓存健康状态
Returns:
健康状态
"""
# 获取当前状态
size_bytes = self.history.get_db_size()
size_mb = size_bytes / (1024 * 1024)
record_count = self.history.get_record_count()
# 检查各项阈值
triggers = []
if size_mb > self.THRESHOLDS["max_size_mb"]:
triggers.append(
f"数据库已达 {size_mb:.1f}MB"
)
if record_count > self.THRESHOLDS["max_records"]:
triggers.append(
f"已累积 {record_count:,} 条调用历史"
)
# TODO: 检查旧记录比例
# old_ratio = self._calculate_old_record_ratio()
# if old_ratio > self.THRESHOLDS["old_ratio"]:
# triggers.append(f"发现 {old_ratio:.0%} 的旧记录")
if triggers:
return HealthStatus(
needs_cleanup=True,
reason=";".join(triggers),
description=self._suggest_cleanup(size_mb, record_count),
can_auto_cleanup=self._is_auto_cleanup_safe(),
current_size_mb=size_mb,
record_count=record_count
)
return HealthStatus(
needs_cleanup=False,
current_size_mb=size_mb,
record_count=record_count
)
def _suggest_cleanup(self, size_mb: float, record_count: int) -> str:
"""生成清理建议
Args:
size_mb: 当前大小
record_count: 记录数量
Returns:
建议文本
"""
if size_mb > self.THRESHOLDS["max_size_mb"]:
return f"建议清理 30 天前的记录以释放空间"
if record_count > self.THRESHOLDS["max_records"]:
return f"建议保留最近 1000 条记录"
return "建议进行缓存清理"
def _is_auto_cleanup_safe(self) -> bool:
"""判断是否可以自动清理
Returns:
是否安全
"""
# TODO: 实现更复杂的安全检查
return True
def cleanup(
self,
strategy: dict,
dry_run: bool = False
) -> CleanupReport:
"""执行清理
Args:
strategy: 清理策略
dry_run: 是否为演练模式(不实际删除)
Returns:
清理报告
"""
# 记录清理前状态
records_before = self.history.get_record_count()
size_before = self.history.get_db_size()
# 执行清理
records_deleted = 0
if strategy["type"] == "age":
if not dry_run:
records_deleted = self.history.cleanup_old_records(
days=strategy["days"]
)
else:
records_deleted = self._estimate_age_cleanup(strategy["days"])
elif strategy["type"] == "count":
# TODO: 实现按数量清理
pass
elif strategy["type"] == "size":
# TODO: 实现按大小清理
pass
# 记录清理后状态
records_after = self.history.get_record_count()
size_after = self.history.get_db_size()
return CleanupReport(
trigger=CleanupTrigger.MANUAL,
strategy_used=strategy,
records_before=records_before,
records_after=records_after,
size_before_mb=size_before / (1024 * 1024),
size_after_mb=size_after / (1024 * 1024),
records_deleted=records_deleted
)
def _estimate_age_cleanup(self, days: int) -> int:
"""估算按时间清理会删除多少记录
Args:
days: 天数
Returns:
估算的删除数量
"""
# TODO: 实现估算逻辑
return 0
def auto_cleanup_if_needed(self) -> Optional[CleanupReport]:
"""如果需要则自动清理
Returns:
清理报告,如果不需要清理则返回 None
"""
health = self.check_health()
if not health.needs_cleanup:
return None
if not health.can_auto_cleanup:
return None
# 使用默认策略
strategy = CleanupStrategy.by_age(days=30)
return self.cleanup(strategy)
def get_cache_stats(self) -> Dict[str, Any]:
"""获取缓存统计信息
Returns:
统计信息字典
"""
health = self.check_health()
return {
"size_mb": health.current_size_mb,
"record_count": health.record_count,
"needs_cleanup": health.needs_cleanup,
"reason": health.reason,
"suggestion": health.suggestion
}
def clear_all(self) -> bool:
"""清空所有缓存
Returns:
是否成功
"""
try:
self.history.clear_all()
return True
except Exception:
return False
"""
技能健康检查模块
检测技能是否缺失、损坏或版本不匹配
"""
import os
import hashlib
from pathlib import Path
from typing import List, Dict, Optional
from enum import Enum
from dataclasses import dataclass
class HealthStatus(Enum):
"""健康状态"""
HEALTHY = "healthy" # 技能正常
MISSING = "missing" # 技能不存在
CORRUPTED = "corrupted" # 技能损坏(如 SKILL.md 缺失)
VERSION_MISMATCH = "outdated" # 版本不匹配
UNKNOWN = "unknown" # 未知状态
@dataclass
class HealthReport:
"""健康报告"""
skill_name: str
status: HealthStatus
message: str
suggested_action: str = ""
class HealthChecker:
"""技能健康检查器"""
def __init__(self, skills_dir: str = None):
"""初始化健康检查器
Args:
skills_dir: 技能目录路径
"""
if skills_dir is None:
skills_dir = os.path.expanduser("~/.claude/skills")
self.skills_dir = Path(skills_dir)
def check_skill(
self,
skill_name: str,
expected_hash: str = None
) -> HealthReport:
"""检查单个技能的健康状态
Args:
skill_name: 技能名称
expected_hash: 期望的文件哈希(用于版本检查)
Returns:
健康报告
"""
skill_path = self.skills_dir / skill_name
# 检查技能是否存在
if not skill_path.exists():
return HealthReport(
skill_name=skill_name,
status=HealthStatus.MISSING,
message=f"技能目录不存在: {skill_path}",
suggested_action="请重新安装该技能或检查技能目录"
)
# 检查关键文件
required_files = ["SKILL.md"]
missing_files = [
f for f in required_files
if not (skill_path / f).exists()
]
if missing_files:
return HealthReport(
skill_name=skill_name,
status=HealthStatus.CORRUPTED,
message=f"缺少关键文件: {', '.join(missing_files)}",
suggested_action="请重新安装该技能"
)
# 检查版本(如果提供了哈希)
if expected_hash:
current_hash = self._calculate_hash(skill_path)
if current_hash != expected_hash:
return HealthReport(
skill_name=skill_name,
status=HealthStatus.VERSION_MISMATCH,
message=f"技能版本已变更(哈希不匹配)",
suggested_action="建议更新技能元数据并迁移历史记录"
)
return HealthReport(
skill_name=skill_name,
status=HealthStatus.HEALTHY,
message="技能健康",
suggested_action=""
)
def check_all_skills(
self,
skill_list: List[str] = None,
hash_map: Dict[str, str] = None
) -> List[HealthReport]:
"""检查所有技能的健康状态
Args:
skill_list: 技能名称列表,None 表示检查目录下所有技能
hash_map: 技能哈希映射
Returns:
健康报告列表
"""
if skill_list is None:
skill_list = [
d.name for d in self.skills_dir.iterdir()
if d.is_dir() and not d.name.startswith(".")
]
reports = []
for skill_name in skill_list:
expected_hash = hash_map.get(skill_name) if hash_map else None
report = self.check_skill(skill_name, expected_hash)
reports.append(report)
return reports
def _calculate_hash(self, skill_path: Path) -> str:
"""计算技能目录的哈希值
Args:
skill_path: 技能目录路径
Returns:
SHA256 哈希值
"""
# 简单实现:计算 SKILL.md 的哈希
skill_file = skill_path / "SKILL.md"
if not skill_file.exists():
return ""
sha256 = hashlib.sha256()
with open(skill_file, "rb") as f:
sha256.update(f.read())
return sha256.hexdigest()
def get_unhealthy_skills(
self,
skill_list: List[str] = None
) -> List[HealthReport]:
"""获取所有不健康的技能
Args:
skill_list: 技能名称列表
Returns:
不健康技能的报告列表
"""
all_reports = self.check_all_skills(skill_list)
return [
r for r in all_reports
if r.status != HealthStatus.HEALTHY
]
def fix_missing_skill(self, skill_name: str) -> bool:
"""尝试修复缺失的技能
Args:
skill_name: 技能名称
Returns:
是否修复成功
"""
# TODO: 实现自动修复逻辑
# 例如:从备份恢复、从 GitHub 重新下载等
return False
"""
生命周期管理模块
处理技能的版本迁移、降级策略等生命周期问题
"""
from typing import Dict, List, Optional
from dataclasses import dataclass
from enum import Enum
from src.maintenance.health import HealthStatus, HealthChecker
class MigrationAction(Enum):
"""迁移动作"""
NONE = "none" # 无需操作
UPDATE_METADATA = "update" # 更新元数据
MIGRATE_HISTORY = "migrate" # 迁移历史记录
REMOVE_FROM_INDEX = "remove" # 从索引移除
@dataclass
class MigrationPlan:
"""迁移计划"""
skill_name: str
action: MigrationAction
reason: str
steps: List[str]
class LifecycleManager:
"""生命周期管理器"""
# 降级映射:主技能不可用时使用的备选技能
FALLBACK_MAP = {
"pdf-skill": ["docx-skill", "generic-document-skill"],
"docx-skill": ["pdf-skill", "generic-document-skill"],
"autogpt-agents": ["agent-call", "manual-creation"],
"brainstorming": ["manual-thinking"],
# 用户可以继续添加...
}
# 版本迁移映射
VERSION_MIGRATIONS = {
"pdf-skill": {
"v1": "migrate_pdf_v1_to_v2",
"v2": "current"
}
# 用户可以继续添加...
}
def __init__(self, health_checker: HealthChecker = None):
"""初始化生命周期管理器
Args:
health_checker: 健康检查器
"""
self.health_checker = health_checker or HealthChecker()
def plan_migration(
self,
skill_name: str,
health_status: HealthStatus
) -> MigrationPlan:
"""规划迁移方案
Args:
skill_name: 技能名称
health_status: 健康状态
Returns:
迁移计划
"""
if health_status == HealthStatus.HEALTHY:
return MigrationPlan(
skill_name=skill_name,
action=MigrationAction.NONE,
reason="技能健康,无需迁移",
steps=[]
)
elif health_status == HealthStatus.MISSING:
return MigrationPlan(
skill_name=skill_name,
action=MigrationAction.REMOVE_FROM_INDEX,
reason="技能已移除,从索引中删除",
steps=[
f"1. 标记 {skill_name} 为已移除",
"2. 保留历史记录以供参考",
"3. 推荐备选技能"
]
)
elif health_status == HealthStatus.VERSION_MISMATCH:
return MigrationPlan(
skill_name=skill_name,
action=MigrationAction.MIGRATE_HISTORY,
reason="技能版本已更新",
steps=[
"1. 计算新的文件哈希",
"2. 更新技能元数据",
"3. 迁移历史记录到新版本",
"4. 验证迁移结果"
]
)
elif health_status == HealthStatus.CORRUPTED:
return MigrationPlan(
skill_name=skill_name,
action=MigrationAction.REMOVE_FROM_INDEX,
reason="技能损坏",
steps=[
"1. 从索引中移除",
"2. 建议用户重新安装"
]
)
return MigrationPlan(
skill_name=skill_name,
action=MigrationAction.NONE,
reason="未知状态",
steps=[]
)
def get_fallback_skills(self, skill_name: str) -> List[str]:
"""获取备选技能列表
Args:
skill_name: 主技能名称
Returns:
备选技能名称列表
"""
return self.FALLBACK_MAP.get(skill_name, [])
def add_fallback(self, primary: str, fallback: List[str]):
"""添加降级映射
Args:
primary: 主技能名称
fallback: 备选技能列表
"""
self.FALLBACK_MAP[primary] = fallback
def check_migrations_needed(
self,
skill_list: List[str],
hash_map: Dict[str, str]
) -> List[MigrationPlan]:
"""检查所有需要迁移的技能
Args:
skill_list: 技能列表
hash_map: 技能哈希映射
Returns:
迁移计划列表
"""
plans = []
for skill_name in skill_list:
expected_hash = hash_map.get(skill_name)
report = self.health_checker.check_skill(skill_name, expected_hash)
if report.status != HealthStatus.HEALTHY:
plan = self.plan_migration(skill_name, report.status)
plans.append(plan)
return plans
def migrate_skill_version(
self,
skill_name: str,
old_version: str,
new_version: str
) -> bool:
"""迁移技能版本
Args:
skill_name: 技能名称
old_version: 旧版本
new_version: 新版本
Returns:
是否迁移成功
"""
# TODO: 实现实际的版本迁移逻辑
# 这里需要更新数据库中的版本信息和哈希
return True
def deprecate_skill(
self,
skill_name: str,
replacement: str = None
) -> bool:
"""弃用技能
Args:
skill_name: 要弃用的技能
replacement: 替代技能
Returns:
是否成功
"""
# TODO: 实现弃用逻辑
# 1. 标记技能为已弃用
# 2. 如果有替代技能,建立映射
# 3. 更新推荐逻辑
if replacement:
self.add_fallback(skill_name, [replacement])
return True
"""
Embedding 模型封装
支持中英双语的文本 embedding,使用 sentence-transformers
"""
from functools import lru_cache
from typing import List, Union
import numpy as np
class EmbeddingModel:
"""文本 Embedding 模型"""
# 默认模型:支持中英双语的轻量级模型
DEFAULT_MODEL = "paraphrase-multilingual-MiniLM-L12-v2"
def __init__(self, model_name: str = DEFAULT_MODEL, device: str = "cpu"):
"""初始化 Embedding 模型
Args:
model_name: 模型名称,默认使用多语言模型
device: 运行设备,"cpu" 或 "cuda"
"""
self.model_name = model_name
self.device = device
self._model = None
self._load_model()
def _load_model(self):
"""延迟加载模型"""
if self._model is None:
try:
from sentence_transformers import SentenceTransformer
self._model = SentenceTransformer(self.model_name, device=self.device)
except ImportError:
raise ImportError(
"sentence-transformers 未安装,请运行: pip install sentence-transformers"
)
def encode(
self,
texts: Union[str, List[str]],
normalize: bool = True,
batch_size: int = 32
) -> np.ndarray:
"""将文本编码为向量
Args:
texts: 单个文本或文本列表
normalize: 是否归一化向量
batch_size: 批处理大小
Returns:
向量数组,形状为 (num_texts, embedding_dim)
"""
if isinstance(texts, str):
texts = [texts]
embeddings = self._model.encode(
texts,
normalize_embeddings=normalize,
batch_size=batch_size,
show_progress_bar=False
)
return embeddings
def similarity(self, text1: Union[str, np.ndarray], text2: Union[str, np.ndarray]) -> float:
"""计算两个文本的相似度
Args:
text1: 文本或向量
text2: 文本或向量
Returns:
相似度分数 [0, 1]
"""
vec1 = self.encode(text1) if isinstance(text1, str) else text1
vec2 = self.encode(text2) if isinstance(text2, str) else text2
# 余弦相似度
return float(np.dot(vec1, vec2.T).flatten()[0])
def similarities(
self,
query: Union[str, np.ndarray],
candidates: List[str]
) -> List[float]:
"""计算查询与多个候选文本的相似度
Args:
query: 查询文本或向量
candidates: 候选文本列表
Returns:
相似度分数列表
"""
query_vec = self.encode(query) if isinstance(query, str) else query
candidate_vecs = self.encode(candidates)
# 批量计算余弦相似度
similarities = np.dot(candidate_vecs, query_vec.T).flatten()
return similarities.tolist()
@property
def dimension(self) -> int:
"""返回向量维度"""
return self._model.get_sentence_embedding_dimension()
@staticmethod
@lru_cache(maxsize=1)
def get_default() -> "EmbeddingModel":
"""获取默认的单例模型"""
return EmbeddingModel()
# 便捷函数
def encode_text(text: str, model: EmbeddingModel = None) -> np.ndarray:
"""便捷函数:编码单个文本"""
if model is None:
model = EmbeddingModel.get_default()
return model.encode(text)[0]
def compute_similarity(text1: str, text2: str, model: EmbeddingModel = None) -> float:
"""便捷函数:计算两个文本的相似度"""
if model is None:
model = EmbeddingModel.get_default()
return model.similarity(text1, text2)
"""
SparkSatchel 灵犀妙计 - 主入口
智能技能检索与推荐系统
"""
import os
from pathlib import Path
from typing import List, Optional, Dict, Any
from datetime import datetime
from dataclasses import dataclass, field
from src.models.embedding import EmbeddingModel
from src.storage.vector_db import VectorStore, SkillMetadata, SearchResult
from src.storage.history import HistoryTracker, SkillCall, SkillStats
from src.analysis.intent import IntentAnalyzer
from src.analysis.confidence import ConfidenceEvaluator
from src.decision import DecisionEngine, DecisionResult
from src.maintenance.health import HealthChecker, HealthStatus
from src.maintenance.lifecycle import LifecycleManager
from src.maintenance.cache import CacheManager, CleanupStrategy
@dataclass
class RetrievalResult:
"""检索结果"""
confidence: float # 置信度 0-1
recommended_skill: str # 推荐的技能
reasoning: str # 推荐理由
# 中置信度时提供备选
alternative_skills: List[str] = field(default_factory=list)
# 低置信度时提供候选列表
candidate_skills: List[Dict[str, Any]] = field(default_factory=list)
# 是否需要用户确认
requires_confirmation: bool = False
# 内部数据(调试用)
intent: str = ""
matched_skills: List[str] = field(default_factory=list)
class SparkSatchel:
"""灵犀妙计 - 智能技能检索器"""
def __init__(
self,
skills_dir: str = None,
data_dir: str = None,
auto_load: bool = True
):
"""初始化 SparkSatchel
Args:
skills_dir: 技能目录
data_dir: 数据目录
auto_load: 是否自动加载现有技能
"""
# 目录设置
if skills_dir is None:
skills_dir = os.path.expanduser("~/.claude/skills")
self.skills_dir = Path(skills_dir)
if data_dir is None:
data_dir = os.path.join(
os.path.dirname(__file__),
"..", "data"
)
self.data_dir = Path(data_dir)
self.data_dir.mkdir(parents=True, exist_ok=True)
# 初始化组件
self.embedding_model = EmbeddingModel.get_default()
self.vector_store = VectorStore(
persist_directory=str(self.data_dir / "collections"),
embedding_model=self.embedding_model
)
self.history = HistoryTracker(
db_path=str(self.data_dir / "history.db")
)
self.intent_analyzer = IntentAnalyzer()
self.confidence_evaluator = ConfidenceEvaluator()
self.decision_engine = DecisionEngine(self.confidence_evaluator)
self.health_checker = HealthChecker(str(self.skills_dir))
self.lifecycle_manager = LifecycleManager(self.health_checker)
self.cache_manager = CacheManager(self.history, str(self.data_dir))
# 加载技能
if auto_load:
self._load_existing_skills()
def _load_existing_skills(self):
"""加载现有技能到向量库"""
# TODO: 扫描技能目录并加载
pass
def retrieve(self, user_request: str) -> RetrievalResult:
"""检索并推荐技能
Args:
user_request: 用户请求文本
Returns:
检索结果
"""
# 1. 意图分析
intent = self.intent_analyzer.analyze(user_request)
# 2. 向量检索
search_results = self.vector_store.search(
query=user_request,
top_k=10,
min_similarity=0.3
)
# 3. 获取历史统计
skill_names = [r.skill_name for r in search_results]
stats_map = {}
for name in skill_names:
stats = self.history.get_skill_stats(name)
if stats:
stats_map[name] = stats
# 4. 决策
decision = self.decision_engine.decide(search_results, stats_map)
# 5. 记录这次检索
self._record_retrieval(user_request, intent, decision)
# 6. 构建返回结果
return RetrievalResult(
confidence=decision.confidence,
recommended_skill=decision.recommended_skill,
reasoning=decision.reasoning,
alternative_skills=decision.alternative_skills,
candidate_skills=decision.candidate_skills,
requires_confirmation=decision.requires_confirmation,
intent=intent.primary,
matched_skills=[r.skill_name for r in search_results]
)
def _record_retrieval(
self,
user_request: str,
intent,
decision: DecisionResult
):
"""记录检索到历史
Args:
user_request: 用户请求
intent: 意图对象
decision: 决策结果
"""
call = SkillCall(
id=None,
timestamp=datetime.now().isoformat(),
user_request=user_request,
intent=intent.primary,
matched_skills=decision.alternative_skills + [decision.recommended_skill],
recommended_skill=decision.recommended_skill,
confidence=decision.confidence,
user_accepted=False, # 待用户确认后更新
execution_success=None,
user_feedback=""
)
# 保存到待确认队列
# TODO: 实现待确认队列
self._pending_call = call
def feedback(
self,
skill_name: str,
success: bool,
feedback: str = ""
):
"""记录用户反馈
Args:
skill_name: 技能名称
success: 是否成功
feedback: 用户反馈
"""
if not hasattr(self, '_pending_call'):
return
# 更新待确认的调用记录
self._pending_call.user_accepted = True
self._pending_call.execution_success = success
self._pending_call.user_feedback = feedback
# 记录到历史
self.history.record_call(self._pending_call)
# 清除待确认记录
delattr(self, '_pending_call')
def add_skill(self, metadata: SkillMetadata):
"""添加技能到向量库
Args:
metadata: 技能元数据
"""
self.vector_store.add_skill(metadata)
def remove_skill(self, skill_name: str, category: str):
"""从向量库移除技能
Args:
skill_name: 技能名称
category: 技能分类
"""
self.vector_store.remove_skill(skill_name, category)
def check_health(self) -> Dict[str, Any]:
"""检查系统健康状态
Returns:
健康状态报告
"""
# 检查缓存健康
cache_health = self.cache_manager.check_health()
# 检查技能健康
all_stats = self.history.get_all_stats()
skill_names = list({s.skill_name for s in all_stats})
unhealthy = self.health_checker.get_unhealthy_skills(skill_names)
return {
"cache": {
"needs_cleanup": cache_health.needs_cleanup,
"size_mb": cache_health.current_size_mb,
"record_count": cache_health.record_count,
"reason": cache_health.reason
},
"skills": {
"unhealthy_count": len(unhealthy),
"unhealthy_list": [
{"skill": r.skill_name, "status": r.status.value}
for r in unhealthy[:10] # 最多显示10个
]
},
"suggestion": self._generate_health_suggestion(cache_health, unhealthy)
}
def _generate_health_suggestion(
self,
cache_health,
unhealthy_skills
) -> str:
"""生成健康建议
Args:
cache_health: 缓存健康状态
unhealthy_skills: 不健康技能列表
Returns:
建议文本
"""
suggestions = []
if cache_health.needs_cleanup:
suggestions.append(f"🗑️ {cache_health.suggestion}")
if unhealthy_skills:
suggestions.append(
f"⚠️ 发现 {len(unhealthy_skills)} 个技能状态异常"
)
return ";".join(suggestions) if suggestions else "系统健康"
def cleanup(self, strategy: dict = None):
"""执行缓存清理
Args:
strategy: 清理策略,None 则使用默认策略
"""
if strategy is None:
strategy = CleanupStrategy.by_age(days=30)
report = self.cache_manager.cleanup(strategy)
return {
"records_deleted": report.records_deleted,
"size_before_mb": report.size_before_mb,
"size_after_mb": report.size_after_mb,
"freed_mb": report.size_before_mb - report.size_after_mb
}
def get_stats(self) -> Dict[str, Any]:
"""获取系统统计信息
Returns:
统计信息
"""
cache_stats = self.cache_manager.get_cache_stats()
skill_counts = self.vector_store.get_skill_count()
return {
"cache": cache_stats,
"skills": {
"total": sum(skill_counts.values()),
"by_category": skill_counts
}
}
def index_skills(self, force: bool = False):
"""扫描并索引技能目录
Args:
force: 是否强制重新索引
"""
indexed_count = 0
for skill_path in self.skills_dir.iterdir():
if not skill_path.is_dir() or skill_path.name.startswith("."):
continue
# 读取 SKILL.md
skill_md = skill_path / "SKILL.md"
if not skill_md.exists():
continue
# 解析技能元数据
metadata = self._parse_skill_md(skill_path.name, skill_md)
if metadata:
self.add_skill(metadata)
indexed_count += 1
return indexed_count
def _parse_skill_md(self, skill_name: str, skill_md_path: Path) -> Optional[SkillMetadata]:
"""解析 SKILL.md 文件
Args:
skill_name: 技能名称
skill_md_path: SKILL.md 文件路径
Returns:
技能元数据
"""
try:
content = skill_md_path.read_text(encoding="utf-8")
# 解析 YAML frontmatter
# TODO: 实现 YAML 解析
# 简单实现:提取描述
description = ""
for line in content.split("\n"):
if "description:" in line.lower():
description = line.split(":", 1)[1].strip()
break
return SkillMetadata(
name=skill_name,
path=str(skill_md_path.parent),
description=description,
tags=[],
trigger_keywords=[],
category="utility" # 默认分类
)
except Exception:
return None
# 单例模式
_instance = None
def get_instance() -> SparkSatchel:
"""获取 SparkSatchel 单例
Returns:
SparkSatchel 实例
"""
global _instance
if _instance is None:
_instance = SparkSatchel()
return _instance
"""
历史记录管理
使用 SQLite 记录技能调用历史,支持成功率统计和学习
"""
import sqlite3
import os
from datetime import datetime
from pathlib import Path
from typing import List, Optional, Dict, Any
from dataclasses import dataclass, asdict
from enum import Enum
class CallStatus(Enum):
"""调用状态"""
SUCCESS = "success"
FAILED = "failed"
REJECTED = "rejected" # 用户拒绝
@dataclass
class SkillCall:
"""技能调用记录"""
id: Optional[str]
timestamp: str
user_request: str
intent: str
matched_skills: List[str]
recommended_skill: str
confidence: float
user_accepted: bool
execution_success: bool
user_feedback: str
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@dataclass
class SkillStats:
"""技能统计信息"""
skill_name: str
total_calls: int
success_count: int
failure_count: int
rejection_count: int
success_rate: float
avg_confidence: float
last_called: str
class HistoryTracker:
"""历史记录追踪器"""
def __init__(self, db_path: str = None):
"""初始化历史记录
Args:
db_path: 数据库路径
"""
if db_path is None:
db_path = os.path.join(
os.path.dirname(__file__),
"..", "..", "data", "history.db"
)
self.db_path = Path(db_path)
self.db_path.parent.mkdir(parents=True, exist_ok=True)
self._init_db()
def _init_db(self):
"""初始化数据库"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 创建调用记录表
cursor.execute("""
CREATE TABLE IF NOT EXISTS skill_calls (
id TEXT PRIMARY KEY,
timestamp TEXT NOT NULL,
user_request TEXT NOT NULL,
intent TEXT NOT NULL,
matched_skills TEXT NOT NULL,
recommended_skill TEXT NOT NULL,
confidence REAL NOT NULL,
user_accepted INTEGER NOT NULL,
execution_success INTEGER,
user_feedback TEXT
)
""")
# 创建技能统计表
cursor.execute("""
CREATE TABLE IF NOT EXISTS skill_stats (
skill_name TEXT PRIMARY KEY,
total_calls INTEGER DEFAULT 0,
success_count INTEGER DEFAULT 0,
failure_count INTEGER DEFAULT 0,
rejection_count INTEGER DEFAULT 0,
avg_confidence REAL DEFAULT 0.0,
last_called TEXT
)
""")
# 创建索引
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_recommended_skill
ON skill_calls(recommended_skill)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_timestamp
ON skill_calls(timestamp)
""")
conn.commit()
conn.close()
def record_call(self, call: SkillCall) -> str:
"""记录一次技能调用
Args:
call: 调用记录
Returns:
记录 ID
"""
import uuid
if call.id is None:
call.id = str(uuid.uuid4())
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 插入调用记录
cursor.execute("""
INSERT INTO skill_calls
(id, timestamp, user_request, intent, matched_skills,
recommended_skill, confidence, user_accepted, execution_success, user_feedback)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
call.id,
call.timestamp,
call.user_request,
call.intent,
json.dumps(call.matched_skills),
call.recommended_skill,
call.confidence,
1 if call.user_accepted else 0,
1 if call.execution_success else 0 if call.execution_success is not None else None,
call.user_feedback
))
# 更新统计
self._update_stats(cursor, call)
conn.commit()
conn.close()
return call.id
def _update_stats(self, cursor, call: SkillCall):
"""更新技能统计"""
skill_name = call.recommended_skill
# 查询现有统计
cursor.execute("SELECT * FROM skill_stats WHERE skill_name = ?", (skill_name,))
row = cursor.fetchone()
if row:
# 更新
total_calls = row[1] + 1
success_count = row[2] + (1 if call.execution_success else 0)
failure_count = row[3] + (1 if call.execution_success is False else 0)
rejection_count = row[4] + (1 if not call.user_accepted else 0)
# 更新平均置信度
avg_confidence = (row[5] * row[1] + call.confidence) / total_calls
cursor.execute("""
UPDATE skill_stats
SET total_calls = ?, success_count = ?, failure_count = ?,
rejection_count = ?, avg_confidence = ?, last_called = ?
WHERE skill_name = ?
""", (total_calls, success_count, failure_count, rejection_count,
avg_confidence, call.timestamp, skill_name))
else:
# 插入
cursor.execute("""
INSERT INTO skill_stats
(skill_name, total_calls, success_count, failure_count,
rejection_count, avg_confidence, last_called)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (skill_name, 1,
1 if call.execution_success else 0,
1 if call.execution_success is False else 0,
1 if not call.user_accepted else 0,
call.confidence,
call.timestamp))
def get_skill_stats(self, skill_name: str) -> Optional[SkillStats]:
"""获取技能统计
Args:
skill_name: 技能名称
Returns:
技能统计信息
"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM skill_stats WHERE skill_name = ?", (skill_name,))
row = cursor.fetchone()
conn.close()
if row:
return SkillStats(
skill_name=row[0],
total_calls=row[1],
success_count=row[2],
failure_count=row[3],
rejection_count=row[4],
success_rate=row[2] / row[1] if row[1] > 0 else 0.0,
avg_confidence=row[5],
last_called=row[6]
)
return None
def get_all_stats(self) -> List[SkillStats]:
"""获取所有技能统计"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM skill_stats")
rows = cursor.fetchall()
conn.close()
return [
SkillStats(
skill_name=row[0],
total_calls=row[1],
success_count=row[2],
failure_count=row[3],
rejection_count=row[4],
success_rate=row[2] / row[1] if row[1] > 0 else 0.0,
avg_confidence=row[5],
last_called=row[6]
)
for row in rows
]
def get_recent_calls(self, limit: int = 100) -> List[SkillCall]:
"""获取最近的调用记录
Args:
limit: 返回数量
Returns:
调用记录列表
"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM skill_calls
ORDER BY timestamp DESC
LIMIT ?
""", (limit,))
rows = cursor.fetchall()
conn.close()
return [
SkillCall(
id=row[0],
timestamp=row[1],
user_request=row[2],
intent=row[3],
matched_skills=json.loads(row[4]),
recommended_skill=row[5],
confidence=row[6],
user_accepted=bool(row[7]),
execution_success=bool(row[8]) if row[8] is not None else None,
user_feedback=row[9] or ""
)
for row in rows
]
def get_db_size(self) -> int:
"""获取数据库文件大小(字节)"""
return self.db_path.stat().st_size if self.db_path.exists() else 0
def get_record_count(self) -> int:
"""获取记录总数"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM skill_calls")
count = cursor.fetchone()[0]
conn.close()
return count
def cleanup_old_records(self, days: int = 30) -> int:
"""清理旧记录
Args:
days: 保留最近 N 天的记录
Returns:
删除的记录数
"""
cutoff = (datetime.now().replace(microsecond=0).isoformat())
# 简单实现,删除指定天数前的记录
# 实际应该用日期计算
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# TODO: 实现基于日期的清理
cursor.execute("""
DELETE FROM skill_calls
WHERE timestamp < ?
""", (cutoff,))
deleted = cursor.rowcount
conn.commit()
conn.close()
return deleted
def clear_all(self):
"""清空所有记录"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM skill_calls")
cursor.execute("DELETE FROM skill_stats")
conn.commit()
conn.close()
# 导入 json 模块
import json
"""
向量数据库层
使用 ChromaDB 进行向量存储和检索,支持分库策略
"""
import json
import os
from pathlib import Path
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, asdict
from enum import Enum
class SkillCategory(Enum):
"""技能分类"""
DOCUMENT = "document" # 文档处理类
AI_TOOLS = "ai_tools" # AI 工具类
DEV = "dev" # 开发工具类
UTILITY = "utility" # 通用工具类
# 分类映射
CATEGORY_MAPPING = {
"document": ["pdf", "docx", "pptx", "xlsx"],
"ai_tools": ["agent-call", "autogpt-agents", "brainstorming"],
"dev": ["skill-creator", "skill-lookup", "git-worktrees"],
"utility": ["disk-cleaner", "work-log", "humanizer"]
}
@dataclass
class SkillMetadata:
"""技能元数据"""
name: str
path: str
description: str
tags: List[str]
trigger_keywords: List[str]
category: str
version: str = "1.0.0"
file_hash: str = ""
installed_at: str = ""
last_updated: str = ""
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "SkillMetadata":
return cls(**data)
@dataclass
class SearchResult:
"""搜索结果"""
skill_name: str
similarity: float
metadata: SkillMetadata
confidence: float = 0.0
class VectorStore:
"""向量存储管理器"""
def __init__(
self,
persist_directory: str = None,
embedding_model=None
):
"""初始化向量存储
Args:
persist_directory: 持久化目录
embedding_model: Embedding 模型实例
"""
if persist_directory is None:
persist_directory = os.path.join(
os.path.dirname(__file__),
"..", "..", "data", "collections"
)
self.persist_dir = Path(persist_directory)
self.persist_dir.mkdir(parents=True, exist_ok=True)
self.embedding_model = embedding_model
self._client = None
self._collections = {}
self._init_client()
def _init_client(self):
"""初始化 ChromaDB 客户端"""
try:
import chromadb
self._client = chromadb.PersistentClient(path=str(self.persist_dir))
except ImportError:
raise ImportError(
"chromadb 未安装,请运行: pip install chromadb"
)
def _get_collection_name(self, category: str) -> str:
"""获取集合名称"""
return f"skills_{category}"
def _get_or_create_collection(self, category: str):
"""获取或创建集合"""
if category not in self._collections:
collection_name = self._get_collection_name(category)
self._collections[category] = self._client.get_or_create_collection(
name=collection_name,
metadata={"category": category}
)
return self._collections[category]
def add_skill(self, metadata: SkillMetadata):
"""添加技能到向量库
Args:
metadata: 技能元数据
"""
# 生成 embedding
text = self._prepare_skill_text(metadata)
if self.embedding_model:
embedding = self.embedding_model.encode(text)[0].tolist()
else:
embedding = None
# 添加到对应分类的集合
collection = self._get_or_create_collection(metadata.category)
collection.add(
documents=[text],
embeddings=[embedding] if embedding else None,
metadatas=[metadata.to_dict()],
ids=[metadata.name]
)
def _prepare_skill_text(self, metadata: SkillMetadata) -> str:
"""准备用于检索的文本"""
parts = [
metadata.name,
metadata.description,
" ".join(metadata.tags),
" ".join(metadata.trigger_keywords)
]
return " ".join(parts)
def search(
self,
query: str,
category: Optional[str] = None,
top_k: int = 5,
min_similarity: float = 0.3
) -> List[SearchResult]:
"""搜索技能
Args:
query: 查询文本
category: 限制分类,None 表示搜索所有分类
top_k: 返回结果数量
min_similarity: 最小相似度阈值
Returns:
搜索结果列表
"""
if self.embedding_model:
query_embedding = self.embedding_model.encode(query)[0].tolist()
else:
query_embedding = None
results = []
# 确定要搜索的分类
if category:
categories = [category]
else:
categories = [c.value for c in SkillCategory]
# 搜索每个分类
for cat in categories:
try:
collection = self._get_or_create_collection(cat)
search_results = collection.query(
query_embeddings=[query_embedding] if query_embedding else None,
query_texts=[query] if not query_embedding else None,
n_results=top_k
)
if search_results and search_results["ids"][0]:
for i, skill_id in enumerate(search_results["ids"][0]):
similarity = search_results["distances"][0][i] if query_embedding else 0.5
# 转换距离为相似度
if query_embedding:
similarity = 1 - similarity
if similarity >= min_similarity:
metadata = SkillMetadata.from_dict(
search_results["metadatas"][0][i]
)
results.append(SearchResult(
skill_name=skill_id,
similarity=similarity,
metadata=metadata,
confidence=similarity
))
except Exception as e:
# 集合可能不存在,跳过
continue
# 按相似度排序
results.sort(key=lambda x: x.similarity, reverse=True)
return results[:top_k]
def remove_skill(self, skill_name: str, category: str):
"""从向量库移除技能
Args:
skill_name: 技能名称
category: 技能分类
"""
collection = self._get_or_create_collection(category)
collection.delete(ids=[skill_name])
def get_skill_count(self, category: Optional[str] = None) -> Dict[str, int]:
"""获取技能数量
Args:
category: 分类,None 表示获取所有分类
Returns:
分类 -> 技能数量的映射
"""
counts = {}
categories = [category] if category else [c.value for c in SkillCategory]
for cat in categories:
try:
collection = self._get_or_create_collection(cat)
counts[cat] = collection.count()
except:
counts[cat] = 0
return counts
def clear_all(self):
"""清空所有数据"""
for cat in SkillCategory:
try:
collection = self._get_or_create_collection(cat.value)
self._client.delete_collection(collection.name)
except:
pass
self._collections.clear()
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
venv/
ENV/
env/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Data directories (don't upload vector databases and history)
data/collections/
data/cache/
data/*.db
data/*.sqlite
# Model cache (large embedding models)
data/cache/models/
*.bin
*.pt
*.pth
*.onnx
# Logs
*.log
# OS
.DS_Store
Thumbs.db
# Temporary files
*.tmp
*.bak
*.swp
.cache/
# Test coverage
.coverage
htmlcov/
.pytest_cache/
# MyPy
.mypy_cache/
.dmypy.json
dmypy.json
# Non-core documentation files (keep only essential skill files)
DESIGN.md
GITHUB_DESCRIPTION.md
# npx skills generated directories
.agent/
.agents/
.qoder/
.trae/
skills-lock.json
MIT License
Copyright (c) 2026 SparkSatchel Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Embedding Model Download Guide
This guide helps you download and configure different embedding models for SparkSatchel.
Quick Download Commands
Default Model (Already Downloaded ✅)
# paraphrase-multilingual-MiniLM-L12-v2 (470MB)
# Status: Pre-downloaded, ready to use
# Location: ~/.cache/huggingface/hub/Chinese-Optimized Model
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('shibing624/text2vec-base-chinese')"High-Accuracy Multilingual Model
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('intfloat/multilingual-e5-large')"Ultra-Lightweight English Model
pip install sentence-transformers
python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')"How to Switch Models
Method 1: Edit Configuration (Permanent)
Edit src/models/embedding.py:
class EmbeddingModel:
DEFAULT_MODEL = "your-chosen-model-name"Method 2: Runtime Configuration (Temporary)
from src.models.embedding import EmbeddingModel
from src.retriever import SparkSatchel
custom_model = EmbeddingModel(model_name="your-model")
sparksatchel = SparkSatchel(embedding_model=custom_model)Model Storage Location
Models are cached at:
- Linux/Mac:
~/.cache/huggingface/hub/ - Windows:
C:\Users\<username>\.cache\huggingface\hub\
GPU Acceleration
If you have NVIDIA GPU with CUDA:
model = EmbeddingModel(
model_name="paraphrase-multilingual-MiniLM-L12-v2",
device="cuda" # Use GPU instead of CPU
)Performance improvement: 5-10x faster inference.
{
"name": "sparksatchel",
"version": "1.0.0",
"description": "灵犀妙计 SparkSatchel - Intelligent skill retrieval system for AI IDEs",
"bin": {
"sparksatchel": "src/retriever.py"
},
"scripts": {
"start": "python src/retriever.py",
"install-deps": "pip install -r requirements.txt"
},
"keywords": [
"skill-retrieval",
"semantic-search",
"claude-code",
"cursor",
"ai-ide",
"embeddings",
"bilingual"
],
"author": "Codestyle Team",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/gccszs/Spark-Satchel.git"
},
"engines": {
"node": ">=14.0.0"
}
}
<div align="center">
🧠 SparkSatchel
Intelligent Skill Retrieval & Recommendation System
  
English | 中文
🌈 🧠 🌈
"身无彩凤双飞翼,心有灵犀一点通"
Without colorful phoenix wings to fly to you, our hearts connect at a single point.
SparkSatchel helps you and your Agent find the perfect SKILL from thousands by inferring the best match for your current task.
A different kind of spark! ⚡
Compatible with Claude Code, Cursor, Windsurf, Trae, and all AI IDEs
</div>
---
📖 Table of Contents
- Overview
- Relationship with find-skills
- Quick Start
- Key Features
- Decision Mechanism
- Features
- Tech Stack
- Documentation
- Contributing
---
🎯 Overview
SparkSatchel is an intelligent skill retrieval and recommendation system designed for all AI IDEs (Claude Code, Cursor, Windsurf, Trae, etc.).
Through semantic analysis, intent inference, and historical learning, it helps users quickly find the most suitable skill from thousands.
Why SparkSatchel?
As the AI skill ecosystem grows, users may install hundreds of skills. When completing a task:
❌ Traditional: Manually search skill names, read descriptions one by one
✅ SparkSatchel: Describe your need, automatically get the best skill recommendation---
🤝 Relationship with find-skills
These two skills are NOT competitors, they are COMPLEMENTS! They solve completely different problems. <img width="1713" height="1038" alt="image" src="https://github.com/user-attachments/assets/d93d944c-a373-4e17-9367-881de020f6f3" />
Core Differences
| Dimension | SparkSatchel | find-skills |
|---|---|---|
| Positioning | Local intelligent recommendation engine | Online skill discovery tool |
| Search Scope | Only searches installed local skills | Searches entire skills.sh ecosystem |
| Method | Semantic embeddings + vector database | Keyword matching + online search |
| Main Function | Select best from existing skills | Discover and install new skills |
| Network Dependency | ✅ Fully offline | ❌ Requires internet |
| Learning | ✅ Historical feedback learning | ❌ No learning mechanism |
| Recommendation Basis | Confidence + historical success rate | Keyword matching score |
📊 Detailed Comparison
1. SparkSatchel
Local skill library (~/.claude/skills/)
↓
Semantic embeddings (paraphrase-multilingual-MiniLM-L12-v2)
↓
Vector similarity matching
↓
Confidence scoring + historical learning
↓
Recommend from installed skillsFeatures:
- 🧠 Smart Understanding: Uses semantic embeddings to understand intent, not just keywords
- 📚 Local Search: Only looks at skills already installed on your machine
- 📈 Gets Smarter: Records usage feedback to optimize recommendations
- 🎯 Confidence Mechanism: Auto-recommends when high confidence, asks user when low
- 🌏 Bilingual: Understands both Chinese and English
Typical Scenario:
You: "Help me process this PDF"
SparkSatchel: "Recommend pdf-skill (92% success rate), designed for PDF processing"2. find-skills
Your need
↓
npx skills find [keywords]
↓
Search skills.sh ecosystem
↓
Return installable skill packages
↓
Install new skills locallyFeatures:
- 🌐 Ecosystem Search: Searches the entire open skills marketplace
- 📦 Install-Oriented: Helps you discover and install new skills
- 🔑 Keyword Matching: Searches repositories via keywords
- 🔗 GitHub Integration: Installs skills directly from GitHub
- 🆕 Discover New Abilities: Expands AI's functional boundaries
Typical Scenario:
You: "Any skills for React performance optimization?"
find-skills: "Found react-best-practices,
run npx skills add xxx/react-best-practices to install"🤝 How They Work Together
┌─────────────────────────────────────────────────────────┐
│ User Need: "I want to do product discussions" │
└─────────────────────────────────────────────────────────┘
↓
┌────────────────┴────────────────┐
↓ ↓
┌─────────────────┐ ┌────────────────────┐
│ SparkSatchel │ │ find-skills │
│ (Local Search) │ │ (Online Search) │
├─────────────────┤ ├────────────────────┤
│ ✓ Search local │ │ ✓ Search skills.sh │
│ ✓ Find installed│ │ ✓ Discover new │
│ brainstorming │ │ discussion-skill │
│ ✓ Recommend use │ │ ✓ Provide install │
└─────────────────┘ └────────────────────┘
↓ ↓
【Use Directly】 【Install Then Use】💡 Practical Scenarios
| Scenario | Use Which? |
|---|---|
| "What skills do I have available?" | SparkSatchel ✅ |
| "Any skills for doing XXX?" | find-skills ✅ |
| "Help me choose the best skill" | SparkSatchel ✅ |
| "I want to install new skills" | find-skills ✅ |
| "Which skill is most successful for this task?" | SparkSatchel ✅ |
| "Any XXX-related skills in community?" | find-skills ✅ |
🎯 Conclusion
They are NOT competitors, they are BEST PARTNERS!
The correct workflow:
1. Use SparkSatchel first to check if suitable skills exist locally 2. If not available locally, use find-skills to search and install from community 3. After installation, SparkSatchel can intelligently recommend the new skill
---
🚀 Quick Start
Method 1: npx Installation (Recommended)
# Quick install with npx
npx skills add gccszs/Spark-Satchel
# Ready to use immediately after installationMethod 2: Git Clone
# Clone repository
git clone https://github.com/gccszs/Spark-Satchel.git
cd Spark-Satchel
# Install dependencies
pip install -r requirements.txt✨ Ready to Use
Great news! The embedding model is pre-downloaded (~470MB), no waiting required:
- ✅ Pre-installed: paraphrase-multilingual-MiniLM-L12-v2
- ✅ Bilingual: Supports 50+ languages
- ✅ Offline: No internet connection needed
- ✅ Plug & Play: Use immediately after installing dependencies
Basic Usage
from src.retriever import SparkSatchel
# Initialize (model pre-installed)
sparksatchel = SparkSatchel()
# Retrieve skills
result = sparksatchel.retrieve("process this PDF")
# Respond based on confidence
if result.confidence > 0.7:
# High confidence - direct recommendation
print(f"✅ Recommend: {result.recommended_skill}")
print(result.reasoning)
elif result.confidence > 0.4:
# Medium confidence - provide alternatives
print(f"💡 Recommend: {result.recommended_skill}")
print(f"Alternatives: {', '.join(result.alternative_skills)}")
else:
# Low confidence - ask user
print("❓ Please choose from:")
for skill in result.candidate_skills:
print(f" - {skill['skill_name']}: {skill['description']}")
# Record feedback (helps system learn)
sparksatchel.feedback(result.recommended_skill, success=True)---
✨ Key Features
🧠 Smart Inference
- Understands natural language descriptions of user intent
- Semantic similarity matching based on embeddings
- Supports 50+ languages with bilingual optimization (Chinese/English)
⚖️ Prudent Decision
Responds intelligently based on confidence:
| Confidence | Action | Example |
|---|---|---|
| High (>70%) | Auto-recommend with reasoning | "Use pdf-skill, 92% success rate" |
| Medium (40-70%) | Recommend + alternatives | "Use docx-skill, alternative: pdf-skill" |
| Low (<40%) | Present candidates + ask user | "Choose: xlsx-skill, pandas-skill..." |
📚 Continuous Learning
- Tracks every skill call
- Records success/failure feedback
- Calculates skill success rates
- Dynamically optimizes recommendation ranking
🔧 Easy Maintenance
- Automatic health checks (monitors skill status)
- Smart cache cleanup (frees storage space)
- Lifecycle management (version migration, fallback strategies)
---
📊 Decision Mechanism
User Request → Intent Analysis → Vector Search → Confidence → Decision
↓
┌─────────────────────────────────┐
│ Confidence │
├─────────────────────────────────┤
│ High (>70%) │ Med (40-70%) │ Low│
├─────────────────────────────────┤
│ Auto-rec │ Rec+Alt │ Ask│
└─────────────────────────────────┘Usage Examples
Example 1: High Confidence
User: "Process this PDF"
SparkSatchel: "I recommend pdf-skill because it specializes in PDF documents (92% historical success rate)"Example 2: Medium Confidence
User: "Create a document"
SparkSatchel: "I suggest docx-skill. pdf-skill is also available. Want me to compare?"Example 3: Low Confidence
User: "Process data"
SparkSatchel: "Found 3 matching skills:
- xlsx-skill: Excel spreadsheet processing
- pandas-skill: Python data analysis
- csv-skill: CSV file handling
Please choose the most suitable one."---
🛠️ Features
1. Semantic Retrieval
| Feature | Description |
|---|---|
| Sharded Storage | Skills organized by category for efficient retrieval |
| Vector Similarity | Semantic matching based on embeddings |
| Bilingual | Default model supports 50+ languages |
2. Intent Analysis
Extracts from user requests:
- Primary intent (e.g., document processing, project creation)
- Keywords (e.g., PDF, Word, Excel)
- Entities (e.g., filenames, formats)
3. Confidence Evaluation
Multi-dimensional scoring:
- Similarity (50%): Semantic matching degree
- History (30%): Success rate and call count
- Relevance (15%): Keyword matching
- Freshness (5%): Recent usage bonus
4. Historical Learning
# Get skill statistics
stats = sparksatchel.history.get_skill_stats("pdf-skill")
print(f"Success rate: {stats.success_rate:.0%}")
print(f"Total calls: {stats.total_calls}")
print(f"Last called: {stats.last_called}")5. Health Checking
# Check system health
health = sparksatchel.check_health()
if health["cache"]["needs_cleanup"]:
print(f"⚠️ {health['suggestion']}")
if health["skills"]["unhealthy_count"] > 0:
print(f"⚠️ Found {health['skills']['unhealthy_count']} unhealthy skills")6. Cache Management
from src.maintenance.cache import CleanupStrategy
# Cleanup by age (delete records older than 30 days)
sparksatchel.cleanup(CleanupStrategy.by_age(days=30))
# Cleanup by count (keep recent 1000 records)
sparksatchel.cleanup(CleanupStrategy.by_count(keep=1000))
# Auto cleanup (if needed)
sparksatchel.cache_manager.auto_cleanup_if_needed()---
🔧 Tech Stack
| Component | Technology | Description |
|---|---|---|
| Language | Python 3.10+ | Main development language |
| Vector DB | ChromaDB | Local vector storage |
| Embedding | sentence-transformers | Bilingual support |
| History | SQLite | Lightweight database |
| Vector Math | NumPy | Efficient numerical computation |
Project Structure
SparkSatchel/
├── SKILL.md # Meta-skill definition
├── README.md # Chinese documentation
├── README_EN.md # This file (English)
├── MODELS.md # Model selection guide
├── requirements.txt # Dependencies
├── package.json # npx configuration
│
├── scripts/ # Utility scripts
│ └── download_model.py # Model download script
│
├── src/ # Source code
│ ├── retriever.py # Main entry point
│ ├── models/ # Embedding wrapper
│ ├── storage/ # Vector DB + history
│ ├── analysis/ # Intent + confidence
│ └── maintenance/ # Health + lifecycle + cache
│
└── data/ # Data directory
├── collections/ # Vector databases (sharded)
├── history.db # Call history
└── cache/ # Cache directory---
📚 Documentation
| Document | Description |
|---|---|
| MODELS.md | Embedding model selection and download guide |
| SKILL.md | Meta-skill definition |
---
🎨 Design Philosophy
Spark (灵犀)
"身无彩凤双飞翼,心有灵犀一点通"
- Spark of understanding user intent
- Semantic similarity matching
- Bilingual support
Satchel (妙计)
"锦囊妙计,随需随取"
- Bag full of skills
- Prudent decision mechanism
- Continuous learning optimization
---
🤝 Contributing
Contributions, issues, and feature requests are welcome!
1. Fork the repository 2. Create your feature branch (git checkout -b feature/AmazingFeature) 3. Commit your changes (git commit -m 'Add some AmazingFeature') 4. Push to the branch (git push origin feature/AmazingFeature) 5. Open a Pull Request
---
📄 License
This project is licensed under the MIT License.
---
🙏 Acknowledgments
- ChromaDB - Excellent vector database
- sentence-transformers - Powerful text embeddings
- All contributors and users
---
<div align="center">
Making every skill call precise ⚡
Compatible with Claude Code, Cursor, Windsurf, Trae, and all AI IDEs
🐝 Made with ❤️ by <a href="https://github.com/codestyle-mafeng">Codestyle Team</a>
</div>
# SparkSatchel 灵犀妙计 - 依赖列表
# Meta-Skill for intelligent skill retrieval and recommendation
# 核心依赖
chromadb>=0.5.0 # 向量数据库
sentence-transformers>=2.7.0 # Embedding 模型
numpy>=1.24.0 # 向量运算
# 可选依赖(增强功能)
tiktoken>=0.5.0 # 文本分词(可选)
openai>=1.0.0 # OpenAI API(可选,用于更精准的 embedding)
# 开发依赖
pytest>=7.4.0 # 测试框架
black>=23.0.0 # 代码格式化
mypy>=1.0.0 # 类型检查
{
"version": 1,
"skills": {
"sparksatchel": {
"source": "gccszs/Spark-Satchel",
"sourceType": "github",
"computedHash": "9da6e755415d8e80d9d2c88466993d4159a1373f07ae4270201fd84f860e241f"
}
}
}
"""
SparkSatchel 灵犀妙计
智能技能检索与推荐系统
一个 Meta-Skill,为 Claude Code 提供智能技能检索、推荐和调用决策能力。
"""
__version__ = "1.0.0"
__author__ = "SparkSatchel Team"
from src.retriever import SparkSatchel
__all__ = ["SparkSatchel"]
"""Analysis module for SparkSatchel."""
from src.analysis.intent import IntentAnalyzer
from src.analysis.confidence import ConfidenceEvaluator
__all__ = ["IntentAnalyzer", "ConfidenceEvaluator"]
"""Models module for SparkSatchel."""
from src.models.embedding import EmbeddingModel
__all__ = ["EmbeddingModel"]
"""Storage module for SparkSatchel."""
from src.storage.vector_db import VectorStore
from src.storage.history import HistoryTracker
__all__ = ["VectorStore", "HistoryTracker"]