当前位置: 首页 > news >正文

从零搭建 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

核心技术栈及选型理由

组件选择替代方案选型理由
搜索 APITavily SearchSerper、Bing API返回结构化数据(标题+URL+摘要),LangChain 官方集成,支持图片/视频搜索
LLMDeepSeek V4GPT-4o、Claude 3.5兼容 OpenAI SDK 格式,成本仅为 GPT-4o 的 1/10,中文能力强
后端框架FastAPIDjango、Flask原生异步支持(async/await),自动生成 OpenAPI 文档,性能是 Django 的 3 倍
前端框架Vue 3 + TypeScriptReact、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_ratio

Step 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 是杠杆,不是替身。会用杠杆的人,才能撬得动更大的东西。


http://www.cnnetsun.cn/news/2519914.html

相关文章:

  • 三方物流城市配送仓运配一体化解决方案(基于JeeWMS·模块化可拆分部署版)
  • AI信息筛选操作系统:从过载到可验证的工程实践
  • 并发数据结构设计与无锁编程实践
  • Meta 裁员约 8000 人:弥补 AI 巨额投资,削减人力成本
  • 为什么 Android App 启动会白一下?——一篇讲透 Android SplashScreen 启动机制演进
  • 全域数学·第三部·数术几何部·平行网格卷 完整专著目录(含拓扑发展史+学科定位·终稿)
  • N维平行整数网格论——基于离散组合拓扑与整数位置分析的全新数论体系
  • 不止于Windows:用QtService源码打造跨平台(Windows/Linux)守护进程的实践指南
  • 蓝桥杯嵌入式实战:手把手教你用STM32CubeMX和HAL库封装PWM控制函数(调频调占空比)
  • 保姆级教程:在YOLOv5s.yaml里给YOLOv5 V7.0模型加上SimAM注意力(附代码)
  • 国产多模态大模型 vs DALL-E:本土化突围与全球竞技
  • 从仿真翻车到波形完美:手把手教你用Multisim搞定LM741反相放大电路(含电源/电容配置避坑)
  • STM32F407 PWM呼吸灯实战:从CubeMX配置到代码调试,手把手教你玩转TIM14
  • 手把手教你用8255和12864 LCD搞定微机原理课设:一个公交报站器的完整实现
  • EI、SCI、Scopus傻傻分不清?一文讲透工程领域核心期刊数据库怎么选
  • MATLAB CVX工具箱保姆级安装与第一个凸优化问题实战
  • 从炼丹到炼蛋白:手把手拆解AlphaFold2的模型架构与训练技巧
  • 远程为海外公司工作的真实体验:钱多事少但有时差——一个软件测试工程师的深度拆解
  • NotebookLM支持实时字幕吗?不,它真正强悍的是这4种高阶语音语义重构能力
  • DeepSeek微服务拆分实战:从单体到弹性集群的7步标准化迁移手册(含流量染色+灰度发布Checklist)
  • 植入式网络广告效果影响因素及投放决策优化【附代码】
  • M1 Mac上搞定Tinker热修复:从7zip报错到成功生成补丁的完整踩坑实录
  • 观察不同时段调用 Taotoken 各类模型的延迟表现
  • Keil MDK中第三方软件包兼容性问题解析与解决
  • ngx_http_set_virtual_server
  • 当自动化运维系统被ai重构后
  • 全开源CRM客户关系管理系统源码完整部署指南附代码
  • RK3588下位机程序无响应问题排查
  • 微信小程序 智能停车场预约推荐系统
  • 嵌入式Linux开发:GDB远程调试ARM平台的完整实战指南