javascript - Hur kan jag skicka strömdat till en Nuxt3-applikation via API?


Hej, jag sitter fast med att skapa ett API för att returnera uppdelad data till Nuxt3-frontenden i realtid. Jag försöker faktiskt implementera strömning av ChatGPT API-svar som denna git-kod uppnår för Next.js i min Nuxt3-app.

https://github.com/shivanshubisht/chatgpt

Följande är den dataflöde som jag försöker få att ske.

(a) Nuxt3 frontend <-> (b) Wrapper API som körs på nitro för Nuxt3 <-> (c) ChatGPT API-hanterare på nitro <-> (d) ChatGPT API

Miljö: "nuxt": "^3.2.3", Windows 11, node.js 16

Jag har ett problem med datatransfer mellan (b) wrapper och (c) handler, medan (c) handler själv fungerar perfekt för att hämta strömmad data från ChatGPT API i realtid. Det verkar finnas ett problem antingen/eller båda med (c) att returnera samma dela data som den mottog från ChatGPT och/eller (b) att ta emot data från (c) i ström. Jag gissar att båda har problem, dock.

Här är de koder som jag tror har några problem:

(b) Wrapper API: /server/api/response.post.ts

import { OpenAIStream, OpenAIStreamPayload } from '@/server/api/openaistream'

type RequestData = {
  currentModel: string
  message: string
}

export default defineEventHandler(async (event) => {
  const { message } = await readBody(event);

  const payload: OpenAIStreamPayload = {
    model: 'gpt-3.5-turbo',
    messages: [{ role: 'user', content: message }],
    temperature: 0.7,
    top_p: 1,
    frequency_penalty: 0,
    presence_penalty: 0,
    max_tokens: 500,
    stream: true,
    n: 1,
  }

  const stream = await OpenAIStream(payload);
  return new Response(stream);
})

(c) ChatGPT-hanterare: /server/api/openaistream.ts

import {
  createParser,
  ParsedEvent,
  ReconnectInterval,
} from 'eventsource-parser';

export type ChatGPTAgent = 'user' | 'system';
export interface ChatGPTMessage {
  //abbreviated
}
export interface OpenAIStreamPayload {
  //abbreviated
}

export async function OpenAIStream(payload: OpenAIStreamPayload) {
  const encoder = new TextEncoder();
  const decoder = new TextDecoder();

  let counter = 0;

  const res = await fetch('https://api.openai.com/v1/chat/completions', {
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ''}`,
    },
    method: 'POST',
    body: JSON.stringify(payload),
  });

  const stream = new ReadableStream({
    async start(controller) {
      // callback
      function onParse(event: ParsedEvent | ReconnectInterval) {
        if (event.type === 'event') {
          const data = event.data;
          // https://beta.openai.com/docs/api-reference/completions/create#completions/create-stream
          if (data === '[DONE]') {
            console.log("END!");
            controller.close();
            return;
          }
          try {
            const json = JSON.parse(data);
            const text = json.choices[0].delta?.content || '';
            // check if it receives chunked data from chatgpt API
            console.log(text);
            if (counter < 2 && (text.match(/\n/) || []).length) {
              // this is a prefix character (i.e., "\n\n"), do nothing
              return;
            }
            const queue = encoder.encode(text);
            controller.enqueue(queue);
            counter++;
          } catch (e) {
            // maybe parse error
            controller.error(e);
          }
        }
      }

      for await (const chunk of res.body as any) {
        //check if it can decode the chunk data to send as stream data
        console.log(decoder.decode(chunk))
        parser.feed(decoder.decode(chunk));
      }
    },
  });

  return stream;
}

Jag antar att h3/nitro för Nuxt3 kan fungera annorlunda när det gäller hantering av strömmad data från Next.js/Vercel, men jag kan inte lista ut hur det borde fungera på grund av mycket få resurser. Kan någon vänligen dela några tankar eller förslag? Tack!

API-hanteraren skickar korrekt uppdelad strömningsdata till en annan API som körs på Nitro i realtid.

Svar #1

Självlöst. Ett annat paket för Next.js fungerar på något sätt: https://github.com/gptlabs/openai-streams

Kombinationen av sendStream, fetch och getReader fungerar perfekt. Nedan är ett exempel på kod för framtida referens för Nuxt-användare.

/server/api/text.get.js

import { OpenAI } from "openai-streams/node";

export default defineEventHandler(async (event) => {
  const decoder = new TextDecoder('utf-8');
  console.log("received request!");
  const stream = await OpenAI(
    "chat",
    {
      model: "gpt-3.5-turbo",
      messages: [
        { "role": "user", "content": "test input" }
      ],
      stream: true
    },
  );
  return sendStream(event, stream);
});

/test.vue

<script>
const keyword = ref('');
var generateText = ref('');

const handleClick = async () => {
  const completion = await fetch('/api/test');

  const reader = completion.body.getReader();
  const decoder = new TextDecoder('utf-8');
  const read = async () => {
      const { done, value } = await reader.read();
      if (done) {
        console.log("release locked");
        return reader.releaseLock();
      }
      
      const chunk = decoder.decode(value, { stream: true });
      const temp = chunk.replace(/\}/g, '},'); //JSON.parse(JSON.stringify(chunk));
      const jsonData = temp
        .split(',')
        .map((data) => {
          const trimData = data.trim();
          console.log(trimData);
          if (trimData === '') return undefined;
          if (trimData === '{"content":"\n\n"}') return undefined;
          if (trimData === '[DONE]') return undefined;
          return trimData;
        })
        .filter((data) => data);
      
      var textOutput = ''

      for (let i = 0; i < jsonData.length; i++) {
        try {
          if (JSON.parse(jsonData[i]).content === "\n\n") {
            textOutput = '';
          } else {
            textOutput = JSON.parse(jsonData[i]);
          }
        } catch(e) {
          console.log(e);
        }

        if (textOutput.content !== undefined) {
          generateText.value = generateText.value + textOutput.content;
        }
      }
      return read();
    };
    await read();
}
</script>

Relaterade artiklar