|
|
# 会话 API 实现逻辑
|
|
|
|
|
|
LobeChat 的大模型 AI 实现主要依赖于 OpenAI 的 API,包括后端的核心会话 API 和前端的集成 API。接下来,我们将分别介绍后端和前端的实现思路和代码。
|
|
|
|
|
|
#### TOC
|
|
|
|
|
|
- [后端实现](#后端实现)
|
|
|
- [核心会话 API](#核心会话-api)
|
|
|
- [会话结果处理](#会话结果处理)
|
|
|
- [前端实现](#前端实现)
|
|
|
- [前端集成](#前端集成)
|
|
|
- [使用流式获取结果](#使用流式获取结果)
|
|
|
|
|
|
## 后端实现
|
|
|
|
|
|
以下代码中移除了鉴权、错误处理等逻辑,仅保留了核心的主要功能逻辑。
|
|
|
|
|
|
### 核心会话 API
|
|
|
|
|
|
在 `src/app/api/openai/chat/route.ts` 中,定义了一个处理 POST 请求的方法,主要负责从请求体中提取 `OpenAIChatStreamPayload` 类型的 payload,并使用 `createBizOpenAI` 函数根据请求和模型信息创建 OpenAI 实例。随后,该方法调用 `createChatCompletion` 来处理实际的会话,并返回响应结果。如果创建 OpenAI 实例过程中出现错误,则直接返回错误响应。
|
|
|
|
|
|
```ts
|
|
|
export const POST = async (req: Request) => {
|
|
|
const payload = (await req.json()) as OpenAIChatStreamPayload;
|
|
|
|
|
|
const openaiOrErrResponse = createBizOpenAI(req, payload.model);
|
|
|
|
|
|
// if resOrOpenAI is a Response, it means there is an error,just return it
|
|
|
if (openaiOrErrResponse instanceof Response) return openaiOrErrResponse;
|
|
|
|
|
|
return createChatCompletion({ openai: openaiOrErrResponse, payload });
|
|
|
};
|
|
|
```
|
|
|
|
|
|
### 会话结果处理
|
|
|
|
|
|
而在 `src/app/api/openai/chat/createChatCompletion.ts` 文件中,`createChatCompletion` 方法主要负责与 OpenAI API 进行交互,处理会话请求。它首先对 payload 中的消息进行预处理,然后通过 `openai.chat.completions.create` 方法发送 API 请求,并使用 `OpenAIStream` 将返回的响应转换为流式格式。如果在 API 调用过程中出现错误,方法将生成并处理相应的错误响应。
|
|
|
|
|
|
```ts
|
|
|
import { OpenAIStream, StreamingTextResponse } from 'ai';
|
|
|
|
|
|
export const createChatCompletion = async ({ payload, openai }: CreateChatCompletionOptions) => {
|
|
|
// 预处理消息
|
|
|
const { messages, ...params } = payload;
|
|
|
// 发送 API 请求
|
|
|
try {
|
|
|
const response = await openai.chat.completions.create(
|
|
|
{
|
|
|
messages,
|
|
|
...params,
|
|
|
stream: true,
|
|
|
} as unknown as OpenAI.ChatCompletionCreateParamsStreaming,
|
|
|
{ headers: { Accept: '*/*' } },
|
|
|
);
|
|
|
const stream = OpenAIStream(response);
|
|
|
return new StreamingTextResponse(stream);
|
|
|
} catch (error) {
|
|
|
// 检查错误是否为 OpenAI APIError
|
|
|
if (error instanceof OpenAI.APIError) {
|
|
|
let errorResult: any;
|
|
|
// 如果错误是 OpenAI APIError,那么会有一个 error 对象
|
|
|
if (error.error) {
|
|
|
errorResult = error.error;
|
|
|
} else if (error.cause) {
|
|
|
errorResult = error.cause;
|
|
|
}
|
|
|
// 如果没有其他请求错误,错误对象是一个类似 Response 的对象
|
|
|
else {
|
|
|
errorResult = { headers: error.headers, stack: error.stack, status: error.status };
|
|
|
}
|
|
|
console.error(errorResult);
|
|
|
// 返回错误响应
|
|
|
return createErrorResponse(ChatErrorType.OpenAIBizError, {
|
|
|
endpoint: openai.baseURL,
|
|
|
error: errorResult,
|
|
|
});
|
|
|
}
|
|
|
console.error(error);
|
|
|
return createErrorResponse(ChatErrorType.InternalServerError, {
|
|
|
endpoint: openai.baseURL,
|
|
|
error: JSON.stringify(error),
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
```
|
|
|
|
|
|
## 前端实现
|
|
|
|
|
|
### 前端集成
|
|
|
|
|
|
在 `src/services/chat.ts` 文件中,我们定义了 `ChatService` 类。这个类提供了一些方法来处理与 OpenAI 聊天 API 的交互。
|
|
|
|
|
|
`createAssistantMessage` 方法用于创建一个新的助手消息。它接收一个包含插件、消息和其他参数的对象,以及一个可选的 `FetchOptions` 对象。这个方法会合并默认的代理配置和传入的参数,预处理消息和工具,然后调用 `getChatCompletion` 方法获取聊天完成任务。
|
|
|
|
|
|
`getChatCompletion` 方法用于获取聊天完成任务。它接收一个 `OpenAIChatStreamPayload` 对象和一个可选的 `FetchOptions` 对象。这个方法会合并默认的代理配置和传入的参数,然后发送 POST 请求到 OpenAI 的聊天 API。
|
|
|
|
|
|
`runPluginApi` 方法用于运行插件 API 并获取结果。它接收一个 `PluginRequestPayload` 对象和一个可选的 `FetchOptions` 对象。这个方法会从工具存储中获取状态,通过插件标识符获取插件设置和清单,然后发送 POST 请求到插件的网关 URL。
|
|
|
|
|
|
`fetchPresetTaskResult` 方法用于获取预设任务的结果。它使用 `fetchAIFactory` 工厂函数创建一个新的函数,这个函数接收一个聊天完成任务的参数,并返回一个 Promise。当 Promise 解析时,返回的结果是聊天完成任务的结果。
|
|
|
|
|
|
`processMessages` 方法用于处理聊天消息。它接收一个聊天消息数组,一个可选的模型名称,和一个可选的工具数组。这个方法会处理消息内容,将输入的 `messages` 数组映射为 `OpenAIChatMessage` 类型的数组,如果存在启用的工具,将工具的系统角色添加到系统消息中。
|
|
|
|
|
|
```ts
|
|
|
class ChatService {
|
|
|
// 创建一个新的助手消息
|
|
|
createAssistantMessage(params: object, fetchOptions?: FetchOptions) {
|
|
|
// 实现细节...
|
|
|
}
|
|
|
|
|
|
// 获取聊天完成任务
|
|
|
getChatCompletion(payload: OpenAIChatStreamPayload, fetchOptions?: FetchOptions) {
|
|
|
// 实现细节...
|
|
|
}
|
|
|
|
|
|
// 运行插件 API 并获取结果
|
|
|
runPluginApi(payload: PluginRequestPayload, fetchOptions?: FetchOptions) {
|
|
|
// 实现细节...
|
|
|
}
|
|
|
|
|
|
// 获取预设任务的结果
|
|
|
fetchPresetTaskResult() {
|
|
|
// 实现细节...
|
|
|
}
|
|
|
|
|
|
// 处理聊天消息
|
|
|
processMessages(messages: ChatMessage[], modelName?: string, tools?: Tool[]) {
|
|
|
// 实现细节...
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 使用流式获取结果
|
|
|
|
|
|
在 `src/utils/fetch.ts` 文件中,我们定义了 `fetchSSE` 方法,该方法使用流式方法获取数据,当读取到新的数据块时,会调用 `onMessageHandle` 回调函数处理数据块,进而实现打字机输出效果。
|
|
|
|
|
|
```ts
|
|
|
export const fetchSSE = async (fetchFn: () => Promise<Response>, options: FetchSSEOptions = {}) => {
|
|
|
const response = await fetchFn();
|
|
|
|
|
|
// 如果不 ok 说明有请求错误
|
|
|
if (!response.ok) {
|
|
|
const chatMessageError = await getMessageError(response);
|
|
|
|
|
|
options.onErrorHandle?.(chatMessageError);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const returnRes = response.clone();
|
|
|
|
|
|
const data = response.body;
|
|
|
|
|
|
if (!data) return;
|
|
|
let output = '';
|
|
|
const reader = data.getReader();
|
|
|
const decoder = new TextDecoder();
|
|
|
|
|
|
let done = false;
|
|
|
|
|
|
while (!done) {
|
|
|
const { value, done: doneReading } = await reader.read();
|
|
|
done = doneReading;
|
|
|
const chunkValue = decoder.decode(value, { stream: true });
|
|
|
|
|
|
output += chunkValue;
|
|
|
options.onMessageHandle?.(chunkValue);
|
|
|
}
|
|
|
|
|
|
await options?.onFinish?.(output);
|
|
|
|
|
|
return returnRes;
|
|
|
};
|
|
|
```
|
|
|
|
|
|
以上就是 LobeChat 会话 API 的核心实现。在理解了这些核心代码的基础上,便可以进一步扩展和优化 LobeChat 的 AI 功能。
|