1. 项目概述:构建一个第三方的GPTs应用商店

最近在折腾GPTs,发现OpenAI官方的GPT Store虽然好用,但总感觉少了点什么。比如,搜索功能有时候不够精准,想找一些特定领域的GPTs得翻半天;再比如,浏览体验比较固定,没法根据自己的习惯来定制。于是,我就琢磨着,能不能自己动手,搭建一个更灵活、功能更强的第三方GPTs商店?这就是“GPTs Works”这个项目的由来。

简单来说,GPTs Works是一个集成了网站、智能索引系统和浏览器插件的完整解决方案。它的核心目标,就是帮你更好地发现、管理和使用那些由社区创建的、五花八门的GPTs应用。无论你是想找一个能帮你写代码的编程助手,还是一个能陪你聊天的创意伙伴,都可以在这里更高效地找到。这个项目特别适合两类朋友:一类是普通的GPTs重度用户,想提升使用效率;另一类是对AI应用开发、向量数据库或者全栈开发感兴趣的技术爱好者,可以把它当作一个不错的学习和实战案例。

2. 核心架构与设计思路拆解

2.1 为什么选择“三位一体”的架构?

在项目启动前,我仔细评估了用户的核心痛点。官方商店的不足主要体现在 发现效率低 使用场景割裂 。用户往往需要记住某个GPTs的准确名称才能找到,或者只能在ChatGPT的固定页面里浏览。因此,我决定采用一个“三位一体”的架构来系统性地解决这些问题:

  1. 网站(Web) :作为主门户和展示中心。它需要提供美观、响应式的界面,支持分类浏览、基础搜索和详情展示,是大多数用户的第一接触点。我选择Next.js框架,是因为它基于React,能提供极佳的开发体验和性能,同时其服务端渲染(SSR)能力对SEO友好,便于项目被更多人发现。
  2. 索引系统(Index System) :作为整个项目的“智能大脑”。这是提升发现效率的关键。传统的关键词搜索在理解用户模糊、语义化的查询时(比如“帮我做PPT的助手”)力不从心。因此,我引入了**向量搜索(Vector Search)**技术。简单来说,就是把每个GPTs的名称、描述等信息,通过AI模型转换成一组数学向量(这个过程叫“嵌入”),这些向量包含了语义信息。当用户用自然语言提问时,系统将问题也转换成向量,然后在向量空间中寻找最“接近”的GPTs。这比单纯匹配关键词要精准得多。
  3. 浏览器扩展(Extension) :作为提升体验的“场景延伸器”。它的目的是打破使用场景的壁垒。想象一下,当你在ChatGPT官网的探索页面浏览时,旁边能实时看到来自GPTs Works的推荐或搜索结果,是不是很方便?这个插件能将我们的服务无缝嵌入到用户最常使用GPTs的环境中去。

这个架构的优势在于 解耦 专注 。三个部分可以独立开发、部署和迭代。网站负责展示和交互,索引系统专注数据处理和智能检索,插件解决特定场景的集成问题。技术栈的选择也遵循了“用合适的工具做合适的事”的原则。

2.2 技术栈选型背后的考量

选型不是追新,而是权衡需求、团队技能和生态后的结果。

  • 前端与全栈框架:Next.js (React) 。对于需要服务端渲染、API路由和快速部署的现代Web应用,Next.js几乎是目前最成熟、生态最丰富的选择。Vercel平台对其的原生支持,让部署变得像“git push”一样简单。
  • 后端索引服务:FastAPI (Python) 。索引系统涉及大量的文本处理、AI模型调用和向量计算,Python在这些领域的库(如LangChain、sentence-transformers)最为丰富。FastAPI是一个高性能的现代Python Web框架,它自动生成交互式API文档的特性,对于内部系统联调和后期维护非常友好。
  • 数据库:Vercel Postgres 。存储GPTs的元数据(如名称、作者、描述等结构化信息)需要一个可靠的关系型数据库。Vercel Postgres与部署平台深度集成,管理方便,无需自己维护数据库服务器,降低了运维成本。
  • 向量数据库:Zilliz Cloud 。这是专门为向量搜索设计的云服务。我们需要存储和检索高维向量(例如1536维),传统数据库无法高效处理。Zilliz Cloud(基于Milvus)是业界知名的向量数据库解决方案,其云服务形态免去了复杂的集群运维,提供了稳定、高效的相似性检索能力。
  • 浏览器扩展框架:Plasmo 。它是一个基于React的Chrome扩展开发框架,让你能用熟悉的现代前端技术栈来开发插件,大大提升了开发效率和代码可维护性,避免了与原生Chrome API打交道时的很多繁琐细节。
  • 部署平台:Vercel 。除了对Next.js的完美支持,Vercel的Serverless Functions非常适合部署我们的FastAPI索引服务(通过配置),实现了前后端在同一个平台下的统一管理和无缝协作。

