两个娃的奶爸深夜折腾FastGPT,用v0搞了个AI应用还顺手做了性能优化

♀谢庆华
2026-07-26 06:03
阅读 677

凌晨两点半,大宝刚退烧睡着,二宝还在哼唧。我轻手轻脚关上儿童房的门,溜回书房,灌了半罐冰可乐,打开电脑。白天在公司被需求追着跑,晚上才是真正属于自己的时间。最近边准备跳槽边刷算法题,顺便还在啃Rust——那所有权机制简直比哄两个孩子睡觉还让人头秃。不过今天不聊Rust,聊聊最近搞技术探索的一些实践心得,顺便分享一下我用FastGPT和v0搭AI应用时做性能优化的踩坑记录。

为啥要折腾这些

说来话长。去年底公司搞了个内部创新项目,领导说要做个"AI赋能"的内部工具,说白了就是蹭AI热度。产品经理一拍脑袋:"我们要做一个智能知识库问答系统,员工可以直接问公司制度、技术文档,AI来回答!"

我一听,这不就是RAG(检索增强生成)嘛。当时市面上开源方案不少,LangChain、Dify、FastGPT都能搞。我花了两个周末调研了一圈,最后选了FastGPT。为啥?一是它开箱即用,部署简单;二是它的知识库管理做得比较直观,对非技术人员也友好;三是社区活跃度还行,遇到问题能搜到答案。

但说实话,刚开始用的时候体验并不好。第一次打开FastGPT的后台,加载要等将近十秒,知识库列表页更是慢得让人想砸键盘。我心里嘀咕:这玩意儿要是给全公司用,第一天就得被投诉到IT部门去。

于是,性能优化这事儿就这么被提上了日程。

第一刀:FastGPT的前端加载优化

先说FastGPT本身。它前端用的Next.js,整体架构没毛病,但默认配置下确实有不少可以抠的地方。

1. 首屏加载慢的元凶

我用Chrome DevTools的Performance面板跑了一下Lighthouse,首屏FCP(First Contentful Paint)居然要4.2秒,LCP(Largest Contentful Paint)更是飙到了6.8秒。这数据放出去,前端同事看了怕是要笑出声。

排查下来,问题主要出在三个地方:

  • JavaScript Bundle太大:主bundle超过800KB,还没算各种chunk
  • 字体文件没做子集化:一个中文字体文件直接干到8MB
  • API请求串行执行:首页加载时,知识库列表、用户信息、系统配置三个接口是串行发的

2. 动手优化

字体问题最好解决。FastGPT默认引入了一个完整的中文字体,我用fonttools做了子集化,只保留常用汉字和ASCII字符,体积直接从8MB干到了120KB。

# 用pyftsubset做字体子集化
pyftsubset SourceHanSansSC-Regular.otf \
  --text-file=common-chars.txt \
  --output-file=SourceHanSansSC-subset.woff2 \
  --flavor=woff2

common-chars.txt里我放了GB2312常用汉字加上一些特殊符号,基本覆盖日常使用场景。

JS Bundle的问题,我调整了Next.js的配置,开启了modularizeImports,把一些大依赖按需加载:

// next.config.js
const nextConfig = {
  modularizeImports: {
    'lodash': {
      transform: 'lodash/{{member}}',
    },
    '@chakra-ui/react': {
      transform: '@chakra-ui/react/dist/esm/components/{{member}}',
    },
  },
  experimental: {
    optimizePackageImports: ['lucide-react', 'dayjs'],
  },
}

这一波操作下来,主bundle从800KB降到了340KB,首屏FCP直接干到了1.8秒。

API串行请求的问题,我改了前端的请求逻辑,用Promise.all把三个接口并行发出去:

// 优化前:串行请求
async function loadHomePage() {
  const user = await fetchUserInfo();
  const kbList = await fetchKBList();
  const config = await fetchSystemConfig();
  return { user, kbList, config };
}

// 优化后:并行请求
async function loadHomePage() {
  const [user, kbList, config] = await Promise.all([
    fetchUserInfo(),
    fetchKBList(),
    fetchSystemConfig(),
  ]);
  return { user, kbList, config };
}

就这么一个改动,首页数据加载时间从2.1秒降到了0.8秒。有时候性能优化就是这么朴实无华,不需要什么高大上的技术方案,把基本的东西做对就行。

第二刀:知识库检索的性能瓶颈

前端优化完,我又把目光投向了后端。FastGPT的知识库检索用的是向量数据库,默认配置下用的是Milvus。我们数据量不大,也就几万条文档,但检索响应时间居然要1.5秒以上。

1. 向量检索慢在哪

我加了几个耗时埋点,发现主要慢在两个环节:

环节 优化前耗时 优化后耗时
文本向量化(Embedding) 820ms 210ms
向量检索 + 排序 480ms 180ms
LLM生成回答 2100ms 2100ms(没动)

