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>
