/** * /api/graph — Public entity-graph endpoints (read-only). * * GET /api/graph?op=neighbors&class=people&id=j-edgar-hoover * → top entities co-mentioned with this one * * GET /api/graph?op=paths&from=PK_A&to=PK_B&max_hops=3 * → shortest paths between two entities via shared chunks * * GET /api/graph?op=co_mention&a=PK_A&b=PK_B * → chunks where both entities appear together */ import { NextRequest } from "next/server"; import { findEntity, getNeighbors, findPaths, getCoMentionChunks } from "@/lib/retrieval/graph"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; function json(data: unknown, status = 200) { return new Response(JSON.stringify(data), { status, headers: { "content-type": "application/json" }, }); } export async function GET(req: NextRequest) { const u = new URL(req.url); const op = u.searchParams.get("op") ?? "neighbors"; try { if (op === "neighbors") { const cls = u.searchParams.get("class") ?? ""; const id = u.searchParams.get("id") ?? ""; if (!cls || !id) return json({ error: "class and id required" }, 400); const ent = await findEntity(cls, id); if (!ent) return json({ error: "entity_not_found", class: cls, id }, 404); const filterClasses = u.searchParams.get("filter_classes")?.split(",").filter(Boolean); const limit = Number(u.searchParams.get("limit") ?? 30); const neighbors = await getNeighbors(ent.entity_pk, { limit, classes: filterClasses }); return json({ entity: ent, neighbors }); } if (op === "paths") { const from = Number(u.searchParams.get("from")); const to = Number(u.searchParams.get("to")); const maxHops = Math.min(Number(u.searchParams.get("max_hops") ?? 3), 4); if (!from || !to) return json({ error: "from and to required" }, 400); const paths = await findPaths(from, to, maxHops); return json({ from, to, max_hops: maxHops, paths }); } if (op === "co_mention") { const a = Number(u.searchParams.get("a")); const b = Number(u.searchParams.get("b")); const limit = Math.min(Number(u.searchParams.get("limit") ?? 20), 100); if (!a || !b) return json({ error: "a and b required" }, 400); const chunks = await getCoMentionChunks(a, b, limit); return json({ a, b, count: chunks.length, chunks }); } return json({ error: "unknown_op", op }, 400); } catch (e) { return json({ error: "graph_unavailable", message: (e as Error).message }, 503); } }