diff --git a/open-sse/config/ocrRegistry.ts b/open-sse/config/ocrRegistry.ts index 5bf27a557f..27e4dcf994 100644 --- a/open-sse/config/ocrRegistry.ts +++ b/open-sse/config/ocrRegistry.ts @@ -62,6 +62,48 @@ export function getOcrTransformation(providerId: string): OcrTransformation { return OCR_PROVIDERS[providerId]?.transformation ?? MISTRAL_PASSTHROUGH; } +const AZURE_DI_API_VERSION = "2024-11-30"; + +function azureDiSource(document: Record | undefined): Record { + if (!document) return {}; + const url = String(document.document_url ?? document.image_url ?? ""); + if (url.startsWith("data:")) { + const comma = url.indexOf(","); + return { base64Source: comma >= 0 ? url.slice(comma + 1) : "" }; + } + return url ? { urlSource: url } : {}; +} + +export const AZURE_DI_TRANSFORMATION: OcrTransformation = { + buildRequest({ baseUrl, token, body, modelId }) { + const root = baseUrl.replace(/\/+$/, ""); + return { + url: `${root}/documentintelligence/documentModels/${modelId}:analyze?api-version=${AZURE_DI_API_VERSION}&outputContentFormat=markdown`, + init: { + method: "POST", + headers: { "Content-Type": "application/json", "Ocp-Apim-Subscription-Key": token }, + body: JSON.stringify(azureDiSource(body.document as Record)), + }, + }; + }, + pollUrl(res) { + return res.headers.get("Operation-Location"); + }, + parseResponse(raw) { + const r = raw as { + analyzeResult?: { content?: string; pages?: unknown[] }; + }; + const pageCount = r.analyzeResult?.pages?.length ?? 1; + // Azure devolve o markdown do documento inteiro em content; espelhamos no shape + // Mistral com uma "página" agregada por padrão (índice 0), preservando pageCount. + return { + pages: [{ index: 0, markdown: r.analyzeResult?.content ?? "" }], + model: "prebuilt-read", + usage_info: { pages_processed: pageCount }, + }; + }, +}; + export const OCR_PROVIDERS: Record = { mistral: { id: "mistral", @@ -70,6 +112,14 @@ export const OCR_PROVIDERS: Record = { authHeader: "bearer", models: [{ id: "mistral-ocr-latest", name: "Mistral OCR" }], }, + "azure-document-intelligence": { + id: "azure-document-intelligence", + baseUrl: "", + authType: "apikey", + authHeader: "Ocp-Apim-Subscription-Key", + models: [{ id: "prebuilt-read", name: "Azure Document Intelligence (Read)" }], + transformation: AZURE_DI_TRANSFORMATION, + }, }; /** diff --git a/tests/unit/ocr-registry-transformations.test.ts b/tests/unit/ocr-registry-transformations.test.ts index df25a1fc05..ce7454ac7d 100644 --- a/tests/unit/ocr-registry-transformations.test.ts +++ b/tests/unit/ocr-registry-transformations.test.ts @@ -26,3 +26,49 @@ test("passthrough parseResponse returns the body unchanged (Mistral is the canon const raw = { pages: [{ index: 0, markdown: "hello" }], model: "mistral-ocr-latest" }; assert.deepEqual(MISTRAL_PASSTHROUGH.parseResponse(raw), raw); }); + +test("azure-document-intelligence builds the prebuilt-read:analyze request", () => { + const t = getOcrTransformation("azure-document-intelligence"); + const { url, init } = t.buildRequest({ + baseUrl: "https://myres.cognitiveservices.azure.com", + token: "azkey", + body: { document: { type: "document_url", document_url: "https://x/d.pdf" } }, + modelId: "prebuilt-read", + }); + assert.equal( + url, + "https://myres.cognitiveservices.azure.com/documentintelligence/documentModels/prebuilt-read:analyze?api-version=2024-11-30&outputContentFormat=markdown" + ); + assert.equal((init.headers as Record)["Ocp-Apim-Subscription-Key"], "azkey"); + const sent = JSON.parse(String(init.body)); + assert.equal(sent.urlSource, "https://x/d.pdf"); +}); + +test("azure-document-intelligence extracts poll URL and parses analyzeResult into Mistral shape", () => { + const t = getOcrTransformation("azure-document-intelligence"); + const res = new Response(null, { + status: 202, + headers: { "Operation-Location": "https://poll/op/1" }, + }); + assert.equal(t.pollUrl?.(res), "https://poll/op/1"); + const parsed = t.parseResponse({ + status: "succeeded", + analyzeResult: { content: "# doc text", pages: [{ pageNumber: 1 }] }, + }); + assert.equal(parsed.pages.length, 1); + assert.equal(parsed.pages[0].index, 0); + assert.equal(parsed.pages[0].markdown, "# doc text"); + assert.equal(parsed.model, "prebuilt-read"); +}); + +test("azure DI maps base64/image_url documents to base64Source/urlSource", () => { + const t = getOcrTransformation("azure-document-intelligence"); + const { init } = t.buildRequest({ + baseUrl: "https://r.example.com", + token: "k", + body: { document: { type: "image_url", image_url: "data:image/png;base64,AAAA" } }, + modelId: "prebuilt-read", + }); + const sent = JSON.parse(String(init.body)); + assert.equal(sent.base64Source, "AAAA"); +});