Embedding环节慢,是因为每次请求都实时调用OpenAI的API做向量化。这玩意儿网络延迟加上模型推理,800ms都算快的了。

我的优化思路是加一层缓存。相同或相似的文本不需要重复计算向量。我用Redis做了一个简单的缓存层:

import { createHash } from 'crypto';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

async function getEmbeddingWithCache(text: string): Promise<number[]> {
  // 用文本hash作为缓存key
  const textHash = createHash('md5').update(text).digest('hex');
  const cacheKey = `embedding:${textHash}`;
  
  // 先查缓存
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }
  
  // 缓存没命中,调用API
  const embedding = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: text,
  });
  
  const vector = embedding.data[0].embedding;
  
  // 写入缓存,过期时间7天
  await redis.setex(cacheKey, 7 * 24 * 3600, JSON.stringify(vector));
  
  return vector;
}

这个改动上线后,重复问题的Embedding环节直接从820ms降到了5ms(Redis读取耗时)。即使是新问题,因为很多时候用户问的内容跟历史问题有重合,缓存命中率也能到40%左右。

向量检索那块,我把Milvus的索引类型从默认的IVF_FLAT换成了HNSW,同时调整了ef参数:

# Milvus collection配置优化
collection_config:
  index_type: HNSW
  metric_type: COSINE
  params:
    M: 16
    efConstruction: 200
    ef: 128  # 检索时的ef参数,越大越精确但越慢

HNSW在中小数据量下的表现比IVF_FLAT好不少,检索时间从480ms降到了180ms,而且召回率基本没掉。

第三刀:用v0快速搭建AI应用前端

说到这儿,得提一嘴v0。这玩意儿是Vercel出的AI生成UI的工具,最近火得不行。我一开始是抱着玩玩的心态用的,没想到真香。

1. v0是什么

简单说,你用自然语言描述你想要的界面,v0就能给你生成React组件代码,基于shadcn/ui和Tailwind CSS。生成的代码质量还不错,至少比我自己从零写要好看。

2. 我拿v0干了啥

我给FastGPT做了一个自定义的对话界面。原生的对话界面功能是全,但样式嘛……只能说"能用"。我想做一个更现代、更适合内部使用的对话界面,但又不想花太多时间在前端样式上——毕竟我晚上时间宝贵,哄完两个孩子睡觉已经九点半了,能用来写代码的时间就那么两三个小时。

用v0,我大概花了四十分钟就搞出了一个像样的对话界面。prompt大概是这么写的:

Design a modern chat interface for an AI knowledge base assistant. 
Features needed:
- Message bubbles with markdown rendering support
- Code syntax highlighting in messages
- A sidebar showing conversation history
- A reference panel showing source documents
- Dark mode support
- Responsive layout for mobile
Style: clean, minimal, similar to ChatGPT but with a blue accent color

v0生成的代码我稍微改改就能用。最让我惊喜的是它对shadcn/ui组件的运用很熟练,生成的代码结构清晰,不是那种一团乱麻的屎山。

3. v0生成代码的性能优化

不过v0生成的代码也不是完美的,有些地方还是得手动优化。比如它生成的消息列表,用的是普通的map渲染,消息多了之后滚动会卡顿。我给它加上了虚拟滚动:

import { Virtuoso } from 'react-virtuoso';

function MessageList({ messages }: { messages: Message[] }) {
  return (
    <Virtuoso
      data={messages}
      followOutput="smooth"
      itemContent={(index, message) => (
        <MessageBubble
          key={message.id}
          message={message}
          isStreaming={index === messages.length - 1 && message.streaming}
        />
      )}
      components={{
        // 自定义滚动条样式
        Scrollbar: (props) => (
          <div {...props} className="w-1.5 bg-gray-200 dark:bg-gray-700 rounded-full" />
        ),
      }}
    />
  );
}

还有一个坑:v0生成的Markdown渲染组件,每次消息更新都会重新解析整个Markdown文本。对于流式输出的场景,这会导致严重的性能问题——每收到一个token就要重新渲染一次,CPU直接起飞。

我的解决方案是做了一个增量渲染的优化:

import { useMemo, useRef } from 'react';
import ReactMarkdown from 'react-markdown';

function StreamingMarkdown({ content, isStreaming }: { content: string; isStreaming: boolean }) {
  const lastRenderedLength = useRef(0);
  
  // 流式输出时,只对新增部分做高亮处理
  // 已渲染的部分不再重复解析
  const { stablePart, streamingPart } = useMemo(() => {
    if (!isStreaming) {
      return { stablePart: content, streamingPart: '' };
    }
    
    // 找到最后一个完整的段落/代码块
    const lastBreakPoint = findLastCompleteBlock(content);
    return {
      stablePart: content.slice(0, lastBreakPoint),
      streamingPart: content.slice(lastBreakPoint),
    };
  }, [content, isStreaming]);

  return (
    <div className="markdown-body">
      <ReactMarkdown>{stablePart}</ReactMarkdown>
      {streamingPart && (
        <span className="streaming-cursor">
          <ReactMarkdown>{streamingPart}</ReactMarkdown>
        </span>
      )}
    </div>
  );
}

