first-update
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db/index';
|
||||
import LLMClient from '@/lib/llm/core/index';
|
||||
import { getModelConfigById } from '@/lib/db/model-config';
|
||||
|
||||
/**
|
||||
* Stream answers from two models for the current question
|
||||
*/
|
||||
export async function GET(request, { params }) {
|
||||
const { projectId, taskId } = params;
|
||||
|
||||
try {
|
||||
if (!projectId || !taskId) {
|
||||
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Fetch task
|
||||
const task = await db.task.findUnique({
|
||||
where: { id: taskId }
|
||||
});
|
||||
|
||||
if (!task || task.taskType !== 'blind-test') {
|
||||
return NextResponse.json({ error: 'Task not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Parse task detail
|
||||
const detail = JSON.parse(task.detail || '{}');
|
||||
const modelInfo = JSON.parse(task.modelInfo || '{}');
|
||||
const { questionIds = [], currentIndex = 0 } = detail;
|
||||
|
||||
// Check if task is completed
|
||||
if (currentIndex >= questionIds.length) {
|
||||
return NextResponse.json({ completed: true });
|
||||
}
|
||||
|
||||
// Fetch current question
|
||||
const currentQuestionId = questionIds[currentIndex];
|
||||
const currentQuestion = await db.evalDatasets.findUnique({
|
||||
where: { id: currentQuestionId }
|
||||
});
|
||||
|
||||
if (!currentQuestion) {
|
||||
return NextResponse.json({ error: 'Question not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Fetch model configs
|
||||
const [modelConfigA, modelConfigB] = await Promise.all([
|
||||
getModelConfigById(modelInfo.modelA.providerId),
|
||||
getModelConfigById(modelInfo.modelB.providerId)
|
||||
]);
|
||||
|
||||
if (!modelConfigA || !modelConfigB) {
|
||||
return NextResponse.json({ error: 'Model configuration not found' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Randomly swap positions (core blind-test behavior)
|
||||
const isSwapped = Math.random() > 0.5;
|
||||
|
||||
// Create streaming response
|
||||
const encoder = new TextEncoder();
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
// Send init message
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
JSON.stringify({
|
||||
type: 'init',
|
||||
question: currentQuestion.question,
|
||||
questionId: currentQuestion.id,
|
||||
questionIndex: currentIndex + 1,
|
||||
totalQuestions: questionIds.length,
|
||||
isSwapped
|
||||
}) + '\n'
|
||||
)
|
||||
);
|
||||
|
||||
// Prepare messages
|
||||
const messages = [
|
||||
{
|
||||
role: 'system',
|
||||
content: "You are a helpful assistant. Provide detailed and accurate answers to the user's question."
|
||||
},
|
||||
{ role: 'user', content: currentQuestion.question }
|
||||
];
|
||||
|
||||
// Create LLM clients
|
||||
const clientA = new LLMClient({
|
||||
projectId,
|
||||
...modelConfigA
|
||||
});
|
||||
|
||||
const clientB = new LLMClient({
|
||||
projectId,
|
||||
...modelConfigB
|
||||
});
|
||||
|
||||
let answerA = '';
|
||||
let answerB = '';
|
||||
const startTime = Date.now();
|
||||
|
||||
// Call both models in parallel (streaming)
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
try {
|
||||
const response = await clientA.chatStreamAPI(messages);
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
answerA += chunk;
|
||||
|
||||
// Send chunk update
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
JSON.stringify({
|
||||
type: 'chunk',
|
||||
model: isSwapped ? 'B' : 'A',
|
||||
content: chunk
|
||||
}) + '\n'
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Model A call failed:', err);
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
model: isSwapped ? 'B' : 'A',
|
||||
error: err.message
|
||||
}) + '\n'
|
||||
)
|
||||
);
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
try {
|
||||
const response = await clientB.chatStreamAPI(messages);
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
answerB += chunk;
|
||||
|
||||
// Send chunk update
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
JSON.stringify({
|
||||
type: 'chunk',
|
||||
model: isSwapped ? 'A' : 'B',
|
||||
content: chunk
|
||||
}) + '\n'
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Model B call failed:', err);
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
JSON.stringify({
|
||||
type: 'error',
|
||||
model: isSwapped ? 'A' : 'B',
|
||||
error: err.message
|
||||
}) + '\n'
|
||||
)
|
||||
);
|
||||
}
|
||||
})()
|
||||
]);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
// Send done message
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
JSON.stringify({
|
||||
type: 'done',
|
||||
duration,
|
||||
answerA: isSwapped ? answerB : answerA,
|
||||
answerB: isSwapped ? answerA : answerB
|
||||
}) + '\n'
|
||||
)
|
||||
);
|
||||
|
||||
controller.close();
|
||||
} catch (error) {
|
||||
console.error('Streaming handler failed:', error);
|
||||
controller.error(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('API error:', error);
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user