diff --git a/README.md b/README.md index a70485bf6..d2149b6a6 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ - 网络请求 - [WebBrowser](https://js.langchain.com/docs/api/tools_webbrowser/classes/WebBrowser) - + - 其它 - [Wiki](https://js.langchain.com/docs/api/tools/classes/WikipediaQueryRun) - DALL-E @@ -106,6 +106,8 @@ [English > FAQ](./docs/faq-en.md) +[Azure OpenAI](./docs/azure-openai-cn.md) + ## 配置页面访问密码 > 配置密码后,用户需要在设置页手动填写访问码才可以正常聊天,否则会通过消息提示未授权状态。 diff --git a/docs/azure-openai-cn.md b/docs/azure-openai-cn.md new file mode 100644 index 000000000..52d733b66 --- /dev/null +++ b/docs/azure-openai-cn.md @@ -0,0 +1,186 @@ +# Azure OpenAI 支持 + +该方案为临时方案,并不保证稳定,也会不定期更新。 + +如使用出现问题请提 [Issues](https://github.com/Hk-Gosuto/ChatGPT-Next-Web-LangChain/issues)。 + +## 如何配置 + +首先根据 [cf-openai-azure-proxy](https://github.com/haibbo/cf-openai-azure-proxy/blob/main/README.md) 文档先部署一个 proxy。 + +之后替换 cf-openai-azure-proxy.js 为如下内容: + +```js +// Last Modified Time 2023-09-22 17:30:47 +// The name of your Azure OpenAI Resource. +const resourceName=RESOURCE_NAME + +// The deployment name you chose when you deployed the model. +const mapper = { + 'gpt-3.5-turbo': DEPLOY_NAME_GPT35, + 'gpt-3.5-turbo-16k': DEPLOY_NAME_GPT35_16K, + 'gpt-4': DEPLOY_NAME_GPT4, + 'gpt-4-32k': DEPLOY_NAME_GPT4_32K, +}; + +const apiVersion = "2023-09-01-preview" + +addEventListener("fetch", (event) => { + event.respondWith(handleRequest(event.request)); +}); + +async function handleRequest(request) { + if (request.method === 'OPTIONS') { + return handleOPTIONS(request) + } + + const url = new URL(request.url); + if (url.pathname.startsWith("//")) { + url.pathname = url.pathname.replace('/', "") + } + if (url.pathname === '/v1/chat/completions') { + var path = "chat/completions" + } else if (url.pathname === '/v1/completions') { + var path = "completions" + } else if (url.pathname === '/v1/models') { + return handleModels(request) + } else { + return new Response('404 Not Found', { status: 404 }) + } + + let body; + if (request.method === 'POST') { + body = await request.json(); + } + + const modelName = body?.model; + const deployName = mapper[modelName] || '' + + if (deployName === '') { + return new Response('Missing model mapper', { + status: 403 + }); + } + const fetchAPI = `https://${resourceName}.openai.azure.com/openai/deployments/${deployName}/${path}?api-version=${apiVersion}` + + const authKey = request.headers.get('Authorization'); + if (!authKey) { + return new Response("Not allowed", { + status: 403 + }); + } + + const payload = { + method: request.method, + headers: { + "Content-Type": "application/json", + "api-key": authKey.replace('Bearer ', ''), + }, + body: typeof body === 'object' ? JSON.stringify(body) : '{}', + }; + + let response = await fetch(fetchAPI, payload); + response = new Response(response.body, response); + response.headers.set("Access-Control-Allow-Origin", "*"); + + if (body?.stream != true) { + return response + } + + let { readable, writable } = new TransformStream() + stream(response.body, writable); + return new Response(readable, response); +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// support printer mode and add newline +async function stream(readable, writable) { + const reader = readable.getReader(); + const writer = writable.getWriter(); + + // const decoder = new TextDecoder(); + const encoder = new TextEncoder(); + const decoder = new TextDecoder(); + // let decodedValue = decoder.decode(value); + const newline = "\n"; + const delimiter = "\n\n" + const encodedNewline = encoder.encode(newline); + + let buffer = ""; + while (true) { + let { value, done } = await reader.read(); + if (done) { + break; + } + buffer += decoder.decode(value, { stream: true }); // stream: true is important here,fix the bug of incomplete line + let lines = buffer.split(delimiter); + + // Loop through all but the last line, which may be incomplete. + for (let i = 0; i < lines.length - 1; i++) { + if (lines[i].includes('"delta"') || lines[i].includes('[DONE]')) { + await writer.write(encoder.encode(lines[i] + delimiter)); + await sleep(20); + } + } + + buffer = lines[lines.length - 1]; + } + + if (buffer) { + await writer.write(encoder.encode(buffer)); + } + await writer.write(encodedNewline) + await writer.close(); +} + +async function handleModels(request) { + const data = { + "object": "list", + "data": [] + }; + + for (let key in mapper) { + data.data.push({ + "id": key, + "object": "model", + "created": 1677610602, + "owned_by": "openai", + "permission": [{ + "id": "modelperm-M56FXnG1AsIr3SXq8BYPvXJA", + "object": "model_permission", + "created": 1679602088, + "allow_create_engine": false, + "allow_sampling": true, + "allow_logprobs": true, + "allow_search_indices": false, + "allow_view": true, + "allow_fine_tuning": false, + "organization": "*", + "group": null, + "is_blocking": false + }], + "root": key, + "parent": null + }); + } + + const json = JSON.stringify(data, null, 2); + return new Response(json, { + headers: { 'Content-Type': 'application/json' }, + }); +} + +async function handleOPTIONS(request) { + return new Response(null, { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': '*', + 'Access-Control-Allow-Headers': '*' + } + }) +} +``` +