From 609a984f9b6004e6d6d090900e2a963c7bad2458 Mon Sep 17 00:00:00 2001 From: mblanke Date: Wed, 18 Feb 2026 20:11:55 -0500 Subject: [PATCH] fix: add POST handler to /api/rag for semantic search + agent proxy - Added POST handler that proxies retrieve/agent/tags actions to RAG API - Maps RAG API response shape (chunks[].rerank_score) to frontend expected format - Normalizes rerank_score (0-10 range) to 0-1 score for display - Disabled tags fetch on mount (blocks RAG API - scrolls all 49k Qdrant points) --- src/app/api/rag/route.ts | 95 ++++++++++++++++++++++++++++++- src/components/SemanticSearch.tsx | 28 ++++----- 2 files changed, 108 insertions(+), 15 deletions(-) diff --git a/src/app/api/rag/route.ts b/src/app/api/rag/route.ts index 2c79948..b87f3cf 100644 --- a/src/app/api/rag/route.ts +++ b/src/app/api/rag/route.ts @@ -1,7 +1,8 @@ -import { NextResponse } from "next/server"; +import { NextRequest, NextResponse } from "next/server"; const RAG_API = process.env.RAG_API_URL || "http://localhost:8099"; +/* ─── GET /api/rag → status + ingest-progress ─── */ export async function GET() { try { const [statusRes, progressRes] = await Promise.all([ @@ -32,3 +33,95 @@ export async function GET() { ); } } + +/* ─── POST /api/rag → proxy retrieve / agent / tags ─── */ +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { action, ...params } = body; + + /* --- tags --------------------------------------------------------- */ + if (action === "tags") { + const res = await fetch(`${RAG_API}/tags`, { + next: { revalidate: 0 }, + }); + const data = await res.json(); + // normalise → always return { tags: string[] } + const tags: string[] = Array.isArray(data) + ? data + : Array.isArray(data.tags) + ? data.tags + : []; + return NextResponse.json({ tags }); + } + + /* --- retrieve (semantic search) ----------------------------------- */ + if (action === "retrieve") { + const res = await fetch(`${RAG_API}/retrieve`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + question: params.question, + top_k: params.top_k ?? 8, + tags: params.tags, + }), + }); + + if (!res.ok) { + const err = await res.text(); + return NextResponse.json( + { error: err || "RAG retrieve failed" }, + { status: res.status } + ); + } + + const data = await res.json(); + + // RAG API returns { chunks: [...] } — map to frontend's expected shape + const chunks = data.chunks ?? data.results ?? (Array.isArray(data) ? data : []); + const results = chunks.map((c: Record) => ({ + title: c.title ?? "", + text: c.text ?? "", + source: c.source_path ?? c.source ?? "", + page: c.page ?? null, + score: c.rerank_score != null + ? Math.min((c.rerank_score as number) / 10, 1) // normalise rerank_score ≈0-10 → 0-1 + : c.score ?? null, + tags: c.tags ?? [], + })); + + return NextResponse.json(results); + } + + /* --- agent --------------------------------------------------------- */ + if (action === "agent") { + const res = await fetch(`${RAG_API}/agent`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + question: params.question, + max_steps: params.max_steps ?? 5, + }), + }); + + if (!res.ok) { + const err = await res.text(); + return NextResponse.json( + { error: err || "RAG agent failed" }, + { status: res.status } + ); + } + + const data = await res.json(); + return NextResponse.json(data); + } + + return NextResponse.json( + { error: `Unknown action: ${action}` }, + { status: 400 } + ); + } catch (err) { + const message = err instanceof Error ? err.message : "Unexpected error"; + return NextResponse.json({ error: message }, { status: 500 }); + } +} diff --git a/src/components/SemanticSearch.tsx b/src/components/SemanticSearch.tsx index 1f8229f..87e7433 100644 --- a/src/components/SemanticSearch.tsx +++ b/src/components/SemanticSearch.tsx @@ -232,20 +232,20 @@ export default function SemanticSearch() { const [agentResult, setAgentResult] = useState(null); // fetch tags on mount - useEffect(() => { - ragFetch({ action: "tags" }) - .then((data) => { - const tags: string[] = Array.isArray(data) - ? data - : Array.isArray(data.tags) - ? data.tags - : []; - setAvailableTags(tags); - }) - .catch(() => { - /* silently ignore – tags are optional */ - }); - }, []); +// useEffect(() => { +// ragFetch({ action: "tags" }) +// .then((data) => { +// const tags: string[] = Array.isArray(data) +// ? data +// : Array.isArray(data.tags) +// ? data.tags +// : []; +// setAvailableTags(tags); +// }) +// .catch(() => { +// /* silently ignore – tags are optional */ +// }); +// }, []); const toggleTag = useCallback((tag: string) => { setSelectedTags((prev) =>