最近不断从几个朋友那里听到openai封锁来自大陆地址访问的账号,那么,我这里就实践一个方案,在非大陆地区架设openai的api反向代理,这样使用此代理就可以绕过封锁检查了。
如果您使用过Axios来处理HTTP请求,您可能会遇到这样的问题:某些API端点返回的数据不是常规响应,而是流式响应。但是,大多数Axios教程都假设您将获得常规JSON或XML响应,并且没有涵盖如何处理流响应的情况。这文章正是在解决openai的chat响应形式(server send event, SSE)问题。
在本文中,我们将探讨如何在Node.js中使用Axios处理混合流和非流响应的情况。具体来说,我们将看一下如何:
- 利用Axios获取流响应。
- 与普通请求混合转发openai的请求。
获取流响应
在处理流响应之前,我们需要了解如何获取它们。 在Axios中,您可以使用responseType
选项来指定期望响应类型。 默认情况下,Axios将响应解析为字符串,但是,您可以将其设置为stream
以获取流响应。
例如,以下代码将使用Axios从某个API端点获取流响应:
const axios = require('axios');
const fs = require('fs');
const response = await axios.get('https://example.com/stream', {
responseType: 'stream'
});
response.data.pipe(fs.createWriteStream('./stream.txt'));
在这个例子中,我们向Axios传递了一个responseType
选项,用于指定响应的类型为stream
。Axios会自动将响应解析为一个可读流,并将其作为data
属性返回。我们可以使用.pipe()
方法将数据写入文件。
注:如果不指定responseType,则没有pipe函数可以调用。
与普通请求混合转发openai的请求。
openai的https://api.openai.com/v1/chat/completions
,可以直接get请求,并得到401
错误报文,如下案例
{
"error": {
"message": "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.",
"type": "invalid_request_error",
"param": null,
"code": null
}
}
但在我的实践中,axios实例调用如果加上了responseType: 'stream'
,那就错误报错,一直在报网络拒绝,因此需要在代码层面兼容响应式返回。
好在chat的post报文中,有个很明显的stream: true
,利用这个字段的值,动态地设置responseType: 'stream'
,其他情况则使用responseType: 'text'
,再使用响应报文中是否有content-length
字段来判断是否要响应式返回。
最终的代码比较简单
async function transforProxy(req, res, to_url) {
const headers = req.headers
const responseType_: ResponseType = req.body.stream ? 'stream' : 'text'
// request proxy
axios.request({
method: req.method,
url: to_url,
headers,
data: req.body,
responseType: responseType_,
}).then((response) => {
res.status(response.status).set(response.headers)
if (response.headers['content-length'])
res.send(response.data) // no stream
else
response.data.pipe(res) // stream
}).catch((error) => {
if (res.headersSent) {
res.end('close stream')
}
else {
if (error.response) {
globalThis.console.log(`catch request error ${error}`)
res.status(error.response.status).set(error.response.headers).end(error.response.data)
}
else {
globalThis.console.error(`catch system error ${error}`)
res.writeHead(500, { 'Content-Type': 'text/plain' })
res.end('500 Internal Server Error')
}
}
})
}
router.all('/v1/chat/completions', async (req, res) => {
const url = `https://api.openai.com${req.url}`
await transforProxy(req, res, url)
})