feat: initialize MCP RAG Prompts server with embedding management
- Add package.json for project configuration and dependencies. - Create src/index.ts as the entry point for the MCP server. - Implement vectorStore for managing embeddings with local and cloud providers. - Add embeddingProviders for local and cloud-based embedding services (OpenAI, Aliyun, SiliconFlow). - Define types for prompts and embeddings in types.ts. - Implement searchPersona tool for semantic search of expert personas. - Create test.ts for validating vector storage and search functionality. - Configure TypeScript with tsconfig.json for strict type checking and module resolution.
This commit is contained in:
53
.env
Normal file
53
.env
Normal file
@@ -0,0 +1,53 @@
|
||||
# ============================================================
|
||||
# MCP RAG Prompts - Embedding 配置
|
||||
# 复制此文件为 .env 并填入你的配置
|
||||
# ============================================================
|
||||
|
||||
# 选择 Embedding 提供者
|
||||
# 可选值: local | openai | aliyun | siliconflow
|
||||
EMBEDDING_PROVIDER=siliconflow
|
||||
|
||||
# ============================================================
|
||||
# 本地模型配置 (provider=local)
|
||||
# ============================================================
|
||||
# 默认使用多语言模型,支持中英文
|
||||
# LOCAL_MODEL_NAME=Xenova/paraphrase-multilingual-MiniLM-L12-v2
|
||||
|
||||
# 其他可选模型:
|
||||
# LOCAL_MODEL_NAME=Xenova/all-MiniLM-L6-v2 # 英文效果更好,体积更小
|
||||
# LOCAL_MODEL_NAME=Xenova/multilingual-e5-small # 多语言 E5 模型
|
||||
|
||||
# ============================================================
|
||||
# OpenAI 配置 (provider=openai)
|
||||
# ============================================================
|
||||
# OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
# OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
||||
|
||||
# 可选模型:
|
||||
# text-embedding-3-small (1536维,便宜)
|
||||
# text-embedding-3-large (3072维,更精准)
|
||||
# text-embedding-ada-002 (1536维,旧版)
|
||||
|
||||
# ============================================================
|
||||
# 阿里云百炼 DashScope 配置 (provider=aliyun)
|
||||
# ============================================================
|
||||
# DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# ALIYUN_EMBEDDING_MODEL=text-embedding-v3
|
||||
|
||||
# 可选模型:
|
||||
# text-embedding-v3 (最新版,推荐)
|
||||
# text-embedding-v2 (旧版)
|
||||
# text-embedding-v1 (更旧)
|
||||
|
||||
# ============================================================
|
||||
# SiliconFlow 硅基流动配置 (provider=siliconflow)
|
||||
# ============================================================
|
||||
SILICONFLOW_API_KEY=
|
||||
SILICONFLOW_EMBEDDING_MODEL=Qwen/Qwen3-Embedding-8B
|
||||
|
||||
# 可选模型 (开源模型,性价比高):
|
||||
# BAAI/bge-m3 # 多语言,效果很好
|
||||
# BAAI/bge-large-zh-v1.5 # 中文专用
|
||||
# BAAI/bge-large-en-v1.5 # 英文专用
|
||||
# nomic-ai/nomic-embed-text-v1.5 # 多语言
|
||||
53
.env.example
Normal file
53
.env.example
Normal file
@@ -0,0 +1,53 @@
|
||||
# ============================================================
|
||||
# MCP RAG Prompts - Embedding 配置
|
||||
# 复制此文件为 .env 并填入你的配置
|
||||
# ============================================================
|
||||
|
||||
# 选择 Embedding 提供者
|
||||
# 可选值: local | openai | aliyun | siliconflow
|
||||
EMBEDDING_PROVIDER=siliconflow
|
||||
|
||||
# ============================================================
|
||||
# 本地模型配置 (provider=local)
|
||||
# ============================================================
|
||||
# 默认使用多语言模型,支持中英文
|
||||
# LOCAL_MODEL_NAME=Xenova/paraphrase-multilingual-MiniLM-L12-v2
|
||||
|
||||
# 其他可选模型:
|
||||
# LOCAL_MODEL_NAME=Xenova/all-MiniLM-L6-v2 # 英文效果更好,体积更小
|
||||
# LOCAL_MODEL_NAME=Xenova/multilingual-e5-small # 多语言 E5 模型
|
||||
|
||||
# ============================================================
|
||||
# OpenAI 配置 (provider=openai)
|
||||
# ============================================================
|
||||
# OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
# OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
||||
|
||||
# 可选模型:
|
||||
# text-embedding-3-small (1536维,便宜)
|
||||
# text-embedding-3-large (3072维,更精准)
|
||||
# text-embedding-ada-002 (1536维,旧版)
|
||||
|
||||
# ============================================================
|
||||
# 阿里云百炼 DashScope 配置 (provider=aliyun)
|
||||
# ============================================================
|
||||
# DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# ALIYUN_EMBEDDING_MODEL=text-embedding-v3
|
||||
|
||||
# 可选模型:
|
||||
# text-embedding-v3 (最新版,推荐)
|
||||
# text-embedding-v2 (旧版)
|
||||
# text-embedding-v1 (更旧)
|
||||
|
||||
# ============================================================
|
||||
# SiliconFlow 硅基流动配置 (provider=siliconflow)
|
||||
# ============================================================
|
||||
SILICONFLOW_API_KEY=sk-fwemdoaytkxelpbjlnohiqvkeqjxxraoduadokrpvtynxoej
|
||||
SILICONFLOW_EMBEDDING_MODEL=Qwen/Qwen3-Embedding-8B
|
||||
|
||||
# 可选模型 (开源模型,性价比高):
|
||||
# BAAI/bge-m3 # 多语言,效果很好
|
||||
# BAAI/bge-large-zh-v1.5 # 中文专用
|
||||
# BAAI/bge-large-en-v1.5 # 英文专用
|
||||
# nomic-ai/nomic-embed-text-v1.5 # 多语言
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
187
README.md
Normal file
187
README.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# MCP RAG Prompts Server
|
||||
|
||||
基于 MCP 协议的 RAG 提示词管理服务器,支持本地模型和云服务 API 进行语义搜索。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🔍 **语义搜索**:Hybrid Search(向量语义 + 关键词加权)
|
||||
- 🔄 **多提供者支持**:本地模型 / OpenAI / 阿里云百炼 / SiliconFlow
|
||||
- 🚀 **MCP 协议**:标准 MCP Server 实现,可与任何 MCP 客户端集成
|
||||
- 💾 **内存缓存**:启动时一次性向量化,搜索速度快
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
mcp-rag-prompts/
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── .env.example # 环境变量配置模板
|
||||
├── data/
|
||||
│ └── prompts.json # Prompt 数据文件
|
||||
└── src/
|
||||
├── index.ts # MCP Server 入口
|
||||
├── test.ts # 测试脚本
|
||||
└── lib/
|
||||
├── types.ts # 类型定义
|
||||
├── embeddingProviders.ts # Embedding 提供者实现
|
||||
└── vectorStore.ts # 向量存储和搜索
|
||||
```
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Embedding 提供者配置
|
||||
|
||||
通过环境变量选择 Embedding 提供者:
|
||||
|
||||
### 方式 1:本地模型(默认)
|
||||
|
||||
无需配置,开箱即用。首次运行会自动下载模型(约 90MB)。
|
||||
|
||||
```bash
|
||||
# 默认配置,无需设置
|
||||
npm start
|
||||
|
||||
# 或显式指定
|
||||
EMBEDDING_PROVIDER=local npm start
|
||||
```
|
||||
|
||||
### 方式 2:OpenAI API
|
||||
|
||||
```bash
|
||||
# Windows PowerShell
|
||||
$env:EMBEDDING_PROVIDER="openai"
|
||||
$env:OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxx"
|
||||
npm start
|
||||
|
||||
# Linux/macOS
|
||||
EMBEDDING_PROVIDER=openai \
|
||||
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx \
|
||||
npm start
|
||||
```
|
||||
|
||||
可选配置:
|
||||
- `OPENAI_BASE_URL` - 自定义 API 地址(支持代理)
|
||||
- `OPENAI_EMBEDDING_MODEL` - 模型名称(默认 `text-embedding-3-small`)
|
||||
|
||||
### 方式 3:阿里云百炼 DashScope
|
||||
|
||||
```bash
|
||||
$env:EMBEDDING_PROVIDER="aliyun"
|
||||
$env:DASHSCOPE_API_KEY="sk-xxxxxxxxxxxxxxxx"
|
||||
npm start
|
||||
```
|
||||
|
||||
可选配置:
|
||||
- `ALIYUN_EMBEDDING_MODEL` - 模型名称(默认 `text-embedding-v3`)
|
||||
|
||||
### 方式 4:SiliconFlow 硅基流动
|
||||
|
||||
```bash
|
||||
$env:EMBEDDING_PROVIDER="siliconflow"
|
||||
$env:SILICONFLOW_API_KEY="sk-xxxxxxxxxxxxxxxx"
|
||||
npm start
|
||||
```
|
||||
|
||||
可选配置:
|
||||
- `SILICONFLOW_EMBEDDING_MODEL` - 模型名称(默认 `BAAI/bge-m3`)
|
||||
|
||||
## 运行
|
||||
|
||||
```bash
|
||||
# 启动 MCP Server
|
||||
npm start
|
||||
|
||||
# 开发模式(热重载)
|
||||
npm run dev
|
||||
|
||||
# 运行测试
|
||||
npx tsx src/test.ts
|
||||
```
|
||||
|
||||
## 本地模型网络问题
|
||||
|
||||
首次运行本地模型时需要从 HuggingFace 下载。如果遇到网络问题:
|
||||
|
||||
```bash
|
||||
# 设置代理
|
||||
$env:HTTPS_PROXY="http://127.0.0.1:7890"
|
||||
|
||||
# 或使用 HuggingFace 镜像(中国大陆)
|
||||
$env:HF_ENDPOINT="https://hf-mirror.com"
|
||||
```
|
||||
|
||||
## MCP Tool
|
||||
|
||||
### `search_expert_persona`
|
||||
|
||||
根据用户问题语义搜索最匹配的专家角色设定。
|
||||
|
||||
**参数**:
|
||||
- `query` (string): 用户的原始问题或需求描述
|
||||
|
||||
**返回示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"matchedExpert": {
|
||||
"id": "python-expert",
|
||||
"tags": ["python", "programming"],
|
||||
"description": "Python 编程专家...",
|
||||
"similarity": 0.75
|
||||
},
|
||||
"systemPrompt": "你是一位资深的 Python 编程专家..."
|
||||
}
|
||||
```
|
||||
|
||||
## 配置 MCP 客户端
|
||||
|
||||
### Claude Desktop
|
||||
|
||||
在 `claude_desktop_config.json` 中添加:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"rag-prompts": {
|
||||
"command": "npx",
|
||||
"args": ["tsx", "C:/path/to/mcp-rag-prompts/src/index.ts"],
|
||||
"env": {
|
||||
"EMBEDDING_PROVIDER": "siliconflow",
|
||||
"SILICONFLOW_API_KEY": "sk-xxxxxxxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义 Prompt 数据
|
||||
|
||||
编辑 `data/prompts.json` 添加你自己的专家角色:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "unique-id",
|
||||
"tags": ["tag1", "tag2"],
|
||||
"description": "用于语义搜索的描述文本(会被向量化)",
|
||||
"content": "实际的 System Prompt 内容"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 提供者对比
|
||||
|
||||
| 提供者 | 优点 | 缺点 |
|
||||
|--------|------|------|
|
||||
| **local** | 免费、离线可用、隐私安全 | 首次加载慢、需下载模型 |
|
||||
| **openai** | 效果好、稳定 | 需要付费、需要网络 |
|
||||
| **aliyun** | 中文效果好、国内访问快 | 需要付费 |
|
||||
| **siliconflow** | 性价比高、支持多种开源模型 | 需要付费 |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
122
data/prompts.json
Normal file
122
data/prompts.json
Normal file
@@ -0,0 +1,122 @@
|
||||
[
|
||||
{
|
||||
"id": "python-expert",
|
||||
"tags": ["python", "programming", "backend", "data-science", "django", "flask", "fastapi"],
|
||||
"description": "Python 编程专家,擅长后端开发、数据科学、机器学习和自动化脚本",
|
||||
"content": "你是一位资深的 Python 编程专家,拥有 10 年以上的开发经验。你精通:\n\n1. **后端开发**:Flask、FastAPI、Django 框架,RESTful API 设计\n2. **数据科学**:Pandas、NumPy、Matplotlib 数据处理与可视化\n3. **机器学习**:Scikit-learn、TensorFlow、PyTorch 模型训练与部署\n4. **自动化**:脚本编写、任务调度、系统运维自动化\n\n回答问题时请:\n- 提供可运行的代码示例\n- 解释代码背后的原理\n- 指出常见的陷阱和最佳实践\n- 推荐相关的库和工具"
|
||||
},
|
||||
{
|
||||
"id": "java-expert",
|
||||
"tags": ["java", "jvm", "spring-boot", "enterprise", "backend", "spring-cloud"],
|
||||
"description": "Java 架构师,精通 Spring Boot 全家桶、JVM 调优、高并发系统设计和企业级应用开发",
|
||||
"content": "你是一位拥有 15 年经验的 Java 首席架构师。你精通现代 Java (JDK 17/21) 生态系统:\n\n1. **核心框架**:Spring Boot 3, Spring Cloud, Hibernate/JPA, Mybatis-Plus\n2. **并发编程**:多线程, 线程池, JUC (java.util.concurrent), 虚拟线程 (Virtual Threads)\n3. **底层原理**:JVM 内存模型, GC 调优, 类加载机制\n4. **架构设计**:DDD (领域驱动设计), 微服务架构, 设计模式\n\n回答规范:\n- 代码必须遵循 Google Java Style Guide\n- 优先使用 Stream API 和 Lambda 表达式\n- 涉及数据库操作时,请考虑事务管理 (@Transactional)\n- 解释性能影响(如装箱拆箱、IO开销)"
|
||||
},
|
||||
{
|
||||
"id": "golang-expert",
|
||||
"tags": ["go", "golang", "microservices", "cloud-native", "concurrency", "grpc"],
|
||||
"description": "Go 语言专家,专注于云原生架构、微服务、高性能网络编程和容器技术",
|
||||
"content": "你是一位追求极致性能的 Go (Golang) 资深工程师,崇尚 'Less is more' 的设计哲学。你擅长:\n\n1. **并发模型**:熟练运用 Goroutines 和 Channels 进行 CSP 风格编程,避免共享内存竞争\n2. **微服务**:gRPC, Protobuf, Gin/Echo 框架, Go-Zero\n3. **云原生**:Kubernetes Operator 开发, Prometheus 监控集成\n4. **标准库**:深入理解 net/http, io, context 等核心库\n\n回答规范:\n- 必须编写 Idiomatic Go (地道的 Go 代码)\n- 严格处理错误 (if err != nil),不忽略任何 error\n- 避免过度设计,优先使用标准库而非第三方库\n- 解释 Context 的传递和生命周期管理"
|
||||
},
|
||||
{
|
||||
"id": "javascript-expert",
|
||||
"tags": ["javascript", "typescript", "nodejs", "frontend", "react", "vue", "npm"],
|
||||
"description": "JavaScript/TypeScript 全栈专家,精通 React、Vue、Node.js 生态和现代前端工程化",
|
||||
"content": "你是一位全栈 JavaScript/TypeScript 专家,深耕 Web 开发领域。你精通:\n\n1. **前端框架**:React 18 (Hooks, Server Components), Vue 3 (Composition API), Next.js, Nuxt\n2. **状态管理**:Redux Toolkit, Zustand, Pinia, Jotai\n3. **Node.js**:Express, Fastify, NestJS, Prisma ORM\n4. **工程化**:Webpack, Vite, ESBuild, Turborepo, pnpm workspace\n\n回答规范:\n- 优先使用 TypeScript,提供完整类型定义\n- 遵循函数式编程范式,避免 class 组件\n- 关注性能优化(懒加载、代码分割、memo)\n- 解释异步处理和事件循环机制"
|
||||
},
|
||||
{
|
||||
"id": "rust-expert",
|
||||
"tags": ["rust", "systems", "memory-safety", "wasm", "performance"],
|
||||
"description": "Rust 系统编程专家,专注于内存安全、高性能计算、WebAssembly 和嵌入式开发",
|
||||
"content": "你是一位 Rust 语言布道者,追求零成本抽象和内存安全。你擅长:\n\n1. **所有权系统**:深入理解 Ownership, Borrowing, Lifetimes\n2. **并发安全**:Send/Sync trait, Arc/Mutex, async/await with Tokio\n3. **系统编程**:FFI 调用, 内联汇编, no_std 嵌入式开发\n4. **WebAssembly**:wasm-bindgen, wasm-pack, 浏览器与服务端 WASM\n\n回答规范:\n- 代码必须通过 cargo clippy 检查\n- 优先使用迭代器和零拷贝操作\n- 解释编译器错误信息和修复方案\n- 说明 unsafe 代码的必要性和安全保证"
|
||||
},
|
||||
{
|
||||
"id": "cpp-expert",
|
||||
"tags": ["cpp", "c++", "systems", "game-dev", "embedded", "performance"],
|
||||
"description": "C++ 底层开发专家,精通现代 C++ 标准、游戏引擎开发、嵌入式系统和高频交易",
|
||||
"content": "你是一位 C++ 领域的资深专家,精通从 C++11 到 C++23 的现代特性。你擅长:\n\n1. **现代 C++**:智能指针, move 语义, constexpr, concepts, ranges\n2. **内存管理**:手动内存管理, 内存池, 自定义 allocator\n3. **性能优化**:SIMD 指令, cache 友好设计, 编译器优化\n4. **应用领域**:游戏引擎 (Unreal), 高频交易, 嵌入式系统\n\n回答规范:\n- 遵循 C++ Core Guidelines\n- 优先使用 RAII 和值语义\n- 避免裸指针,使用 unique_ptr/shared_ptr\n- 解释未定义行为 (UB) 的风险"
|
||||
},
|
||||
{
|
||||
"id": "devops-expert",
|
||||
"tags": ["devops", "docker", "kubernetes", "ci-cd", "jenkins", "github-actions", "terraform"],
|
||||
"description": "DevOps 工程师,专注于 CI/CD 流水线、容器编排、基础设施即代码和云平台运维",
|
||||
"content": "你是一位 DevOps 文化的践行者,致力于打通开发与运维的壁垒。你精通:\n\n1. **容器化**:Docker 最佳实践, 多阶段构建, 镜像优化\n2. **编排调度**:Kubernetes 集群管理, Helm Charts, Operator 模式\n3. **CI/CD**:GitHub Actions, GitLab CI, Jenkins Pipeline, ArgoCD\n4. **IaC**:Terraform, Pulumi, Ansible, CloudFormation\n\n回答规范:\n- 提供可复用的配置文件和脚本\n- 遵循 GitOps 原则\n- 考虑安全性(Secret 管理, RBAC)\n- 解释监控和告警策略"
|
||||
},
|
||||
{
|
||||
"id": "database-expert",
|
||||
"tags": ["database", "mysql", "postgresql", "mongodb", "redis", "sql", "nosql"],
|
||||
"description": "数据库专家,精通关系型与 NoSQL 数据库设计、性能调优、高可用架构和数据迁移",
|
||||
"content": "你是一位数据库领域的资深 DBA 和架构师。你精通:\n\n1. **关系型数据库**:MySQL 8, PostgreSQL 15, 索引优化, 查询调优\n2. **NoSQL**:MongoDB 分片集群, Redis 数据结构与持久化, Elasticsearch\n3. **架构设计**:分库分表, 读写分离, 主从复制, 分布式事务\n4. **数据治理**:备份恢复, 数据迁移, 版本管理 (Flyway/Liquibase)\n\n回答规范:\n- 提供 EXPLAIN 分析和优化建议\n- 考虑 ACID 与 CAP 权衡\n- 给出具体的 SQL 或配置示例\n- 解释锁机制和并发控制"
|
||||
},
|
||||
{
|
||||
"id": "security-expert",
|
||||
"tags": ["security", "cybersecurity", "penetration", "encryption", "owasp"],
|
||||
"description": "网络安全专家,专注于渗透测试、漏洞挖掘、安全架构设计和合规审计",
|
||||
"content": "你是一位白帽黑客和安全架构师,致力于构建安全可靠的系统。你擅长:\n\n1. **渗透测试**:Web 漏洞 (OWASP Top 10), 网络渗透, 社会工程学\n2. **安全开发**:安全编码规范, 代码审计, SAST/DAST 工具\n3. **密码学**:对称/非对称加密, 数字签名, TLS/SSL, JWT\n4. **合规安全**:等保 2.0, GDPR, SOC 2, ISO 27001\n\n回答规范:\n- 只讨论防御性安全技术\n- 提供漏洞修复的具体代码\n- 解释攻击原理以便更好防御\n- 推荐安全工具和最佳实践"
|
||||
},
|
||||
{
|
||||
"id": "ai-ml-expert",
|
||||
"tags": ["ai", "machine-learning", "deep-learning", "llm", "pytorch", "tensorflow", "nlp"],
|
||||
"description": "AI/机器学习专家,精通深度学习框架、大语言模型、计算机视觉和 MLOps",
|
||||
"content": "你是一位 AI 研究员和机器学习工程师,站在人工智能的前沿。你精通:\n\n1. **深度学习**:PyTorch, TensorFlow, 神经网络架构设计\n2. **大语言模型**:Transformer 原理, 微调技术 (LoRA, QLoRA), RAG 系统\n3. **计算机视觉**:CNN, 目标检测 (YOLO), 图像分割, 多模态模型\n4. **MLOps**:模型训练流水线, 模型部署 (ONNX, TensorRT), A/B 测试\n\n回答规范:\n- 提供可运行的训练/推理代码\n- 解释算法的数学原理(如需要)\n- 给出超参数调优建议\n- 讨论模型的局限性和改进方向"
|
||||
},
|
||||
{
|
||||
"id": "frontend-ui-expert",
|
||||
"tags": ["ui", "ux", "css", "tailwind", "design-system", "accessibility", "responsive"],
|
||||
"description": "前端 UI/UX 专家,精通 CSS 架构、设计系统、响应式布局和无障碍设计",
|
||||
"content": "你是一位追求像素级完美的前端 UI 专家。你擅长:\n\n1. **CSS 架构**:Tailwind CSS, CSS Modules, Styled Components, CSS-in-JS\n2. **设计系统**:组件库设计, Design Tokens, Storybook 文档\n3. **响应式设计**:移动优先, 弹性布局, Container Queries\n4. **无障碍 (a11y)**:WCAG 标准, ARIA 属性, 键盘导航, 屏幕阅读器\n\n回答规范:\n- 提供语义化的 HTML 结构\n- CSS 代码需考虑浏览器兼容性\n- 关注性能(减少重排重绘)\n- 确保设计对色盲用户友好"
|
||||
},
|
||||
{
|
||||
"id": "mobile-expert",
|
||||
"tags": ["mobile", "ios", "android", "react-native", "flutter", "swift", "kotlin"],
|
||||
"description": "移动端开发专家,精通 iOS/Android 原生开发和 React Native、Flutter 跨平台方案",
|
||||
"content": "你是一位全能的移动端开发专家,覆盖原生与跨平台方案。你精通:\n\n1. **iOS 开发**:Swift, SwiftUI, UIKit, Core Data, Combine\n2. **Android 开发**:Kotlin, Jetpack Compose, Room, Coroutines\n3. **跨平台**:React Native (Expo), Flutter (Dart), 性能优化\n4. **发布流程**:App Store/Google Play 审核, CI/CD, 热更新\n\n回答规范:\n- 遵循各平台的设计规范 (HIG/Material Design)\n- 考虑不同设备尺寸的适配\n- 关注启动速度和内存占用\n- 解释原生模块桥接机制"
|
||||
},
|
||||
{
|
||||
"id": "legal-advisor",
|
||||
"tags": ["law", "legal", "contract", "compliance", "intellectual-property"],
|
||||
"description": "法律顾问,专注于合同审查、知识产权、公司法和合规咨询",
|
||||
"content": "你是一位经验丰富的法律顾问,专注于以下领域:\n\n1. **合同法**:合同起草、审查、谈判和争议解决\n2. **知识产权**:专利、商标、版权保护策略\n3. **公司法**:公司设立、股权架构、并购重组\n4. **合规咨询**:数据隐私、行业监管、风险管理\n\n回答问题时请:\n- 提供清晰的法律分析框架\n- 引用相关法律法规(如适用)\n- 指出潜在的法律风险\n- 建议具体的应对措施\n- 提醒用户在必要时寻求专业律师意见"
|
||||
},
|
||||
{
|
||||
"id": "creative-writer",
|
||||
"tags": ["writing", "creative", "storytelling", "copywriting", "content"],
|
||||
"description": "创意写作专家,擅长故事创作、文案撰写、内容策划和品牌叙事",
|
||||
"content": "你是一位才华横溢的创意写作专家,擅长:\n\n1. **故事创作**:小说、剧本、短篇故事的构思与写作\n2. **商业文案**:广告文案、品牌故事、营销内容\n3. **内容策划**:社交媒体内容、博客文章、视频脚本\n4. **品牌叙事**:品牌定位、价值主张、情感连接\n\n创作时请:\n- 注重情感共鸣和读者体验\n- 运用修辞手法增强表达力\n- 保持风格一致性和创意新颖性\n- 根据目标受众调整语言风格\n- 提供多个创意方向供选择"
|
||||
},
|
||||
{
|
||||
"id": "product-manager",
|
||||
"tags": ["product", "pm", "agile", "scrum", "roadmap", "user-story"],
|
||||
"description": "产品经理,擅长需求分析、产品规划、敏捷开发流程和用户增长策略",
|
||||
"content": "你是一位经验丰富的产品经理,具备技术背景和商业洞察力。你擅长:\n\n1. **需求管理**:用户调研, 需求挖掘, PRD 文档, 用户故事\n2. **产品规划**:产品路线图, 优先级排序 (RICE/ICE), OKR 制定\n3. **敏捷实践**:Scrum/Kanban, Sprint 规划, 站会, 复盘\n4. **增长策略**:用户留存, A/B 测试, 北极星指标, AARRR 模型\n\n回答规范:\n- 以用户价值为导向思考问题\n- 用数据驱动决策\n- 平衡技术可行性与商业价值\n- 提供可落地的执行方案"
|
||||
},
|
||||
{
|
||||
"id": "data-analyst",
|
||||
"tags": ["data-analysis", "sql", "excel", "tableau", "power-bi", "statistics"],
|
||||
"description": "数据分析师,精通 SQL 查询、数据可视化、统计分析和商业智能报表",
|
||||
"content": "你是一位数据驱动的分析专家,善于从数据中发现洞察。你精通:\n\n1. **数据处理**:SQL 高级查询, Python/R 数据清洗, ETL 流程\n2. **可视化**:Tableau, Power BI, ECharts, 仪表盘设计\n3. **统计分析**:描述性统计, 假设检验, 回归分析, 时间序列\n4. **商业分析**:用户分群, 漏斗分析, 归因模型, LTV 计算\n\n回答规范:\n- 提供可执行的 SQL 或代码\n- 解释数据背后的业务含义\n- 给出可视化图表建议\n- 注意数据的统计显著性"
|
||||
},
|
||||
{
|
||||
"id": "english-teacher",
|
||||
"tags": ["english", "language", "grammar", "writing", "ielts", "toefl"],
|
||||
"description": "英语教师,擅长语法讲解、写作指导、口语训练和雅思托福备考",
|
||||
"content": "你是一位耐心专业的英语教师,拥有丰富的教学经验。你擅长:\n\n1. **语法教学**:从基础到高级语法的系统讲解,易错点分析\n2. **写作指导**:学术写作, 商务邮件, 雅思/托福作文批改\n3. **口语训练**:发音纠正, 地道表达, 场景对话练习\n4. **考试备考**:雅思, 托福, GRE, 四六级备考策略\n\n教学原则:\n- 用简单易懂的方式解释复杂语法\n- 提供大量例句和对比练习\n- 指出中式英语的常见错误\n- 鼓励学生多练习,及时给予反馈"
|
||||
},
|
||||
{
|
||||
"id": "fitness-coach",
|
||||
"tags": ["fitness", "workout", "nutrition", "health", "gym", "exercise"],
|
||||
"description": "健身教练,提供训练计划制定、动作指导、营养建议和体态改善方案",
|
||||
"content": "你是一位专业的健身教练和营养顾问,帮助人们实现健康目标。你擅长:\n\n1. **训练计划**:力量训练, 有氧运动, HIIT, 分化训练安排\n2. **动作指导**:标准动作讲解, 常见错误纠正, 替代动作推荐\n3. **营养指导**:宏量营养素计算, 增肌/减脂饮食, 补剂建议\n4. **体态改善**:圆肩驼背矫正, 骨盆前倾改善, 拉伸放松\n\n指导原则:\n- 根据个人情况定制方案\n- 安全第一,循序渐进\n- 解释训练背后的原理\n- 提醒热身和恢复的重要性"
|
||||
},
|
||||
{
|
||||
"id": "finance-advisor",
|
||||
"tags": ["finance", "investment", "stock", "fund", "financial-planning"],
|
||||
"description": "财务顾问,提供个人理财规划、投资策略、税务优化和财务分析",
|
||||
"content": "你是一位专业的财务顾问,帮助个人和家庭实现财务自由。你擅长:\n\n1. **理财规划**:预算管理, 储蓄策略, 应急基金, 保险配置\n2. **投资分析**:股票基本面分析, 基金筛选, 资产配置, 风险评估\n3. **税务优化**:个税筹划, 专项扣除, 税收优惠政策\n4. **财务分析**:财务报表解读, 估值方法, 行业分析\n\n建议原则:\n- 强调风险与收益的平衡\n- 根据风险承受能力给建议\n- 不推荐具体股票,只讲方法论\n- 提醒投资有风险,入市需谨慎"
|
||||
},
|
||||
{
|
||||
"id": "psychologist",
|
||||
"tags": ["psychology", "mental-health", "counseling", "emotion", "stress"],
|
||||
"description": "心理咨询师,提供情绪疏导、压力管理、人际关系和自我成长指导",
|
||||
"content": "你是一位温暖有同理心的心理咨询师,致力于帮助人们获得心理健康。你擅长:\n\n1. **情绪支持**:倾听、共情、情绪识别与表达\n2. **压力管理**:焦虑缓解、正念冥想、放松技巧\n3. **人际关系**:沟通技巧、边界设定、冲突解决\n4. **自我成长**:自我认知、价值观探索、目标设定\n\n咨询原则:\n- 营造安全、不评判的谈话环境\n- 以来访者为中心,尊重个人选择\n- 使用专业但易懂的语言\n- 必要时建议寻求线下专业帮助"
|
||||
}
|
||||
]
|
||||
2561
package-lock.json
generated
Normal file
2561
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "mcp-rag-prompts",
|
||||
"version": "1.0.0",
|
||||
"description": "MCP Server for RAG-based prompt management with local embeddings",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"start": "tsx src/index.ts",
|
||||
"build": "tsc",
|
||||
"dev": "tsx watch src/index.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"mcp",
|
||||
"rag",
|
||||
"embeddings",
|
||||
"prompt-management"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"@xenova/transformers": "^2.17.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
124
src/index.ts
Normal file
124
src/index.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* MCP RAG Prompts Server 入口文件
|
||||
* 基于 MCP 协议的 RAG 提示词管理服务器
|
||||
*/
|
||||
|
||||
// 加载 .env 文件(必须在最前面)
|
||||
import 'dotenv/config';
|
||||
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
import { vectorStore } from './lib/vectorStore.js';
|
||||
import {
|
||||
TOOL_NAME,
|
||||
TOOL_DESCRIPTION,
|
||||
inputJsonSchema,
|
||||
handleSearchPersona,
|
||||
type SearchPersonaInput,
|
||||
} from './tools/searchPersona.js';
|
||||
|
||||
/**
|
||||
* 创建并配置 MCP Server
|
||||
*/
|
||||
function createServer(): Server {
|
||||
const server = new Server(
|
||||
{
|
||||
name: 'mcp-rag-prompts',
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 处理 tools/list 请求
|
||||
* 返回服务器提供的所有工具列表
|
||||
*/
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
console.error('[Server] 收到 tools/list 请求');
|
||||
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: TOOL_NAME,
|
||||
description: TOOL_DESCRIPTION,
|
||||
inputSchema: inputJsonSchema,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* 处理 tools/call 请求
|
||||
* 执行指定的工具并返回结果
|
||||
*/
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
console.error(`[Server] 收到 tools/call 请求: ${name}`);
|
||||
console.error(`[Server] 参数: ${JSON.stringify(args)}`);
|
||||
|
||||
// 路由到对应的工具处理函数
|
||||
switch (name) {
|
||||
case TOOL_NAME:
|
||||
return handleSearchPersona(args as SearchPersonaInput);
|
||||
|
||||
default:
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: `未知工具: ${name}`,
|
||||
}),
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数:初始化并启动服务器
|
||||
*/
|
||||
async function main(): Promise<void> {
|
||||
console.error('='.repeat(50));
|
||||
console.error('MCP RAG Prompts Server 启动中...');
|
||||
console.error('='.repeat(50));
|
||||
|
||||
try {
|
||||
// 1. 初始化向量存储(加载数据并生成 embeddings)
|
||||
console.error('\n[Main] 步骤 1: 初始化向量存储');
|
||||
await vectorStore.initialize();
|
||||
|
||||
// 2. 创建 MCP Server
|
||||
console.error('\n[Main] 步骤 2: 创建 MCP Server');
|
||||
const server = createServer();
|
||||
|
||||
// 3. 创建 Stdio Transport 并连接
|
||||
console.error('\n[Main] 步骤 3: 启动 Stdio Transport');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
console.error('\n[Main] ✓ MCP Server 已启动,等待客户端连接...');
|
||||
console.error('='.repeat(50));
|
||||
} catch (error) {
|
||||
console.error('\n[Main] ✗ 启动失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
main();
|
||||
305
src/lib/embeddingProviders.ts
Normal file
305
src/lib/embeddingProviders.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* Embedding 提供者实现
|
||||
* 支持本地模型和多种云服务 API
|
||||
*/
|
||||
|
||||
import type { IEmbeddingProvider, EmbeddingConfig } from './types.js';
|
||||
|
||||
// ============================================================
|
||||
// 本地模型提供者 (使用 @xenova/transformers)
|
||||
// ============================================================
|
||||
export class LocalEmbeddingProvider implements IEmbeddingProvider {
|
||||
readonly name = 'local';
|
||||
private modelName: string;
|
||||
private extractor: any = null;
|
||||
|
||||
constructor(modelName: string = 'Xenova/paraphrase-multilingual-MiniLM-L12-v2') {
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (this.extractor) return;
|
||||
|
||||
console.error(`[LocalProvider] 正在加载本地模型: ${this.modelName}`);
|
||||
// 动态导入,避免在使用 API 时也加载这个大依赖
|
||||
const { pipeline } = await import('@xenova/transformers');
|
||||
this.extractor = await pipeline('feature-extraction', this.modelName);
|
||||
console.error(`[LocalProvider] 模型加载完成`);
|
||||
}
|
||||
|
||||
async embed(text: string): Promise<number[]> {
|
||||
if (!this.extractor) {
|
||||
throw new Error('LocalProvider 尚未初始化');
|
||||
}
|
||||
const output = await this.extractor(text, {
|
||||
pooling: 'mean',
|
||||
normalize: true,
|
||||
});
|
||||
return Array.from(output.data as Float32Array);
|
||||
}
|
||||
|
||||
async embedBatch(texts: string[]): Promise<number[][]> {
|
||||
// 本地模型逐个处理
|
||||
const results: number[][] = [];
|
||||
for (const text of texts) {
|
||||
results.push(await this.embed(text));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// OpenAI 兼容 API 提供者
|
||||
// 支持 OpenAI 官方 API 和兼容接口(如 Azure OpenAI)
|
||||
// ============================================================
|
||||
export class OpenAIEmbeddingProvider implements IEmbeddingProvider {
|
||||
readonly name = 'openai';
|
||||
private apiKey: string;
|
||||
private baseUrl: string;
|
||||
private model: string;
|
||||
|
||||
constructor(config: { apiKey: string; baseUrl?: string; model?: string }) {
|
||||
this.apiKey = config.apiKey;
|
||||
this.baseUrl = config.baseUrl || 'https://api.openai.com/v1';
|
||||
this.model = config.model || 'text-embedding-3-small';
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
console.error(`[OpenAIProvider] 使用模型: ${this.model}`);
|
||||
console.error(`[OpenAIProvider] API 地址: ${this.baseUrl}`);
|
||||
}
|
||||
|
||||
async embed(text: string): Promise<number[]> {
|
||||
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
input: text,
|
||||
model: this.model,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`OpenAI API 错误: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.data[0].embedding;
|
||||
}
|
||||
|
||||
async embedBatch(texts: string[]): Promise<number[][]> {
|
||||
// OpenAI 支持批量请求
|
||||
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
input: texts,
|
||||
model: this.model,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`OpenAI API 错误: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
// 按 index 排序确保顺序正确
|
||||
return data.data
|
||||
.sort((a: any, b: any) => a.index - b.index)
|
||||
.map((item: any) => item.embedding);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 阿里云百炼 DashScope API 提供者
|
||||
// ============================================================
|
||||
export class AliyunEmbeddingProvider implements IEmbeddingProvider {
|
||||
readonly name = 'aliyun';
|
||||
private apiKey: string;
|
||||
private model: string;
|
||||
private baseUrl = 'https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding';
|
||||
|
||||
constructor(config: { apiKey: string; model?: string }) {
|
||||
this.apiKey = config.apiKey;
|
||||
this.model = config.model || 'text-embedding-v3';
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
console.error(`[AliyunProvider] 使用模型: ${this.model}`);
|
||||
}
|
||||
|
||||
async embed(text: string): Promise<number[]> {
|
||||
const response = await fetch(this.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
input: {
|
||||
texts: [text],
|
||||
},
|
||||
parameters: {
|
||||
text_type: 'query',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`阿里云 API 错误: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.output?.embeddings?.[0]?.embedding) {
|
||||
return data.output.embeddings[0].embedding;
|
||||
}
|
||||
|
||||
throw new Error(`阿里云 API 返回格式错误: ${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
async embedBatch(texts: string[]): Promise<number[][]> {
|
||||
const response = await fetch(this.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
input: {
|
||||
texts: texts,
|
||||
},
|
||||
parameters: {
|
||||
text_type: 'query',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`阿里云 API 错误: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.output?.embeddings) {
|
||||
return data.output.embeddings
|
||||
.sort((a: any, b: any) => a.text_index - b.text_index)
|
||||
.map((item: any) => item.embedding);
|
||||
}
|
||||
|
||||
throw new Error(`阿里云 API 返回格式错误: ${JSON.stringify(data)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SiliconFlow API 提供者
|
||||
// 硅基流动,支持多种开源 Embedding 模型
|
||||
// ============================================================
|
||||
export class SiliconFlowEmbeddingProvider implements IEmbeddingProvider {
|
||||
readonly name = 'siliconflow';
|
||||
private apiKey: string;
|
||||
private model: string;
|
||||
private baseUrl = 'https://api.siliconflow.cn/v1/embeddings';
|
||||
|
||||
constructor(config: { apiKey: string; model?: string }) {
|
||||
this.apiKey = config.apiKey;
|
||||
// 默认使用 BGE-M3,多语言效果好
|
||||
this.model = config.model || 'BAAI/bge-m3';
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
console.error(`[SiliconFlowProvider] 使用模型: ${this.model}`);
|
||||
}
|
||||
|
||||
async embed(text: string): Promise<number[]> {
|
||||
const response = await fetch(this.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
input: text,
|
||||
encoding_format: 'float',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`SiliconFlow API 错误: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.data[0].embedding;
|
||||
}
|
||||
|
||||
async embedBatch(texts: string[]): Promise<number[][]> {
|
||||
// SiliconFlow 支持批量(与 OpenAI 兼容)
|
||||
const response = await fetch(this.baseUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
input: texts,
|
||||
encoding_format: 'float',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`SiliconFlow API 错误: ${response.status} - ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.data
|
||||
.sort((a: any, b: any) => a.index - b.index)
|
||||
.map((item: any) => item.embedding);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 工厂函数:根据配置创建对应的提供者
|
||||
// ============================================================
|
||||
export function createEmbeddingProvider(config: EmbeddingConfig): IEmbeddingProvider {
|
||||
switch (config.provider) {
|
||||
case 'local':
|
||||
return new LocalEmbeddingProvider(config.local?.modelName);
|
||||
|
||||
case 'openai':
|
||||
if (!config.openai?.apiKey) {
|
||||
throw new Error('OpenAI 配置缺少 apiKey');
|
||||
}
|
||||
return new OpenAIEmbeddingProvider(config.openai);
|
||||
|
||||
case 'aliyun':
|
||||
if (!config.aliyun?.apiKey) {
|
||||
throw new Error('阿里云配置缺少 apiKey');
|
||||
}
|
||||
return new AliyunEmbeddingProvider(config.aliyun);
|
||||
|
||||
case 'siliconflow':
|
||||
if (!config.siliconflow?.apiKey) {
|
||||
throw new Error('SiliconFlow 配置缺少 apiKey');
|
||||
}
|
||||
return new SiliconFlowEmbeddingProvider(config.siliconflow);
|
||||
|
||||
default:
|
||||
throw new Error(`不支持的 Embedding 提供者: ${config.provider}`);
|
||||
}
|
||||
}
|
||||
84
src/lib/types.ts
Normal file
84
src/lib/types.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 类型定义文件
|
||||
* 定义 Prompt 数据结构和向量存储相关类型
|
||||
*/
|
||||
|
||||
/**
|
||||
* 原始 Prompt 数据结构(从 JSON 文件读取)
|
||||
*/
|
||||
export interface PromptData {
|
||||
id: string;
|
||||
tags: string[];
|
||||
description: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带向量的 Prompt 数据(内存中缓存)
|
||||
*/
|
||||
export interface PromptWithEmbedding extends PromptData {
|
||||
embedding: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索结果结构
|
||||
*/
|
||||
export interface SearchResult {
|
||||
prompt: PromptData;
|
||||
similarity: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Embedding 提供者类型
|
||||
*/
|
||||
export type EmbeddingProvider = 'local' | 'openai' | 'aliyun' | 'siliconflow';
|
||||
|
||||
/**
|
||||
* Embedding 配置接口
|
||||
*/
|
||||
export interface EmbeddingConfig {
|
||||
/** 提供者类型 */
|
||||
provider: EmbeddingProvider;
|
||||
|
||||
/** 本地模型配置 */
|
||||
local?: {
|
||||
modelName: string;
|
||||
};
|
||||
|
||||
/** OpenAI 配置 */
|
||||
openai?: {
|
||||
apiKey: string;
|
||||
baseUrl?: string; // 支持自定义 base URL(如代理)
|
||||
model?: string; // 默认 text-embedding-3-small
|
||||
};
|
||||
|
||||
/** 阿里云百炼配置 */
|
||||
aliyun?: {
|
||||
apiKey: string;
|
||||
model?: string; // 默认 text-embedding-v3
|
||||
};
|
||||
|
||||
/** SiliconFlow 配置 */
|
||||
siliconflow?: {
|
||||
apiKey: string;
|
||||
model?: string; // 默认 BAAI/bge-m3
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Embedding 提供者接口
|
||||
* 所有 Embedding 实现都需要遵循此接口
|
||||
*/
|
||||
export interface IEmbeddingProvider {
|
||||
/** 提供者名称 */
|
||||
readonly name: string;
|
||||
|
||||
/** 初始化(如加载模型) */
|
||||
initialize(): Promise<void>;
|
||||
|
||||
/** 生成单个文本的向量 */
|
||||
embed(text: string): Promise<number[]>;
|
||||
|
||||
/** 批量生成向量(可选优化) */
|
||||
embedBatch?(texts: string[]): Promise<number[][]>;
|
||||
}
|
||||
232
src/lib/vectorStore.ts
Normal file
232
src/lib/vectorStore.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* 向量存储模块 (增强版)
|
||||
* 支持本地模型和云服务 API 切换
|
||||
* 核心功能:Hybrid Search (向量语义 + 关键词加权)
|
||||
*/
|
||||
|
||||
import { readFile } from 'fs/promises';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import type {
|
||||
PromptData,
|
||||
PromptWithEmbedding,
|
||||
SearchResult,
|
||||
EmbeddingConfig,
|
||||
IEmbeddingProvider,
|
||||
} from './types.js';
|
||||
import { createEmbeddingProvider } from './embeddingProviders.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// 相似度阈值
|
||||
const SIMILARITY_THRESHOLD = 0.4;
|
||||
|
||||
// 关键词命中加分
|
||||
const KEYWORD_BOOST_SCORE = 0.3;
|
||||
|
||||
/**
|
||||
* 从环境变量读取配置
|
||||
* 优先级:环境变量 > 默认值
|
||||
*/
|
||||
function getConfigFromEnv(): EmbeddingConfig {
|
||||
const provider = (process.env.EMBEDDING_PROVIDER || 'local') as EmbeddingConfig['provider'];
|
||||
|
||||
const config: EmbeddingConfig = {
|
||||
provider,
|
||||
local: {
|
||||
modelName: process.env.LOCAL_MODEL_NAME || 'Xenova/paraphrase-multilingual-MiniLM-L12-v2',
|
||||
},
|
||||
openai: {
|
||||
apiKey: process.env.OPENAI_API_KEY || '',
|
||||
baseUrl: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
|
||||
model: process.env.OPENAI_EMBEDDING_MODEL || 'text-embedding-3-small',
|
||||
},
|
||||
aliyun: {
|
||||
apiKey: process.env.DASHSCOPE_API_KEY || '',
|
||||
model: process.env.ALIYUN_EMBEDDING_MODEL || 'text-embedding-v3',
|
||||
},
|
||||
siliconflow: {
|
||||
apiKey: process.env.SILICONFLOW_API_KEY || '',
|
||||
model: process.env.SILICONFLOW_EMBEDDING_MODEL || 'BAAI/bge-m3',
|
||||
},
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量存储类
|
||||
*/
|
||||
export class VectorStore {
|
||||
private prompts: PromptWithEmbedding[] = [];
|
||||
private embeddingProvider: IEmbeddingProvider | null = null;
|
||||
private initialized = false;
|
||||
private config: EmbeddingConfig;
|
||||
|
||||
constructor(config?: EmbeddingConfig) {
|
||||
// 使用传入配置或从环境变量读取
|
||||
this.config = config || getConfigFromEnv();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算余弦相似度
|
||||
*/
|
||||
private cosineSimilarity(vecA: number[], vecB: number[]): number {
|
||||
if (vecA.length !== vecB.length) return 0;
|
||||
|
||||
let dotProduct = 0;
|
||||
let normA = 0;
|
||||
let normB = 0;
|
||||
|
||||
for (let i = 0; i < vecA.length; i++) {
|
||||
dotProduct += vecA[i] * vecB[i];
|
||||
normA += vecA[i] * vecA[i];
|
||||
normB += vecB[i] * vecB[i];
|
||||
}
|
||||
|
||||
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
||||
return magnitude === 0 ? 0 : dotProduct / magnitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化向量存储
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
|
||||
console.error('='.repeat(50));
|
||||
console.error('[VectorStore] 开始初始化...');
|
||||
console.error(`[VectorStore] Embedding 提供者: ${this.config.provider}`);
|
||||
|
||||
// 创建并初始化 Embedding 提供者
|
||||
this.embeddingProvider = createEmbeddingProvider(this.config);
|
||||
await this.embeddingProvider.initialize();
|
||||
|
||||
// 读取 Prompt 数据
|
||||
const dataPath = join(__dirname, '../../data/prompts.json');
|
||||
const rawData = await readFile(dataPath, 'utf-8');
|
||||
const promptsData: PromptData[] = JSON.parse(rawData);
|
||||
|
||||
console.error(`[VectorStore] 读取到 ${promptsData.length} 条 Prompt 数据`);
|
||||
|
||||
// 准备待向量化的文本
|
||||
const textsToEmbed = promptsData.map(
|
||||
(prompt) => `${prompt.tags.join(' ')} ${prompt.description}`
|
||||
);
|
||||
|
||||
// 生成向量(优先使用批量接口)
|
||||
let embeddings: number[][];
|
||||
|
||||
if (this.embeddingProvider.embedBatch) {
|
||||
console.error('[VectorStore] 使用批量向量化...');
|
||||
embeddings = await this.embeddingProvider.embedBatch(textsToEmbed);
|
||||
} else {
|
||||
console.error('[VectorStore] 逐个向量化...');
|
||||
embeddings = [];
|
||||
for (const text of textsToEmbed) {
|
||||
embeddings.push(await this.embeddingProvider.embed(text));
|
||||
}
|
||||
}
|
||||
|
||||
// 组装数据
|
||||
for (let i = 0; i < promptsData.length; i++) {
|
||||
this.prompts.push({
|
||||
...promptsData[i],
|
||||
embedding: embeddings[i],
|
||||
});
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
console.error(`[VectorStore] ✓ 初始化完成,加载了 ${promptsData.length} 条数据`);
|
||||
console.error('='.repeat(50));
|
||||
}
|
||||
|
||||
/**
|
||||
* 语义搜索
|
||||
*/
|
||||
async search(query: string): Promise<SearchResult | null> {
|
||||
if (!this.initialized || !this.embeddingProvider) {
|
||||
throw new Error('VectorStore 尚未初始化');
|
||||
}
|
||||
|
||||
console.error(`[VectorStore] 搜索: "${query}"`);
|
||||
|
||||
// 生成查询向量
|
||||
const queryEmbedding = await this.embeddingProvider.embed(query);
|
||||
const queryLower = query.toLowerCase();
|
||||
|
||||
// 混合打分
|
||||
const results: SearchResult[] = this.prompts.map((prompt) => {
|
||||
// A. 向量相似度
|
||||
const vectorScore = this.cosineSimilarity(queryEmbedding, prompt.embedding);
|
||||
|
||||
// B. 关键词加分
|
||||
let boostScore = 0;
|
||||
if (prompt.tags && prompt.tags.length > 0) {
|
||||
for (const tag of prompt.tags) {
|
||||
if (queryLower.includes(tag.toLowerCase())) {
|
||||
boostScore = KEYWORD_BOOST_SCORE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最终得分
|
||||
const finalScore = Math.min(vectorScore + boostScore, 1.0);
|
||||
|
||||
return {
|
||||
prompt: {
|
||||
id: prompt.id,
|
||||
tags: prompt.tags,
|
||||
description: prompt.description,
|
||||
content: prompt.content,
|
||||
},
|
||||
similarity: finalScore,
|
||||
};
|
||||
});
|
||||
|
||||
// 排序
|
||||
results.sort((a, b) => b.similarity - a.similarity);
|
||||
const bestMatch = results[0];
|
||||
|
||||
// 调试输出
|
||||
console.error(`--- Top 3 候选 ---`);
|
||||
results.slice(0, 3).forEach((r, i) => {
|
||||
console.error(`${i + 1}. [${r.prompt.id}] 得分: ${r.similarity.toFixed(4)}`);
|
||||
});
|
||||
|
||||
// 阈值检查
|
||||
if (bestMatch.similarity < SIMILARITY_THRESHOLD) {
|
||||
console.error(
|
||||
`[VectorStore] ✗ 最高分 ${bestMatch.similarity.toFixed(4)} 低于阈值 ${SIMILARITY_THRESHOLD}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.error(
|
||||
`[VectorStore] ✓ 选中: ${bestMatch.prompt.id} (得分: ${bestMatch.similarity.toFixed(4)})`
|
||||
);
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的 Prompt ID
|
||||
*/
|
||||
getAvailablePrompts(): string[] {
|
||||
return this.prompts.map((p) => p.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前使用的 Embedding 提供者信息
|
||||
*/
|
||||
getProviderInfo(): { provider: string; initialized: boolean } {
|
||||
return {
|
||||
provider: this.config.provider,
|
||||
initialized: this.initialized,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例(使用环境变量配置)
|
||||
export const vectorStore = new VectorStore();
|
||||
184
src/test.ts
Normal file
184
src/test.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* 测试脚本:验证向量存储和搜索功能
|
||||
* 包含多场景测试用例
|
||||
*/
|
||||
|
||||
// 加载 .env 文件
|
||||
import 'dotenv/config';
|
||||
|
||||
import { vectorStore } from './lib/vectorStore.js';
|
||||
|
||||
// 定义测试用例结构
|
||||
interface TestCase {
|
||||
query: string;
|
||||
expectedId: string | null; // null 表示期望无匹配
|
||||
category: string;
|
||||
}
|
||||
|
||||
// 测试用例集
|
||||
const testCases: TestCase[] = [
|
||||
// ==================== 编程语言类 ====================
|
||||
{ query: '如何用 Python 写一个爬虫?', expectedId: 'python-expert', category: '编程-Python' },
|
||||
{ query: '帮我用 FastAPI 搭建一个后端服务', expectedId: 'python-expert', category: '编程-Python' },
|
||||
{ query: 'Django ORM 怎么做多表关联查询?', expectedId: 'python-expert', category: '编程-Python' },
|
||||
|
||||
{ query: '如何用 Java 实现单例模式?', expectedId: 'java-expert', category: '编程-Java' },
|
||||
{ query: 'Spring Boot 项目如何配置多数据源?', expectedId: 'java-expert', category: '编程-Java' },
|
||||
{ query: 'JVM 垃圾回收机制是怎样的?', expectedId: 'java-expert', category: '编程-Java' },
|
||||
|
||||
{ query: '用 Go 写一个高并发的 HTTP 服务', expectedId: 'golang-expert', category: '编程-Go' },
|
||||
{ query: 'Golang 的 channel 和 goroutine 怎么用?', expectedId: 'golang-expert', category: '编程-Go' },
|
||||
{ query: '如何用 gRPC 实现微服务通信?', expectedId: 'golang-expert', category: '编程-Go' },
|
||||
|
||||
{ query: 'React Hooks 的最佳实践是什么?', expectedId: 'javascript-expert', category: '编程-JS/TS' },
|
||||
{ query: 'TypeScript 泛型怎么用?', expectedId: 'javascript-expert', category: '编程-JS/TS' },
|
||||
{ query: 'Vue 3 的 Composition API 和 Options API 有什么区别?', expectedId: 'javascript-expert', category: '编程-JS/TS' },
|
||||
{ query: 'Node.js 如何处理大文件上传?', expectedId: 'javascript-expert', category: '编程-JS/TS' },
|
||||
|
||||
{ query: 'Rust 的所有权机制是什么?', expectedId: 'rust-expert', category: '编程-Rust' },
|
||||
{ query: '如何用 Rust 编写 WebAssembly 模块?', expectedId: 'rust-expert', category: '编程-Rust' },
|
||||
|
||||
{ query: 'C++ 智能指针有哪几种?', expectedId: 'cpp-expert', category: '编程-C++' },
|
||||
{ query: '如何优化 C++ 程序的内存使用?', expectedId: 'cpp-expert', category: '编程-C++' },
|
||||
{ query: 'Unreal Engine 游戏开发入门', expectedId: 'cpp-expert', category: '编程-C++' },
|
||||
|
||||
// ==================== DevOps/运维类 ====================
|
||||
{ query: 'Docker 镜像怎么优化体积?', expectedId: 'devops-expert', category: 'DevOps' },
|
||||
{ query: 'Kubernetes 如何实现滚动更新?', expectedId: 'devops-expert', category: 'DevOps' },
|
||||
{ query: 'GitHub Actions 如何配置 CI/CD?', expectedId: 'devops-expert', category: 'DevOps' },
|
||||
{ query: 'Terraform 怎么管理云资源?', expectedId: 'devops-expert', category: 'DevOps' },
|
||||
|
||||
// ==================== 数据库类 ====================
|
||||
{ query: 'MySQL 索引优化有哪些技巧?', expectedId: 'database-expert', category: '数据库' },
|
||||
{ query: 'Redis 缓存穿透怎么解决?', expectedId: 'database-expert', category: '数据库' },
|
||||
{ query: 'MongoDB 如何设计 Schema?', expectedId: 'database-expert', category: '数据库' },
|
||||
{ query: '如何实现数据库读写分离?', expectedId: 'database-expert', category: '数据库' },
|
||||
|
||||
// ==================== 安全类 ====================
|
||||
{ query: 'SQL 注入攻击如何防范?', expectedId: 'security-expert', category: '安全' },
|
||||
{ query: 'HTTPS 证书怎么配置?', expectedId: 'security-expert', category: '安全' },
|
||||
{ query: 'JWT Token 安全最佳实践', expectedId: 'security-expert', category: '安全' },
|
||||
|
||||
// ==================== AI/ML 类 ====================
|
||||
{ query: '如何用 PyTorch 训练一个图像分类模型?', expectedId: 'ai-ml-expert', category: 'AI/ML' },
|
||||
{ query: '大语言模型微调有哪些方法?', expectedId: 'ai-ml-expert', category: 'AI/ML' },
|
||||
{ query: 'RAG 系统怎么搭建?', expectedId: 'ai-ml-expert', category: 'AI/ML' },
|
||||
{ query: 'YOLO 目标检测怎么用?', expectedId: 'ai-ml-expert', category: 'AI/ML' },
|
||||
|
||||
// ==================== 前端 UI 类 ====================
|
||||
{ query: 'Tailwind CSS 怎么自定义主题?', expectedId: 'frontend-ui-expert', category: '前端UI' },
|
||||
{ query: '如何实现响应式布局?', expectedId: 'frontend-ui-expert', category: '前端UI' },
|
||||
{ query: '网站无障碍设计要注意什么?', expectedId: 'frontend-ui-expert', category: '前端UI' },
|
||||
|
||||
// ==================== 移动端类 ====================
|
||||
{ query: 'SwiftUI 和 UIKit 哪个更好?', expectedId: 'mobile-expert', category: '移动端' },
|
||||
{ query: 'Flutter 性能优化技巧', expectedId: 'mobile-expert', category: '移动端' },
|
||||
{ query: 'React Native 如何调用原生模块?', expectedId: 'mobile-expert', category: '移动端' },
|
||||
{ query: 'Android Kotlin 协程怎么用?', expectedId: 'mobile-expert', category: '移动端' },
|
||||
|
||||
// ==================== 非技术类 ====================
|
||||
{ query: '帮我写一份劳动合同', expectedId: 'legal-advisor', category: '法律' },
|
||||
{ query: '商标注册流程是什么?', expectedId: 'legal-advisor', category: '法律' },
|
||||
{ query: '公司股权架构怎么设计?', expectedId: 'legal-advisor', category: '法律' },
|
||||
|
||||
{ query: '帮我写一个广告文案', expectedId: 'creative-writer', category: '写作' },
|
||||
{ query: '如何写一个吸引人的故事开头?', expectedId: 'creative-writer', category: '写作' },
|
||||
{ query: '品牌故事怎么写才能打动人?', expectedId: 'creative-writer', category: '写作' },
|
||||
|
||||
{ query: '如何做用户调研?', expectedId: 'product-manager', category: '产品' },
|
||||
{ query: 'PRD 文档怎么写?', expectedId: 'product-manager', category: '产品' },
|
||||
{ query: 'Scrum 和 Kanban 有什么区别?', expectedId: 'product-manager', category: '产品' },
|
||||
|
||||
{ query: '用户留存率怎么分析?', expectedId: 'data-analyst', category: '数据分析' },
|
||||
{ query: 'SQL 窗口函数怎么用?', expectedId: 'data-analyst', category: '数据分析' },
|
||||
{ query: 'Power BI 仪表盘怎么设计?', expectedId: 'data-analyst', category: '数据分析' },
|
||||
|
||||
{ query: '雅思写作怎么拿高分?', expectedId: 'english-teacher', category: '英语' },
|
||||
{ query: '英语语法中的虚拟语气怎么用?', expectedId: 'english-teacher', category: '英语' },
|
||||
{ query: '托福口语怎么准备?', expectedId: 'english-teacher', category: '英语' },
|
||||
|
||||
{ query: '制定一个增肌训练计划', expectedId: 'fitness-coach', category: '健身' },
|
||||
{ query: '减脂期间怎么吃?', expectedId: 'fitness-coach', category: '健身' },
|
||||
{ query: '深蹲的标准动作是什么?', expectedId: 'fitness-coach', category: '健身' },
|
||||
|
||||
{ query: '基金定投策略有哪些?', expectedId: 'finance-advisor', category: '理财' },
|
||||
{ query: '个人所得税怎么优化?', expectedId: 'finance-advisor', category: '理财' },
|
||||
{ query: '如何分析一只股票?', expectedId: 'finance-advisor', category: '理财' },
|
||||
|
||||
{ query: '最近工作压力很大怎么办?', expectedId: 'psychologist', category: '心理' },
|
||||
{ query: '如何克服社交焦虑?', expectedId: 'psychologist', category: '心理' },
|
||||
{ query: '和同事关系紧张怎么处理?', expectedId: 'psychologist', category: '心理' },
|
||||
|
||||
// ==================== 无匹配/边界测试 ====================
|
||||
{ query: '今天天气怎么样?', expectedId: null, category: '无匹配' },
|
||||
{ query: '推荐一部好看的电影', expectedId: null, category: '无匹配' },
|
||||
{ query: '附近有什么好吃的?', expectedId: null, category: '无匹配' },
|
||||
];
|
||||
|
||||
async function runTests() {
|
||||
console.log('='.repeat(60));
|
||||
console.log('MCP RAG Prompts - 综合测试');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// 初始化
|
||||
await vectorStore.initialize();
|
||||
|
||||
const providerInfo = vectorStore.getProviderInfo();
|
||||
console.log(`\n当前 Embedding 提供者: ${providerInfo.provider}`);
|
||||
console.log(`总测试用例数: ${testCases.length}\n`);
|
||||
|
||||
// 统计
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const failedCases: { query: string; expected: string | null; actual: string | null }[] = [];
|
||||
|
||||
// 按类别分组显示
|
||||
let currentCategory = '';
|
||||
|
||||
for (const testCase of testCases) {
|
||||
// 打印类别标题
|
||||
if (testCase.category !== currentCategory) {
|
||||
currentCategory = testCase.category;
|
||||
console.log(`\n--- ${currentCategory} ---`);
|
||||
}
|
||||
|
||||
const result = await vectorStore.search(testCase.query);
|
||||
const actualId = result?.prompt.id ?? null;
|
||||
const isPass = actualId === testCase.expectedId;
|
||||
|
||||
if (isPass) {
|
||||
passed++;
|
||||
console.log(`✓ "${testCase.query.slice(0, 30)}..." → ${actualId ?? '(无匹配)'}`);
|
||||
} else {
|
||||
failed++;
|
||||
failedCases.push({
|
||||
query: testCase.query,
|
||||
expected: testCase.expectedId,
|
||||
actual: actualId,
|
||||
});
|
||||
console.log(`✗ "${testCase.query.slice(0, 30)}..."`);
|
||||
console.log(` 期望: ${testCase.expectedId ?? '(无匹配)'}, 实际: ${actualId ?? '(无匹配)'}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 打印统计结果
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('测试结果统计');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`总计: ${testCases.length} 个用例`);
|
||||
console.log(`通过: ${passed} (${((passed / testCases.length) * 100).toFixed(1)}%)`);
|
||||
console.log(`失败: ${failed} (${((failed / testCases.length) * 100).toFixed(1)}%)`);
|
||||
|
||||
if (failedCases.length > 0) {
|
||||
console.log('\n失败用例详情:');
|
||||
failedCases.forEach((fc, i) => {
|
||||
console.log(`${i + 1}. "${fc.query}"`);
|
||||
console.log(` 期望: ${fc.expected ?? '(无匹配)'}`);
|
||||
console.log(` 实际: ${fc.actual ?? '(无匹配)'}`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n测试完成!');
|
||||
}
|
||||
|
||||
runTests().catch(console.error);
|
||||
129
src/tools/searchPersona.ts
Normal file
129
src/tools/searchPersona.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* MCP Tool: search_expert_persona
|
||||
* 根据用户问题语义搜索最匹配的专家角色设定
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { vectorStore } from '../lib/vectorStore.js';
|
||||
|
||||
/**
|
||||
* 工具名称
|
||||
*/
|
||||
export const TOOL_NAME = 'search_expert_persona';
|
||||
|
||||
/**
|
||||
* 工具描述
|
||||
*/
|
||||
export const TOOL_DESCRIPTION =
|
||||
'根据用户的具体问题或场景,利用语义搜索找到最匹配的专家角色设定 (System Prompt)。' +
|
||||
'输入用户的问题或需求描述,返回最适合处理该问题的专家角色 Prompt。';
|
||||
|
||||
/**
|
||||
* 参数 Schema(使用 Zod 定义)
|
||||
*/
|
||||
export const inputSchema = z.object({
|
||||
query: z
|
||||
.string()
|
||||
.min(1, '查询内容不能为空')
|
||||
.describe('用户的原始问题或需求描述,例如:"如何用 Python 实现一个 REST API?"'),
|
||||
});
|
||||
|
||||
/**
|
||||
* 参数类型
|
||||
*/
|
||||
export type SearchPersonaInput = z.infer<typeof inputSchema>;
|
||||
|
||||
/**
|
||||
* 将 Zod Schema 转换为 JSON Schema(供 MCP SDK 使用)
|
||||
*/
|
||||
export const inputJsonSchema = {
|
||||
type: 'object' as const,
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: '用户的原始问题或需求描述,例如:"如何用 Python 实现一个 REST API?"',
|
||||
},
|
||||
},
|
||||
required: ['query'],
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行搜索的处理函数
|
||||
* @param input 经过验证的输入参数
|
||||
* @returns MCP 工具响应内容
|
||||
*/
|
||||
export async function handleSearchPersona(input: SearchPersonaInput): Promise<{
|
||||
content: Array<{ type: 'text'; text: string }>;
|
||||
isError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
// 验证输入
|
||||
const validatedInput = inputSchema.parse(input);
|
||||
|
||||
// 执行语义搜索
|
||||
const result = await vectorStore.search(validatedInput.query);
|
||||
|
||||
if (!result) {
|
||||
// 没有找到匹配结果
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(
|
||||
{
|
||||
success: false,
|
||||
message: '未找到与您问题匹配的专家角色。请尝试更具体地描述您的需求。',
|
||||
availableExperts: vectorStore.getAvailablePrompts(),
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 返回匹配结果
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(
|
||||
{
|
||||
success: true,
|
||||
matchedExpert: {
|
||||
id: result.prompt.id,
|
||||
tags: result.prompt.tags,
|
||||
description: result.prompt.description,
|
||||
similarity: Math.round(result.similarity * 100) / 100,
|
||||
},
|
||||
systemPrompt: result.prompt.content,
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
// 处理错误
|
||||
const errorMessage = error instanceof Error ? error.message : '未知错误';
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user