function findLastCompleteBlock(text: string): number {
  // 找最后一个完整的代码块或段落
  const lastCodeBlockEnd = text.lastIndexOf('```');
  const lastParagraphBreak = text.lastIndexOf('\n\n');
  
  if (lastCodeBlockEnd > lastParagraphBreak) {
    // 如果代码块没闭合,回到代码块之前
    const lastCodeBlockStart = text.lastIndexOf('```', lastCodeBlockEnd - 1);
    if (lastCodeBlockStart !== -1 && text.slice(lastCodeBlockEnd).split('```').length % 2 === 0) {
      return lastCodeBlockStart;
    }
    return lastCodeBlockEnd + 3;
  }
  
  return lastParagraphBreak > 0 ? lastParagraphBreak : text.length;
}

这个优化让流式渲染时的CPU占用从35%降到了8%左右,滚动也丝滑多了。

综合效果与数据说话

折腾了大概两周(都是利用晚上和周末的时间,白天还得正常搬砖),整体效果还是不错的。我把优化前后的关键指标做了个对比:

指标 优化前 优化后 提升幅度
首屏FCP 4.2s 1.8s 57%
首屏LCP 6.8s 2.9s 57%
知识库检索响应 1.5s 0.4s 73%
对话首字响应 2.8s 1.1s 61%
流式渲染CPU占用 35% 8% 77%
Lighthouse总分 42 89 112%

这些数据汇报给领导的时候,他还是很满意的。虽然他觉得"AI嘛,快一点慢一点用户也感知不到",但我觉得做技术的人还是要有追求的。你说是不是?

一些碎碎念

写到这里,已经凌晨四点了。二宝刚才又醒了一次,老婆起来哄的,我趁这会儿赶紧把文章收尾。

说实话,做技术探索这事儿,在公司里有时候挺尴尬的。你花时间去优化性能,业务价值短期内看不出来,OKR上也不好写。但不做吧,自己心里又过不去那道坎——明明可以更快,为什么要让用户等?

我媳妇总说我,"两个孩子你都不管,天天晚上对着电脑,到底在搞什么名堂。"我能怎么说呢,说我在给公司的AI应用做性能优化?她只会回我一句:"优化完了能涨工资吗?"

嗯……不能。但最近在准备跳槽,这些实战经验写进简历里,面试的时候能聊,也算是一种积累吧。而且说实话,折腾FastGPT和v0的过程中,确实学到了不少东西。比如向量数据库的索引策略、Next.js的性能优化技巧、流式渲染的处理方式,这些都是实打实的干货。

另外最近还在啃Rust,越看越觉得这语言有意思。所有权、生命周期、模式匹配……每一块都让人头疼,但又让人欲罢不能。等把FastGPT这边稳定下来,我打算用Rust重写一下知识库检索的核心模块,看看能不能再榨出点性能来。当然,这可能又是一个大坑,但程序员嘛,不就是在不断踩坑和填坑中成长起来的吗。

对了,如果你也在搞AI应用开发,有几个小建议分享:

  1. 别迷信开箱即用。FastGPT、Dify这些工具确实方便,但默认配置往往不是最优的,一定要根据自己的场景做调优
  2. 缓存是性能优化的万金油。能缓存的地方尽量缓存,但要注意缓存一致性和过期策略
  3. 前端体验很重要。AI应用的响应速度直接影响用户感知,流式输出、骨架屏、虚拟滚动这些手段该用就用
  4. 善用AI工具提效。v0这类工具可以帮你快速搭建原型,把省下来的时间花在核心逻辑上
  5. 数据说话。优化完了一定要有对比数据,不然你说了半天,别人也不知道你到底做了啥

好了,不扯了,天都快亮了。趁两个小祖宗还没醒,我得赶紧去洗个澡换身衣服,八点还得打卡上班呢。中年程序员的日常就是这样,白天给公司打工,晚上给自己"打工",痛并快乐着。

如果你也有类似的技术探索经历,欢迎在评论区交流。说不定咱们还能聊聊Rust——虽然我现在还在跟借用检查器斗智斗勇,编译报错多到怀疑人生。

就这样吧,晚安(或者说早安?)。


本文提到的所有代码均经过脱敏处理,如有雷同纯属巧合。FastGPT和v0都是优秀的开源/AI产品,文中提到的优化思路仅供参考,具体方案请根据自身场景调整。

评论 0

最热最新
暂无评论
♀谢庆华Lv.1
0
影响力
0
文章
0
粉丝