QVAC OCR で PDF と画像の解析を追加する
QVAC OCR タスクを再利用して、スキャンされたページや画像を LocalDocument に変える。
置く場所: OCR 呼び出しは src/qvac.ts、アダプタのつなぎ目は
src/files.ts。パイプラインの残りは知らなくて構いません。
LocalLens は現在、プレーンテキスト形式(Markdown、ソースコード、JSON、 YAML)をインデックスします。画像やスキャンされた PDF を同じ brain に 取り込みたいときに、サードパーティのパーサーは不要です。QVAC SDK には OCR タスクが含まれており、チャットモデルや埋め込みモデルの隣に配線 できます。
公式の QVAC OCR リファレンスは
docs.qvac.tether.io/sdk/examples/ai-tasks/ocr
にあります。以下のレシピはそれを開いた状態で進める想定です。
QVAC OCR の表面
import {
loadModel,
ocr,
unloadModel,
OCR_LATIN_RECOGNIZER_1,
} from "@qvac/sdk";1 つのモデル(OCR_LATIN_RECOGNIZER_1)が認識器を駆動します。
ocr({ modelId, image, options }) は blocks Promise を返し、{ text, bbox?, confidence? }
のオブジェクト配列で解決されます。
4 ステップのレシピ
1. ゲートウェイに OCR メソッドを追加する
QvacGateway を拡張し、OCR モデルをチャット/埋め込みモデルと並んで
遅延読み込みし、extractText 1 つのヘルパーを公開します。
import {
loadModel,
ocr,
unloadModel,
OCR_LATIN_RECOGNIZER_1,
} from "@qvac/sdk";
export class QvacGateway {
// existing fields…
private ocrModelId: string | undefined;
private async ensureOcrReady(): Promise<void> {
if (this.ocrModelId) return;
this.ocrModelId = await loadModel({
modelSrc: OCR_LATIN_RECOGNIZER_1,
modelType: "ocr",
modelConfig: {
langList: ["en"],
useGPU: true,
timeout: 30000,
},
});
}
async extractText(imagePath: string): Promise<string> {
await this.ensureOcrReady();
const { blocks } = ocr({
modelId: required(this.ocrModelId, "QVAC OCR model is not loaded."),
image: imagePath,
options: { paragraph: false },
});
const result = await blocks;
return result
.map((block) => block.text)
.filter((line) => line.trim().length > 0)
.join("\n");
}
}ゲートウェイの他の部分と同じパターンです。遅延読み込み、required
ヘルパー経由で進行中の Promise を共有、タスクごとに 1 メソッド。OCR
モデルはチャットと埋め込みから独立しているので、その読み込みが
パイプラインの残りをブロックしません。
2. 読み込み時に拡張子で分岐する
discoverTextDocuments(と browserDocumentsFromInput)を更新して、
画像形式のファイルは LocalDocument になる前に OCR を通すようにします。
LocalDocument の形は変わりません。コンテンツの出所だけが変わります。
import { QvacGateway } from "./qvac.ts";
const ocrExtensions = new Set([".bmp", ".jpg", ".jpeg", ".png", ".tiff"]);
export async function discoverTextDocuments(
rootPath: string,
gateway: QvacGateway,
): Promise<LocalDocument[]> {
// … existing folder walk …
const ext = path.posix.extname(absolutePath).toLowerCase();
let content: string;
if (ocrExtensions.has(ext)) {
content = await gateway.extractText(absolutePath);
} else {
content = await readFile(absolutePath, "utf8").catch(() => "");
}
// … rest unchanged …
}supportedExtensions に OCR 用のセットが加わります。下にあるルール
(null バイトなし、2 MB 以下、非空のコンテンツ)はそのまま同じように
フィルタします。認識テキストが空の OCR 画像は、空のテキストファイルと
同様にスキップされます。
3. PDF はページ単位で処理する
QVAC OCR は PDF ではなく画像を受け取ります。先に各 PDF ページを画像に
変換してください。ラスタライザは何でも構いません(pdftoppm、
pdf-poppler、または Node バインディング)。そのあと、ページごとに
gateway.extractText を呼んで結果を結合します。
async function extractPdfText(filePath: string, gateway: QvacGateway): Promise<string> {
const pageImages = await rasterisePdfToTempImages(filePath); // [path1.png, path2.png, …]
try {
const pages = await Promise.all(
pageImages.map((img) => gateway.extractText(img)),
);
return pages.map((text, i) => `--- page ${i + 1} ---\n${text}`).join("\n\n");
} finally {
await Promise.all(pageImages.map((img) => unlink(img).catch(() => undefined)));
}
}結合テキスト内のページマーカーは、後でチャンク化されたときにページ境界 を引用の中で見えるようにしておくのに役立ちます。
4. ワークフローからゲートウェイを渡す
LocalLensApp はすでに QvacGateway を持っています。それを
discoverTextDocuments に渡し、ファイルアダプタがモデルライフサイクルを
知らずに extractText を呼べるようにします。
async createBrainFromFolder(input: CreateBrainFromFolderInput): Promise<Brain> {
const folderPath = path.resolve(input.folderPath);
const documents = await discoverTextDocuments(folderPath, this.qvac);
return this.createBrainFromLocalDocuments(input.name.trim(), folderPath, documents);
}変わるシグネチャはこれだけです。残りのワークフロー(チャンク化、
取り込み、JSON ストア)は、LocalDocument[] を相手にこれまでどおり動きます。
変えなくて良いもの
rag.ts— チャンク化は変わりません。LocalDocumentを受け取るだけで、 内容がディスクテキスト由来か OCR 由来かは気にしません。store.ts— JSON の形は同じです。domain.ts—LocalDocumentはすでにrelativePath、content、checksum、bytesを持ちます。パイプラインの残りが必要とするのは それで全部です。
これがファイルアダプタのつなぎ目を持っていたことの効用です。新しい形式 への対応は、ゲートウェイメソッド 1 つとファイルウォーカー内の分岐 1 つで 済みます。
インデックスが終わったら OCR を片付ける
OCR モデルはゲートウェイのライフサイクルが終わるまで読み込んだままに
できます。CLI のワンショット実行のあとにメモリを解放したい場合は、
QvacGateway.close() 内、close() の前に
await unloadModel({ modelId: this.ocrModelId, clearStorage: false })
を呼んでください。
外部リファレンス
- QVAC OCR リファレンス
OCR_LATIN_RECOGNIZER_1モデル — 言語オプションと他の認識器。