File size: 7,485 Bytes
19df402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import { NextRequest, NextResponse } from "next/server";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

// Initialize Anthropic client lazily
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 no API key, return demo data
    if (!apiKey) {
      return NextResponse.json(getDemoResponse(query));
    }

    const claude = await getClaude();
    const startTime = Date.now();

    // ── Pipeline A: Baseline RAG ────────────────────────
    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;

    // ── Pipeline B: GraphRAG ────────────────────────────
    const graphragStart = Date.now();

    // Step 1: Extract keywords
    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 : "{}";

    // Step 2: Entity extraction (simulated graph traversal)
    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 { /* ignore parse errors */ }

    // Step 3: Generate with structured graph context
    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;

    // ── Adaptive Routing ────────────────────────────────
    let complexity = 0.5;
    let queryType = "unknown";
    let recommended = "baseline";

    if (adaptiveRouting) {
      // Simple heuristic + LLM analysis
      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 demo data on 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,
  };
}