1056 lines
45 KiB
TypeScript
1056 lines
45 KiB
TypeScript
import { releaseVersion } from "../config/version.js";
|
|
|
|
const ref = (name: string) => ({ $ref: `#/components/schemas/${name}` });
|
|
|
|
const jsonContent = (schema: Record<string, unknown>, example?: unknown) => ({
|
|
"application/json": {
|
|
schema,
|
|
...(example === undefined ? {} : { example })
|
|
}
|
|
});
|
|
|
|
const jsonResponse = (description: string, schema: Record<string, unknown>, example?: unknown) => ({
|
|
description,
|
|
content: jsonContent(schema, example)
|
|
});
|
|
|
|
const serverError = jsonResponse("Internal or upstream service error.", ref("Error"));
|
|
|
|
export const openApiDocument = {
|
|
openapi: "3.1.1",
|
|
info: {
|
|
title: "RAG Service API",
|
|
version: releaseVersion,
|
|
description: "HTTP API for ingesting, retrieving, answering from, and evaluating scoped RAG knowledge."
|
|
},
|
|
jsonSchemaDialect: "https://json-schema.org/draft/2020-12/schema",
|
|
servers: [
|
|
{ url: "https://rag.por-correo.com", description: "Production" },
|
|
{ url: "http://localhost:3000", description: "Local development" }
|
|
],
|
|
tags: [
|
|
{ name: "Discovery", description: "API contract and browser playground." },
|
|
{ name: "Status", description: "Service capabilities and available resources." },
|
|
{ name: "Ingestion", description: "Knowledge ingestion and cleanup." },
|
|
{ name: "OCR Review", description: "Authenticated OCR progress, review, correction, and decisions." },
|
|
{ name: "Lifecycle", description: "Knowledge source versions, activation, rollback, and purge." },
|
|
{ name: "Retrieval", description: "Context retrieval and model-backed answers." },
|
|
{ name: "Evaluation", description: "Evaluation log capture and review." }
|
|
],
|
|
paths: {
|
|
"/help": {
|
|
get: {
|
|
tags: ["Discovery"],
|
|
summary: "Discover the API",
|
|
description: "Returns a concise endpoint catalog generated from this OpenAPI document.",
|
|
responses: {
|
|
"200": jsonResponse("API discovery catalog.", ref("HelpResponse"))
|
|
}
|
|
}
|
|
},
|
|
"/openapi.json": {
|
|
get: {
|
|
tags: ["Discovery"],
|
|
summary: "Get the OpenAPI contract",
|
|
responses: {
|
|
"200": jsonResponse("OpenAPI 3.1.1 document.", { type: "object" })
|
|
}
|
|
}
|
|
},
|
|
"/playground": {
|
|
get: {
|
|
tags: ["Discovery"],
|
|
summary: "Open the browser playground",
|
|
responses: {
|
|
"200": {
|
|
description: "Playground HTML page.",
|
|
content: { "text/html": { schema: { type: "string" } } }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/health": {
|
|
get: {
|
|
tags: ["Status"],
|
|
summary: "Check service health and capabilities",
|
|
responses: {
|
|
"200": jsonResponse("Current service status.", ref("HealthResponse")),
|
|
"503": jsonResponse("One or more required services are unavailable.", ref("HealthResponse"))
|
|
}
|
|
}
|
|
},
|
|
"/sources": {
|
|
get: {
|
|
tags: ["Status"],
|
|
summary: "List available knowledge scopes",
|
|
responses: {
|
|
"200": jsonResponse("Scopes currently stored in the vector database.", {
|
|
type: "array",
|
|
items: ref("AvailableScope")
|
|
}),
|
|
"503": serverError,
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/sources/{sourceId}": {
|
|
get: {
|
|
tags: ["Lifecycle"],
|
|
summary: "Get a catalog source",
|
|
parameters: [{ name: "sourceId", in: "path", required: true, schema: { type: "string" } }],
|
|
responses: {
|
|
"200": jsonResponse("Catalog source.", ref("AvailableScope")),
|
|
"404": jsonResponse("Source not found.", ref("Error")),
|
|
"503": serverError
|
|
}
|
|
}
|
|
},
|
|
"/sources/{sourceId}/versions": {
|
|
get: {
|
|
tags: ["Lifecycle"],
|
|
summary: "List source versions",
|
|
parameters: [{ name: "sourceId", in: "path", required: true, schema: { type: "string" } }],
|
|
responses: {
|
|
"200": jsonResponse("Source versions.", { type: "array", items: ref("SourceVersion") }),
|
|
"503": serverError
|
|
}
|
|
}
|
|
},
|
|
"/sources/{sourceId}/versions/{versionId}": {
|
|
get: {
|
|
tags: ["Lifecycle"],
|
|
summary: "Get one source version",
|
|
parameters: [
|
|
{ name: "sourceId", in: "path", required: true, schema: { type: "string" } },
|
|
{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }
|
|
],
|
|
responses: {
|
|
"200": jsonResponse("Source version.", ref("SourceVersion")),
|
|
"404": jsonResponse("Version not found.", ref("Error")),
|
|
"503": serverError
|
|
}
|
|
},
|
|
delete: {
|
|
tags: ["Lifecycle"],
|
|
summary: "Purge a non-active source version",
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{ name: "sourceId", in: "path", required: true, schema: { type: "string" } },
|
|
{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }
|
|
],
|
|
responses: {
|
|
"200": jsonResponse("Version purged.", ref("PurgeResponse")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"409": jsonResponse("Active versions cannot be purged.", ref("Error")),
|
|
"503": serverError
|
|
}
|
|
}
|
|
},
|
|
"/sources/{sourceId}/versions/{versionId}/activate": {
|
|
post: {
|
|
tags: ["Lifecycle"],
|
|
summary: "Activate a ready or superseded version",
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{ name: "sourceId", in: "path", required: true, schema: { type: "string" } },
|
|
{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }
|
|
],
|
|
requestBody: { required: true, content: jsonContent(ref("ActivateVersionRequest")) },
|
|
responses: {
|
|
"200": jsonResponse("Activated source version.", ref("SourceVersion")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"409": jsonResponse("Active version precondition failed.", ref("Error")),
|
|
"503": serverError
|
|
}
|
|
}
|
|
},
|
|
"/sources/{sourceId}/rollback": {
|
|
post: {
|
|
tags: ["Lifecycle"],
|
|
summary: "Rollback a source to a previous version",
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: "sourceId", in: "path", required: true, schema: { type: "string" } }],
|
|
requestBody: { required: true, content: jsonContent(ref("RollbackRequest")) },
|
|
responses: {
|
|
"200": jsonResponse("Rollback activated source version.", ref("SourceVersion")),
|
|
"400": jsonResponse("Required rollback fields are missing.", ref("Error")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"409": jsonResponse("Active version precondition failed.", ref("Error")),
|
|
"503": serverError
|
|
}
|
|
}
|
|
},
|
|
"/sources/{sourceId}/versions/{versionId}/retry": {
|
|
post: {
|
|
tags: ["Lifecycle"],
|
|
summary: "Explicitly retry a failed or orphaned version by re-submitting a durable source",
|
|
security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{ name: "sourceId", in: "path", required: true, schema: { type: "string" } },
|
|
{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }
|
|
],
|
|
requestBody: { required: false, content: jsonContent(ref("RetryVersionRequest")) },
|
|
responses: {
|
|
"201": jsonResponse("Replacement version created by explicit retry.", ref("IngestResponse")),
|
|
"202": jsonResponse("Retry instructions returned because no durable source payload was supplied.", ref("RetryVersionResponse")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"409": jsonResponse("Version state is not retryable.", ref("Error")),
|
|
"503": serverError
|
|
}
|
|
}
|
|
},
|
|
"/models/answer": {
|
|
get: {
|
|
tags: ["Status"],
|
|
summary: "List answer models",
|
|
description: "Returns the configured default and up to 80 selectable answer models.",
|
|
responses: {
|
|
"200": jsonResponse("Available answer models.", ref("AnswerModelsResponse")),
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/ingest": {
|
|
post: {
|
|
tags: ["Ingestion"],
|
|
summary: "Ingest a server-accessible file or folder",
|
|
requestBody: {
|
|
required: true,
|
|
content: jsonContent(ref("IngestRequest"), {
|
|
sourceType: "folder",
|
|
sourceRef: "/data/project-docs",
|
|
mode: "mechanical",
|
|
tags: ["project-docs"]
|
|
})
|
|
},
|
|
responses: {
|
|
"201": jsonResponse("Lifecycle ingestion completed and committed.", ref("IngestResponse")),
|
|
"202": jsonResponse("OCR ingestion accepted, or legacy ingestion accepted while lifecycle enforcement is disabled.", { oneOf: [ref("OcrAccepted"), ref("IngestResponse")] }),
|
|
"400": jsonResponse("Invalid lifecycle activation request.", ref("Error")),
|
|
"409": jsonResponse("Concurrent active version change or reusable version in progress.", ref("Error")),
|
|
"422": jsonResponse("Empty or unsupported source.", ref("Error")),
|
|
"503": serverError,
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/ingest/upload": {
|
|
post: {
|
|
tags: ["Ingestion"],
|
|
summary: "Upload and ingest a file or ZIP folder",
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"multipart/form-data": {
|
|
schema: ref("UploadIngestRequest")
|
|
}
|
|
}
|
|
},
|
|
responses: {
|
|
"201": jsonResponse("Lifecycle upload ingested successfully.", ref("UploadIngestResponse")),
|
|
"202": jsonResponse("OCR upload accepted, or legacy upload accepted while lifecycle enforcement is disabled.", { oneOf: [ref("OcrUploadAccepted"), ref("UploadIngestResponse")] }),
|
|
"400": jsonResponse("The file field is missing.", ref("Error"), { ok: false, error: "Missing file upload" }),
|
|
"409": jsonResponse("Concurrent active version change or reusable version in progress.", ref("Error")),
|
|
"422": jsonResponse("Empty or unsupported source.", ref("Error")),
|
|
"503": serverError,
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/ingestions/{versionId}": {
|
|
get: {
|
|
tags: ["OCR Review"], summary: "Get OCR ingestion progress", security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
responses: {
|
|
"200": jsonResponse("Current document and page progress.", ref("IngestionStatus")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"404": jsonResponse("OCR is disabled or the ingestion does not exist.", ref("Error")),
|
|
"503": serverError, "500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/ingestions/{versionId}/review": {
|
|
get: {
|
|
tags: ["OCR Review"], summary: "Inspect an OCR candidate", security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
responses: {
|
|
"200": jsonResponse("Review candidate with page evidence.", ref("OcrReview")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"404": jsonResponse("OCR is disabled or the candidate does not exist.", ref("Error")),
|
|
"409": jsonResponse("The version is not awaiting review.", ref("Error")),
|
|
"503": serverError, "500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/ingestions/{versionId}/recover": {
|
|
post: {
|
|
tags: ["OCR Review"], summary: "Close an unrecoverable OCR review candidate", security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
requestBody: { required: true, content: jsonContent(ref("OcrRecoveryRequest")) },
|
|
responses: {
|
|
"200": jsonResponse("Candidate was closed without approval, indexing, or activation.", ref("OcrRecoveryResponse")),
|
|
"400": jsonResponse("Recovery actor and reason are required.", ref("Error")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"404": jsonResponse("OCR candidate does not exist.", ref("Error")),
|
|
"409": jsonResponse("Candidate state or evidence precondition does not permit recovery.", ref("Error")),
|
|
"422": jsonResponse("OCR artifact format or integrity is invalid.", ref("Error")),
|
|
"503": serverError, "500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/ingestions/{versionId}/documents/{documentId}/pages/{page}/image": {
|
|
get: {
|
|
tags: ["OCR Review"], summary: "Get a private OCR review page image", security: [{ bearerAuth: [] }],
|
|
parameters: [
|
|
{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } },
|
|
{ name: "documentId", in: "path", required: true, schema: { type: "string" } },
|
|
{ name: "page", in: "path", required: true, schema: { type: "integer", minimum: 1 } }
|
|
],
|
|
responses: {
|
|
"200": { description: "Integrity-validated private PNG review image.", content: { "image/png": { schema: { type: "string", format: "binary" } } } },
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"404": jsonResponse("OCR is disabled or the review image does not exist.", ref("Error")),
|
|
"503": serverError, "500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/ingestions/{versionId}/approve": {
|
|
post: {
|
|
tags: ["OCR Review"], summary: "Correct and approve an OCR candidate", security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
requestBody: { required: true, content: jsonContent(ref("OcrApprovalRequest")) },
|
|
responses: {
|
|
"200": jsonResponse("Candidate indexed and settled according to activation intent.", ref("OcrDecisionResponse")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"404": jsonResponse("OCR is disabled or the candidate does not exist.", ref("Error")),
|
|
"409": jsonResponse("Candidate, correction, state, or active-version precondition conflict.", ref("Error")),
|
|
"503": serverError, "500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/ingestions/{versionId}/reject": {
|
|
post: {
|
|
tags: ["OCR Review"], summary: "Reject an OCR candidate", security: [{ bearerAuth: [] }],
|
|
parameters: [{ name: "versionId", in: "path", required: true, schema: { type: "string", format: "uuid" } }],
|
|
requestBody: { required: true, content: jsonContent(ref("OcrRejectionRequest")) },
|
|
responses: {
|
|
"200": jsonResponse("Candidate rejected without indexing or activation.", ref("OcrDecisionResponse")),
|
|
"400": jsonResponse("Required rejection fields are missing.", ref("Error")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token.", ref("Error")),
|
|
"404": jsonResponse("OCR is disabled or the candidate does not exist.", ref("Error")),
|
|
"409": jsonResponse("Candidate hash or review state conflict.", ref("Error")),
|
|
"503": serverError, "500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/cleanup": {
|
|
post: {
|
|
tags: ["Ingestion"],
|
|
summary: "Delete chunks matching a source scope",
|
|
description: "Requires sourceId or sourceRef to prevent accidental mass deletion. Tags alone are not accepted.",
|
|
requestBody: {
|
|
required: true,
|
|
content: jsonContent(ref("CleanupRequest"), {
|
|
scope: { sourceId: "customer-a:manual:v1" }
|
|
})
|
|
},
|
|
responses: {
|
|
"200": jsonResponse("Matching chunks deleted.", ref("CleanupResponse")),
|
|
"400": jsonResponse("Scope is missing or unsafe for cleanup.", ref("Error")),
|
|
"401": jsonResponse("Missing or invalid lifecycle admin token when lifecycle enforcement is active.", ref("Error")),
|
|
"410": jsonResponse("Legacy cleanup is disabled while lifecycle enforcement is active.", ref("Error")),
|
|
"503": serverError,
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/retrieve": {
|
|
post: {
|
|
tags: ["Retrieval"],
|
|
summary: "Retrieve scoped context",
|
|
description: "Returns retrieved chunks. Set useModelInRetrieve to true to also generate modelSummary.",
|
|
requestBody: {
|
|
required: true,
|
|
content: jsonContent(ref("RetrieveRequest"), {
|
|
mode: "documental",
|
|
intent: "specific",
|
|
query: "How is the service deployed?",
|
|
scope: { sourceId: "project-docs" },
|
|
useModelInRetrieve: false
|
|
})
|
|
},
|
|
responses: {
|
|
"200": jsonResponse("Retrieved context.", ref("RetrieveResponse")),
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/answer": {
|
|
post: {
|
|
tags: ["Retrieval"],
|
|
summary: "Answer using retrieved RAG context",
|
|
requestBody: {
|
|
required: true,
|
|
content: jsonContent(ref("AnswerRequest"))
|
|
},
|
|
responses: {
|
|
"200": jsonResponse("Model answer with retrieval citations.", ref("AnswerResponse")),
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/answer/direct": {
|
|
post: {
|
|
tags: ["Retrieval"],
|
|
summary: "Answer without retrieving RAG context",
|
|
description: "Primarily used to compare direct model output with RAG-backed output.",
|
|
requestBody: {
|
|
required: true,
|
|
content: jsonContent(ref("DirectAnswerRequest"))
|
|
},
|
|
responses: {
|
|
"200": jsonResponse("Direct model answer.", ref("DirectAnswerResponse")),
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/chat": {
|
|
post: {
|
|
tags: ["Retrieval"],
|
|
summary: "Chat with optional bootstrap and additional retrieval",
|
|
requestBody: {
|
|
required: true,
|
|
content: jsonContent(ref("ChatRequest"))
|
|
},
|
|
responses: {
|
|
"200": jsonResponse("Chat response and retrieval usage metadata.", ref("ChatResponse")),
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/logs/recent": {
|
|
get: {
|
|
tags: ["Evaluation"],
|
|
summary: "List recent evaluation logs",
|
|
parameters: [{
|
|
name: "limit",
|
|
in: "query",
|
|
required: false,
|
|
description: "Maximum number of logs. Defaults to 20.",
|
|
schema: { type: "integer", default: 20 }
|
|
}],
|
|
responses: {
|
|
"200": jsonResponse("Recent evaluation logs.", { type: "array", items: ref("EvaluationLog") }),
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/logs/manual": {
|
|
post: {
|
|
tags: ["Evaluation"],
|
|
summary: "Create a manual evaluation log",
|
|
requestBody: {
|
|
required: true,
|
|
content: jsonContent(ref("ManualLogRequest"))
|
|
},
|
|
responses: {
|
|
"201": jsonResponse("Evaluation log created.", ref("EvaluationLog")),
|
|
"500": serverError
|
|
}
|
|
}
|
|
},
|
|
"/logs/{id}": {
|
|
patch: {
|
|
tags: ["Evaluation"],
|
|
summary: "Update evaluation log review status",
|
|
parameters: [{
|
|
name: "id",
|
|
in: "path",
|
|
required: true,
|
|
schema: { type: "string" }
|
|
}],
|
|
requestBody: {
|
|
required: true,
|
|
content: jsonContent(ref("UpdateLogRequest"))
|
|
},
|
|
responses: {
|
|
"200": jsonResponse("Updated evaluation log.", ref("EvaluationLog")),
|
|
"404": jsonResponse("Evaluation log not found.", ref("Error"), { ok: false, error: "Log not found" }),
|
|
"500": serverError
|
|
}
|
|
}
|
|
}
|
|
},
|
|
components: {
|
|
securitySchemes: {
|
|
bearerAuth: { type: "http", scheme: "bearer" }
|
|
},
|
|
schemas: {
|
|
Error: {
|
|
type: "object",
|
|
required: ["ok", "error"],
|
|
properties: {
|
|
ok: { type: "boolean", const: false },
|
|
error: { type: "string" },
|
|
code: { type: "string" },
|
|
action: { type: "string" }
|
|
}
|
|
},
|
|
Scope: {
|
|
type: "object",
|
|
properties: {
|
|
sourceId: { type: "string" },
|
|
sourceRef: { type: "string" },
|
|
tags: { type: "array", items: { type: "string" } }
|
|
}
|
|
},
|
|
SourceVersionState: {
|
|
type: "string",
|
|
enum: ["pending", "indexing", "review_required", "ready", "active", "superseded", "failed", "rejected", "purging", "purged"]
|
|
},
|
|
AvailableScope: {
|
|
type: "object",
|
|
required: ["sourceId", "sourceRef", "chunkModes", "tags"],
|
|
properties: {
|
|
sourceId: { type: "string" },
|
|
sourceRef: { type: "string" },
|
|
chunkModes: { type: "array", items: { $ref: "#/components/schemas/ChunkMode" } },
|
|
tags: { type: "array", items: { type: "string" } },
|
|
activeVersionId: { type: ["string", "null"], format: "uuid" },
|
|
activeVersionNumber: { type: ["integer", "null"] },
|
|
state: { ...ref("SourceVersionState"), nullable: true },
|
|
needsReingest: { type: "boolean" },
|
|
updatedAt: { type: "string", format: "date-time" }
|
|
}
|
|
},
|
|
ChunkMode: { type: "string", enum: ["documental", "codigo", "auto"] },
|
|
RetrieveIntent: { type: "string", enum: ["bootstrap", "specific"] },
|
|
IngestRequest: {
|
|
type: "object",
|
|
required: ["sourceType", "sourceRef"],
|
|
properties: {
|
|
sourceId: { type: "string" },
|
|
sourceType: { type: "string", enum: ["file", "folder"] },
|
|
sourceRef: { type: "string" },
|
|
readPath: { type: "string", description: "Internal filesystem path used when the logical sourceRef differs from the read location." },
|
|
mode: { type: "string", enum: ["mechanical", "interactive"], default: "mechanical" },
|
|
tags: { type: "array", items: { type: "string" } },
|
|
activate: { type: "boolean", default: true },
|
|
expectedActiveVersionId: { type: ["string", "null"], format: "uuid", description: "Required when activate is true. Use null for first activation." }
|
|
}
|
|
},
|
|
IngestResponse: {
|
|
type: "object",
|
|
required: ["accepted", "sourceId", "filesDiscovered", "documentsProcessed", "chunksStored", "collectionName"],
|
|
properties: {
|
|
accepted: { type: "boolean", const: true },
|
|
source: ref("IngestRequest"),
|
|
sourceId: { type: "string" },
|
|
versionId: { type: "string", format: "uuid" },
|
|
versionNumber: { type: "integer" },
|
|
state: ref("SourceVersionState"),
|
|
previousVersionId: { type: ["string", "null"], format: "uuid" },
|
|
filesDiscovered: { type: "integer" },
|
|
documentsProcessed: { type: "integer" },
|
|
chunksStored: { type: "integer" },
|
|
activated: { type: "boolean" },
|
|
noOp: { type: "boolean" },
|
|
collectionName: { type: "string" }
|
|
}
|
|
},
|
|
UploadIngestRequest: {
|
|
type: "object",
|
|
required: ["file"],
|
|
properties: {
|
|
file: { type: "string", format: "binary" },
|
|
sourceId: { type: "string" },
|
|
sourceRef: { type: "string", minLength: 1, description: "Optional logical source reference; trimmed and defaults to the uploaded filename." },
|
|
mode: { type: "string", enum: ["mechanical", "interactive"], default: "mechanical" },
|
|
tags: { type: "string", description: "Comma-separated tags." },
|
|
isZipFolder: { type: "string", enum: ["true", "false"], default: "false" },
|
|
activate: { type: "string", enum: ["true", "false"], default: "true" },
|
|
expectedActiveVersionId: { type: "string", description: "UUID or literal null when activate is true." }
|
|
}
|
|
},
|
|
UploadIngestResponse: {
|
|
allOf: [
|
|
ref("IngestResponse"),
|
|
{
|
|
type: "object",
|
|
required: ["uploadedResource"],
|
|
properties: { uploadedResource: { type: "string" } }
|
|
}
|
|
]
|
|
},
|
|
OcrPhase: {
|
|
type: "string",
|
|
enum: ["native_extracting", "ocr_queued", "ocr_running", "review_required", "indexing", "ready", "active", "failed", "rejected"]
|
|
},
|
|
OcrAccepted: {
|
|
type: "object",
|
|
required: ["accepted", "sourceId", "versionId", "versionNumber", "state", "phase", "statusUrl", "reviewUrl", "activated"],
|
|
properties: {
|
|
accepted: { type: "boolean", const: true }, sourceId: { type: "string" }, versionId: { type: "string", format: "uuid" },
|
|
versionNumber: { type: "integer" }, state: { type: "string", const: "indexing" }, phase: { type: "string", const: "ocr_queued" },
|
|
statusUrl: { type: "string" }, reviewUrl: { type: "null" }, activated: { type: "boolean", const: false }
|
|
}
|
|
},
|
|
OcrUploadAccepted: {
|
|
allOf: [ref("OcrAccepted"), { type: "object", required: ["uploadedResource"], properties: { uploadedResource: { type: "string" } } }]
|
|
},
|
|
IngestionStatus: {
|
|
type: "object",
|
|
required: ["sourceId", "versionId", "state", "phase", "activated", "documents", "error", "statusUrl", "reviewUrl"],
|
|
properties: {
|
|
sourceId: { type: "string" }, versionId: { type: "string", format: "uuid" }, state: ref("SourceVersionState"), phase: ref("OcrPhase"), activated: { type: "boolean" },
|
|
documents: { type: "array", items: ref("OcrProgressDocument") },
|
|
error: { oneOf: [ref("OcrStatusError"), { type: "null" }] }, statusUrl: { type: "string" }, reviewUrl: { type: ["string", "null"] }
|
|
}
|
|
},
|
|
OcrProgressDocument: {
|
|
type: "object", required: ["documentId", "state", "completedPages", "totalPages", "pages"],
|
|
properties: {
|
|
documentId: { type: "string" }, state: { type: "string", enum: ["native_complete", "ocr_queued", "ocr_running", "ocr_complete", "failed"] },
|
|
completedPages: { type: "integer" }, totalPages: { type: "integer" }, pages: { type: "array", items: ref("OcrProgressPage") }
|
|
}
|
|
},
|
|
OcrProgressPage: {
|
|
type: "object", required: ["page", "method", "state"],
|
|
properties: { page: { type: "integer" }, method: { type: "string", enum: ["native", "ocr", "blank"] }, state: { type: "string", enum: ["pending", "native_complete", "ocr_queued", "ocr_running", "ocr_complete", "blank", "failed"] }, errorCode: { type: "string" } }
|
|
},
|
|
OcrStatusError: { type: "object", required: ["code", "message", "retryable"], properties: { code: { type: "string" }, message: { type: "string" }, retryable: { type: "boolean" } } },
|
|
OcrLine: {
|
|
type: "object", required: ["lineId", "text", "confidence", "bbox", "lineSha256"],
|
|
properties: { lineId: { type: "string" }, text: { type: "string" }, confidence: { type: "number" }, bbox: { type: "array", items: { type: "number" }, minItems: 4, maxItems: 4 }, lineSha256: { type: "string", pattern: "^[a-f0-9]{64}$" } }
|
|
},
|
|
OcrReview: {
|
|
type: "object",
|
|
required: ["versionId", "sourceId", "state", "candidateSha256", "baseActiveVersionId", "currentActiveVersionId", "activateRequested", "processingFingerprint", "metadataHash", "documents"],
|
|
properties: {
|
|
versionId: { type: "string", format: "uuid" }, sourceId: { type: "string" }, state: { type: "string", const: "review_required" }, candidateSha256: { type: "string", pattern: "^[a-f0-9]{64}$" },
|
|
baseActiveVersionId: { type: ["string", "null"], format: "uuid" }, currentActiveVersionId: { type: ["string", "null"], format: "uuid" }, activateRequested: { type: "boolean" }, processingFingerprint: { type: "string" }, metadataHash: { type: "string" },
|
|
documents: { type: "array", items: ref("OcrReviewDocument") }
|
|
}
|
|
},
|
|
OcrReviewDocument: { type: "object", required: ["documentId", "pages"], properties: { documentId: { type: "string" }, pages: { type: "array", items: ref("OcrReviewPage") } } },
|
|
OcrReviewPage: {
|
|
type: "object", required: ["page", "imageUrl", "nativeText", "ocr", "candidateText", "differences", "risks"],
|
|
properties: { page: { type: "integer" }, imageUrl: { type: ["string", "null"] }, nativeText: { type: "string" }, ocr: { type: "object", required: ["text", "lines"], properties: { text: { type: "string" }, lines: { type: "array", items: ref("OcrLine") } } }, candidateText: { type: "string" }, differences: { type: "array", items: { type: "string" } }, risks: { type: "array", items: { type: "string" } } }
|
|
},
|
|
OcrCorrection: {
|
|
type: "object", required: ["documentId", "page", "lineId", "expectedLineSha256", "replacementText"],
|
|
properties: { documentId: { type: "string" }, page: { type: "integer" }, lineId: { type: "string" }, expectedLineSha256: { type: "string", pattern: "^[a-f0-9]{64}$" }, replacementText: { type: "string" } }
|
|
},
|
|
OcrApprovalRequest: {
|
|
type: "object", required: ["candidateSha256", "expectedActiveVersionId", "reviewedBy", "corrections"],
|
|
properties: { candidateSha256: { type: "string", pattern: "^[a-f0-9]{64}$" }, expectedActiveVersionId: { type: ["string", "null"], format: "uuid" }, reviewedBy: { type: "string" }, corrections: { type: "array", items: ref("OcrCorrection") } }
|
|
},
|
|
OcrRejectionRequest: {
|
|
type: "object", required: ["candidateSha256", "reviewedBy", "reason"],
|
|
properties: { candidateSha256: { type: "string", pattern: "^[a-f0-9]{64}$" }, reviewedBy: { type: "string" }, reason: { type: "string" } }
|
|
},
|
|
OcrRecoveryRequest: {
|
|
type: "object", required: ["recoveredBy", "reason"],
|
|
properties: { recoveredBy: { type: "string" }, reason: { type: "string" } }
|
|
},
|
|
OcrRecoveryResponse: {
|
|
type: "object", required: ["versionId", "state", "outcome"],
|
|
properties: { versionId: { type: "string", format: "uuid" }, state: { type: "string", const: "failed" }, outcome: { type: "string", const: "closed_failed" } }
|
|
},
|
|
OcrDecisionResponse: {
|
|
type: "object", required: ["versionId", "state", "activated"],
|
|
properties: { versionId: { type: "string", format: "uuid" }, state: { type: "string", enum: ["indexing", "rejected"] }, activated: { type: "boolean" } }
|
|
},
|
|
CleanupRequest: {
|
|
type: "object",
|
|
required: ["scope"],
|
|
properties: { scope: ref("Scope") }
|
|
},
|
|
CleanupResponse: {
|
|
type: "object",
|
|
required: ["ok", "deleted"],
|
|
properties: {
|
|
ok: { type: "boolean" },
|
|
deleted: { type: "integer" }
|
|
}
|
|
},
|
|
BootstrapMeta: {
|
|
type: "object",
|
|
properties: {
|
|
query: { type: "string" },
|
|
mode: ref("ChunkMode"),
|
|
scope: ref("Scope"),
|
|
model: { type: "string" },
|
|
usedModelSummary: { type: "boolean" }
|
|
}
|
|
},
|
|
RetrieveRequest: {
|
|
type: "object",
|
|
required: ["query"],
|
|
properties: {
|
|
mode: { ...ref("ChunkMode"), default: "auto" },
|
|
intent: { ...ref("RetrieveIntent"), default: "specific" },
|
|
query: { type: "string" },
|
|
scope: ref("Scope"),
|
|
model: { type: "string", description: "Model override used only when useModelInRetrieve is true." },
|
|
useModelInRetrieve: { type: "boolean", default: false },
|
|
bootstrapMeta: ref("BootstrapMeta")
|
|
}
|
|
},
|
|
RetrievedItem: {
|
|
type: "object",
|
|
required: ["chunkId", "documentId", "sourceId", "title", "content", "score"],
|
|
properties: {
|
|
chunkId: { type: "string" },
|
|
documentId: { type: "string" },
|
|
sourceId: { type: "string" },
|
|
sourceVersionId: { type: "string", format: "uuid" },
|
|
sourceVersionNumber: { type: "integer" },
|
|
title: { type: "string" },
|
|
sectionTitle: { type: "string" },
|
|
content: { type: "string" },
|
|
score: { type: "number" },
|
|
chunkMode: ref("ChunkMode"),
|
|
startLine: { type: "integer" },
|
|
endLine: { type: "integer" }
|
|
}
|
|
},
|
|
RetrieveResponse: {
|
|
type: "object",
|
|
required: ["mode", "intent", "summary", "topics", "criticalPoints", "items", "followUpRefs"],
|
|
properties: {
|
|
mode: ref("ChunkMode"),
|
|
intent: ref("RetrieveIntent"),
|
|
model: { type: "string" },
|
|
summary: { type: "string" },
|
|
modelSummary: { type: "string" },
|
|
topics: { type: "array", items: { type: "string" } },
|
|
criticalPoints: { type: "array", items: { type: "string" } },
|
|
items: { type: "array", items: ref("RetrievedItem") },
|
|
followUpRefs: { type: "array", items: { type: "string" } },
|
|
scope: ref("Scope")
|
|
}
|
|
},
|
|
AnswerRequest: {
|
|
type: "object",
|
|
required: ["query"],
|
|
properties: {
|
|
mode: { ...ref("ChunkMode"), default: "auto" },
|
|
intent: { ...ref("RetrieveIntent"), default: "specific" },
|
|
query: { type: "string" },
|
|
scope: ref("Scope"),
|
|
model: { type: "string" },
|
|
preloadedContext: { type: "string" },
|
|
bootstrapMeta: ref("BootstrapMeta")
|
|
}
|
|
},
|
|
Citation: {
|
|
type: "object",
|
|
required: ["chunkId", "documentId", "title"],
|
|
properties: {
|
|
chunkId: { type: "string" },
|
|
documentId: { type: "string" },
|
|
sourceVersionId: { type: "string", format: "uuid" },
|
|
sourceVersionNumber: { type: "integer" },
|
|
title: { type: "string" },
|
|
sectionTitle: { type: "string" },
|
|
startLine: { type: "integer" },
|
|
endLine: { type: "integer" }
|
|
}
|
|
},
|
|
AnswerResponse: {
|
|
type: "object",
|
|
required: ["mode", "intent", "model", "answer", "summary", "topics", "criticalPoints", "citations"],
|
|
properties: {
|
|
mode: ref("ChunkMode"),
|
|
intent: ref("RetrieveIntent"),
|
|
model: { type: "string" },
|
|
answer: { type: "string" },
|
|
summary: { type: "string" },
|
|
topics: { type: "array", items: { type: "string" } },
|
|
criticalPoints: { type: "array", items: { type: "string" } },
|
|
citations: { type: "array", items: ref("Citation") },
|
|
scope: ref("Scope")
|
|
}
|
|
},
|
|
DirectAnswerRequest: {
|
|
type: "object",
|
|
required: ["query"],
|
|
properties: {
|
|
query: { type: "string" },
|
|
model: { type: "string" },
|
|
preloadedContext: { type: "string" }
|
|
}
|
|
},
|
|
DirectAnswerResponse: {
|
|
type: "object",
|
|
required: ["model", "answer"],
|
|
properties: {
|
|
model: { type: "string" },
|
|
answer: { type: "string" }
|
|
}
|
|
},
|
|
ChatMessage: {
|
|
type: "object",
|
|
required: ["role", "content"],
|
|
properties: {
|
|
role: { type: "string", enum: ["user", "assistant"] },
|
|
content: { type: "string" }
|
|
}
|
|
},
|
|
ChatRequest: {
|
|
type: "object",
|
|
required: ["message"],
|
|
properties: {
|
|
message: { type: "string" },
|
|
history: { type: "array", items: ref("ChatMessage") },
|
|
mode: { ...ref("ChunkMode"), default: "documental" },
|
|
model: { type: "string" },
|
|
preloadedContext: { type: "string" },
|
|
allowAdditionalRetrieve: { type: "boolean", default: false },
|
|
scope: ref("Scope"),
|
|
bootstrapMeta: ref("BootstrapMeta")
|
|
}
|
|
},
|
|
ChatResponse: {
|
|
type: "object",
|
|
required: ["model", "answer", "usedBootstrapContext", "usedAdditionalRetrieve"],
|
|
properties: {
|
|
model: { type: "string" },
|
|
answer: { type: "string" },
|
|
usedBootstrapContext: { type: "boolean" },
|
|
usedAdditionalRetrieve: { type: "boolean" },
|
|
retrieved: ref("RetrieveResponse")
|
|
}
|
|
},
|
|
EvaluationLog: {
|
|
type: "object",
|
|
required: ["id", "trigger", "operation", "reason", "query", "createdAt", "retrievedItemsCount", "chunkIds", "documentIds", "reviewStatus", "severity"],
|
|
properties: {
|
|
id: { type: "string" },
|
|
trigger: { type: "string", enum: ["automatic", "manual"] },
|
|
operation: { type: "string", enum: ["retrieve", "answer", "chat"] },
|
|
reason: { type: "string" },
|
|
query: { type: "string" },
|
|
mode: ref("ChunkMode"),
|
|
intent: ref("RetrieveIntent"),
|
|
scope: ref("Scope"),
|
|
model: { type: "string" },
|
|
note: { type: "string" },
|
|
createdAt: { type: "string", format: "date-time" },
|
|
usedBootstrapContext: { type: "boolean" },
|
|
bootstrapQuery: { type: "string" },
|
|
bootstrapMode: ref("ChunkMode"),
|
|
bootstrapScope: ref("Scope"),
|
|
bootstrapModel: { type: "string" },
|
|
bootstrapUsedModelSummary: { type: "boolean" },
|
|
usedAdditionalRetrieve: { type: "boolean" },
|
|
responseSummary: { type: "string" },
|
|
retrievedItemsCount: { type: "integer" },
|
|
chunkIds: { type: "array", items: { type: "string" } },
|
|
documentIds: { type: "array", items: { type: "string" } },
|
|
reviewStatus: { type: "string", enum: ["pending", "in_progress", "resolved", "ignored"] },
|
|
severity: { type: "string", enum: ["low", "medium", "high"] },
|
|
reviewedAt: { type: "string", format: "date-time" },
|
|
reviewedBy: { type: "string" },
|
|
resolutionNote: { type: "string" },
|
|
fixReference: { type: "string" },
|
|
supersedesLogId: { type: "string" }
|
|
}
|
|
},
|
|
ManualLogRequest: {
|
|
type: "object",
|
|
required: ["query"],
|
|
properties: {
|
|
operation: { type: "string", enum: ["retrieve", "answer", "chat"], default: "answer" },
|
|
reason: { type: "string", default: "manual_review_requested" },
|
|
query: { type: "string" },
|
|
mode: ref("ChunkMode"),
|
|
intent: ref("RetrieveIntent"),
|
|
scope: ref("Scope"),
|
|
model: { type: "string" },
|
|
note: { type: "string" },
|
|
usedBootstrapContext: { type: "boolean" },
|
|
bootstrapMeta: ref("BootstrapMeta"),
|
|
usedAdditionalRetrieve: { type: "boolean" },
|
|
responseSummary: { type: "string" },
|
|
retrievedItems: { type: "array", items: ref("RetrievedItem") }
|
|
}
|
|
},
|
|
UpdateLogRequest: {
|
|
type: "object",
|
|
properties: {
|
|
reviewStatus: { type: "string", enum: ["pending", "in_progress", "resolved", "ignored"] },
|
|
severity: { type: "string", enum: ["low", "medium", "high"] },
|
|
reviewedBy: { type: "string" },
|
|
resolutionNote: { type: "string" },
|
|
fixReference: { type: "string" },
|
|
supersedesLogId: { type: "string" }
|
|
}
|
|
},
|
|
HealthResponse: {
|
|
type: "object",
|
|
required: ["ok", "service", "version", "revision", "environment", "embeddings", "answer", "vectorStore", "postgres", "knowledgeLifecycle", "parsers", "chunking"],
|
|
properties: {
|
|
ok: { type: "boolean" },
|
|
service: { type: "string", const: "rag" },
|
|
version: { type: "string" },
|
|
revision: { type: "string" },
|
|
environment: { type: "string" },
|
|
embeddings: ref("ProviderModel"),
|
|
answer: ref("ProviderModel"),
|
|
vectorStore: { type: "object", additionalProperties: true },
|
|
postgres: { type: "object", additionalProperties: true },
|
|
knowledgeLifecycle: { type: "object", additionalProperties: true },
|
|
parsers: { type: "array", items: { type: "string" } },
|
|
chunking: { type: "object", additionalProperties: true }
|
|
}
|
|
},
|
|
SourceVersion: {
|
|
type: "object",
|
|
required: ["versionId", "sourceId", "versionNumber", "state", "tags", "embeddingProvider", "embeddingModel", "embeddingDimensions", "expectedDocumentCount", "expectedPointCount", "verifiedPointCount", "qdrantCollection"],
|
|
properties: {
|
|
versionId: { type: "string", format: "uuid" },
|
|
sourceId: { type: "string" },
|
|
versionNumber: { type: "integer" },
|
|
previousVersionId: { type: ["string", "null"], format: "uuid" },
|
|
state: ref("SourceVersionState"),
|
|
tags: { type: "array", items: { type: "string" } },
|
|
sourceContentHash: { type: ["string", "null"] },
|
|
processingFingerprint: { type: "string" },
|
|
metadataHash: { type: "string" },
|
|
embeddingProvider: { type: "string" },
|
|
embeddingModel: { type: "string" },
|
|
embeddingDimensions: { type: "integer" },
|
|
expectedDocumentCount: { type: "integer" },
|
|
expectedPointCount: { type: "integer" },
|
|
verifiedPointCount: { type: "integer" },
|
|
qdrantCollection: { type: "string" }
|
|
}
|
|
},
|
|
ActivateVersionRequest: {
|
|
type: "object",
|
|
required: ["expectedActiveVersionId"],
|
|
properties: { expectedActiveVersionId: { type: ["string", "null"], format: "uuid" } }
|
|
},
|
|
RollbackRequest: {
|
|
type: "object",
|
|
required: ["targetVersionId", "expectedActiveVersionId"],
|
|
properties: {
|
|
targetVersionId: { type: "string", format: "uuid" },
|
|
expectedActiveVersionId: { type: "string", format: "uuid" }
|
|
}
|
|
},
|
|
RetryVersionRequest: {
|
|
type: "object",
|
|
properties: {
|
|
sourceType: { type: "string", enum: ["file", "folder"] },
|
|
sourceRef: { type: "string" },
|
|
readPath: { type: "string" },
|
|
mode: { type: "string", enum: ["mechanical", "interactive"], default: "mechanical" },
|
|
tags: { type: "array", items: { type: "string" } },
|
|
activate: { type: "boolean", default: true },
|
|
expectedActiveVersionId: { type: ["string", "null"], format: "uuid" }
|
|
}
|
|
},
|
|
RetryVersionResponse: {
|
|
type: "object",
|
|
required: ["ok", "retryRequired", "sourceId", "versionId", "message"],
|
|
properties: {
|
|
ok: { type: "boolean", const: true },
|
|
retryRequired: { type: "boolean", const: true },
|
|
sourceId: { type: "string" },
|
|
versionId: { type: "string", format: "uuid" },
|
|
message: { type: "string" }
|
|
}
|
|
},
|
|
PurgeResponse: {
|
|
type: "object",
|
|
required: ["ok", "purged", "versionId"],
|
|
properties: {
|
|
ok: { type: "boolean", const: true },
|
|
purged: { type: "boolean", const: true },
|
|
versionId: { type: "string", format: "uuid" }
|
|
}
|
|
},
|
|
ProviderModel: {
|
|
type: "object",
|
|
required: ["provider", "model"],
|
|
properties: {
|
|
provider: { type: "string" },
|
|
model: { type: "string" }
|
|
}
|
|
},
|
|
AnswerModelsResponse: {
|
|
type: "object",
|
|
required: ["defaultModel", "models"],
|
|
properties: {
|
|
defaultModel: { type: "string" },
|
|
models: { type: "array", items: { type: "string" }, maxItems: 80 }
|
|
}
|
|
},
|
|
HelpEndpoint: {
|
|
type: "object",
|
|
required: ["method", "path", "summary", "tags"],
|
|
properties: {
|
|
method: { type: "string" },
|
|
path: { type: "string" },
|
|
summary: { type: "string" },
|
|
tags: { type: "array", items: { type: "string" } }
|
|
}
|
|
},
|
|
HelpResponse: {
|
|
type: "object",
|
|
required: ["service", "version", "description", "documentation", "authentication", "endpoints"],
|
|
properties: {
|
|
service: { type: "string" },
|
|
version: { type: "string" },
|
|
description: { type: "string" },
|
|
documentation: {
|
|
type: "object",
|
|
required: ["openapi", "openapiPurpose", "playground"],
|
|
properties: {
|
|
openapi: { type: "string" },
|
|
openapiPurpose: { type: "string" },
|
|
playground: { type: "string" }
|
|
}
|
|
},
|
|
authentication: { type: "string" },
|
|
endpoints: { type: "array", items: ref("HelpEndpoint") }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} as const;
|
|
|
|
type OpenApiOperation = {
|
|
summary?: string;
|
|
tags?: readonly string[];
|
|
};
|
|
|
|
export function buildApiHelp() {
|
|
const endpoints = Object.entries(openApiDocument.paths).flatMap(([routePath, pathItem]) =>
|
|
Object.entries(pathItem).map(([method, operation]) => {
|
|
const details = operation as OpenApiOperation;
|
|
return {
|
|
method: method.toUpperCase(),
|
|
path: routePath,
|
|
summary: details.summary ?? "",
|
|
tags: details.tags ?? []
|
|
};
|
|
})
|
|
);
|
|
|
|
return {
|
|
service: openApiDocument.info.title,
|
|
version: openApiDocument.info.version,
|
|
description: openApiDocument.info.description,
|
|
documentation: {
|
|
openapi: "/openapi.json",
|
|
openapiPurpose: "Consult this contract for complete parameters, request bodies, responses, errors, and examples for every endpoint.",
|
|
playground: "/playground"
|
|
},
|
|
authentication: "Bearer authentication is required for lifecycle administration and all OCR candidate routes; other routes remain public.",
|
|
endpoints
|
|
};
|
|
}
|