Skip to content

Chat API.zh CN

Rdmclin2 edited this page Jun 22, 2024 · 2 revisions

会话 API 实现逻辑

LobeVidol 的大模型 AI 实现主要依赖于 OpenAI 的 API,包括后端的核心会话 API 和前端的集成 API。接下来,我们将分别介绍后端和前端的实现思路和代码。

TOC

后端实现

以下代码中移除了鉴权、错误处理等逻辑,仅保留了核心的主要功能逻辑。

核心会话 API

src/app/api/chat/openai/route.ts 中,定义了一个处理 POST 请求的方法,主要负责从请求体中提取 OpenAIChatStreamPayload 类型的 payload,并使用 createBizOpenAI 函数根据请求和模型信息创建 OpenAI 实例。随后,该方法调用 createChatCompletion 来处理实际的会话,并返回响应结果。如果创建 OpenAI 实例过程中出现错误,则直接返回错误响应。

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 调用过程中出现错误,方法将生成并处理相应的错误响应。

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。

processMessages 方法用于处理聊天消息。它接收一个聊天消息数组,一个可选的模型名称,和一个可选的工具数组。这个方法会处理消息内容,将输入的 messages 数组映射为 OpenAIChatMessage 类型的数组,如果存在启用的工具,将工具的系统角色添加到系统消息中。

class ChatService {
  // 创建一个新的助手消息
  createAssistantMessage(params: object, fetchOptions?: FetchOptions) {
    // 实现细节...
  }

  // 获取聊天完成任务
  getChatCompletion(payload: OpenAIChatStreamPayload, fetchOptions?: FetchOptions) {
    // 实现细节...
  }

  // 处理聊天消息
  processMessages(messages: ChatMessage[], modelName?: string, tools?: Tool[]) {
    // 实现细节...
  }
}

使用流式获取结果

src/utils/fetch.ts 文件中,我们定义了 fetchSSE 方法,该方法使用流式方法获取数据,当读取到新的数据块时,会调用 onMessageHandle 回调函数处理数据块,进而实现打字机输出效果。

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;
};

以上就是 LobeVidol 会话 API 的核心实现。在理解了这些核心代码的基础上,便可以进一步扩展和优化 LobeVidol 的 AI 功能。