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)
This commit is contained in:
2026-02-18 20:11:55 -05:00
parent ed125f613d
commit 609a984f9b
2 changed files with 108 additions and 15 deletions

View File

@@ -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"; const RAG_API = process.env.RAG_API_URL || "http://localhost:8099";
/* ─── GET /api/rag → status + ingest-progress ─── */
export async function GET() { export async function GET() {
try { try {
const [statusRes, progressRes] = await Promise.all([ 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<string, unknown>) => ({
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 });
}
}

View File

@@ -232,20 +232,20 @@ export default function SemanticSearch() {
const [agentResult, setAgentResult] = useState<AgentResponse | null>(null); const [agentResult, setAgentResult] = useState<AgentResponse | null>(null);
// fetch tags on mount // fetch tags on mount
useEffect(() => { // useEffect(() => {
ragFetch({ action: "tags" }) // ragFetch({ action: "tags" })
.then((data) => { // .then((data) => {
const tags: string[] = Array.isArray(data) // const tags: string[] = Array.isArray(data)
? data // ? data
: Array.isArray(data.tags) // : Array.isArray(data.tags)
? data.tags // ? data.tags
: []; // : [];
setAvailableTags(tags); // setAvailableTags(tags);
}) // })
.catch(() => { // .catch(() => {
/* silently ignore tags are optional */ // /* silently ignore tags are optional */
}); // });
}, []); // }, []);
const toggleTag = useCallback((tag: string) => { const toggleTag = useCallback((tag: string) => {
setSelectedTags((prev) => setSelectedTags((prev) =>