开发
借助Cloudflare Workers打造一个专属OpenAI API
目前已上线积薪,未来会支持本博客
积薪功能包括根据文章内容生成分类、标签和摘要,曾使用百度API,但准确度较差。建议将AI接口迁移到OpenAI上,通过Cloudflare或Vercel方便调用。演示如何搭建专属OpenAI API,配置路由、保障安全。测试时部署到Cloudflare。这样即可获得稳定的、适用于SEO展示的摘要和其他AI服务。
- #OpenAI
- #API
- #Cloudflare
- #Workers
- #AI
2003
积薪有一个功能,即根据文章内容生成分类、标签和摘要,此前使用的是百度的API。这也成了反馈最为集中的功能,因为百度的AI实在是太拉垮了。
不仅分类经常出错,摘要也经常是驴唇不对马嘴,甚至就是文章内容的随机拼凑,连语序通顺都做不到。
当初选百度纯粹是因为用起来方便,但这个准确度实在太差。GPT的准确度比较高,不如把AI接口迁移到OpenAI上。
但使用OpenAI可以说是穿墙领域最难的事了,你要同时对抗两个大国。如果只是单纯地调用API,很容易遭到封禁。
于是利用Cloudflare或Vercel这种具有边缘计算功能的平台来调用API,成了最方便稳妥的选择。
本文将示范如何通过Workers搭建一个你的专属OpenAI API。
前提
首先你需要一个OpenAI的账号,搞定支付相关事项。至于怎么搞定,你自己想办法。
然后你需要一个Cloudflare账号。
安装wrangler,在本地编写Workers代码:
typescript
npm install wrangler
然后新建一个Workers项目:
typescript
npm create cloudflare@latest
通用AI接口
workers.ts是主入口,所有请求都会经过这里。
不过先不用管它,我们从最末端的功能写起。
我希望这个接口能更通用,通过不同的路由指向不同的prompt。比如请求/summary并发送一段文本,就会返回摘要;请求/category会返回这段文本的分类。
这就需要有一个函数来负责这项工作,接收两个参数:prompt和content。
typescript
// 接收一个propmt和内容,请求OpenAI,返回结果 interface OpenAIResponse { choices: { "finish_reason": string; "index": number; "message": { "content": string; "role": string; } } []; created: number; id: string; model: string; object: string; usage: { "completion_tokens": number; "prompt_tokens": number; "total_tokens": number; } } async function openAI(prompt: string, content: string, key: string) { const message = [ { "role": "system", "content": prompt }, { "role": "user", "content": content } ]; const apiURL = 'https://api.openai.com/v1/chat/completions'; const model = content.length > 3000 ? 'gpt-3.5-turbo-16k-0613' : 'gpt-3.5-turbo-0613'; const response = await fetch(apiURL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` }, body: JSON.stringify({ messages: message, model: model, max_tokens: 500, temperature: 0.5, stop: ['\n'] }) }); const data: OpenAIResponse = await response.json(); const result = data.choices[0].message.content; return result; } export default openAI;
这段应该不难理解。为了节省开销,我做了个简单的判断:文本长度超出一定限制时使用gpt-3.5-turbo-16k-0613模型,未超出默认使用gpt-3.5-turbo-0613。当然文本长度并不等于token,这只是个简单的判断,够用就行。
当你向该函数传入预设条件和待处理文本时,返回的就是一个纯文本。
设定prompt
通用函数写好,接下来就是处理不同接口对应的prompt了。
假设我希望在访问/seo时,返回这段文字适用于SEO展示的摘要。
typescript
// 导入刚才的通用函数 import openAI from "./open-ai"; interface Env { API_KEY: string; } const prompt = "我需要根据下面的这段文字, 针对搜索引擎优化, 写一段摘要, 要求写明文字的主要内容, 字数不得超过120个汉字"; export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const content = await request.text(); return new Response(await openAI(prompt, content, env.API_KEY), { headers: { "Content-Type": "text/plain" } }); } }
这里的prompt很重要,由于我们希望这是一个接口,输出的内容保持稳定,因此你可以在prompt的设置上多花些心思。比如你可以使用更严谨的语言:
根据文本内容,输出1-3个与内容相关的标签。格式如下: “标签1”, “标签2”
这里面可以优化的空间太多了,在此不赘述。
配置路由
接下来需要在workers.ts里进行设置,以便在请求 /seo的时候能访问到相应代码。
typescript
import generateSEO from './seo'; interface Env { API_KEY: string; } // Export a default object containing event handlers export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const url = new URL(request.url); switch (url.pathname) { case '/seo': return generateSEO.fetch(request, env, ctx); } return new Response( `This is a private API and cannot be accessed without authorization`, { headers: { 'Content-Type': 'text/html' } } ); }, };
这段代码的作用是,读取环境变量中的token,根据请求的endpoint,分发到相应处理逻辑中。你可以照此增加更多prompt和逻辑。
OpenAI的token可以在Workers的Dashboard - 设置中添加。
安全性
为了防止接口被滥用,可以稍稍增加一点安全措施。你可以随便生成一段token,放在请求头里,对于token不正确的请求一律拒绝。
最终workers.ts的代码是这样的:
typescript
import generateSEO from './seo'; interface Env { API_KEY: string; TOKEN: string; } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { // verify if the bearer token is valid const auth = request.headers.get('Authorization'); if (!auth || auth !== `Bearer ${env.TOKEN}`) { return new Response( `This is a private API and cannot be accessed without authorization`, { headers: { 'Content-Type': 'text/html' } } ); } const url = new URL(request.url); switch (url.pathname) { case '/seo': return generateSEO.fetch(request, env, ctx); } return new Response( `This is a private API and cannot be accessed without authorization`, { headers: { 'Content-Type': 'text/html' } } ); }, };
测试
为了避免封号,建议一律先部署到Cloudflare,然后在云端测试。
执行npx wrangler deploy即可上传代码。
然后在该Workers内点击“快速编辑”,就可以在窗口中进行调试了。
这样你就有了一个稳定的专属API,只要向其不同的endpoint发送文本,就会根据预设的prompt返回相应的回复。
目前这个接口已经上线积薪,但我计划把本博客的后端彻底从头开发一遍,到时候也在这里提供相应的AI服务。这是个大工程,不知道什么时候能完成。