| import { NextRequest, NextResponse } from "next/server"; |
|
|
| export const runtime = "nodejs"; |
| export const dynamic = "force-dynamic"; |
|
|
| |
| async function getClaude() { |
| const Anthropic = (await import("@anthropic-ai/sdk")).default; |
| return new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); |
| } |
|
|
| interface CompareRequest { |
| query: string; |
| adaptiveRouting?: boolean; |
| topK?: number; |
| hops?: number; |
| } |
|
|
| export async function POST(req: NextRequest) { |
| try { |
| const body: CompareRequest = await req.json(); |
| const { query, adaptiveRouting = true } = body; |
|
|
| if (!query?.trim()) { |
| return NextResponse.json({ error: "Query required" }, { status: 400 }); |
| } |
|
|
| const apiKey = process.env.ANTHROPIC_API_KEY; |
|
|
| |
| if (!apiKey) { |
| return NextResponse.json(getDemoResponse(query)); |
| } |
|
|
| const claude = await getClaude(); |
| const startTime = Date.now(); |
|
|
| |
| const baselineStart = Date.now(); |
| const baselineMsg = await claude.messages.create({ |
| model: "claude-sonnet-4-20250514", |
| max_tokens: 512, |
| system: "You are a helpful assistant. Answer the question accurately and concisely. If you don't have enough information, say so.", |
| messages: [{ role: "user", content: `Question: ${query}\n\nAnswer:` }], |
| }); |
|
|
| const baselineText = baselineMsg.content[0].type === "text" ? baselineMsg.content[0].text : ""; |
| const baselineLatency = Date.now() - baselineStart; |
| const baselineCost = |
| (baselineMsg.usage.input_tokens / 1000) * 0.003 + |
| (baselineMsg.usage.output_tokens / 1000) * 0.015; |
|
|
| |
| const graphragStart = Date.now(); |
|
|
| |
| const kwMsg = await claude.messages.create({ |
| model: "claude-sonnet-4-20250514", |
| max_tokens: 256, |
| system: "Extract search keywords. Return JSON only: {\"high_level\": [\"themes\"], \"low_level\": [\"entities\"]}", |
| messages: [{ role: "user", content: query }], |
| }); |
| const kwText = kwMsg.content[0].type === "text" ? kwMsg.content[0].text : "{}"; |
|
|
| |
| const entityMsg = await claude.messages.create({ |
| model: "claude-sonnet-4-20250514", |
| max_tokens: 1024, |
| system: `You are a knowledge graph builder. Extract entities and relationships for the question. |
| Return JSON: {"entities": [{"name": "...", "type": "PERSON|ORG|LOCATION|EVENT|CONCEPT"}], "relations": [{"source": "name", "target": "name", "type": "RELATIONSHIP_TYPE", "description": "brief"}]}`, |
| messages: [{ role: "user", content: query }], |
| }); |
| const entityText = entityMsg.content[0].type === "text" ? entityMsg.content[0].text : "{}"; |
|
|
| let entities: string[] = []; |
| let relations: string[] = []; |
| try { |
| const parsed = JSON.parse(entityText); |
| entities = (parsed.entities || []).map((e: { name: string }) => e.name); |
| relations = (parsed.relations || []).map( |
| (r: { source: string; type: string; target: string; description?: string }) => |
| `${r.source} -[${r.type}]-> ${r.target}: ${r.description || ""}` |
| ); |
| } catch { } |
|
|
| |
| const graphContext = `### Entities Found:\n${entities.map((e) => `- ${e}`).join("\n")}\n\n### Relationships:\n${relations.map((r) => `- ${r}`).join("\n")}`; |
|
|
| const graphragMsg = await claude.messages.create({ |
| model: "claude-sonnet-4-20250514", |
| max_tokens: 512, |
| system: "You are a knowledgeable assistant with access to a knowledge graph. Use the entities and relationships to answer accurately. Follow relationship chains for multi-hop reasoning. Be concise but thorough.", |
| messages: [{ role: "user", content: `Context:\n${graphContext}\n\nQuestion: ${query}\n\nAnswer:` }], |
| }); |
|
|
| const graphragText = graphragMsg.content[0].type === "text" ? graphragMsg.content[0].text : ""; |
| const graphragLatency = Date.now() - graphragStart; |
| const graphragTokens = |
| kwMsg.usage.input_tokens + kwMsg.usage.output_tokens + |
| entityMsg.usage.input_tokens + entityMsg.usage.output_tokens + |
| graphragMsg.usage.input_tokens + graphragMsg.usage.output_tokens; |
| const graphragCost = |
| ((kwMsg.usage.input_tokens + entityMsg.usage.input_tokens + graphragMsg.usage.input_tokens) / 1000) * 0.003 + |
| ((kwMsg.usage.output_tokens + entityMsg.usage.output_tokens + graphragMsg.usage.output_tokens) / 1000) * 0.015; |
|
|
| |
| let complexity = 0.5; |
| let queryType = "unknown"; |
| let recommended = "baseline"; |
|
|
| if (adaptiveRouting) { |
| |
| const hasMultipleEntities = entities.length > 2; |
| const hasComparison = /same|both|compare|which.*first|who.*born/i.test(query); |
| const hasMultiHop = relations.length > 2; |
|
|
| complexity = (hasMultipleEntities ? 0.3 : 0) + (hasComparison ? 0.2 : 0) + (hasMultiHop ? 0.3 : 0.1); |
| complexity = Math.min(complexity + 0.1, 1.0); |
| queryType = hasComparison ? "comparison" : hasMultiHop ? "multi_hop" : "factoid"; |
| recommended = complexity >= 0.6 ? "graphrag" : "baseline"; |
| } |
|
|
| return NextResponse.json({ |
| baseline: { |
| answer: baselineText, |
| tokens: baselineMsg.usage.input_tokens + baselineMsg.usage.output_tokens, |
| latencyMs: baselineLatency, |
| costUsd: baselineCost, |
| entities: [], |
| relations: [], |
| }, |
| graphrag: { |
| answer: graphragText, |
| tokens: graphragTokens, |
| latencyMs: graphragLatency, |
| costUsd: graphragCost, |
| entities, |
| relations, |
| }, |
| complexity, |
| queryType, |
| recommended, |
| totalTimeMs: Date.now() - startTime, |
| }); |
| } catch (error) { |
| console.error("Compare API error:", error); |
| |
| return NextResponse.json(getDemoResponse("")); |
| } |
| } |
|
|
| function getDemoResponse(query: string) { |
| return { |
| baseline: { |
| answer: "Based on available information, both Scott Derrickson and Ed Wood were American filmmakers, so yes, they shared the same nationality.", |
| tokens: 847, |
| latencyMs: 1240, |
| costUsd: 0.000203, |
| entities: [], |
| relations: [], |
| }, |
| graphrag: { |
| answer: "Yes. Scott Derrickson (born in Denver, Colorado, USA) and Ed Wood (born in Poughkeepsie, New York, USA) were both American. Following the NATIONALITY relationships in the knowledge graph: Derrickson β Denver β USA; Wood β Poughkeepsie β USA. Both paths converge at United States, confirming shared American nationality.", |
| tokens: 2134, |
| latencyMs: 3820, |
| costUsd: 0.000518, |
| entities: ["Scott Derrickson", "Ed Wood", "United States", "Denver", "Poughkeepsie"], |
| relations: [ |
| "Scott Derrickson -[BORN_IN]-> Denver, Colorado", |
| "Denver -[LOCATED_IN]-> United States", |
| "Ed Wood -[BORN_IN]-> Poughkeepsie, New York", |
| "Poughkeepsie -[LOCATED_IN]-> United States", |
| ], |
| }, |
| complexity: 0.72, |
| queryType: "comparison", |
| recommended: "graphrag", |
| totalTimeMs: 5060, |
| }; |
| } |
|
|