借助Cloudflare Workers打造一个专属OpenAI API
目前已上线积薪,未来会支持本博客
开发
-积薪有一个功能,即根据文章内容生成分类、标签和摘要,此前使用的是百度的API。这也成了反馈最为集中的功能,因为百度的AI实在是太拉垮了。
不仅分类经常出错,摘要也经常是驴唇不对马嘴,甚至就是文章内容的随机拼凑,连语序通顺都做不到。
当初选百度纯粹是因为用起来方便,但这个准确度实在太差。GPT的准确度比较高,不如把AI接口迁移到OpenAI上。
但使用OpenAI可以说是穿墙领域最难的事了,你要同时对抗两个大国。如果只是单纯地调用API,很容易遭到封禁。
于是利用Cloudflare或Vercel这种具有边缘计算功能的平台来调用API,成了最方便稳妥的选择。
本文将示范如何通过Workers搭建一个你的专属OpenAI API。
前提
首先你需要一个OpenAI的账号,搞定支付相关事项。至于怎么搞定,你自己想办法。
然后你需要一个Cloudflare账号。
安装wrangler,在本地编写Workers代码:
npm install wrangler
然后新建一个Workers项目:
npm create cloudflare@latest
通用AI接口
workers.ts 是主入口,所有请求都会经过这里。
不过先不用管它,我们从最末端的功能写起。
我希望这个接口能更通用,通过不同的路由指向不同的prompt。比如请求 /summary 并发送一段文本,就会返回摘要;请求 /category 会返回这段文本的分类。
这就需要有一个函数来负责这项工作,接收两个参数:prompt和content。
// 接收一个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展示的摘要。
// 导入刚才的通用函数
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 的时候能访问到相应代码。
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 的代码是这样的:
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服务。这是个大工程,不知道什么时候能完成。