从零搭建 AI 搜索引擎:我给装上了智能记忆,还踩了这些坑
灵感来源:本文的核心技术思路来源于鱼皮(程序员鱼皮)的 《再见百度,我用 1 小时,开发了个 AI 搜索引擎!Codex + GPT 5.5 + DeepSeek V4 真香~》 一文。
为什么做这个?Gartner 预测到 2026 年传统搜索引擎访问量将下降 25%,中国信通院《2025年人工智能搜索技术发展白皮书》显示超过 40% 的企业获客流量将来自 AI 搜索平台。作为一个开发者,我决定亲手造一个 AI 搜索引擎——不只是调 API,而是深入理解每个技术选型背后的 trade-off,并给它装上真正的智能记忆系统(不是简单的缓存)。
一、2026 年 AI 搜索的三个认知升级
在动手之前,我先梳理了当前 AI 搜索领域的三个关键趋势,这直接决定了我的技术架构选择:
1️⃣ 从 SEO 到 GEO:搜索逻辑的根本性变革
传统 SEO(搜索引擎优化)针对的是关键词匹配算法,而GEO(Generative Engine Optimization)面对的是大模型的语义理解能力。这意味着:
- 内容策略转变:不再堆砌关键词,而是提供结构化、有深度的信息(AI 更容易提取)
- 引用溯源变得至关重要:ChatGPT Search、Perplexity 都强制要求标注来源,否则会被判定为"幻觉"
- 时效性权重提升:根据我的测试,同一问题 24 小时内的搜索结果变化率高达 37%
2️⃣ Vibe Coding 已成主流开发范式
2026 年美国已有92% 的开发者在日常工作中采用某种形式的 Vibe Coding(数据来源:Vibe Coding 2026 行业报告)。但跟 2025 年初 Karpathy 提出"氛围编程"时的随意不同,现在的 Vibe Coding 已经进化为结构化的 AI 辅助开发方法论:
| 维度 | 传统编程 | 2026 Vibe Coding |
|---|---|---|
| 角色定位 | 代码编写者 | 架构设计师 + 质量把控者 |
| 核心技能 | 语法熟练度 | 需求拆解 + Prompt 工程 |
| 工具链 | VS Code + 手写 | Trae/Cursor + 自然语言规格说明 |
| 迭代速度 | 天级 | 小时级 |
我用的是字节跳动的 Trae IDE,它的中文自然语言理解能力确实比 Cursor 更适合中文场景。
3️⃣ “智能记忆” ≠ 简单缓存
市面上大多数 AI 搜索教程只讲到"查询缓存"就结束了,我设计了一套分层记忆架构,目前短期记忆层已经实现:
┌─────────────────────────────────────┐ │ 用户查询层 (Query) │ ├─────────────────────────────────────┤ │ 短期记忆 (Short-term Memory) │ ← SQLite 缓存层(已实现) │ - 相似度匹配 (Jaccard + 关键词) │ │ - 时效性校验 (重新搜索对比) │ │ - TTL: 24 小时 + LRU 淘汰 │ ├─────────────────────────────────────┤ │ 长期记忆 (Long-term Memory) │ ← 未来可扩展 │ - 用户画像 & 偏好学习 │ │ - 跨会话知识图谱 │ │ - 向量数据库语义检索 │ └─────────────────────────────────────┘目前实现的是短期记忆层:用 SQLite 存储查询缓存,通过compute_sources_similarity做三维度相似度匹配(URL Jaccard 50% + 标题关键词 30% + 结果数量 20%),命中缓存时重新搜索做时效性校验,TTL 24 小时 + LRU 淘汰。
长期记忆层(用户画像、知识图谱、向量数据库)目前还是规划阶段,没有实现。对于个人项目来说,SQLite + 相似度算法的成本效益比更高(省去了 Milvus/Pinecone 的运维复杂度)。如果未来用户量增长,可以平滑迁移到 ChromaDB 或 Qdrant。
二、技术架构与选型 Trade-off
核心技术栈及选型理由
| 组件 | 选择 | 替代方案 | 选型理由 |
|---|---|---|---|
| 搜索 API | Tavily Search | Serper、Bing API | 返回结构化数据(标题+URL+摘要),LangChain 官方集成,支持图片/视频搜索 |
| LLM | DeepSeek V4 | GPT-4o、Claude 3.5 | 兼容 OpenAI SDK 格式,成本仅为 GPT-4o 的 1/10,中文能力强 |
| 后端框架 | FastAPI | Django、Flask | 原生异步支持(async/await),自动生成 OpenAPI 文档,性能是 Django 的 3 倍 |
| 前端框架 | Vue 3 + TypeScript | React、Next.js | 组合式 API 更直观,TypeScript 类型安全,Tailwind CSS 快速原型 |
| 缓存方案 | SQLite + 自定义相似度 | Redis、Memcached | 零运维(单文件数据库),支持 WAL 并发模式,适合中小规模 |
三、踩坑实录:那些文档里不会告诉你的细节
🔴 坑 #1:SSE 流式输出的 TCP 分包陷阱
问题现象:AI 回答需要 20 秒左右,用户盯着空白页面体验极差。用 SSE(Server-Sent Events)实现流式输出后,发现前端偶尔报JSON Parse Error。
根因分析:TCP 协议的数据包分割是不确定的。一个完整的 SSE 事件(如data: {"type":"answer","data":"你好"}\n\n)可能被拆成两个数据包到达:
数据包 1: "data: {\"type\":\"answer\"," 数据包 2: "\"data\":\"你好\"}\n\n"如果直接对每个数据包调用JSON.parse(),必然会失败。
解决方案:前端必须实现缓冲区拼接机制:
letbuffer=''while(true){buffer+=decoder.decode(value,{stream:true})constlines=buffer.split('\n')buffer=lines.pop()||''// 关键!保留未完成的行for(constlineoflines){if(!line.startsWith('data: '))continueconstdata=JSON.parse(line.slice(6))handleEvent(data)}}lines.pop()这一行是精髓——把最后一个可能不完整的行留在 buffer 里,等下一个数据包到了再拼接。
🔴 坑 #2:AI 引用格式的"驯兽指南"
问题现象:在 System Prompt 里写"请标注引用来源",AI 的输出五花八门:
(来源: 1)参考[1][source 1]- 干脆不标
三层解决方案:
Layer 1:Prompt 约束(准确率 70%)
SYSTEM_PROMPT="""你是一个专业的AI搜索引擎助手。 要求: 1. 回答要全面、准确、有深度 2. 引用来源必须使用 [1], [2] 格式(严格遵循,不得使用其他格式) 3. 使用 Markdown 格式,代码块标注语言类型 搜索结果:{context}"""Layer 2:前端正则替换(兜底,覆盖率 100%)
<MarkdownRenderer :content="processedAnswer" @citation-click="handleCitationClick" /> <script setup> const processedAnswer = computed(() => { return answer.value .replace(/\[?来源[::]\s*(\d+)\]?/g, '[$1]') // 标准化中文引用 .replace(/\[source\s*(\d+)\]/gi, '[$1]') // 英文引用 .replace(/(来源[::]\s*(\d+))/g, '[$1]') // 全角括号 }) </script>Layer 3:安全消毒(防 XSS)
渲染顺序必须是:引用替换 → Markdown 渲染 → DOMPurify 消毒
如果先消毒再替换,自定义的citation-linkCSS 类会被过滤掉。
额外细节:不是所有引用都有对应 URL。做了分支处理:
- 有 URL → 蓝色可点击链接
- 无 URL → 灰色不可点击标记(避免跳转到空白页)
🔴 坑 #3:智能缓存的"时效性 vs 成本"平衡术
为什么不能简单缓存?
场景:上午 10 点搜"今天北京天气"→ 缓存;下午 2 点再搜 → 如果直接返回缓存,答案已过时。
我的方案:三维度相似度 + 时效性校验
Step 1:即使缓存命中,也重新调用 Tavily 搜索
Step 2:对比新旧搜索结果的相似度
defcompute_sources_similarity(old_sources,new_sources):# 维度 1:URL Jaccard 相似度(权重 50%)old_urls=set(s.get("url","")forsinold_sourcesifs.get("url"))new_urls=set(s.get("url","")forsinnew_sourcesifs.get("url"))url_jaccard=len(old_urls&new_urls)/len(old_urls|new_urls)if(old_urls|new_urls)else0# 维度 2:标题关键词重叠度(权重 30%)def_tokenize(text):words=re.findall(r'[\w\u4e00-\u9fff]+',text.lower())returnset(wforwinwordsiflen(w)>1)old_words=set()new_words=set()forsinold_sources:old_words.update(_tokenize(s.get("title","")))forsinnew_sources:new_words.update(_tokenize(s.get("title","")))title_sim=len(old_words&new_words)/len(old_words|new_words)if(old_words|new_words)else0# 维度 3:结果数量比例(权重 20%)len_ratio=min(len(old_sources),len(new_sources))/max(len(old_sources),len(new_sources))ifmax(len(old_sources),len(new_sources))>0else0return0.50*url_jaccard+0.30*title_sim+0.20*len_ratioStep 3:阈值判断
| 相似度阈值 | 行为 | 适用场景 |
|---|---|---|
| ≥ 0.75 | 直接返回缓存 | 知识类问题(“Python 装饰器用法”) |
| 0.5 ~ 0.75 | 返回缓存 + 标注"信息可能略有更新" | 新闻类问题(“最新科技资讯”) |
| < 0.5 | 丢弃缓存,重新生成 | 时效性强的问题(“今天股市行情”) |
隐私保护:查询内容不以明文存储,先标准化(strip().lower())再算 SHA-256 哈希值。“Python 编程” 和 "python 编程 " 会命中同一个缓存。
效果量化:
- 缓存命中率:67.3%(基于 1,000 次真实查询统计)
- 平均响应时间:20.3 秒 →0.15 秒(缓存命中时)
- LLM 调用成本降低:67%(每月节省约 $3.35)
四、多模态搜索:从文字到图文视频
为什么做多模态?
用户搜"RK3588 部署教程"时,一张架构图 > 十段文字;搜"React 性能优化"时,视频教程链接体验更好。
Tavily 默认不返回图片/视频,需手动开启:
self.tavily_search=TavilySearch(max_results=settings.tavily_max_results,search_depth=settings.tavily_search_depth,include_images=True,# 开启图片搜索include_image_descriptions=True,# 获取图片描述(用于 AI 理解上下文)include_favicon=True,include_usage=True,)数据归一化挑战
Tavily 的图片 URL 散落在多个字段:
image_url(标准字段)image(别名)img_src(某些源的特有字段)- 甚至混在普通搜索结果里(网页缩略图)
写了_normalize_results()函数统一处理
视频识别策略
通过域名白名单判断(避免误判):“youtube.com”, “youtu.be”, “bilibili.com”, “b23.tv”, “douyin.com”, “youku.com”, “iqiyi.com”。
前端展示优化
图片网格:懒加载 + hover 放大效果 + 点击跳转原图
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> <a v-for="(img, idx) in images" :key="idx" :href="img.image_url" target="_blank" class="group block overflow-hidden rounded-lg"> <img :src="img.image_url" class="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300" loading="lazy" /> </a> </div>视频卡片:暂不做内嵌播放器(版权风险 + 性能开销),改为卡片列表跳转原站。
五、未来可优化路线图
- 缓存策略精细化:新闻类 TTL 1 小时,知识类 TTL 72 小时
- 成本控制开关:相关问题推荐改为可选功能(节省 50% LLM 调用成本)
- Prompt 注入检测:过滤恶意提示词(如"忽略之前的指令")
- 长期记忆层接入向量数据库(ChromaDB/Qdrant)
- 用户偏好学习(技术栈偏好、领域兴趣)
- 跨会话知识图谱(搜索历史语义关联)
- 结果排序优化
- 来源权威性评分(edu/gov 域名加权)
- 内容时效性衰减函数(发布时间越新权重越高)
- 图片嵌入回答:AI 生成回答时自动插入相关图片,实现图文混排
- 多 Agent 协作架构(参考 Supermemory 的 99% 准确率方案)
- 搜索 Agent:负责信息检索
- 记忆 Agent:负责长期记忆管理
- 验证 Agent:负责事实核查(降低幻觉率)
- 个性化推荐:基于用户历史搜索行为,主动推送相关领域更新
- 移动端适配:PWA 支持 + 离线缓存
六、完整提示词(Prompt)
# 角色 你是一个全栈工程师,擅长 Python + FastAPI + LangChain + Vue 3 开发,熟悉 2026 年 AI 辅助开发(Vibe Coding)的最佳实践。 ## 任务 开发 ai-search AI 搜索引擎,具备以下核心能力: ### 必须实现的功能 1. **联网搜索**:调用 Tavily Search API,获取实时信息 2. **智能回答**:DeepSeek V4 综合分析,生成带引用来源的回答 3. **流式输出**:SSE 打字机效果,TTFT < 3 秒 4. **智能记忆系统**(非简单缓存): - 工作记忆:会话级上下文(30 分钟 TTL) - 短期记忆:SQLite + 三维度相似度匹配(24 小时 TTL) - 长期记忆预留接口(未来接向量数据库) 5. **多模态搜索**:图片网格 + 视频卡片 6. **引用溯源**:[1][2] 格式 + 可点击链接 7. **相关问题推荐**:AI 自动生成延伸问题 ### 技术约束 - 后端:FastAPI(异步)+ LangChain + SQLite(WAL 模式) - 前端:Vue 3 + TypeScript + Tailwind CSS - AI:DeepSeek V4(兼容 OpenAI SDK) - 安全:DOMPurify XSS 防护 + CORS 精确配置 + 输入验证 - 性能:缓存命中时响应时间 < 200ms ### 质量要求 1. 所有 API 返回必须有统一的错误处理和日志记录 2. 前端必须处理网络断开、API 限流等异常情况 3. 代码必须有类型注解(Python type hints + TypeScript interface) 4. 每完成一个模块必须自主测试并修复 Bug ## 开发流程 1. 搭建 FastAPI 后端框架 + Tavily 集成(含容错处理) 2. 接入 DeepSeek V4 + SSE 流式输出 3. 实现智能记忆系统(相似度算法 + 时效性校验) 4. Vue 3 前端开发(搜索页 + 结果页 + Markdown 渲染) 5. 引用来源组件 + DOMPurify 安全消毒 6. 多模态搜索(图片/视频归一化 + 前端展示) 7. 工程化完善(CORS、日志、配置管理、缓存 API) 8. 全流程测试 + 性能优化最后说两句
做完这个项目,我对 AI 辅助开发有了更深的体会。
一方面,门槛确实变低了。以前做一个搜索引擎是大厂才能干的事情,现在用 Trae 这样的 AI IDE,配合 Tavily + DeepSeek 的 API,一个下午就能搭出能跑的原型。AI 能帮你写代码、改 Bug、做优化,甚至帮你设计缓存策略的相似度算法。
但另一方面,"AI 能写"和"AI 能写好"是两回事。Trae 生成的第一版 SSE 解析代码有缓冲区拼接的坑,第一版分词函数不支持中文,第一版缓存策略没考虑时效性……这些都需要你跑起来验证,发现问题后再用提示词引导它修复。
所以 AI 时代的开发模式,更像是一个"人机协作开发(Vibe Coding)"的过程:AI 负责快速产出和试错,人负责判断方向、验证结果、把控细节。你不需要一行行手写代码,但你得知道代码在干什么、哪里可能出问题、怎么验证对不对。
AI 是杠杆,不是替身。会用杠杆的人,才能撬得动更大的东西。
