分类: ai

  • 【视频教程】开发AI求职助手,一起走上全职远程之路(一)

    【视频教程】开发AI求职助手,一起走上全职远程之路(一)

    新系列简介

    开个新坑。其实类似的想法我去年就有了,但是一直没有做,除了懒之外,很大的问题就是我不会爬虫。而且在我的认识里,爬虫是一个很依赖后续维护的工作,不符合我写完能用很久的预期。不过我最近在思否看到 亮数据,似乎可以很好的弥补我的不足。于是我决定先把坑挖起来。

    本文是系列的第一集,会先介绍我的动机(找到全职远程工作);我设想的做法;介绍亮数据;分析我的代码(踩坑经验);最终初步抓取到 Vuejobs 的远程数据。

    创造新工具帮我们找到远程工作

    如何找到全职远程工作

    全职远程工作有很多好处,比如可以去泰国曼谷耍泼水节,只要安排好时间,工作娱乐两不误。很多朋友问我,怎么找到一份全职远程的工作,我有几个建议分享给大家:

    1. 不断提升自己,扩展技术栈。因为全职远程跟大公司做螺丝钉不同,更倾向于独当一面,所以你的技术栈越全面,能做的工作越多,找到全职远程工作的可能性也越大。
    2. 利用现在的工作机会,给自己打造可靠的个人品牌,形成良好的合作团队,利用好自己的工作副产品,给独立工作或者远程工作做好准备。
    3. 培养自己的自控力。远程工作比较重视结果,你越能控制自己,稳定输出,找到远程的机会也就越多。

    除去以上三点,今天要重点分享的,是如何找到尽可能多的远程工作机会;或者,要找到你需要弥补、增强的能力。这个过程,我们要学会利用好各种工具服务和提升自己。如果没有现成的工具,我们就自己开发需要的工具。

    关于亮数据(Bright data)

    我前几天在思否上看到一个小广告,叫 亮数据。看介绍,我发现它能很好的帮助我补强网络爬虫、内容抓取的能力。尤其是看其功能设计,能解决我前面说的“重维护”问题,我觉得值得一试。

    至于做什么,我觉得以前设想的“ 应用创意:AI 求职助手”很合适。只不过,我早先设想时,把简历上传、AI 分析放在第一步;现在我觉得,可以把工作机会获取、AI 分析与提示,放在第一步。即:

    1. 有一个爬虫,帮我四处收集招聘信息,尤其是全职远程
    2. AI 帮我分析 JD,并根据我的基本简历,生成求职信+针对性简历,投递
    3. AI 帮我准备面试,直至入职

    编写爬虫脚本

    想好就动手。今天的目标是做完第一步,也就是数据抓取,后面再继续做 AI 分析JD 和处理简历。我起初想用他们家的在线 IDE,尝试之后发现不太符合要求,调试起来也比较费力,遂放弃,改用亮数据浏览器(Scraping browser)。

    亮数据浏览器是他们部署在全球的服务,我们可以用 puppeteer-core 连接,然后发起请求,抓取目标网页。他们会帮我们解决一般的访问限制,甚至宣称可以通过验证码(我没试)。我觉得这样设计最大的好处是,我们可以在本地简单的开发爬虫脚本,然后直接上线使用,可以与既有的开发习惯轻松融合。

    这一步的脚本很简单,我就不详细介绍了,大家可以直接在 我的 GitHub 仓库 里查看;我的视频里也有详细讲解。这里只列举一下我踩过的坑:

    1. 连接亮数据浏览器需要使用 puppeteer-core,不能用 puppeteer,否则会超时,不知道为什么。
    2. 使用前必须付费,或者,请大家用我的 分享链接 完成注册,这样你就有 $10 的试用额度
    3. 因为 puppeteer-core 要使用 WebSocket 连接,之后每步操作也都要走 WS,所以网络就非常重要。我建议大家用云服务器来跑,我用的是博客服务器,美国 DO。
    4. 每次请求 打开一个网页,抓取一些信息。如果需要打开多个网页,就多次连接亮数据浏览器、打开页面

    配置亮数据

    调试好脚本之后,我们需要把它连接到亮数据浏览器。请大家使用我的 分享链接 完成注册,这样你就有 $10 的额度可以使用。

    登录之后,在 代理&爬虫基础设施 里找到“亮数据浏览器”,点击“开始使用”按钮,创建可用实例。

    如果参考我的脚本,可以先复制 .env.example.env,然后把用户名密码放在 BRIGHT_DATA_AUTH,把主机放在 BRIGHT_DATA_SBR_WS_ENDPOINT 即可完成配置。如果你自己编写脚本,也请注意让配置生效。

    至此,再找一台合适的服务器,就能完成抓取了。可能 Vue jobs 平日的访问量也不大,所以没有什么防护策略,至少我的简单脚本用起来没问题。如果未来遇到难抓的网站,我再尝试进阶用法。

    视频教程

    小结

    时间关系,今天先介绍第一部分,也是我最不熟悉的爬虫部分。后面会集成 AI 分析和记入数据库,那个我就比较熟了。

    对远程工作、爬虫开发、全栈开发等有兴趣、有问题的同学,欢迎留言讨论。也请大家多多支持我的文章和视频,给我动力尽快更新下一期。


    请大家使用我的分享链接注册 亮数据,这样你我都能获得 $10 的使用额度,我也会尽快更新下一篇。

  • 尝试用 DALL-E 3 制作漫画《姆伊姆伊》

    尝试用 DALL-E 3 制作漫画《姆伊姆伊》

    DALL-E 3 发布有一阵子了,今天跟一个朋友聊到 AI 制作漫画,我突然想起来,还没测试过 DALL-E 3 的漫画制作能力,所以简单试了试。我认为它的表现不错,做绘本类的产品应该问题不大。下面是我的作品。

    Once upon a time, there is a dog, his name is Muimui

    (这是我早年帮孩子写的英文演讲的作文。)

    Once upon a time, there is a dog, his name is Muimui. Muimui is a corgi.

    Muimui is a good dog, everything is fine except that he like barking very much.

    When I cleaning the room, he barks.

    When my mom and dad talk to me loudly, he barks.

    One day, I was eating hot dog bread, I like hot dog bread.

    Suddenly, my mom said to me: “Do you finish your homework?”, when I turn my head to her, I fall the hot dog bread down to the ground!

    (这张图我尝试了很多次,都没法把说话气泡指向妈妈。)

    I was shocked, and my hot dog bread fell to the floor.

    Muimui run fast to the hot dog bread immediately, grabbed the bread and happily ate it.

    This time, he didn’t bark at all!

    简评 DALL-E

    DALL-E 作为 OpenAI 的产品,它最大的优势就是可以很好的理解我们的 Prompt,不需要我们事先学习模型训练时的标记,大大降低了普通用户使用 text2img 功能的难度。Prompt 会试图把我们的 Prompt 扩写得更全面,比如第一幅图,实际生效的 Prompt 是:

    Watercolor painting of Muimui the corgi looking out of a large window in a comfortable home, with curtains gently swaying.

    如果我们要调整图片,可以继续跟 DALL-E 对话,它会自动帮我们整合 Prompt,然后输出新的图片。我们可以要求它基于某个图片进行修改,以便产出我们想要的效果。

    如果你仔细观察,会发现上面几张图片里,有几张里面的角色存在明显的特征,比如坐在沙发上的那个小男孩,戴眼镜穿衬衣,卡其色的裤子都很接近,我不知道它是怎么做到的,不过如果能维持这个稳定性,可能真的可以用来画漫画。

    我并没有尝试对角色稳定性做出约束,比如什么样的发型、什么样的衣服等,不知道会不会对输出的结果产生影响。

    不过 DALL-E 也有劣势:我们不能自己训练小模型(Lora),所以大家的产出可能会千篇一律。另外 DALL-E 有很多“安全性”预设 Prompt,我们不能随意要求它画出一些 NSFW 的作品。

    总结

    不知道读者觉得这个作品如何?欢迎留言讨论。

  • AI 产品开发小科普

    AI 产品开发小科普

    今天有个朋友问我,有没有本地运行过 AI 模型,他想做一个 text2sql 工具,自动分析数据库,然后就能自然语言查询,并进一步做智能分析,乃至逻辑推演。我分享了一些知识、理解给他。我发现,虽然我一直觉得自己懂得少,没啥可分享的;但是总有朋友比我懂得更少,需要学习和纠正。所以写一篇小科普,希望帮到大家。

    text2sql 产品的问题

    类似的产品比较多见,比如早期的明星作品 ChatExcel.com,现在 TiDB、Supabase 也都提供了 AI 生成 sql 的功能。不过我觉得都不好用,原因如下:

    1. 控制不住幻想。
    2. 没法很好的理解数据库结构,无法根据我的那些可能并不准确的字段名推断表之间的关系,并合理的输出 SQL。
    3. 没有根据环境微调,输出的代码可能没法直接跑。比如 TiDB 也会输出 PG SQL。反之亦然。

    幻想”是目前 LLM 最常见的问题。我们可以把 LLMs 理解成小学生,他们迫切地想满足用户(老师、家长)的要求(或者说他们被造出来就是这个目的)。所以,假如用户提出的需求他们无法满足,他们也不愿意给出零回答或者负面回答(“我不知道”、“我不会”、“不太可能”,等等),而是编造一个答案。在某些情况下,尤其是自然语言交互时,幻想答案可能问题不大;但是在编程领域,幻想答案就完全不可接受了。

    尤其是,如果这些工具无法预先理解数据库结构、也无法判断执行环境,幻想就会频频发生在各种不同的地方,让人防不胜防。

    朋友的想法

    他想在本地跑 Code Llama 模型,实现 text2sql。他打算做一些微调,以便突破我前面说的问题。

    之所以想在本地跑,是希望借助苹果 CPU 内存数量大的架构优势,突破 GPT 的上下文容量限制。

    他不想从 prompt 入手,主要觉得太 low,不够技术。

    我的建议

    我的观点则相反:一定要从 prompt 入手。Prompt 不是玄学,事实证明,合理优化 prompt,可以得到更好的结果。

    其次,不管哪家模型,都已经做好了完整的基础设施建设,基本上不会在生成端遇到问题。开发者只需要关心自己的业务逻辑开发。

    从 text2sql 的角度来看,fine-tuning 当然可以得到最好的结果,但是 fine-tuning 之后,数据库结构就跟模型绑定了。否则的话,一定要想办法把数据库结构解释给 LLM,也就是说,一定要把数据结构放进 prompt。这是基本原理,也意味着我们不需要绕开 prompt engineer。

    总结起来,我的看法是,如果要做这样的产品,应该:

    1. 想办法解析表结构、数据库结构
    2. 把解析结果,准确描述给 LLM(prompt)
    3. 解析用户的要求,尽量还原成数据库结构
    4. 然后一起交给 LLM 来执行

    这里就存在一些难点:

    1. 理解数据结构
    2. 理解用户的需求,和数据结构建立关联
    3. 输出正确的语句,不要出现幻觉
    4. 输出稳定,能够被你的后续处理代码正确处理

    如果模型比较好,可以节省(3)的时间。但是我猜测如果参数少,比如 7b、13b,那么(1)(2)都会很麻烦。所以,公共模型好,还是自己微调的模型好,最终效果不好判断。

    总结

    目前 LLM 尚且无法独立完成工作,应该说是我们开发者的幸运。不过,基于 LLM 开发应用跟以往不同,不太好套用以往的经验,比如上来就自建+调优。

    我建议大家要对 LLM 的基本原理有一些了解,知道 LLM、fine-tuning、embedding+searching 等之类的功能是怎么实现的,能解决什么问题,会有哪些缺陷。这样就能选择最快捷方便有效的方案。

    希望我那个朋友一切顺利。如果各位读者对 LLM 或者应用开发有什么问题,欢迎留言讨论。

  • Prompt Engineering 经验分享

    Prompt Engineering 经验分享

    我这大半年来都在围绕 ChatGPT API 做事,积累了一些 Prompt 相关的经验,大部分跟编程有关。即拿到结果后,我们不直接输出,而是使用代码处理这些结果,然后再输出。Functional Calling 对编程当然有用,不过有时候,配合文中的一些方法,可以得到更好用的结果。

    ChatGPT 的基本原理

    ChatGPT 是一个生成式大语言模型,它由海量的数据训练而来。所以当我们输入一些内容作为启动数据之后,它就会计算出来最可能最合理的新内容。比如,输入“白日依山尽”,那么最合理的接续多半是“黄河入海流”。当然,由于 ChatGPT 已经针对“聊天”这个场景做过优化,所以,他可能会多说一些过渡性的内容。

    通常来说,我们发给 ChatGPT 的内容不会这么好预期,所以它会产出的结果也存在很大变数。这对我们来说有好处也有坏处。好处是,多变的结果,会让我们有更多期待,也更有机会拿到想要的结果;坏处是,结果质量可能忽高忽低,格式也飘忽不定,难以在程序里使用。

    Prompt 入门

    要写出好的、有效的 Prompt,第一步应该去认真阅读 OpenAI 官方的 GPT最佳实践(GPT best practices)。这里我简单总结一下:

    请求里包含必要的信息

    比如我家孩子想让 GPT 帮他写作文,如果只说:“帮我写一篇作文”,效果就不好。因为作文有很多可能,不同的阶段、不同的文体、不同的主题,写出来的作文可能完全不同。

    这时候就要耐心跟 GPT 讲清楚:“我是一名小学五年级的学生,请帮我写一篇作文,大约 300 字,记录我们家过中秋节的故事,我们吃了月饼,看了晚会;我们本来想出去旅游,但是爸爸妈妈猜到处都是人,就没有出去。”

    让 GPT 扮演一个角色

    GPT 已把成千上万的角色融于一身。还是上面的例子,我家孩子如果只让 GPT 帮他写作文,能写,但是未必敢交给老师。所以此时就要让 GPT 扮演同样的小学五年级的学生来写作文。

    给 GPT 提供周边信息的时候,要把边界标清楚

    没有格式的文字,无论是人还是机器都无法理解。所以我们可以使用各种 XML 标记,或者三连引号,让 GPT 知道哪些是我们的请求、哪些是我们给它的参考资料。

    提前帮 GPT 分解任务

    GPT 目前的逻辑能力有限,如果我们有更靠谱的解法,直接教给它会更有效率。

    提供例子让 GPT 参考

    这个策略在二次开发领域会大量使用,我觉得比 functional calling 更常用。

    指定输出内容的长度

    我们知道,语言都存在信息密度,想把一件事情说清楚,可能需要很多文字;而过多的文字,也可能存在一些“废话”。所以限制输出长度往往也可以行之有效地改进结果。

    不过实际上,内容长度会跟很多因素有关,往往不能简单一限了之;如果篇幅限制,实在说不清楚,GPT 也可能会忽略我们的某个要求,大量文字一吐为快。

    我的经验

    对编程来说,稳定性非常重要,因为我们的代码无法适配各种各样千奇百怪的输出。这些输出在 ChatGPT 的聊天界面里,面向使用自然语言的普通人,其表达能力没有问题,但是对我们的程序来说,一些微微的差异也可能破坏代码功能。

    总则:把 GPT 当成态度超好但能力一般的实习生

    GPT 拥有海量的知识,但是缺少足够的逻辑思维能力去组织、架构这些知识。于是我们不能指望 GPT 能够很好的利用这些知识帮我们做事情。更多的时候,我们要先想清楚怎么做,拆解出来步骤,再把任务逐一分配给 GPT,让它尽量简单地做执行工作。

    但是 GPT 态度绝对好,绝对耐心,它可以不厌其烦的反复尝试我们交代的工作,毫无怨言。真是一个能力平平的社畜……

    如果你不知道下一步该怎么做,不妨把 ChatGPT 当成一位无法独立处理工作的实习生,尝试带领它工作,而不是期待他能解决你都不知道该怎么解决的问题。

    减少歧义,尤其是隐含的歧义

    有时候,我们的表达会有一些隐含的歧义。比如,我们去吃饭,想点一份不辣的鱼香肉丝或者回锅肉,这里面就包含歧义——按照川菜里的标准定义,鱼香肉丝和回锅肉都有辣。如果是在不常吃辣的地方,厨师可能可以试一试;如果实在巴蜀本地,那多半厨师要谢绝接待了。

    我们向 ChatGPT 提要求的时候也要注意。举个例子,我厂的产品会要求 ChatGPT 帮忙写一封信,这封信需要遵循一定格式,我们才好解析它并重新格式化。但是我们发现,GPT 在写开头(intro)的时候,经常会只写:Dear Meathill,即问候语(greetings),然后漏掉我们希望有的第一段。反复换模型也没有效果。后来我把要求改成

    intro: greetings, then one paragraph of introduction about 50 words,终于解决了问题。

    因为对于 GPT 来说,一句 greeting 也可以是 intro,只要求写 intro,它搞不清我们的目的,输出就远不如后面准确。

    使用 YAML 传递格式化数据

    JSON 格式要求很严格,很容易出错,而且在得到完整结果前,也很难解析。所以我建议大家如果需要格式化数据,不要用 JSON,用 YAML。YAML 格式更简单,不容易出错;而且 YAML 在流式传播的时候,不耽误我们实时解析并且输出,效果更好。

    比如这样:

    Please, as a Christian minister, help me choose a thought-provoking verse from the Bible, tell me why you chose it, and then write a prayer for me. Please write to me in the following YAML formats. No other content.

    “`yaml
    verse: the verse content
    reference: the verse you select for me
    thought: teach me about this verse, about 80 words
    prayer: use it to lead me to prayer, about 80 words
    “`

    控制 Prompt 的长度

    正如前面所说:

    1. ChatGPT 的推理能力并没有传说中那么强;
    2. 自然语言里难免会存在前后矛盾之处

    所以过长的 prompt 很容易导致得到不稳定、不可靠的结果。网上能找到各种洋洋洒洒一大篇的超长 Prompt,实际上以我的经验,这些 Prompt 要么实际效果一般,要么有许多限制条件并不必要。尤其是那些限制 ChatGPT 应该说这个不应该说那个的,多半因为前后矛盾实际上并未生效。

    我建议大家保证遵守上面最佳实践的六点之后,尽量用简短无歧义的语言提出要求,得到的结果会更加可靠。

    (案例待补充)

    Embedding + Searching 中文一般,英文略好

    经我们测试,中文 Embedding 的结果差强人意,检索匹配度很差,感觉跟传统关键词搜索的效果差不多,自然语言与原文表达相似的意思,但是词汇完全不同的时候,经常搜不出结果。

    英文略好一些,不过也好不到哪儿去,事实搜索强于表意搜索,做知识库知识管理的话,问题不大;期待做回复系统的话,我认为并不可行。实际上,我体验那些所谓名人聊天工具时,感觉也是如此。

    比如,类似 trickle.com 这样的知识管理工具,存进去一些统计数据,如股票价格、销量等,然后基于自然语言进行检索:“苹果股价最高时是多少?”一般来说没有问题。但是如果写日记,然后搜索:“我那天特别开心,是怎么了来着?”,就基本没有结果。

    解决方案当然也是有的,在 Embedding 存入数据库时,预设一些搜索场景,然后让 ChatGPT 帮助生成搜索辅助内容,最后一起 Embedding 存入数据库,这样搜索的时候就有更大概率能找到。比如:

    这是我的日记,请分析我日记中所表述的心情、印象、态度,概括为 10~20 个形容词。请只用 TypeScript `string[]` 的格式输出。No more other content.

    “””长假期间天气好热,想出去玩,但想到这么热人又多就懒得走了……”””。

    总结

    ChatGPT 非常强,但要让他发挥全部战力,我们开发者的努力也不可或缺。以上是我这几个月来学习总结得到的经验,希望对大家有用。也期待看到更多开发者从编程角度,分享二次开发的经验。

  • 应用创意:AI 求职助手

    应用创意:AI 求职助手

    好久没发应用创意。我之前想做一个 AI 简历工具,琢磨来琢磨去,我觉得只关注简历是不对的,太狭隘。简历本身价值很有限,作用也不太大,用户关注简历的时间很短,做简历很难做成一个长期项目,用户大概率会用完就走,也不太容易会付费。

    所以必须升级成 AI 求职助手,才有做的必要。这里把我的想法放出来跟大家交流一下。

    首先,我们来想一想,在求职中,目前通行的各环节作用是什么?我的观点是:

    1. 简历,证明我适合这个岗位。
    2. 面试,证明我的简历所言非虚。
    3. 上家公司的薪资,证明我值得这份钱。

    具体的分析可以参见 从招聘方的角度理解求职,本文不再详叙。

    有些同学不会写简历,我认为一方面因为不理解简历的作用,另一方面,现阶段各种写简历的工具过分关注于简历本身,而缺少对于写好简历的教育。

    另外,写好简历只是第一步,后面还有面试和入职,现在也缺少相应的工具来帮助用户。其实“刷题”这个需求已经被满足得很好了,但是“模拟面试”的工具,感觉还不太够。

    再及,因为我自己是远程工作,所以我比较关注这个领域。我发现很多自由职业者、独立开发者,其实也有做简历的需要。未来可能会有很多开发者不仅仅面向一份全职全时工作,而是同时在多个领域多个方向做开发,那么同时维护多份简历也很常见。

    所以我想创建这样一款工具:

    1. 不要求用户一次性完成一份简历,而是鼓励用户不断添加自己的工作履历和项目经验。
    2. 根据一些既定规则,判断一段工作履历和项目经验的描述是否合格、有何改进空间,提升每段经历的表达质量。
    3. 当我决定投递简历的时候,AI 求职助手会根据招聘目标、JD,帮我自动生成一份高质量的简历。
    4. 如果我的经历经验跟目标岗位要求不符,它会提醒我,还会告诉我应该补足哪些的知识。
    5. 它还可以针对目标岗位,给出模拟面试题,我做完面试题之后,它会给我打分。
    6. 面试之后,我们可以反馈面试结果,让 AI 求职助手帮我们判断面试表现,复盘整体表现,提升下次面试的表现。
    7. 它还可以主动出击,去全网搜集适合我的 JD,主动帮我们生成、投递简历,帮我们准备面试,帮我们复盘,等等。

    短期内这样的面试助手只会针对 Web 前端、全栈这样我最熟悉的岗位,未来可以的话,再慢慢扩展到更多的岗位(需要其它岗位上经验丰富的同学来充当内容编辑)。

    我希望这样的产品是订阅制的,按月计费。考虑到可能需要使用 GPT-4,我觉得定价太低也不行,估计得在 100~200 这个区间吧。不知道各位读者有什么意见、建议,欢迎在评论里告诉我。

    希望大家都能找到满意的工作。

  • 使用 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. 处理用户注册/登录

    建议阅读

  • 我的 AI 学习一周/月总结:四月三场 Hackathon

    我的 AI 学习一周/月总结:四月三场 Hackathon

    这篇文章在我草稿箱里躺了一个月,因为种种原因,内容不够充足,就一直没发。周末想了想,还是补一些内容把它发出来。经过连续几个月的高强度信息获取、吸收,我觉得现在已经进展到必须躬行实践的阶段。未来类似这种信息汇编、浅尝辄止的博客会减少,还是回到案例分析、实践分享的老路上吧。

    最近几周,我一直在努力 release 产品,先死磕计费系统、然后跟 Chrome Extension 在各个网站里搏斗,基本上眼睛一睁一闭,一天就过去了;与此同时,之前报名参加 Hackathon 开始进入正赛阶段,最近三周一周一场,时间非常紧张,周更文章根本来不及……

    不过笔记该写还得写,近期也学到一些东西,记一记。

    我不看好 AutoGPT

    近期最火的产品应该是 AutoGPT。顾名思义,AutoGPT 就是在 ChatGPT 的基础上,让 AI 自己决策、自己尝试、自己评估。这样,我们只需要告诉 AI 我们的需求,然后等待,就能拿到自己需要的东西。

    理想状态,尤其是电影里,这样当然是可行的,然后天网就来了……但是我认为,现阶段这种尝试就是纯烧钱,效果跟猴子打字机差不多。我甚至怀疑这个产品是 OpenAI 自己放出来做压力测试的。

    ChatGPT 的确从人类生产的文本知识里获得到了很多经验,以至于可以回答比如“我想做一个XXX,我应该怎么做”这样的问题。我们普通人多半也是这么开始工作的:先找出一些方案,然后尝试这些方案;遇到问题就再想方案,再解决;重复若干次,直至达成主要目的。似乎用 ChatGPt 也能做到这一点。

    但是我们的决策大量依赖经验、和人类与生俱来的直觉(生物进化 37 亿年得到)。很多时候,经验丰富的老手和新人之间的区别,就是判断哪些方向可以尝试、哪些方向应该优先尝试。所以我们可以把 ChatGPT 当成一名新晋毕业生,刚刚学了一堆知识,但是实操经验几乎为零,然后你告诉他:按照你的想法随便搞,预算不限。结果可能好么?

    我看好在 ChatGPT 等 LLM 加持下,胶水层、DSL、专家系统能够达到新高度,但我不看好 AutoGPT 这样的无头苍蝇。

    OpenAI 发布 Embedding 指南

    openai-cookbook/Question_answering_using_embeddings.ipynb at main · openai/openai-cookbook · GitHub

    昨天 OpenAI 发布了 Embedding 指南,其中一个要点:为什么(Embedding)搜索要好于微调(fine-tuning)?

    文中有个比喻:模型权重就像长期记忆,微调模型就像是为了 闭卷 考试而学习,到了考试的时候,记忆还是会混乱,还是会做错题。搜索就像是开卷考试,通过携带信息(现场翻书),答案会更准确,通常得分也更高。

    显然,使用 Embedding 搜索时,发送的内容会更多,能用到的上下文就会更受限,而且价格会更高。所以最终怎么选择,需要我们自己权衡。

    这篇文章还提到了 Embedding 不适用的场合,以及如何 debug+调优,非常值得一看,推荐给大家。

    四月份参加三场 AIGC Hackathon

    四月连续参加三场 Hackathon,连续三周都在各种赶工,累得够呛,也是本系列文章搁置的原因。我在 复盘文章 里比较详细的记录了参赛过程和作品设计开发,感兴趣的同学可以去看看。简单来说,我们努力开发了一款 web app,叫做 拜拜,可以让用户在任何场合都能求神拜佛,并得到一些心灵的慰藉。

    目前我们已经让神佛可以用语音的方式跟信众交流,结合回声效果,实测效果不错,欢迎大家试用、反馈。

    下一步我们会添加用户体系,并先在大陆地区以外上线。大陆地区以内当然也不会放弃,不过要确保我们合法合规,肯定会慢很多。所以,一步一步来吧,顺利的话,我们希望今年能够提交到各种市场、平台上线;不顺利的话,把主要流程做出来跑通,找个公司卖掉也好。

    一些碎碎念

    我这两天再看 Supabase 的文档,发现它们在 Supabase AI 搜索的下面增加了这样一个补充说明:

    Supabase AI is experimental and may produce incorrect answers.

    Always verify the output before executing.

    看起来,利用 ChatGPT 提升文档可用性并非一帆风顺,乱编答案的问题可能会长期困扰我们。期待大家进一步的最佳实践。

    目前更大上下文规模的模型也不断放出,比如前两天公布的 Claude,支持 100K 上下文,大家可以试一试。

    AI 倦怠期

    ChatGPT 去年引爆行业,今年过完春节破圈,接下来整个 234 月,大家都沉迷于各种 AI 新技术无法自拔。终于,过完五一之后,舆论环境终于开始降温,我相信各位读者老爷跟我的感觉应该差不多吧。

    其实也很正常,密集的投入开发之后,目前 AI 技术能干什么、不能干什么、什么能干好、花多少钱能干好之类的边界,虽然不能说非常清晰吧,但是大概在哪里,大家应该都知道了。割韭菜的那些我们不聊,只说付出辛勤劳动就能有所收获的部分,其实也不太多,或者成本不低。

    所以最近新东西不太多,更多的则是降本增效,比如用小模型换取接近的效果,比如用开源产品替代闭源产品等等。我相信,这是技术发展的必然,也是新一波创新前的宁静,后面会更好。


    总结

    地球生物的身体是自然界进化了 37 亿年得到的,我们可以非常高效地使用资源,比如一个馒头,就够我们活蹦乱跳几个小时。但是当需求上升到一定阶段后,效率就要让位给绝对值,比如硬拉世界纪录,也只有区区 500+kg,但是大型机械随便就是几十吨。

    AI 其实复刻了这条道路。它在思想深度广度上,都不如人类;但是凭借现在的算力冗余,大力出奇迹,从另一个角度超越了人类。将来怎么走,不好说,希望我们能像使用机械一样,使用 AI。

    就目前的状况来看,AI 基础设施现在已经非常好了,应用层存在大量机会。我维持之前的判断不变,建议大家有机会多多接触,早日加入。

  • 使用 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 方案感兴趣或者有问题,欢迎留言讨论。

  • 我的 AI 学习一周总结:ChatGPT 开放插件系统

    我的 AI 学习一周总结:ChatGPT 开放插件系统

    本周主攻产品的注册、付费、计费系统,遇到不少问题,投入到 AI 学习的时间不太多,本来以为攒不够内容,没想到临近周末 OpenAI 又放了个大招,所以赶紧表达下我的观点,把这周总结发了。

    好好学习,天天向上。卷到最后,就是胜利。

    OpenAI 发布 ChatGPT 插件系统

    今天早上醒来,到处是 ChatGPT 插件系统的新闻,官方新闻在这里:

    We’ve implemented initial support for plugins in ChatGPT. Plugins are tools designed specifically for language models with safety as a core principle, and help ChatGPT access up-to-date information, run computations, or use third-party services.

    ChatGPT plugins (openai.com)

    除了一般的能力整合,更吸引我们注意的是,这次 OpenAI 还公开了一些带来更多可能性的功能(通过官方插件,不知道会不会开放给第三方)。

    网络浏览能力 Browsing

    类似 New Bing,这个功能让 ChatGPT 可以从互联网上获取资讯,然后结合强大的文本理解和推理能力,一方面对接用户的自然语言询问,另一方面代替用户从茫茫文海中查找合适的内容。

    这个能力十分让人期待,不过也让我隐隐有些担心:大家都依赖 ChatGPT 快速获得答案之后,互联网信息污染会不会越来越容易,也越来越容易造成破坏了?

    另外一个问题。ChatGPT 能否像搜索引擎那样广开善源,即从大量信息源总结出结果,而不是只有几个少数的信息源,比如 CNN?如果信息来源很多,它能不能正确的总结和分辨?又能不能在合适的时间内给出结果呢?有待观察。

    网络沙箱执行能力 Code interpreter

    GPT 的模型本身就能很好的理解和生成代码,但由于它并不是真的理解语法规范,所以生成的代码能不能执行比较看运气。如今,通过 Code interpreter,它可以在页面沙箱里执行的 python 代码,于是至少有三个方面的改善:

    1. 解决数学问题。以前逢数学题必错,将来不会了。
    2. 直接进行数据分析和可视化
    3. 文件类型转换

    信息提取 Retrieval

    ChatGPT Retrieval 插件可以访问个人和组织专属信息源,根据用户自己的数据生成更精准的回答。这个产品是开源的,可以部署在用户自己的环境里,相当炸裂,这下 LangChain 和 LlamaIndex 岌岌可危,我之前想搞的 Second Me 也可以换个方向了。

    但是,这也印证了开发者的担忧:大厂偷家怎么办?本来核心技术就在大厂,我们辛辛苦苦帮他们把产品方案趟出来,裤衩一声,大厂发布了自己的版本,我们呢?

    我对 ChatGPT 插件系统的想法

    插件系统会把用户留在 ChatGPT,大大威胁 Google 和其它传统入口

    之前 ChatGPT 最大的问题就是数据太旧,很多知识它都不知道,还特别喜欢编造结果,使得我们不能依赖它做决策。如今它可以从互联网上获得资讯,理论上可靠性大大提升;而且它的界面是自然语言,非常友好。未来可能会有越来越多的人依赖 https://chat.openai.com/chat 获取信息、做决策;也会有越来越多的语音助手接入他们的服务。于是用 Google、百度、Bing 获取信息的人就会减少,使用 hao123 这种目录网站的人几乎就是史前动物了。我认为对 Google 他们是大利空。

    可以想像,就像抖音快手分别带火大量新品牌一样,我认为所有厂商都不会放过这个机会,大家一定会涌入 ChatGPT 插件市场,试图用 AI 给自己引入新的用户。未来,CEO(Chat Engine Optimization)可能会和 SEO 一样重要。比如,通过在知乎问答“脸上长痘怎么办”里注入自己的品牌内容,可能比做一堆垃圾内容农场,更有价值。这么一想,利好知乎、Quora。

    插件系统并不会威胁其它 AI 厂商

    我倒不觉得插件系统会对其它 AI 厂商带来什么威胁——或者不如说,不会让威胁更大,因为模型领先太多。相反,ChatGPT 很可能通过自己的发展,给其它厂商打了个样出来,让大家知道怎么做产品。

    比如,多模态重要还是接入互联网重要?我觉得可能是后者,至少对百度而言,过去一周被反复处刑的文生图功能,是不是可以先放放?集中精力优化大模型,然后多做一些应用层的接口,可能更受市场欢迎。

    API 能不能用插件系统,怎么用?

    作为应用开发者,我觉得,插件能带来的功能,我用 App、自己的网站、浏览器扩展,都可以完成。(正如我在 之前的文章 说过的那样)。只要能获得合适的收益,在 iOS、Web、还是 ChatGPT 平台上做产品,对我来说都差不多,都挺好。

    所以我更关心技术天花板在哪里。比如访问网络的能力,我能不能在 API 里使用?怎么用?这关系到我能否拥有自己的产品入口,以及我应该怎么搭建我的基础设施。

    OpenAI 的云够用么?面向公共开放后,收费贵么?

    现在几乎所有 AI 产品都在排队,OpenAI 更是排队大户。我觉得除了产品本身的成熟度之外,他们的云服务承载能力和运营成本也是排队的一个重要原因。

    假设将来产品基本成熟了,单客成本会是多少呢?有多少用户能用上呢?我觉得也是个问题。所有的工具,都会加剧人与人之间的差异,将来善用 AI 工具的人一定比全手工的人高效很多,那么 AI 会不会导致新的不平等呢?

    Stable Diffusion 小进展

    这些问题可能都是国内才会遇到的,即然我是简中写作,那可能还有些作用吧。我们使用的是 AutoDL 的服务器,看起来是建在华为云上。服务器官方提供代理,可以加速 GitHub 访问,但是我们这个区刚好没有,惨兮兮……当真是没有困难创造困难也要上。

    安装扩展

    SD webui 默认仅允许本地环境在线安装扩展,我们部署在服务器上的环境必须添加 --enable-insecure-extension-accessCOMMANDLINE_ARGS 里才可以。配置并重启服务之后就可以了。

    直接 clone https 协议的仓库可能会被墙影响,然而默认情况下,基本上所有扩展都要依赖 git clone https://github.com/xxxx 安装。此时我的解决方案是先生成 ssh-key:

    ssh-keygen -t ed25519 -C "meathill+sd@gmail.com"

    然后把 key 添加到 GitHub。再接着把 ssh 协议的仓库链接复制到“Extension > Install from URL”里安装,即可。

    安装完成后,很多时候仅重载前端界面不行,还是要重启整个服务。

    面部修复

    AutoDL 提供的镜像默认不包含面部修复功能,在第一次使用该功能时,SD webui 会尝试下载对应的包。因为服务器在墙内,下载地址在 GitHub,所以速度很慢,可能会超时或者断线失败。此时可以手动辅助处理,先下载到本地,然后上传到服务器。大约有以下几个文件:

    使用 Vercel Edge Function 调用 OpenAI API 的经验

    我总结了最近几周的开发经验,主要是使用 Vercel Edge Function 提供 API 服务时踩过的坑,汇集成一篇博客,推荐有类似需求、做类似技术选型的同学参考:

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

    一堆新品

    各大厂商发布了不少围绕 AI 打造的新品和改进的老产品,正如下图所示:

    这里简单列几个,大家有空可以试试。不过大部分都只是有限开放,要先加入 wishlist,等排队。

    • GitHub Copilot X 更好的代码辅助工具,可惜只能用在 VS Code
    • 文心一言 百度的竞品,目前还不能打,不过持续关注吧
    • Office Copilot 整合 GPT 到 Office 里,可能产生质的变化
    • Google Bard Google 的竞品,好像也要差一些
    • Adobe Firefly Adobe 把绘图类 AIGC 整合到自家的产品里,大大降低美术制图的门槛

    市场很热,OpenAI 的掌舵者对节奏的把握也很强,隔三差五就把大家刺激一波。我的观点还是:书上得来终觉浅,绝知此事要躬行。工程上总会有各种各样奇奇怪怪的问题等待我们去解决,有些可以用 AI 加速,有些则需要自己摸索。所以对大家来说,越早动手,工程积累越多,优势越大。

    大家加油吧。有空别忘了去排队。

  • 使用 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 他试过,没啥特别的。我的观点不是这样。所谓纸上得来终觉浅,绝知此事要躬行。很多东西,确实不复杂,照着官方教程弄,三下五除二,在本地跑起来,并不难。但是想弄得干净利索,在生产环境里跑顺,遇到什么问题都能快速解决,也不是随随便便就能搞定的。

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