diff --git a/docs/plans/2026-01-25-ai-search-system-design.md b/docs/plans/2026-01-25-ai-search-system-design.md new file mode 100644 index 0000000..cee2272 --- /dev/null +++ b/docs/plans/2026-01-25-ai-search-system-design.md @@ -0,0 +1,502 @@ +# AI 智能搜索系统设计文档 + +**日期**: 2026-01-25 +**作者**: Claude Code +**状态**: 设计阶段 + +## 概述 + +为项目列表添加 AI 语义搜索功能,用户可通过自然语言描述需求,系统通过向量相似度匹配返回相关项目,而非传统的关键词模糊搜索。 + +### 核心目标 + +- ✅ 支持自然语言查询(如"帮我找做图像生成的项目") +- ✅ 基于向量相似度的语义匹配 +- ✅ 完全在 n8n 中实现 AI 逻辑,Next.js 应用保持纯净 +- ✅ 利用现有 Neon 数据库的 pgvector 扩展 + +--- + +## 架构设计 + +### 整体架构 + +``` +前端(Next.js) + ↓ +API 代理(/api/search/ai) + ↓ +n8n 工作流(AI 逻辑) + ↓ +Neon 数据库(pgvector) +``` + +### 核心组件 + +**数据层(Neon + pgvector)** +- `Project` 表添加 `embedding` 字段存储向量 +- HNSW 索引加速相似度搜索 +- 向量维度:1536(OpenAI text-embedding-3-small) + +**服务层(n8n 工作流)** +- **向量化工作流**:定时扫描未向量化项目,调用 OpenAI API 生成向量 +- **AI 搜索工作流**:接收查询 → 生成向量 → 相似度搜索 → 返回结果 + +**前端层(Next.js)** +- 搜索框增加 AI 模式切换按钮 +- 显示相似度评分和匹配原因 + +--- + +## 数据库设计 + +### Schema 修改 + +```prisma +model Project { + // ... 现有字段 + + // 新增:向量嵌入字段 + embedding vector(1536)? // pgvector 类型 + embeddingUpdatedAt DateTime? // 向量化更新时间 +} +``` + +### 迁移 SQL + +```sql +-- 启用 pgvector 扩展 +CREATE EXTENSION IF NOT EXISTS vector; + +-- 添加向量列 +ALTER TABLE "Project" ADD COLUMN "embedding" vector(1536); +ALTER TABLE "Project" ADD COLUMN "embeddingUpdatedAt" TIMESTAMP; + +-- 创建 HNSW 索引(余弦距离) +CREATE INDEX idx_project_embedding_cosine +ON "Project" USING hnsw ("embedding" vector_cosine_ops) +WITH (m = 16, ef_construction = 64); + +-- 复合索引(查找未向量化项目) +CREATE INDEX idx_project_embedding_null +ON "Project" ("id") +WHERE "embedding" IS NULL; +``` + +### 向量化内容策略 + +**字段权重分配:** +- 名称(40%):`name` + `nameEn` +- 描述(40%):`description` + `descriptionEn` +- 标签(15%):`tags`(逗号连接) +- 详细内容(5%):`content` + `contentEn`(截取前 500 字) + +**示例输入文本:** +``` +AI Video Generator +一个基于人工智能的视频生成工具,可以自动从文本生成高质量视频... +人工智能, 视频生成, AIGC +详细功能介绍... +``` + +--- + +## n8n 工作流设计 + +### 工作流 1:定时向量化 + +**触发器**:Cron 表达式(每 5 分钟) + +``` +*/5 * * * * +``` + +**流程**: +``` +触发器 + ↓ +查询未向量化项目(LIMIT 20) + ↓ +批量处理(每批 5 个) + ↓ + 构造文本内容(合并字段) + ↓ + 调用 OpenAI Embeddings API + ↓ + 更新数据库 embedding 字段 + ↓ + 等待 1 秒(控制速率) + ↓ +下一批 +``` + +**PostgreSQL 查询**: +```sql +SELECT id, name, "nameEn", description, "descriptionEn", + content, "contentEn" +FROM "Project" +WHERE "embedding" IS NULL + AND "status" = 'ACTIVE' +LIMIT 20 +``` + +**错误处理**: +- API 限流:指数退避重试(1s → 2s → 4s) +- 最多重试 3 次 +- 失败记录日志 + +### 工作流 2:AI 搜索 + +**触发器**:Webhook(`/webhook/ai-search`) + +**流程**: +``` +Webhook 接收查询 + ↓ +接收参数:{ query, locale, limit, filters } + ↓ +调用 OpenAI Embeddings API(生成查询向量) + ↓ +PostgreSQL 向量相似度搜索 + ↓ +应用过滤条件(tags, status) + ↓ +格式化结果(添加相似度评分) + ↓ +返回 JSON 响应 +``` + +**PostgreSQL 查询**: +```sql +SELECT + id, name, "nameEn", slug, description, "descriptionEn", + 1 - (embedding <=> '{{ query_vector }}'::vector) as similarity +FROM "Project" +WHERE "embedding" IS NOT NULL + AND "status" = 'ACTIVE' +ORDER BY embedding <=> '{{ query_vector }}'::vector +LIMIT {{ limit || 20 }} +``` + +**响应格式**: +```json +{ + "results": [ + { + "project": { /* 项目数据 */ }, + "similarity": 0.89, + "matchReason": "项目名称和描述与图像生成高度相关" + } + ], + "total": 42, + "searchTime": 156 +} +``` + +--- + +## Next.js API 设计 + +### 路由配置 + +**文件**:`src/app/api/search/ai/route.ts` + +```typescript +import { NextResponse } from 'next/server' + +const N8N_WEBHOOK_URL = process.env.N8N_AI_SEARCH_WEBHOOK + +export async function POST(request: Request) { + try { + const body = await request.json() + + // 转发到 n8n 工作流 + const n8nResponse = await fetch(N8N_WEBHOOK_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }) + + const results = await n8nResponse.json() + + return NextResponse.json(results) + + } catch (error) { + return NextResponse.json( + { error: 'Search failed' }, + { status: 500 } + ) + } +} +``` + +**请求格式**: +```json +{ + "query": "帮我找做图像生成的AI工具", + "locale": "zh", + "limit": 20, + "filters": { + "tags": ["AIGC"], + "status": "ACTIVE" + } +} +``` + +--- + +## 前端交互设计 + +### UI 组件修改 + +**文件**:`src/components/search/SearchBar.tsx` + +**功能**: +- 添加 AI 模式切换按钮(Sparkles 图标) +- AI 模式时按钮高亮(金色背景) +- 不同模式的占位符提示 +- 加载状态优化 + +**关键代码**: +```tsx + +``` + +### AI 搜索结果展示 + +**文件**:`src/components/search/AISearchResults.tsx` + +**功能**: +- 显示相似度指示条(左侧彩色条) +- 相似度评分(0-100%) +- 匹配原因说明(可选) +- 颜色编码:绿色(>0.8)、黄色(>0.6)、红色(<0.6) + +### 搜索模式对比 + +| 特性 | 传统搜索 | AI 搜索 | +|------|---------|---------| +| 占位符 | "搜索项目名称..." | "描述你想要的项目..." | +| 匹配方式 | 关键词模糊匹配 | 向量语义相似度 | +| 返回速度 | 极快(<100ms) | 较快(1-3s) | +| 结果增强 | 无 | 相似度评分 + 匹配原因 | +| 提示信息 | 无 | "💡 试试:'帮我找能生成视频的 AI 工具'" | + +--- + +## 环境变量配置 + +### Next.js 应用(`.env.local`) + +```bash +# Neon 数据库(已有) +DATABASE_URL=postgres://... + +# n8n Webhook +N8N_AI_SEARCH_WEBHOOK=https://your-n8n-instance.com/webhook/ai-search +N8N_WEBHOOK_API_KEY=your-webhook-key # 可选,用于安全验证 +``` + +### n8n 工作流 + +```bash +# OpenAI API(用于 Embeddings) +OPENAI_API_KEY=sk-... + +# Neon 数据库(与 Next.js 共享) +NEON_DATABASE_URL=postgres://... +``` + +--- + +## 错误处理与优化 + +### 错误处理策略 + +**n8n 工作流**: +- API 限流:指数退避重试 +- API Key 无效:发送告警 +- 数据库查询失败:返回友好错误信息 +- 向量未就绪:提示用户稍后重试 + +**前端**: +- 10 秒超时限制 +- 超时或错误时自动降级到传统搜索 +- Toast 消息提示用户 + +### 性能优化 + +**数据库查询**: +- 只返回必要字段(不返回 content) +- 使用 HNSW 索引 +- 设置查询超时(5s) + +**缓存策略(可选)**: +- Redis 缓存常见查询结果(5 分钟 TTL) +- 内存缓存热门查询 + +### 监控指标 + +- 平均搜索响应时间 +- API 调用次数/成本 +- 向量化完成率 +- 错误率 + +--- + +## 成本估算 + +### OpenAI Embeddings API + +**定价**: +- text-embedding-3-small:$0.00002 / 1K tokens + +**估算**: +- 单个项目(500 tokens):$0.00001 +- 1000 个项目:$0.01 +- 单次搜索(10 tokens):$0.0000002 +- 1000 次搜索:$0.0002 + +**月度预算**:$5 可处理 50 万个项目或 2500 万次搜索 + +### Neon 免费套餐 + +**限制**: +- 存储:0.5GB +- 计算:300 小时/月 + +**向量存储**: +- 单个项目(1536 维):3KB(半精度) +- 1000 个项目:3MB +- 10,000 个项目:30MB ✅ + +**结论**:免费套餐完全够用(可支持 5,000-10,000 个项目) + +--- + +## 测试计划 + +### 单元测试 + +```typescript +// src/__tests__/search.test.ts +describe('AI Search', () => { + it('should handle empty query', async () => { + const response = await fetch('/api/search/ai', { + method: 'POST', + body: JSON.stringify({ query: '' }) + }) + expect(response.status).toBe(400) + }) + + it('should fallback to traditional search on error', async () => { + // 测试错误降级逻辑 + }) +}) +``` + +### E2E 测试 + +```typescript +// tests/e2e/ai-search.spec.ts +test('AI 搜索功能', async ({ page }) => { + await page.goto('/zh/projects') + await page.click('[data-testid="ai-mode-toggle"]') + await page.fill('input[name="search"]', '视频生成工具') + await page.press('input[name="search"]', 'Enter') + await expect(page.locator('.ai-search-results')).toBeVisible() +}) +``` + +### 集成测试 + +- 测试 n8n 工作流端到端 +- 验证向量搜索结果准确性 +- 测试错误场景(API 限流、数据库连接失败) + +--- + +## 部署清单 + +### 数据库准备 + +- [x] 运行数据库迁移 +- [x] 验证 pgvector 扩展已启用 +- [x] 检查 HNSW 索引创建成功 + +### n8n 配置 + +- [ ] 创建向量化工作流 + - [ ] 配置 Cron 触发器(每 5 分钟) + - [ ] 配置 PostgreSQL 节点 + - [ ] 配置 OpenAI Embeddings 节点 + - [ ] 添加错误处理和重试逻辑 + +- [ ] 创建 AI 搜索工作流 + - [ ] 配置 Webhook 触发器 + - [ ] 配置 OpenAI Embeddings 节点 + - [ ] 配置 PostgreSQL 向量查询 + - [ ] 配置结果格式化 + +- [ ] 测试工作流 + - [ ] 手动触发向量化流程 + - [ ] 测试 Webhook 搜索 + - [ ] 验证错误处理 + +### Next.js 部署 + +- [ ] 添加环境变量(N8N_WEBHOOK_URL) +- [ ] 创建 API 路由(`/api/search/ai`) +- [ ] 更新 SearchBar 组件 +- [ ] 创建 AISearchResults 组件 +- [ ] 本地测试完整流程 +- [ ] 部署到 Vercel + +### 验证步骤 + +1. **向量化测试**: + ```sql + -- 检查已向量化项目数 + SELECT COUNT(*) FROM "Project" WHERE embedding IS NOT NULL; + ``` + +2. **搜索测试**: + - 输入自然语言查询 + - 验证返回结果相关性 + - 检查相似度评分 + +3. **性能测试**: + - 测量平均响应时间 + - 验证并发处理能力 + +--- + +## 后续优化方向 + +1. **混合搜索**:结合关键词搜索和向量搜索,提升准确率 +2. **查询缓存**:Redis 缓存热门查询结果 +3. **A/B 测试**:对比传统搜索和 AI 搜索的用户体验 +4. **多模态搜索**:支持图片、语音输入 +5. **个性化排序**:基于用户历史行为优化结果 +6. **自动标签建议**:AI 分析项目内容推荐标签 + +--- + +## 参考资料 + +- [Neon pgvector 文档](https://neon.com/docs/extensions/pgvector) +- [OpenAI Embeddings API](https://platform.openai.com/docs/guides/embeddings) +- [pgvector GitHub](https://github.com/pgvector/pgvector) +- [HNSW 算法论文](https://arxiv.org/abs/1603.09320)