Skip to content

Commit 7fcbafa

Browse files
authored
Use direct fetch (#1193)
1 parent 056dc28 commit 7fcbafa

File tree

1 file changed

+112
-41
lines changed

1 file changed

+112
-41
lines changed

apps/sim/app/api/wand-generate/route.ts

Lines changed: 112 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -119,74 +119,145 @@ export async function POST(req: NextRequest) {
119119
`[${requestId}] About to create stream with model: ${useWandAzure ? wandModelName : 'gpt-4o'}`
120120
)
121121

122-
// Create the stream without AbortController for Node.js runtime compatibility
123-
const streamCompletion = await client.chat.completions.create({
124-
model: useWandAzure ? wandModelName : 'gpt-4o',
125-
messages: messages,
126-
temperature: 0.3,
127-
max_tokens: 10000,
128-
stream: true,
129-
stream_options: { include_usage: true },
122+
// Use native fetch for streaming to avoid OpenAI SDK issues with Node.js runtime
123+
const apiUrl = useWandAzure
124+
? `${azureEndpoint}/openai/deployments/${wandModelName}/chat/completions?api-version=${azureApiVersion}`
125+
: 'https://api.openai.com/v1/chat/completions'
126+
127+
const headers: Record<string, string> = {
128+
'Content-Type': 'application/json',
129+
}
130+
131+
if (useWandAzure) {
132+
headers['api-key'] = azureApiKey!
133+
} else {
134+
headers.Authorization = `Bearer ${openaiApiKey}`
135+
}
136+
137+
logger.debug(`[${requestId}] Making streaming request to: ${apiUrl}`)
138+
139+
const response = await fetch(apiUrl, {
140+
method: 'POST',
141+
headers,
142+
body: JSON.stringify({
143+
model: useWandAzure ? wandModelName : 'gpt-4o',
144+
messages: messages,
145+
temperature: 0.3,
146+
max_tokens: 10000,
147+
stream: true,
148+
stream_options: { include_usage: true },
149+
}),
130150
})
131151

132-
logger.info(`[${requestId}] Stream created successfully, starting response`)
152+
if (!response.ok) {
153+
const errorText = await response.text()
154+
logger.error(`[${requestId}] API request failed`, {
155+
status: response.status,
156+
statusText: response.statusText,
157+
error: errorText,
158+
})
159+
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
160+
}
161+
162+
logger.info(`[${requestId}] Stream response received, starting processing`)
133163

134-
// Create a TransformStream for Node.js runtime compatibility
164+
// Create a TransformStream to process the SSE data
135165
const encoder = new TextEncoder()
166+
const decoder = new TextDecoder()
167+
136168
const readable = new ReadableStream({
137169
async start(controller) {
170+
const reader = response.body?.getReader()
171+
if (!reader) {
172+
controller.close()
173+
return
174+
}
175+
138176
try {
139-
logger.info(`[${requestId}] Starting stream processing`)
177+
let buffer = ''
140178
let chunkCount = 0
141179

142-
for await (const chunk of streamCompletion) {
143-
chunkCount++
180+
while (true) {
181+
const { done, value } = await reader.read()
144182

145-
if (chunkCount === 1) {
146-
logger.info(`[${requestId}] Received first chunk`)
183+
if (done) {
184+
logger.info(`[${requestId}] Stream completed. Total chunks: ${chunkCount}`)
185+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true })}\n\n`))
186+
controller.close()
187+
break
147188
}
148189

149-
// Process the chunk
150-
const content = chunk.choices?.[0]?.delta?.content || ''
151-
if (content) {
152-
// Send data in SSE format
153-
const data = `data: ${JSON.stringify({ chunk: content })}\n\n`
154-
controller.enqueue(encoder.encode(data))
155-
}
156-
157-
// Check for usage data
158-
if (chunk.usage) {
159-
logger.info(`[${requestId}] Received usage data: ${JSON.stringify(chunk.usage)}`)
160-
}
161-
162-
// Log progress periodically
163-
if (chunkCount % 10 === 0) {
164-
logger.debug(`[${requestId}] Processed ${chunkCount} chunks`)
190+
// Decode the chunk
191+
buffer += decoder.decode(value, { stream: true })
192+
193+
// Process complete SSE messages
194+
const lines = buffer.split('\n')
195+
buffer = lines.pop() || '' // Keep incomplete line in buffer
196+
197+
for (const line of lines) {
198+
if (line.startsWith('data: ')) {
199+
const data = line.slice(6).trim()
200+
201+
if (data === '[DONE]') {
202+
logger.info(`[${requestId}] Received [DONE] signal`)
203+
controller.enqueue(
204+
encoder.encode(`data: ${JSON.stringify({ done: true })}\n\n`)
205+
)
206+
controller.close()
207+
return
208+
}
209+
210+
try {
211+
const parsed = JSON.parse(data)
212+
const content = parsed.choices?.[0]?.delta?.content
213+
214+
if (content) {
215+
chunkCount++
216+
if (chunkCount === 1) {
217+
logger.info(`[${requestId}] Received first content chunk`)
218+
}
219+
220+
// Forward the content
221+
controller.enqueue(
222+
encoder.encode(`data: ${JSON.stringify({ chunk: content })}\n\n`)
223+
)
224+
}
225+
226+
// Log usage if present
227+
if (parsed.usage) {
228+
logger.info(
229+
`[${requestId}] Received usage data: ${JSON.stringify(parsed.usage)}`
230+
)
231+
}
232+
233+
// Log progress periodically
234+
if (chunkCount % 10 === 0) {
235+
logger.debug(`[${requestId}] Processed ${chunkCount} chunks`)
236+
}
237+
} catch (parseError) {
238+
// Skip invalid JSON lines
239+
logger.debug(
240+
`[${requestId}] Skipped non-JSON line: ${data.substring(0, 100)}`
241+
)
242+
}
243+
}
165244
}
166245
}
167246

168-
logger.info(`[${requestId}] Stream completed. Total chunks: ${chunkCount}`)
169-
170-
// Send completion signal
171-
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true })}\n\n`))
172-
controller.close()
173-
174247
logger.info(`[${requestId}] Wand generation streaming completed successfully`)
175248
} catch (streamError: any) {
176249
logger.error(`[${requestId}] Streaming error`, {
177250
name: streamError?.name,
178251
message: streamError?.message || 'Unknown error',
179-
code: streamError?.code,
180-
status: streamError?.status,
181252
stack: streamError?.stack,
182-
useWandAzure,
183-
model: useWandAzure ? wandModelName : 'gpt-4o',
184253
})
185254

186255
// Send error to client
187256
const errorData = `data: ${JSON.stringify({ error: 'Streaming failed', done: true })}\n\n`
188257
controller.enqueue(encoder.encode(errorData))
189258
controller.close()
259+
} finally {
260+
reader.releaseLock()
190261
}
191262
},
192263
})

0 commit comments

Comments
 (0)