mirror of
https://github.com/NVIDIA/dgx-spark-playbooks.git
synced 2026-04-23 10:33:51 +00:00
201 lines
6.7 KiB
TypeScript
201 lines
6.7 KiB
TypeScript
//
|
|
// SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { llmService, LLMMessage } from '@/lib/llm-service';
|
|
|
|
/**
|
|
* API endpoint for batch Ollama operations
|
|
* POST /api/ollama/batch - Process multiple texts in batch for triple extraction
|
|
*/
|
|
|
|
interface BatchTripleRequest {
|
|
texts: string[];
|
|
model?: string;
|
|
temperature?: number;
|
|
maxTokens?: number;
|
|
concurrency?: number;
|
|
}
|
|
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
const {
|
|
texts,
|
|
model = 'qwen3:1.7b',
|
|
temperature = 0.1,
|
|
maxTokens = 8192,
|
|
concurrency = 5
|
|
}: BatchTripleRequest = await req.json();
|
|
|
|
if (!texts || !Array.isArray(texts) || texts.length === 0) {
|
|
return NextResponse.json({
|
|
error: 'Texts array is required and must not be empty'
|
|
}, { status: 400 });
|
|
}
|
|
|
|
if (texts.length > 100) {
|
|
return NextResponse.json({
|
|
error: 'Batch size limited to 100 texts maximum'
|
|
}, { status: 400 });
|
|
}
|
|
|
|
// Validate all texts are strings
|
|
const invalidTexts = texts.filter(text => !text || typeof text !== 'string');
|
|
if (invalidTexts.length > 0) {
|
|
return NextResponse.json({
|
|
error: `Invalid texts found at indices: ${texts.map((text, i) =>
|
|
(!text || typeof text !== 'string') ? i : null
|
|
).filter(i => i !== null).join(', ')}`
|
|
}, { status: 400 });
|
|
}
|
|
|
|
console.log(`Starting batch triple extraction for ${texts.length} texts using model ${model}`);
|
|
|
|
// Create system prompt for triple extraction
|
|
const systemPrompt = `You are a knowledge graph builder that extracts structured information from text.
|
|
Extract subject-predicate-object triples from the following text.
|
|
|
|
Guidelines:
|
|
- Extract only factual triples present in the text
|
|
- Normalize entity names to their canonical form
|
|
- Return results in JSON format as an array of objects with "subject", "predicate", "object" fields
|
|
- Each triple should represent a clear relationship between two entities
|
|
- Focus on the most important relationships in the text`;
|
|
|
|
// Prepare batch messages
|
|
const messagesBatch: LLMMessage[][] = texts.map(text => [
|
|
{
|
|
role: 'system' as const,
|
|
content: systemPrompt
|
|
},
|
|
{
|
|
role: 'user' as const,
|
|
content: `Extract triples from this text:\n\n${text}`
|
|
}
|
|
]);
|
|
|
|
// Process batch with Ollama
|
|
const batchResult = await llmService.generateOllamaBatchCompletion(
|
|
model,
|
|
messagesBatch,
|
|
{ temperature, maxTokens, concurrency }
|
|
);
|
|
|
|
// Parse responses to extract triples
|
|
const processedResults = batchResult.results.map((response, index) => {
|
|
let triples = [];
|
|
|
|
if (response) {
|
|
try {
|
|
// Try to parse as JSON first
|
|
const jsonMatch = response.match(/\[[\s\S]*\]/);
|
|
if (jsonMatch) {
|
|
triples = JSON.parse(jsonMatch[0]);
|
|
} else {
|
|
// Fallback: parse line by line
|
|
triples = parseTriplesFallback(response);
|
|
}
|
|
} catch (parseError) {
|
|
console.warn(`Failed to parse response for text ${index}:`, parseError);
|
|
triples = parseTriplesFallback(response);
|
|
}
|
|
}
|
|
|
|
return {
|
|
textIndex: index,
|
|
originalText: texts[index].substring(0, 200) + (texts[index].length > 200 ? '...' : ''),
|
|
triples: triples.map((triple: any, tripleIndex: number) => ({
|
|
...triple,
|
|
confidence: 0.8, // Default confidence for Ollama extractions
|
|
metadata: {
|
|
entityTypes: [],
|
|
source: texts[index].substring(0, 100) + '...',
|
|
context: texts[index].substring(0, 200) + '...',
|
|
extractionMethod: 'ollama_batch',
|
|
model: model,
|
|
textIndex: index,
|
|
tripleIndex: tripleIndex
|
|
}
|
|
})),
|
|
tripleCount: triples.length,
|
|
success: !batchResult.errors.some(error => error.index === index)
|
|
};
|
|
});
|
|
|
|
// Calculate summary statistics
|
|
const totalTriples = processedResults.reduce((sum, result) => sum + result.tripleCount, 0);
|
|
const successfulTexts = processedResults.filter(result => result.success).length;
|
|
|
|
return NextResponse.json({
|
|
results: processedResults,
|
|
summary: {
|
|
totalTexts: texts.length,
|
|
successfulTexts: successfulTexts,
|
|
failedTexts: batchResult.errors.length,
|
|
totalTriples: totalTriples,
|
|
averageTriples: successfulTexts > 0 ? (totalTriples / successfulTexts).toFixed(2) : 0
|
|
},
|
|
batchInfo: {
|
|
model: model,
|
|
concurrency: concurrency,
|
|
processingTime: Date.now(), // Could be enhanced with actual timing
|
|
method: 'ollama_batch'
|
|
},
|
|
errors: batchResult.errors,
|
|
success: true
|
|
});
|
|
} catch (error) {
|
|
console.error('Error in Ollama batch triple extraction:', error);
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Failed to process batch triple extraction with Ollama',
|
|
details: error instanceof Error ? error.message : String(error)
|
|
},
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Fallback parser for when JSON parsing fails (reused from single endpoint)
|
|
function parseTriplesFallback(text: string): Array<{subject: string, predicate: string, object: string}> {
|
|
const triples = [];
|
|
const lines = text.split('\n');
|
|
|
|
for (const line of lines) {
|
|
// Look for patterns like "Subject - Predicate - Object" or similar
|
|
const tripleMatch = line.match(/^[\s\-\*\d\.]*(.+?)\s*[\-\|]\s*(.+?)\s*[\-\|]\s*(.+)$/);
|
|
if (tripleMatch) {
|
|
triples.push({
|
|
subject: tripleMatch[1].trim(),
|
|
predicate: tripleMatch[2].trim(),
|
|
object: tripleMatch[3].trim()
|
|
});
|
|
}
|
|
|
|
// Also look for JSON-like objects in the text
|
|
const jsonObjectMatch = line.match(/\{\s*"subject"\s*:\s*"([^"]+)"\s*,\s*"predicate"\s*:\s*"([^"]+)"\s*,\s*"object"\s*:\s*"([^"]+)"\s*\}/);
|
|
if (jsonObjectMatch) {
|
|
triples.push({
|
|
subject: jsonObjectMatch[1],
|
|
predicate: jsonObjectMatch[2],
|
|
object: jsonObjectMatch[3]
|
|
});
|
|
}
|
|
}
|
|
|
|
return triples;
|
|
}
|