注意 :技术选型不是一成不变的。例如,如果你对Python生态不熟,索引系统也可以用Node.js (Express/Nest.js) 配合专门的向量搜索库来实现。但综合考虑开发效率、社区资源和项目目标,上述组合是一个经过验证的、平衡性很好的方案。

3. 核心模块实现细节与实操要点

3.1 网站模块:从零搭建展示门户

网站模块位于 /web 目录,是用户直接交互的界面。我的目标是打造一个快速、直观且信息丰富的商店首页。

核心页面与功能设计:

  • 首页 ( / ) :展示精选、热门和最新的GPTs列表。采用卡片式设计,每张卡片清晰展示GPTs的头像、名称、简短描述和作者。加入了无限滚动加载,避免分页打断浏览体验。
  • 详情页 ( /gpts/[id] ) :展示单个GPTs的完整信息,包括详细描述、使用说明、示例对话等(从 detail JSON字段中解析)。这里是说服用户使用该GPTs的关键页面。
  • 搜索页 ( /search ) :提供搜索框,用户输入查询后,前端将请求发送到我们的索引系统API,并将向量搜索的结果以列表形式展示。我特意设计了搜索建议和搜索历史功能来提升体验。

关键实现步骤与代码解析:

  1. 环境与依赖初始化

    # 进入web目录
    cd path-to-project/web
    # 使用pnpm(速度更快,磁盘空间更省)安装依赖
    pnpm install
    

    package.json 中,核心依赖包括 next (框架)、 react (UI)、 tailwindcss (样式)以及 @vercel/postgres (数据库客户端)。

  2. 数据库连接与数据获取 : 在Next.js中,我使用 Server Components 来直接获取数据,这能保证首屏内容直接被渲染在HTML中,对性能和SEO都有利。

    // app/page.js 或 app/page.tsx
    import { sql } from '@vercel/postgres';
    
    export default async function HomePage() {
      // 直接从数据库获取数据,在服务端执行
      const { rows } = await sql`
        SELECT uuid, name, description, avatar_url, author_name
        FROM gpts
        ORDER BY created_at DESC
        LIMIT 50
      `;
      return (
        // ... 使用rows数据渲染首页列表
      );
    }
    

    实操心得 :使用 async/await 在Server Component中直接操作数据库非常简洁。务必注意错误处理,可以添加 try...catch 或在顶层使用 error.js 文件来优雅地处理数据库查询失败的情况。

  3. 搜索功能集成 : 搜索功能需要调用独立的索引系统API。我创建了一个专用的API Route( app/api/search/route.js )作为代理,一方面可以隐藏后端API的密钥,另一方面可以在服务端进行请求聚合或缓存。

    // app/api/search/route.js
    export async function POST(request) {
      const { question } = await request.json();
      const indexApiUrl = process.env.INDEX_API_BASE_URI + '/gpts/index';
      
      try {
        const response = await fetch(indexApiUrl, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${process.env.INDEX_API_KEY}`
          },
          body: JSON.stringify({ question })
        });
        const data = await response.json();
        return Response.json(data);
      } catch (error) {
        return Response.json({ error: '搜索服务暂时不可用' }, { status: 500 });
      }
    }
    

    前端页面则通过 fetch 调用这个内部API路由。

3.2 索引系统:构建智能搜索的引擎

索引系统是项目的技术核心,位于 /index 目录。它的工作流程可以概括为: 数据准备 -> 向量化 -> 存储 -> 检索

核心流程拆解:

  1. 数据同步与处理 : 系统需要定期从源(如项目感谢中提到的GPTs Hunter)同步GPTs数据,或通过手动方式导入。数据清洗是关键一步,需要去除无效信息、规范格式,并将关键的文本字段(如 name , description , detail 中的 intro )拼接起来,作为后续生成嵌入向量的“原料”。

  2. 文本向量化(Embedding) : 这是将文本转换为数学向量的过程。我选择了Azure OpenAI的 text-embedding-ada-002 模型。这个模型专门为生成高质量的文本嵌入而设计,能将语义相似的文本映射到向量空间中相近的位置。

    # 简化示例,使用 langchain 库
    from langchain.embeddings import OpenAIEmbeddings
    # 注意:实际使用Azure OpenAI,需配置azure_endpoint等参数
    embeddings = OpenAIEmbeddings(model="text-embedding-ada-002", chunk_size=1)
    
    # 为一段文本生成向量
    text = "一个能帮助你编写和调试代码的AI助手"
    vector = embeddings.embed_query(text) # 得到一个1536维的列表
    

    注意事项 :嵌入模型有输入长度限制(通常几千个token)。对于过长的GPTs描述,需要进行合理的截断或分割处理,否则会丢失信息或导致错误。

  3. 向量存储与索引构建 : 生成的向量需要存入向量数据库。我使用Zilliz Cloud,它提供了简单的SDK。

    from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType
    
    # 连接到Zilliz Cloud
    connections.connect(uri=STORE_URI, token=STORE_TOKEN)
    
    # 定义集合(类似表)的模式
    fields = [
        FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
        FieldSchema(name="gpts_uuid", dtype=DataType.VARCHAR, max_length=255),
        FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=STORE_DIM) # dim=1536
    ]
    schema = CollectionSchema(fields)
    collection = Collection("gpts", schema)
    
    # 插入数据(假设`all_vectors`是向量列表,`all_gpts_ids`是对应的GPTs UUID)
    mr = collection.insert([all_gpts_ids, all_vectors])
    # 创建索引以加速搜索
    index_params = {"index_type": "IVF_FLAT", "metric_type": "L2", "params": {"nlist": 128}}
    collection.create_index("embedding", index_params)
    

    参数选择解析 :这里 IVF_FLAT 是一种索引类型,它在精度和速度之间取得了很好的平衡。 nlist=128 是将向量空间划分为128个单元,搜索时先定位到相关单元,再在其中精确查找,这比全量扫描快得多。 L2 (欧氏距离)是衡量向量间距离的常用方法,距离越小越相似。

  4. 语义搜索API实现 : 使用FastAPI快速搭建搜索端点。

    from fastapi import FastAPI, HTTPException, Header
    from pydantic import BaseModel
    
    app = FastAPI()
    
    class SearchRequest(BaseModel):
        question: str
    
    @app.post("/gpts/index")
    async def search_gpts(request: SearchRequest, authorization: str = Header(None)):
        # 1. 验证API Key
        if authorization != f"Bearer {INDEX_API_KEY}":
            raise HTTPException(status_code=403, detail="Invalid API Key")
        
        # 2. 将用户问题转换为向量
        query_vector = embeddings.embed_query(request.question)
        
        # 3. 在向量数据库中搜索
        search_params = {"metric_type": "L2", "params": {"nprobe": 10}} # nprobe是搜索的单元数
        results = collection.search(
            data=[query_vector],
            anns_field="embedding",
            param=search_params,
            limit=10, # 返回前10个最相似的结果
            output_fields=["gpts_uuid"] # 同时返回关联的UUID
        )
        
        # 4. 根据UUID从主数据库(Postgres)获取完整的GPTs信息
        uuids = [hit.entity.get('gpts_uuid') for hit in results[0]]
        # ... 查询Postgres,组装返回数据
        return {"data": gpts_list}
    

    关键点 nprobe 参数控制搜索的广度,值越大,搜索越精确但越慢,通常设置为 sqrt(nlist) 左右,这里设为10是一个经验值。

3.3 浏览器扩展:无缝集成用户体验

扩展模块位于 /extension 目录,目标是让GPTs Works的服务出现在ChatGPT的探索页面旁。

实现原理:

  1. 内容脚本(Content Script) :这是扩展的核心。它被注入到 chat.openai.com 的页面中。脚本会监听页面变化,当检测到用户进入了探索页面(URL匹配 https://chat.openai.com/explore )时,便开始工作。
  2. DOM操作与UI注入 :脚本在探索页面的侧边栏或合适位置,动态创建一个容器( <div> ),然后通过 fetch 调用GPTs Works网站或索引系统的公开API,获取推荐或搜索列表,并将结果渲染到这个容器里。
  3. 通信与存储 :扩展的弹出页面(Popup)或选项页面(Options)可以用于进行简单配置(如选择推荐策略)。配置信息通过Chrome的 storage.sync API保存,并与内容脚本通过 chrome.runtime.sendMessage 进行通信。

Plasmo框架的优势 : 使用Plasmo后,你可以像开发React组件一样开发扩展的各个部分。例如,内容脚本直接写成一个React组件:

// content.tsx
import { createRoot } from "react-dom/client"

function SidebarApp() {
  const [gptsList, setGptsList] = useState([]);
  
  useEffect(() => {
    // 组件挂载后,从你的API获取数据
    fetch('https://your-gpts-works-site.com/api/recommend')
      .then(res => res.json())
      .then(data => setGptsList(data));
  }, []);
  
  return (
    <div style={{width: '300px', padding: '10px'}}>
      <h3>来自 GPTs Works 的推荐</h3>
      {gptsList.map(gpt => ( /* 渲染列表 */ ))}
    </div>
  );
}

// Plasmo 会自动处理组件在目标页面的注入
export default SidebarApp;

Plasmo的构建系统会帮你处理好打包、资源注入等所有底层细节,你只需要关注业务逻辑。

4. 本地开发与部署全流程指南

4.1 本地开发环境搭建

第一步:克隆与基础准备

git clone https://github.com/all-in-aigc/gpts-works.git
cd gpts-works

第二步:数据库初始化

  1. 你需要一个PostgreSQL数据库。本地可以安装PostgreSQL,或者使用云服务(如Neon, Supabase)获取一个免费连接串。
  2. 使用 psql 或任何数据库管理工具(如DBeaver, TablePlus)连接你的数据库,执行项目提供的 CREATE TABLE gpts SQL语句来创建表结构。
  3. 准备数据 :这是本地开发最大的挑战。你需要一些初始的GPTs数据。可以:
    • 手动从OpenAI网站收集一些GPTs信息,编写脚本插入数据库。
    • 寻找公开的GPTs数据集(请注意版权和许可)。
    • 项目感谢了GPTs Hunter,你可以研究其方式,但请遵守其服务条款。 切勿滥用或爬取受保护的数据

第三步:配置索引系统

  1. 进入 /index 目录,复制 .env.example 文件为 .env ,并填写所有配置。
    • DATABASE_URL :指向你上一步创建的数据库。
    • AZURE_* OPENAI_* :你需要一个能调用Embedding模型和Chat模型(用于可能的数据处理)的API。Azure OpenAI或OpenAI官方API均可。
    • STORE_* :前往Zilliz Cloud创建一个免费集群,获取连接信息(URI和Token)并创建集合(Collection)。
  2. 安装Python依赖: pip install -r requirements.txt 。建议使用虚拟环境( venv conda )。
  3. 启动FastAPI服务: make dev uvicorn app.main:app --reload --port 8068 。服务将在 http://127.0.0.1:8068 运行。
  4. 构建初始向量索引 :确保数据库中有数据后,调用索引构建接口:
    curl -X POST -H "Authorization: Bearer your-index-api-key" http://127.0.0.1:8068/gpts/index
    
    这个过程可能会持续几分钟,取决于数据量大小。你可以在Zilliz Cloud控制台看到向量数量的增长。

第四步:配置并启动网站

  1. 进入 /web 目录,复制 .env.local.example .env.local
  2. 填写 POSTGRES_URL (同上),以及 INDEX_API_BASE_URI (指向你本地运行的索引服务,如 http://localhost:8068 )和对应的 INDEX_API_KEY
  3. 安装依赖: pnpm install
  4. 启动开发服务器: pnpm dev 。网站将在 http://localhost:8067 运行。

第五步:配置并调试浏览器扩展

  1. 进入 /extension 目录,安装依赖: pnpm install
  2. 构建开发版本: pnpm dev 。这会在 build 目录下生成插件的开发包。
  3. 打开Chrome浏览器,进入 chrome://extensions/
  4. 开启右上角的“开发者模式”。
  5. 点击“加载已解压的扩展程序”,选择 /extension/build/chrome-mv3-dev 目录。
  6. 现在,访问 https://chat.openai.com/explore ,你应该能在页面侧边看到注入的GPTs Works推荐栏。

4.2 一键部署到Vercel

项目提供了极其便捷的Vercel一键部署按钮。这主要部署的是 网站部分

部署前准备:

  1. 拥有一个Vercel账户(可使用GitHub登录)。
  2. 在Vercel控制台创建一个Postgres数据库,并记下连接字符串( POSTGRES_URL )。
  3. 准备好你的索引系统API的基础URL和密钥( INDEX_API_BASE_URI , INDEX_API_KEY )。索引系统本身也需要单独部署(可以部署到Vercel的Serverless Functions,或其他云服务器/容器服务)。

部署流程:

  1. 点击项目README中的“Deploy with Vercel”按钮。
  2. 它会引导你进入Vercel的导入项目页面,关联你的GitHub仓库。
  3. 在环境变量配置页,填入准备好的三个环境变量。
  4. 点击部署。Vercel会自动完成构建和部署。

部署后:

  • 你的网站将拥有一个 vercel.app 的域名。
  • 你需要在Vercel的项目设置中,配置自定义域名(如果你有自己的域名)。
  • 网站现在会使用你配置的索引系统API。请确保索引服务已在线且可公开访问(注意设置好API密钥认证和CORS)。

重要提示 :一键部署主要解决网站前端和Serverless API的托管。生产环境的索引系统(特别是涉及向量数据库连接和AI模型调用)可能需要更稳定的部署环境,建议考虑部署在可长期运行的云服务器(如AWS EC2, Google Cloud Run)或容器服务(如Docker on Railway)上,并设置好进程守护(如systemd, pm2)。

5. 常见问题、优化思路与避坑指南

在实际开发和部署过程中,我遇到了不少坑,也总结了一些优化方向。

5.1 常见问题排查速查表

问题现象 可能原因 排查步骤与解决方案
网站打开报数据库连接错误 1. POSTGRES_URL 环境变量未设置或错误。
2. Vercel Postgres数据库未启动或IP白名单限制。
1. 检查Vercel项目环境变量设置。
2. 登录Vercel控制台,检查Postgres数据库状态,并确保允许所有连接(或添加Vercel的IP范围)。
搜索功能返回空或错误 1. 索引系统API地址或密钥错误。
2. 索引系统服务未启动。
3. 向量数据库(Zilliz)中无数据或索引未构建。
4. CORS策略阻止了前端请求。
1. 检查网站环境变量 INDEX_API_BASE_URI INDEX_API_KEY
2. 检查索引系统服务日志,确认其正常运行。
3. 调用索引系统的 /gpts/index (POST)接口触发构建,并检查Zilliz控制台集合中是否有向量。
4. 在索引系统FastAPI应用中添加CORS中间件。
浏览器扩展不显示 1. 扩展未成功加载。
2. 内容脚本注入的目标URL匹配失败。
3. 扩展请求的API地址因跨域(CORS)被阻止。
1. 检查 chrome://extensions/ 中扩展是否已启用,并重新加载。
2. 检查Plasmo配置 manifest.json 中的 matches 字段是否正确匹配ChatGPT探索页面URL。
3. 扩展请求的API需要正确配置CORS头,允许来自 chrome-extension:// 你扩展ID的请求。
向量搜索速度慢 1. 向量集合数据量过大,索引参数不合适。
2. 网络延迟高(如向量数据库在海外)。
1. 优化Zilliz索引参数,如尝试 IVF_SQ8 等量化索引以减小内存占用和提升速度(会轻微损失精度)。
2. 考虑将向量数据库部署在离你应用服务器更近的区域。
嵌入(Embedding)API调用超限或费用高 1. 数据同步过于频繁,重复生成相同文本的向量。
2. 文本未做长度处理,导致token消耗过多。
1. 实现去重逻辑,仅对新GPTs或描述发生变化的GPTs生成向量。
2. 在调用嵌入模型前,对长文本进行智能截断或分块(chunking)。

5.2 性能与成本优化实战心得

  1. 缓存是王道 :对于首页列表、热门推荐等不常变化的数据,一定要用缓存。Vercel自身提供了强大的边缘缓存( fetch 选项可配置 next: { revalidate: 3600 } )。对于搜索热点词的结果,也可以在索引系统或网站API层引入Redis等内存缓存,显著降低数据库和向量搜索的压力。
  2. 异步处理耗时任务 :构建全量向量索引是一个耗时操作,不能阻塞正常的API响应。应该将其改为异步任务,例如通过消息队列(如RabbitMQ, Redis Queue)触发,或使用Vercel的Serverless Functions配置长时间运行的任务(注意超时限制)。
  3. 按需加载与分页 :网站前端列表一定要做分页或虚拟滚动,避免一次性加载成千上万条数据。搜索API的 limit 参数也要合理设置。
  4. 监控与告警 :上线后,务必监控关键指标:API响应时间、错误率、数据库连接数、向量搜索QPS、以及Embedding API的调用量和费用。可以利用Vercel Analytics、Zilliz Cloud监控以及云服务商自带的监控工具设置告警。
  5. 数据更新策略 :GPTs世界日新月异。你需要设计一个稳健的数据更新管道(Data Pipeline)。可以定期(如每天)运行一个脚本,从可靠来源获取更新的GPTs列表,与现有数据库对比,只更新或新增有变动的记录,并触发部分向量索引的更新,而不是全量重建。

5.3 安全与合规注意事项

  • API密钥管理 :永远不要将API密钥硬编码在代码或前端。 INDEX_API_KEY 、数据库连接串、各类云服务密钥都必须通过环境变量管理。Vercel的环境变量功能很好用。
  • 输入验证与限流 :索引系统的搜索接口对外开放,必须做好输入验证,防止恶意注入。同时要实施限流(Rate Limiting),防止被爬虫或攻击者滥用,产生高昂的AI API费用。FastAPI可以使用 slowapi 等中间件轻松实现。
  • 数据版权与隐私 :尊重数据来源。如果你从第三方网站(如GPTs Hunter)获取数据,务必仔细阅读其服务条款,明确是否允许商用、二次分发。展示GPTs信息时,最好能提供指向官方GPTs链接的途径,尊重原创作者。
  • 浏览器扩展的审核 :如果打算发布到Chrome Web Store,需要仔细阅读谷歌的开发者政策,确保扩展不会干扰网站正常功能,并提供清晰的隐私声明。

这个项目从构思到实现,是一个典型的全栈+AI应用开发过程。它涉及了现代Web开发、数据库设计、AI模型集成、向量检索以及浏览器生态等多个领域。最大的收获不是做出了一个可用的工具,而是在这个过程中,如何将不同的技术组件像拼图一样组合起来,解决一个真实存在的问题。如果你对其中任何一个环节感兴趣,都可以以此为起点,深入探索下去。比如,你可以尝试换用不同的向量数据库(如Pinecone, Weaviate),或者用更轻量的句子转换器(Sentence Transformers)模型本地化生成嵌入,甚至为GPTs添加用户评分和评论系统,让它变成一个真正的社区驱动的商店。

更多推荐