O seam de document-parsing do motor: alembic ocr <img> transforma um screenshot/página escaneada em texto — offline e determinístico por default ($0), e com --online fala o contrato HTTP EXATO do infer.py do Unlimited-OCR contra um servidor SGLang local. O mesmo desenho da lição 21: um kernel de despacho com Zod nas DUAS bordas, backends injetados, e um seam de text-artifacts que dá dentes ao gate de vídeo do marketing.
packages/ocr/src/types.ts espelha o types.ts de @alembic/embeddings "1:1" (auto-descrição em types.ts:12–16): um schema Zod de request, um de resultado, e um port de chamada única cuja saída não-confiável é re-validada na borda. O request aceita imagePath OU imageBase64 — e o .refine do schema impõe o XOR: "provide exactly one of imagePath or imageBase64" (types.ts:40–52). Um request ambíguo ou vazio morre ANTES de tocar qualquer backend.
O resultado é { text, blocks?, lang?, pages? } (types.ts:90–99): text pode ser vazio — "a blank page is a valid parse" (types.ts:91) — e blocks é OPCIONAL porque o Unlimited-OCR emite Markdown, não caixas por palavra (types.ts:58–62); quando presentes, cada bloco exige texto não-vazio e bbox de exatamente 4 números finitos não-negativos (types.ts:63–78). Sucesso/falha NÃO são campos: colapsam no Result, "so a failure never masquerades as empty text" (types.ts:88–89).
Pense como… a recepção de um cartório: ou você entrega o documento físico (path) ou uma cópia autenticada (base64) — nunca os dois, nunca nenhum. O atendente confere a entrada ANTES de carimbar e confere o carimbo ANTES de devolver. Onde quebra: aqui até a página em branco recebe protocolo (text: '' é válido) — o que não existe é protocolo falso para falha.
createOfflineOcrBackend (packages/ocr/src/offline-backend.ts) devolve OCR(${source}) — um placeholder estável derivado só da fonte da imagem (offline-backend.ts:41), com blocks ecoando o texto e pages: 1 (offline-backend.ts:42–47). Para inline base64, a "fonte" vira um fingerprint hex de 32 bits calculado por um fold polinomial puro (offline-backend.ts:75–81) — imagens distintas ⇒ labels distintos, sem ecoar o blob gigante e sem crypto.
Na CLI, esse é o caminho de alembic ocr <imagePath> sem flags (apps/cli/src/commands.ts:4692–4693): recognition de verdade NÃO acontece — o que você ganha é wiring, cache e plumbing testáveis a custo zero, "the engine's --offline path" (offline-backend.ts:3–5). O texto do arquivo é direto: "The text is NOT a real recognition" (offline-backend.ts:12).
CLAUDE.md) exige que TODO pacote com backend pesado tenha um irmão determinístico. O OCR segue à risca: nada de Math.random()/Date.now() (offline-backend.ts:8–10 — a plan-VM proíbe ambos), então o mesmo request produz o MESMO resultado para sempre.createSglangOcrBackend (packages/ocr/src/sglang-backend.ts) é um fetch fino sobre o endpoint OpenAI-compatível /v1/chat/completions do servidor SGLang do Unlimited-OCR. O diferencial é a disciplina de reverse-engineering: o cabeçalho do arquivo CITA o contrato exato, linha a linha, do infer.py original — payload com temperature: 0, skip_special_tokens: false, stream: true, images_config.image_mode: "gundam" e o prompt "document parsing." (sglang-backend.ts:14–31; o body montado em :204–225).
E é offline-first até quando é o backend online: sem baseUrl configurada, devolve um err tipado SEM NENHUMA tentativa de rede (sglang-backend.ts:133–141). Com ela, todo fracasso de transporte vira err — throw de rede, status non-2xx (:244–246), body não-JSON (:301–308). A resposta é parseada nos DOIS formatos que o servidor pode falar: SSE streamado (concatena os deltas data: até data: [DONE], pulando chunks malformados exatamente como o infer.py faz — :278–291) ou JSON único (choices[0].message.content — :294–298).
if (args.online) { const baseUrl = process.env.ALEMBIC_OCR_BASEURL; if (baseUrl === undefined || baseUrl.length === 0) { return err(new Error( 'ocr --online needs the Unlimited-OCR SGLang server (set ALEMBIC_OCR_BASEURL, ' + 'e.g. http://127.0.0.1:10000; see docs/ocr-setup.md). No base URL set, so no ' + 'network attempt was made. Omit --online for the deterministic offline backend.')); } backend = createSglangOcrBackend({ baseUrl, apiKey: process.env.ALEMBIC_OCR_TOKEN }); } else { backend = createOfflineOcrBackend(/* lang opcional */); }
alembic ocr foto.png --online sem ALEMBIC_OCR_BASEURL no ambiente. O que acontece?commands.ts:4677–4687) e repete no backend (sglang-backend.ts:133–141) — sem base URL, err explícito SEM rede. Não há fallback silencioso (você pediu online; degradar escondido mentiria) nem default de porta (nada de URL hardcoded).No gate de QA de vídeo, a pergunta "tem texto QUEIMADO nos frames?" é respondida por OCR — via o mesmo padrão de port da lição 21: FrameOcr (packages/marketing-factory/src/validate.ts:45–47), default noopFrameOcr que devolve '' (validate.ts:174–176). A CLI injeta os dentes reais com createFrameOcr sobre @alembic/ocr (apps/cli/src/marketing-seams.ts:34–41).
O fluxo: extractFrames amostra o mp4 com ffmpeg (validate.ts:399) na taxa frameRate default de 1 frame/segundo (validate.ts:110–111); scanFrames roda o OCR em cada frame e coleta TextArtifact { frame, text } para todo texto não-vazio (validate.ts:443–460). A varredura é OPT-IN — checkFrames = noBakedText || forbiddenVisuals.length > 0 (validate.ts:518) — e noBakedText default false, porque "many ad types legitimately use on-screen text/kinetic typography" (validate.ts:105–109): com a trava ligada, artefatos derrubam o gate (failures: 'text-artifact: N frame(s)…', validate.ts:579–580); desligada, são só informativos no relatório. O veredito final é pass = failures.length === 0 (validate.ts:593).
| Peça | Onde | Papel |
|---|---|---|
FrameOcr (port) | validate.ts:45–47 | a FORMA de "leia este frame" |
noopFrameOcr | validate.ts:174–176 | default honesto: '' — sem fingir cobertura |
createFrameOcr | marketing-seams.ts:34–41 | os dentes reais, injetados pela CLI |
extractFrames | validate.ts:399 | ffmpeg amostra 1 fps (default) |
--no-text | validate.ts:105–109 | opt-in: artefato ⇒ falha do gate |
marketing validate SEM --no-text, o texto na tela…noBakedText é opt-in (default false, validate.ts:105–109) exatamente porque texto na tela é legítimo em muitos formatos. Nota da opção b: os frames ainda PODEM ser amostrados se forbiddenVisuals estiver presente (checkFrames, :518) — as duas varreduras compartilham a amostragem.Tudo $0 e read-only (o --online só é mostrado na forma que FALHA FECHADO sem servidor):
# o XOR de fonte única + o resultado com blocks/bbox sed -n '40,52p;63,99p' packages/ocr/src/types.ts # o kernel de duas bordas sed -n '35,52p' packages/ocr/src/ocr.ts # OCR offline determinístico, $0 — rode duas vezes e compare alembic ocr ~/qualquer/imagem.png --json alembic ocr ~/qualquer/imagem.png --json # byte-idêntico # o contrato verbatim do infer.py dentro do backend sglang sed -n '14,31p;204,225p' packages/ocr/src/sglang-backend.ts # prova do fail-closed do --online sem servidor (nenhuma rede é tentada) env -u ALEMBIC_OCR_BASEURL alembic ocr ~/qualquer/imagem.png --online # o seam de text-artifacts no gate de vídeo sed -n '45,47p;105,111p;174,176p;443,473p' packages/marketing-factory/src/validate.ts sed -n '29,41p' apps/cli/src/marketing-seams.ts
text: '' — sucesso/falha colapsam no Result; página em branco É um parse válido (types.ts:88–91).OCR(source) + fingerprint puro de 32 bits para base64 (offline-backend.ts:41, 75–81).ALEMBIC_OCR_BASEURL, --online erra fechado SEM rede (commands.ts:4677–4687).--no-text — tipografia cinética é legítima (validate.ts:105–109).