/** * Admin Panel: RAG Configuration Console (GET/POST /admin/rag-config*). * * Configures the ACTUAL resume-matching RAG pipeline in ResumeParser.py * -- chunking.max_chars, retrieval.top_k/similarity_threshold, and * generation model/temperature/max_tokens are now read from here at * runtime (rag_config.py), not hardcoded. Prompt templates are versioned * and loaded at runtime too -- editing one here changes what * ResumeParser.py actually sends to the LLM on its next run, once * activated. * * This repo's RAG use case is JD-to-resume-chunk matching for recruiter * summarization, not an interactive document-QA chatbot -- retrieval * preview and evaluation here reflect that: real retrieval against the * real on-disk semantic index, with an explicit "insufficient evidence" * state when a similarity threshold filters everything out, and no * invented Recall@K/NDCG/groundedness scores, since no labeled query- * relevance dataset exists to compute them from. * * Lifecycle: Draft -> Validate -> Activate -> Rollback (same pattern as * the Azure Service Hub's configuration console) -- an invalid draft can * never replace the active configuration, and a config change always * forces revalidation (a fresh "healthy" signal isn't assumed to carry * over) before anything new goes live. */ function RagConfiguration(props) { var api = props.api; var permissions = props.permissions || []; var tabState = React.useState("overview"); var tab = tabState[0]; var setTab = tabState[1]; var canWrite = permissions.indexOf("rag.config.write") !== -1; var canActivate = permissions.indexOf("rag.config.activate") !== -1; var canPreview = permissions.indexOf("rag.preview.run") !== -1; var canManagePrompts = permissions.indexOf("rag.prompts.manage") !== -1; return (

RAG Configuration

Configures the real resume-matching RAG pipeline (chunking, retrieval, generation, and prompt templates) used by ResumeParser.py. This repo's RAG use case matches job descriptions to resume chunks for recruiter summarization -- it is not a document-QA chatbot, so retrieval preview and evaluation below reflect that real pipeline, not an invented one.

{window.CDPI_CONFIG.mode === "mock" && (
Mock mode: no real semantic index exists in this browser-only demo -- retrieval preview honestly reports "no real index" rather than fabricating retrieved chunks. Switch config.js to mode:"real" against a deployed backend with real processed resumes to see actual retrieval.
)}
{tab === "overview" && } {tab === "config" && } {tab === "prompts" && } {tab === "preview" && } {tab === "history" && }
); } function RagConfigOverview(props) { var api = props.api; var snapshot = CDPI_REPORT_SNAPSHOT.useReportSnapshot("rag_config_overview", function () { return api.getRagConfig(); }); var data = { loading: snapshot.state.loading && !snapshot.state.data, error: snapshot.state.error, data: snapshot.state.data }; var load = snapshot.refresh; if (data.error && !data.data) return
Couldn't load RAG configuration: {data.error}
; if (data.loading) return

Loading…

; var src = data.data.source_status; var active = data.data.active; return (

Knowledge source status

Distinct facts, not one status dot: a source can be configured but not connected, connected but empty, or indexed but still fail a real probe query.

{src.configured ? "configured" : "not configured"} {src.connected ? "connected" : "not connected"} {src.indexed ? src.chunk_count + " chunks indexed" : "not indexed"} {src.retrievable ? "retrievable (probe query succeeded)" : "not confirmed retrievable"}
{src.error &&

{src.error}

}

Active configuration (config version {active.config_version})

{active.note &&

{active.note}

}
Chunkingmax_chars = {active.chunking.max_chars}
Retrievaltop_k = {active.retrieval.top_k}, similarity_threshold = {active.retrieval.similarity_threshold}
Generation (chunk summarization){active.generation.chunk_summarization.model}, temp={active.generation.chunk_summarization.temperature}, max_tokens={active.generation.chunk_summarization.max_tokens}
Generation (merge extraction){active.generation.merge_extraction.model}, temp={active.generation.merge_extraction.temperature}, max_tokens={active.generation.merge_extraction.max_tokens}

Embedding model/dimensions are NOT configurable here -- this repo has exactly one embedding path (OpenAI text-embedding-3-small, 1536-dim) hardcoded in semantic-library/embeddings.py, and no second model is wired up to switch to. Exposing it as an editable field would misrepresent what the code can actually do.

{data.data.draft && (
A draft configuration (version {data.data.draft.config_version}) is pending -- go to the Configuration tab to review, validate, or activate it.
)}
); } function RagConfigEditor(props) { var api = props.api; var canWrite = props.canWrite; var canActivate = props.canActivate; var state = React.useState({ loading: true, error: null, active: null, draft: null }); var data = state[0]; var setData = state[1]; var formState = React.useState(null); var form = formState[0]; var setForm = formState[1]; var saveState = React.useState({ saving: false, error: null, validation: null }); var save = saveState[0]; var setSave = saveState[1]; function load() { setData(function (d) { return Object.assign({}, d, { loading: true, error: null }); }); api.getRagConfig() .then(function (result) { setData({ loading: false, error: null, active: result.active, draft: result.draft }); setForm(result.draft || result.active); }) .catch(function (err) { setData(function (d) { return Object.assign({}, d, { loading: false, error: err.message }); }); }); } React.useEffect(function () { load(); }, []); function setField(path, value) { setForm(function (f) { var next = JSON.parse(JSON.stringify(f)); var parts = path.split("."); var node = next; for (var i = 0; i < parts.length - 1; i++) node = node[parts[i]]; node[parts[parts.length - 1]] = value; return next; }); setSave({ saving: false, error: null, validation: null }); } function handleValidate() { api.validateRagConfig({ chunking: form.chunking, retrieval: form.retrieval, generation: form.generation }) .then(function (result) { setSave({ saving: false, error: null, validation: result }); }) .catch(function (err) { setSave({ saving: false, error: err.message, validation: null }); }); } function handleSaveDraft() { setSave({ saving: true, error: null, validation: null }); api.saveRagConfigDraft({ chunking: form.chunking, retrieval: form.retrieval, generation: form.generation }) .then(function () { setSave({ saving: false, error: null, validation: null }); load(); }) .catch(function (err) { setSave({ saving: false, error: err.message, validation: null }); }); } function handleActivate(acknowledgeReindexImpact) { if (!acknowledgeReindexImpact && !window.confirm("Activate this configuration? It will replace what ResumeParser.py reads on its next run. The previous configuration stays in history and can be rolled back.")) return; setSave({ saving: true, error: null, validation: null }); api.activateRagConfig(acknowledgeReindexImpact) .then(function () { setSave({ saving: false, error: null, validation: null }); load(); }) .catch(function (err) { // A 409 here specifically means chunking changed -- existing embeddings // won't reflect it until resumes are reprocessed. Ask for a SECOND, // more specific confirmation describing that impact before retrying // with acknowledge_reindex_impact -- never silently retry. if (err.status === 409 && /reindex/i.test(err.message)) { if (window.confirm(err.message + "\n\nActivate anyway?")) { handleActivate(true); return; } setSave({ saving: false, error: null, validation: null }); return; } setSave({ saving: false, error: err.message, validation: null }); }); } if (data.error) return
{data.error}
; if (data.loading || !form) return

Loading…

; return (

Editing here creates/updates a DRAFT -- the active configuration is never changed until you explicitly activate. Changing chunking may require existing resumes to be reprocessed to reflect the new chunk boundaries; this page does not trigger that automatically.

Chunking

Retrieval

No hybrid/keyword search or reranking is exposed here -- this repo's retrieval engine only implements cosine-similarity vector search. Listing options it doesn't support would be misleading.

Generation -- chunk summarization step

Generation -- merge/structured-extraction step

{canWrite && (
{canActivate && data.draft && }
)} {save.validation && (
{save.validation.valid ? "Configuration valid." : save.validation.errors.join(" ")}
)} {save.error &&
{save.error}
}
); } function RagPromptsTab(props) { var api = props.api; var canManagePrompts = props.canManagePrompts; var state = React.useState({ loading: true, error: null, prompts: null }); var data = state[0]; var setData = state[1]; var editState = React.useState({}); // prompt_id -> draft template text being edited var edits = editState[0]; var setEdits = editState[1]; var actionState = React.useState({ busy: false, error: null }); var action = actionState[0]; var setAction = actionState[1]; function load() { setData(function (d) { return Object.assign({}, d, { loading: true, error: null }); }); api.listRagPrompts() .then(function (result) { setData({ loading: false, error: null, prompts: result.prompts }); }) .catch(function (err) { setData(function (d) { return Object.assign({}, d, { loading: false, error: err.message }); }); }); } React.useEffect(function () { load(); }, []); function handleCreateDraft(promptId) { var template = edits[promptId]; if (!template) return; setAction({ busy: true, error: null }); api.createRagPromptDraft(promptId, template) .then(function () { setAction({ busy: false, error: null }); setEdits(function (e) { return Object.assign({}, e, { [promptId]: undefined }); }); load(); }) .catch(function (err) { setAction({ busy: false, error: err.message }); }); } function handleActivateVersion(promptId, version) { if (!window.confirm("Activate prompt version " + version + "? ResumeParser.py will use this text on its next run.")) return; setAction({ busy: true, error: null }); api.activateRagPromptVersion(promptId, version) .then(function () { setAction({ busy: false, error: null }); load(); }) .catch(function (err) { setAction({ busy: false, error: err.message }); }); } if (data.error) return
{data.error}
; if (data.loading) return

Loading…

; return (

The two real prompt templates ResumeParser.py loads at runtime -- editing and activating one here changes what the LLM actually sees on the next resume processed, not just documentation. Every version is kept (never deleted); activating a new one supersedes, never erases, the old one.

{action.error &&
{action.error}
} {Object.keys(data.prompts).map(function (promptId) { var versions = data.prompts[promptId].slice().sort(function (a, b) { return b.version - a.version; }); var active = versions.filter(function (v) { return v.status === "active"; })[0]; return (

{promptId}

Active: version {active ? active.version : "none"}

{versions.map(function (v) { return ( ); })}
VersionStatusCreated by
{v.version} {v.status} {v.created_by} {canManagePrompts && v.status !== "active" && }
{canManagePrompts && (