标签: edge function

  • 使用 Vercel、Supabase、Stripe 打造 OpenAI 计费系统:2. 处理用户注册/登录

    使用 Vercel、Supabase、Stripe 打造 OpenAI 计费系统:2. 处理用户注册/登录

    嗯,是的,我回来填坑了。既然要打广告,就要对得起各位金主,总是水文是不行的,还是要输出干货。

    广告继续。本博客即日起开始常年招商,欢迎各位想推广产品的老板投广告,目前定价 4800/年,亦可增加评测文章、教学文章配合,详情可与我联系。具体详情更新在 本站广告 2023 年招商


    Supabase 用户体系入门

    Supabase 提供 Serverless 数据库,自带用户体系,开发用户系统非常简单。而且 SDK 很完善,适配各种框架,大部分功能开箱即用;官方教程方面做得也很好。所以,首先请大家先认真阅读这两篇官方文档、教程:

    第二篇教程文章详细的介绍了使用 Supabae 开发用户管理系统的过程,配合上面的文档,相信大家都可以轻松入门。唯一的难点(对部分同学来说)可能是英文,我就先不复述。

    接下来的文章主要介绍官方没有细说的最佳实践和各种实用细节。

    邮件登录与邮件模版

    虽然官方提供了很多方案,但是我想,对于大部分同学来说,邮件+密码登录才是最常用的,最多再加上第三方比如 Gmail 登录。

    Supabase 提供了发邮件的功能,方便用户校验邮箱、找回密码。但是,正如很多邮件服务器那样,它的到达率并不理想。我猜测有些用户即使没有滥用 Supabase 的邮件服务,也会因为邮件模版过于简单而被误杀。所以大家第一步应该先配置邮件模版,尽量多体现自己的产品相关信息,尽量跟大家的通用模版区分开,减少误报误杀的可能。

    Supabase 默认要求用户校验邮箱之后才能登录,如果你暂时不关心这一点,可以考虑在 Auth ProviderS > Email关掉这个功能。此后,用户完成注册后就可以登录,不过他们的用户状态在数据里仍然是未验证,我们也可以借此对用户进行一些差异化的控制。

    注册与登录,自动注册

    有一点我不太喜欢。Supabase 提供两个方法:signUpsignInWithPassword,分别用来注册和登录。但是通常情况下,我并不希望区分这两个方法。我认为我们的网站对用户来说,只是一个小工具,我觉得他们不会在意是否在这里注册过,他们只希望做他们要做的事情,注册登录只是我们为了方便自己而做的步骤。

    所以我一般仍然会通过不同的路径区分用户是想注册,还是想登录。但是我会在用户 signUp 后检查错误信息,如果是重复注册,就自动帮他们登录。

    const { data: { user }, error } = await supabase.auth[method]({
      email,
      password,
      options,
    });
    if (error) {
      // registered, login
      if (error.message === 'User already registered') {
        return login(email, password);
      }
    throw error;
    }

    Nuxt3+Pinia 实现用户身份校验及额度查询

    教程中的例子比较简单,通常来说我们的产品会复杂很多,比如,我们可能需要全局状态管理工具,方便不同页面不同组件中使用。在 Nuxt3 中使用 Pinia 需要用到特有的 Module:@pinia/nuxt,简单安装官方教程配置一下即可。这里主要分享我摸索的其它未提及要点。

    额度查询

    Supabase 在创建项目的时候已经帮我们建好了用户表,并且放在默认不对外的库里面。所以我们最好不要直接修改表,而是如教程所讲,创建一个新的表,把用户账户相关的其它数据放进去,比如在这样一款典型的 ChatGPT 产品中,就是帐户额度,然后通过关联 id 的方式访问。

    这就带来一个问题:什么时候去读这个表?

    对 SPA 来说,这个问题比较简单,登录之后,以及网页初始化的时候读取即可,反正所有请求都由我们全权控制。但是对于 Nuxt3 来说就值得讨论一下。因为 Nuxt3 要进行服务器端渲染,所以我们通常希望它在渲染页面的时候,这个数据已经就位了。

    这里我推荐配合 Pinia Store,使用 supabase.auth.onAuthStateChange 函数。我会建立一个 userStore,在里面完成用户注册、登录、登出等动作,同时注册 onAuthStateChange 事件,完成用户状态变更后的处理。

    const useUserStore = defineStore('user', () => {
      const supabase = useSupabaseClient();
      const user = useSupabaseUser();
    
      async function login(email: string, password: string, isSignUp = false): Promise<void> {}
      async function loginWithGoogle(): Promise<void> {}
      async function logout(): Promise<void> {}
      
      // 注册事件处理函数
      supabase.auth.onAuthStateChange((event, session) => {
        switch (event) {
          case 'INITIAL_SESSION':
          case 'SIGNED_IN':
          // 在这里处理登录后和初始化时拿到用户身份后的操作,比如加载用户账户的额度
          // 这里需要注意:此时 `user.value` 尚未被填充,所以我们不能依赖它。有两个方案:1. 延迟执行 `setTimeout`;2. 直接从 `session` 参数里拿到用户信息然后处理
          // 我的做法是,如果需要请求 API,那就选择方案2;否则,使用方案1,等 100ms
        }
      });
    });
    export useUserStore;

    请仔细看上方代码的注释,非常重要,看起来很简单,但其实是血泪铺就的道路。

    判断第三方登录,及 UI 呈现

    当用户选择第三方登录,比如 Gmail 的时候,Supabase 目前只支持跳转这一种方式,不如 Auth0,可以用 popup,省很多事。

    完整的第三方登录流程是:

    1. 跳转到第三方登录页面
    2. 登录
    3. 跳转到 Supabase 中间页
    4. 带着 access_token 跳回我们的网站,Supabase SDK 负责读取 access_token,验证用户身份等工作

    其中涉及到两个(或更多)网站的配置,因为文档中写的有,我就不再详述,需要的同学留言我再补充。

    最麻烦的是(4),跳转回来之后,从页面初始化成功到完成登录,中间可能会隔很久,短则1、2秒,长则10s 都有可能。这期间用户可以继续操作,会产生各种误会和误操作。这里我建议:

    1. 登录时传递 redirectTo,要求最终跳转页面是某个登录落地页
    2. 初始化的时候,检查 URL 里的 #access_token=
    3. 如果有,说明是第三方登录返回,然后就显示登录中的状态
    4. 等待 user.value 变化,说明完成登录,跳转到原先应该去的页面,或者继续之前没做完的操作
    5. 如果没有 access_token,直接跳转到其他落地页。

    总结

    上面这些内容看起来不复杂,但实际上对用户体验影响很大,并且文档、教程说得也不清楚,导致我的老板对产品一直不太满意。希望这篇文章可以节省大家的宝贵时间。

    如果有关于 Supabase、Vercel、Nuxt3 的问题,欢迎评论里面留言。

    系列文章

    1. 使用 Vercel、Supabase、Stripe 打造 OpenAI 计费系统:1. 系统篇
    2. 【本文】使用 Vercel、Supabase、Stripe 打造 OpenAI 计费系统:2. 处理用户注册/登录

    建议阅读

  • 聊聊 Serverless 与 Edge Function

    聊聊 Serverless 与 Edge Function

    熟悉我的同学可能记得,前几年我经常推荐 Serverless,而且我的博客里现在还挂着 LeanCloud 的推荐链接。我觉得 Serverless 对前端、全栈开发者来说简直是个福音,很多原本很难的开发工作,有了 Serverless 之后,都能很容易的由前端处理,大大扩展了前端的能力覆盖范围。

    时至今日,我们不仅有 Serverless,还有 Edge Function,后者几乎没有冷启动的问题,更能提供 SSR 功能,好上加好。很多数据存储服务也针对性地提供好用的接口。今天的开发环境对前端来说,简直无与伦比。所以我想聊聊这个话题,希望每个前端都能在 Serverless/Edge Function 得帮助下成长为全栈开发。

    Serverless 是什么?

    我就不去抄准确定义了,按照我的体验来分享:

    1. 不需要维护独立的服务器
    2. 分为两个部分:
      1. 基于行级权限控制的数据库
      2. 可以自动扩容、降容的云函数执行器
    3. 云数据库,配合行级权限,配合合理的数据逻辑,可以在前端完成大部分数据处理的工作
    4. 云函数可以自动扩容、降容,可以休眠、启动,可以完成云数据库无法放在前端的操作
    5. 云函数以函数为一个执行单位,实现更精准的资源控制

    总之,前端有了 Serverless,就可以在没有后端、没有运维的配合下,独立完成完整应用的开发,并且具备相当强的扩展能力,费用也很低。

    其它应用场景

    除此之外,Serverless 还可以用在各种有突发计算高峰的场景里,比如音视频处理。我们可以利用 Serverless 在没人使用时自动休眠(不计费),有人访问时自动启动的特性,有需求的时候才启动,完成计算后自动关机,既节省成本,又提供很高的理论并发。

    Serverless 的问题

    早期的 Serverless 调度系统有些问题。出于学习成本的考虑,早期 Serverless 多数是基于容器技术,虽然可以完整支持大部分编程语言及其 API,但是冷启动一般都需要不少时间,少则十几秒,多则几十秒。

    这么久的启动时间,对于计算密集型领域来说,问题并不严重。因为在这些场景里,几十秒的启动时间占比不高,通常来说用户需要等待的时间远比它久,自然有队列和轮询放在前面。但是对于一般的网站应用,服务普通客户,如果启动一次要几十秒,那就很难接受了。

    比如最近大热的 ChatGPT,有些同行尝试用 Serverless 做转发,就受困于冷启动,经常超时报错。

    一代更比一代强:Edge Function

    我不太确定 Edge Function 是什么时候诞生的,最初我以为 Edge Function 无非就是跑在运算力富裕的边缘节点上,本质上跟 Serverless 没什么区别,直到前些日子开始大规模使用 Vercel,深入了解之后,才明白 Edge Function 的优势。

    简单来说,无论是 Vercel、Supabase、还是 Cloudflare Worker,都抛弃了以前基于 Docker 的做法,或者精简运行时,或者使用 deno 这样更快更小的核心,将冷启动的速度降到毫秒级——基本就是网络传输的延迟级别。

    于是 Edge Function 的可用性就大大提升,可以用在各种速度敏感型的场合,比如现代框架的 SSR、代理服务器、HTML 内容改写,等等。

    目前,我的 Edge Function 主要用在以下几个场景:

    1. Nuxt.js/Next.js 的 SSR
    2. 代理 OpenAI 的 API 访问
    3. 操作 Upstash Redis 存储和修改数据
    4. 响应 Stripe 的付款请求

    Vercel Edge Function 的限制

    我用 Vercel 比较多,这里介绍一下 Vercel Edge Function 的限制。

    首先,Vercel Edge Function 是 node.js 的子集。除了拥有完全一致的语法和基本对象之外,Edge Function 并不能使用全部的系统 API,比如 fs、http/https 等。所以类似 axios 这样知名的 ajax 封装就不能使用;也不能使用 OpenAI 的官方 npm 包。

    Edge Function 只支持 ESM,所以依赖里面的包即使没有用到系统 API,也必须使用 ESM 才能使用。所以我们必须经常性的处理一下公共包。

    虽然支持 TypeScript,但部署的时候,会使用 ESM 编译,所以 import 必须使用相对路径,否则会报错。报错的内容是使用了不支持的格式,非常有误导性。

    其它就是一些系统性要求,比如代码限制、内存限制、请求体限制等,我目前都还没有撞到。大家小心点就是。比较可能遇到的问题是,Edge Function 30s 内必须开始返回内容,这在 OpenAI 开发中很容易遇到问题,开启 stream: true 就好。

    更好的生态环境

    Vercel、CloudFlare 均提供非常完整的网络环境,包括网站、CDN 缓存层等必备服务。

    如果要使用数据库,选项非常丰富,除了 Vercel 自带的各种不解渴的“小样”,我们还可以使用 Upstash Redis,MongoDB,PlantScale MySQL,Supabase PosgreSQL,以及 TiDB Cloud Serverless。

    基本上,应有尽有,几乎所有 Web 开发所需要的存储方案都能找到有免费额度的试用装。开发环境真是盛世空前。

    我们应该怎么做

    现在经济大环境不好,很多同学面临失业、降薪,V2EX 上经常有同学讨论是开网约车好还是送外卖好……这个我也没办法。但是从另一个角度来说,我们的开发环境却是史无前例的好,部署、运维成本非常低,几乎所有必须的服务都有免费试用,每家都提供清晰的文档、易用的 SDK。

    我建议,大家好好利用闲暇时间,利用现在各种生成式模型大爆发,但是市场还没稳定下来的时间节点,多多尝试,即使做不出惊世之作,也能为自己的将来累积足够多的资本,坚持到云开日出那一天。

    广告时间:我的全栈开发课程

    发现忘记打广告了,赶紧补上。

    我目前正在连载一套云原生全栈开发课程,使用 Nuxt3 + Vercel + Serverless 数据库开发。目前发布了三集,本周计划制作第四集(如果有时间就做第五集,嗯嗯),请大家观看,有号的话还请帮我一键三连,多谢。

    B站地址:Nuxt3+Vercel+Serverless 数据库全栈开发

    油管地址:Nuxt3+Vercel+Serverless数据库全栈开发 – YouTube

    总结

    曾经面试金山的时候,有位面试官问我:你这么大年纪,你觉得你比年轻人有什么优势?我答道:因为我年纪足够大,见识过足够多的技术发展,所以我能更快地学会一门技术,也能更准确的判断一门技术是否值得学习。我一直很看好 Serverless,如今有了 Edge Function,serverless 更好用;有了各种服务,serverless 更强大。Serverless 是未来全栈不可或缺的技术。

    希望大家都学会使用。有任何问题、建议、意见,欢迎留言讨论。

  • 使用 Vercel、Supabase、Stripe 打造 OpenAI 计费系统:1. 系统篇

    使用 Vercel、Supabase、Stripe 打造 OpenAI 计费系统:1. 系统篇

    过去两周,我基本都在跟这套付费/计费系统死磕,相对来说投入到 AI 学习里的时间不多,以至于 我的 AI 学习周记 系列本周停更。如今终于基本搞定这套系统,基本概念、开发调试都不存在难以逾越的问题,所以打算写几篇博客总结一下,给后来者分享我收获的知识、踩到的坑。也让自己需要的时候可以翻查笔记。

    OpenAI 计费系统现状

    目前我们讨论的 OpenAI 计费,基本是针对 ChatGPT 对应的 gpt-3.5-turbo 和 gpt-4 这两个模型,在 API 调用方面的费用。官方规定,gpt-3.5-turbo 是 $0.002/1000 tokens,gpt-4 根据容量不同,最多要贵 30 倍,所以提供服务时,我们基本还是以 3.5 为主。如果用户愿意多付钱,我们也可以提供 gpt-4(已经拥有权限)。

    不使用 stream: true 的时候,OpenAI 会返回使用的 token 数量,这个时候,数据准确可靠。但是为用户体验考虑,也为云服务考虑,使用 stream: true 模式明显更好。但是在这种模式下,OpenAI 不返回使用的 token 数量。(我猜测这跟 Transformer 模型的工作原理有关。)所以我们就需要手动统计 token 的消耗。

    这个时候得到的结果可能并不准确,但是没办法,我们只能这么做。

    我们开始也不知道 OpenAI 怎么统计 token 数,好在官方提供了计算页面:https://platform.openai.com/tokenizer 和推荐方案(看起来是传说中的 GPT 地牢作者的作品),可以帮我们完成计算。至于详情,将来会具体介绍。

    基于 Edge Function 的计费系统设计

    首先,我们要选择服务器架构。

    传统方案是配置一台云服务器,然后前面架一个 CDN。但由于我们是一个面向全球的服务,这样的架构不甚理想。而且基于 stream: true 的方案需要长时间维持网络连接,单机容量也不够。全球部署的话,成本太高,初创团队不现实。

    所以很自然的,我们准备使用 Edge Function。Edge Function 的优势在于:

    1. 它借助云服务厂商的边缘节点提供服务,节点分布广泛,可以就近服务用户,大大改进响应时间。
    2. Edge Function 在用户请求的时候才工作,平时休眠。这种按需付费成本更低。
    3. 相比于传统的 Serverless Function,Edge Function 一般都会修改运行时,用减少负载的方式提升启动速度,以几乎 0 延迟的方式启动,用户体验很好。

    以上几点我均已在之前的试做项目中在 Vercel 平台上体验过,所以这次没怎么纠结,直接选择 Vercel Edge Function 开发。

    也欢迎大家阅读我前一篇分享文:使用 Vercel Edge Function 访问 OpenAI API 的注意事项

    数据库选择:Supabase

    确定使用 Edge Function 之后,下一步要选择数据库。传统的、基于数据库协议的方式不可行,必须支持 http 请求,必须支持连接池,可选方案不太多。刚好前阵子我为了抽键盘,了解到有一家叫 Supabase 的新 serverless 服务商,可以很好满足我们的需求。

    首先,他们能满足 Vercel Edge Function 的需要,也是官方推荐的厂家之一。

    其次,他们的服务基于 PosgreSQL,具备丰富的插件生态,可以实现各种功能,包括未来给 ChatGPT 提供内容拓展的 pg_vector,可以不用担心将来需求延伸。

    再次,作为一家 serverless 服务商,他们家的免费额度看起来还不错,应该可以满足我们早期验证产品的目标(薅羊毛就是爽)。而且,自带 RLS(行级安全策略)也会给未来的开发带来很多方便,比如我们可以放心的把用户身份有关的读取操作放在客户端,不用单独开发接口,节省很多人力。

    最后,我们决定选用 Supabase,作为数据库服务供应商。

    其实 Supabase 也提供 Edge Function,效果理论上并不比 Vercel 差,但它是基于 Deno 的封装,生态和环境都跟 Node.js 不太一样。考虑到学习成本,以及分散投入有利于多嫖资源,所以我暂时不打算用,还是先集中在他们家的数据库上。

    收费选择:Stripe

    作为超迷你初创团队,主攻海外英文市场,我们的策略自然是一切从简,那么付费方案很自然就选择了 Stripe。Stripe 的功能全,文档强大,很适合我们这种小团队使用:

    1. 接受多种收费方式,各种信用卡不一而足,尽可能满足用户
    2. 提供订阅式购买
    3. 支持优惠码等促销手段
    4. 提供 SaaS 服务,方便管理用户
    5. 可以用 Payment link 创建付费页面,省去几乎所有购买流程的开发成本
    6. 提供 webhook,对接我们的账户体系
    7. 提供大量 API,如果有需要,将来我们可以逐步迁移到自己的平台

    最终选择

    基本上,我们最终形成了这样一套方案:

    1. Vercel Edge Function 提供 OpenAI API 的封装,我们借由它完成用户请求、额度拦截、计费等功能
    2. Supabase 提供存储,我们借由它存储用户的账户状态、付费记录、消费记录等功能
    3. Stripe 提供购买功能,我们用它的 Payment link 让用户完成购买付费

    这些产品都支持 TS/JS 为主的语言,提供基于 npm 的 SDK,开发环境很大众,开发体验不错。未来属于 JS。

    这套架构在初期没什么成本,可以覆盖几乎全球的用户,提供不俗的性能。将来用户量增长,需要扩容的时候,也不需要我们再手动开发扩容功能,只要根据用户量、使用量付费给云服务商就好。如果将来我们要提供 embedding,嵌入用户自己的数据,也可以很容易的在现有框架下实现扩展。

    乐观估计,未来半年到一年内,我们都可以在这个体系下开发。


    小结

    我感觉全世界就我一个人在做计费系统,其他人:

    1. 要么是不知道是弄了很多免费账号还是自己负担费用先抢用户,反正是敞开给用户免费用
    2. 要么是收一大笔钱割韭菜,反正收的钱多,普通用户随便用也用不完

    总之都不在意区分用户,也不在意回本。于是几乎看不到有人讨论如何做自己的付费系统。

    我们认为成熟的商品,还是要在成本、收益、风险上形成稳定、友好的比例,计费系统早晚都得做,不如先做好。

    如果你对 OpenAI 计费系统,对我们这套基于公共云平台的 Edge Function 方案感兴趣或者有问题,欢迎留言讨论。

  • 使用 Vercel Edge Function 访问 OpenAI API 的注意事项

    使用 Vercel Edge Function 访问 OpenAI API 的注意事项

    从某天开始,OpenAI API 无法从国内直接访问。而且,也不是每个人都有自己的云服务器,能够搭建独立服务。那么,还有别的办法能比较容易的访问到 OpenAI 么?当然是有的,比如 Vercel Edge Function,或者 CloudFlare Edge Function。

    这里我准备结合前阵子的开发经验,分享一下使用 Vercel Edge Function 访问 OpenAI API 的注意事项,让新来的开发者能少走弯路。

    推荐阅读

    开始之前,我建议大家先花点时间了解一下 Edge Function,以及如何使用 Vercel Edge Function 开发 OpenAI 应用。因为我后面要分享的主要是踩过的坑,所以先系统性了解会好很多:

    Building a GPT-3 app with Next.js and Vercel Edge Functions

    官方教程,还有 Demo 网站GitHub 项目,非常友好。虽然是英文写的,不过并不难懂,实在不行就用 Edge 浏览器自带的翻译功能吧,建议大家好好学习英文。

    自有域名+CNAME 实现国内访问

    Vercel 给免费版用户也提供子域名+ SSL 证书,很多时候都够用,但可惜,vercel.app 大域名被墙了,连带所有子域名都无法访问。好在 Vercel CDN 在国内也还能用,所以我们只需要一个自己的域名即可。

    申请域名的选择有很多,国内几大云服务商都能注册,国外的域名供应商也可以放心使用。我比较常用的是 namecheap.com。便宜的比如 .xyz 域名首年只要几块钱,随便注册一个就能用。

    注册完域名之后,在 Vercel 后台找到自己的应用,在“Setting > Domains“里添加域名,然后 Vercel 会告诉你怎么配置 DNS。复制解析目标,在域名供应商 DNS 配置页面完成 CNAME 配置,稍等片刻,解析生效后,即可得到一个国内也能正常访问的域名。

    使用 Edge Function

    Vercel Edge Function 与我们日常开发的 node.js 服务器略有区别。它并非完整的 node.js,而是 Edge 基于 V8 专门打造的袖珍运行时,尽可能轻量化,裁剪掉很多系统 API。功能少,但是速度很快,几乎零启动时间。(我之前将它跟 Supabase 记混了,以为它也是基于 deno 的。)

    使用 Edge Function 的好处,简单来说:省运维;详细来说,大概有这么几点:

    1. 性能更好。比随便买个小水管强得多。
    2. 自带弹性伸缩。不管访问量怎么成长,都有 Vercel 集群帮我们自动伸缩。(当然可能需要付钱)
    3. 启动速度比 serverless 快很多,基本没有等待时间。
    4. 免费额度足够初期 MVP 验证。

    坏处当然也有。首先,Edge Function 里跑的是 TS,这就意味着很多兼容 JS 的开源仓库都不能用。其次,Edge Function 很多原生 API 都不支持,所以没有特意兼容的仓库也不能用。举个例子,要完成网络请求,大家最熟悉的 Axios 就不能用,只能用系统原生的 fetch

    解决超时问题

    由于算法原因,OpenAI API 返回数据的总时间可能比较长,而 Edge Function 的等待时间又限制得很严。所以如果等待 OpenAI 返回全部数据再渲染,可能因为等太久,在 Edge Function 这里会超时。

    解决方案就是使用流(stream)式传播。在这种情况下,OpenAI 会逐步返回结果(差不多一个单词一个单词这样蹦),只要在客户端进行组合,就能看到类似实时输入的效果。

    完整的范例代码上面的官方文章有,我就不复制粘贴了,大家注意就好。

    Edge Function 的流不是最初的流

    这里有个坑,虽然我们在 Edge Function 里获取了 OpenAI API 的流,然后转发出来,但实际上我们接收到的流并不是最初的流。最初的流里,每次发送的数据都是完整的 JSON 文件,可以直接解析;但是 Edge Function 里转发给我们的却是前后合并后随机切分的结果。

    于是我们必须重新整理响应体。大概方案如下:

    1. 在每次返回的响应体里找到两个 json 的连接处
    2. 截断,拿到前面的 json,解析,得到自己想要的数据
    3. 继续查找完整的 json,如果没有,则和下一次响应体连接起来处理

    核心代码如下:

    class fetchGpt {
      fetch() {
        // 前面的代码参考官方例子
        // 我从循环读取开始
        while (!done) {
          const { value, done: doneReading } = await reader.read();
          done = doneReading;
          if (!value) {
            break;
          }
    
          // readableStream 读出来的是 Uint8Array,不能直接合并
          if (chunkValue.length > offset) {
            lastValue = concatUint8Array(lastValue, value);
          } else {
            lastValue = value;
            offset = 0;
          }
          chunkValue = decoder.decode(lastValue);
          [finishReason, offset] = this.parseChunk(chunkValue, offset);
        }
      }
      parseChunk(chunk: string, offset = 0): [string, number] {
        let finishReason = '';
        let nextOffset = offset;
        while (nextOffset !== -1) {
          nextOffset = chunk.indexOf(splitter, nextOffset + 1);
          const text = nextOffset !== -1
            ? chunk.substring(offset, nextOffset)
            : chunk.substring(offset);
          try {
            const json = JSON.parse(text);
            const [ choice ] = json.choices;
            const { delta, finish_reason } = choice;
            const chunkContent = delta?.content || '';
            // 这里我把数据交给事件 和 pinia 处理
            this.emit(MessengerEvent.MESSAGE, chunkContent, json.id, json.created);
            this.store.appendTextToLastItem(chunkContent, {
              id: json.id,
              created: json.created,
              system: this.options.system || '',
            });
            finishReason = finish_reason;
            offset = nextOffset !== -1 ? nextOffset : chunk.length;
          } catch (e) {
            //- ignore
          }
        }
        return [finishReason, offset];
      }

    常见 API 使用错误

    大家都知道,OpenAI 按照请求响应的 token 数算钱。所以我就想精打细算,通过在请求参数里减少 max_tokens,尽量少返回些内容。

    事实证明这个做法不成立。首先,OpenAI 对请求的兼容性不高,max_tokens 如果是 NaN 或者带有小数,都会报错。其次,ChatGPT 很啰嗦,内容量少了,它不过瘾,就会反复要求继续(finish_reason: 'length'),尤其在极端条件下,如果我们自动 continue,可能会误入死循环。

    所以我建议,max_tokens 最少 128,尽量 256 以上。


    前几天,有朋友在我的博客下面留言,说 Whisper 他试过,没啥特别的。我的观点不是这样。所谓纸上得来终觉浅,绝知此事要躬行。很多东西,确实不复杂,照着官方教程弄,三下五除二,在本地跑起来,并不难。但是想弄得干净利索,在生产环境里跑顺,遇到什么问题都能快速解决,也不是随随便便就能搞定的。

    希望有类似需求,寻求类似解决方案的同学能少走弯路;也欢迎大家多多分享,有意见建议,欢迎留言讨论。

  • 我的 AI 学习一周总结:ChatGPT API 发布

    我的 AI 学习一周总结:ChatGPT API 发布

    即然接下来的时间准备投身 AI 行业应用层开发,那就隔三岔五分享一下近期学到的知识、在做的事情吧。(配图是今天用第一次用 SD 生成的图片,prompt:“a beautiful girl“。)

    ChatGPT API 发布

    今天(2023-03-02,美国时间 03-01) OpenAI 正式发布了 ChatGPT API,即 基于 gpt-3.5-turbo 模型的。它的价格更便宜,$0.002/1k tokens,比之前 text-davince-003 便宜 10 倍。它能提供更好的对话质量,还提供了新的结构化数据接口,相信会给应用开发者带来更多的空间。

    chatgpt 等 npm 包也相应更新,如果没有办法搞定绑卡操作,可以继续用这些社区接口白嫖。

    尝试 Vercel Edge Function

    之前开发 GPT-3 插件的时候,有同学介绍了 Building a GPT-3 app with Next.js and Vercel Edge Functions 一文,于是我尝试把 API 挪到 Vercel Edge Function 上,然后失败。

    今天写本文的时候,又看了眼 TwitterBio 的例子,发现自己并没有做错。排查来排查去,还是自己犯蠢,部署环节出了问题。如今已经可以使用了。回头重构下插件,添加一些功能进去。

    学习社区 ChatGPT API 仓库

    ChatGPT 的开发商 OpenAI 提供两大块服务,API 和 ChatGPT 网页版。API 需要绑卡,有一定门槛;网页版可以免费用,不过存在偶尔连不上、响应慢等情况,据说买 plus 之后会有好转。

    于是社区就开发了网页版转 API 的工具,可以用来搭建自己的 ChatGPT API。但是需要使用反向代理服务器作为中转,开发者也不提供反向代理服务器的源码,存在一些风险,所以我看完就不打算使用了。

    本地搭建 Stable Diffusion 环境

    搭建过程其实很简单,难点主要在下载 pip 包和模型上,受限于墙内的网络环境,原本简单的下载变得异常艰辛。还好在换用国内 pip 源之后,我终于在本地搭建成功 Stable Diffusion Web UI 环境。大体过程如下:

    1. 安装必须的软件环境,比如 cmake、python3.10 等
    2. clone AUTOMATIC1111/stable-diffusion-webui: Stable Diffusion web UI (github.com)
    3. 修改 pip 源
    4. 反复执行 ./webui 直至安装成功
    5. 换模型可以在 Civitai 下载

    Whisper 模型

    Whisper 模型是 OpenAI 释出的开源模型,可以用来做语音识别,据说效果非常好。它不仅可以识别语音内容,还可以根据声纹,区分不同的发言人。所以用途也很广,比如视频会议之后,可以用它生成会议的文字记录。如果再结合 ChatGPT,就可以进行内容总结、会议摘要等工作,想象空间很大。

    因为开源,所以可以自己搭建服务器,据说不需要很强的计算能力,庶民可用。OpenAI 这次也放出了 Whisper API,方便用户使用。

    ChatGPT 新知

    中文语料少的副作用

    使用英文要求 ChatGPT 创作哈姆雷特的故事,它会拒绝,因为它知道哈姆雷特,新故事如果背景差异过大,它就会拒绝。但如果用中文,因为语料不足,哈姆雷特对它来说也只是个人命,它就会很配合。

    名人资料

    GPT-3 会大量混淆中国名人,比如郭德纲、岳云鹏;相对来说,ChatGPT 就好很多。


    现在 AI 工具与 AI 基础设施层出不穷,日新月异应接不暇,学起来既有动力也有压力。下一步希望能把所有工具的环境都搭建起来,先积累感性认识再说。