はじめに
最近、LLM系生成AIの能力向上速度が落ち着いてきましたね。(いつまで経ってもGPT-5は出ないし…🤔)
そのため、これからはLLM系の生成AIをシステムに組み込む方法が重要になってくると予想しています。
システムに組み込むための必須条件として、「決まった形式(特にJSON)で返答を取得すること」があります。
今回はOCRアプリの開発を通して、主要なLLM系サービスを比較検証した結果のまとめになります!
📌 前提条件
OCRアプリの開発目的で検証しました。
- 各サービスとはAPIで連携
- OCR対象ファイルを読み込み、JSONで結果取得
- JSONの整合性を確認
① ChatGPT(OpenAI)💬
普段使い慣れているため今回の本命でした。
手順
- Assistantを作成(返答形式を「json_object」に限定)
- ファイルを保存したVectorStoreを利用
- Threadを作成し、プロンプトと共に質問を投入
- 結果を取得
// vectorStoreの準備
if(vectorStoreId){
const files = await openai.beta.vectorStores.files.list(vectorStoreId);
for (const file of files.data) {
await openai.beta.vectorStores.files.del(vectorStoreId, file.id);
await openai.files.del(file.id);
}
await openai.beta.vectorStores.files.create(
vectorStoreId,
{ file_id: fileId }
);
}
// Thread作成
const thread = await openai.beta.threads.create();
// 質問追加
await openai.beta.threads.messages.create(thread.id, {
role: 'user',
content: question,
});
// 回答生成
const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistantId,
});
// 処理完了待機
let runStatus;
do {
runStatus = await openai.beta.threads.runs.retrieve(thread.id, run.id);
if (runStatus.status === 'completed') break;
await new Promise((resolve) => setTimeout(resolve, 2000));
} while (['queued', 'in_progress'].includes(runStatus.status));
// 回答取得
const messages = await openai.beta.threads.messages.list(thread.id);
const response = messages.data
.filter((msg) => msg.role === 'assistant')
.pop()?.content[0]?.text?.value;
return response;
結果
結果は・・・出来てる!🎉
プロンプトに返答用のJSONサンプルを付けておいたところ、まさにその形どおりに返答がありました。
さすがChatGPT、頼れる存在です。
ということで、他のLLMのことはひとまず横に置いておき、ChatGPTで引き続き検証を続けていましたが、いくつかテストを重ねるうちに残念な問題が発生。
具体的には、OCR対象のファイルに含まれる内容(文字数など)が増えていくにつれて、ChatGPTさん、勝手に内容を省略しはじめました😅。
念のためUI上で確認したところ、省略された結果に対して何回か「省略するな!」と伝えると、渋々(?)全文を返してくれるので、読み込めないわけではないようですが…。
システム組み込みという視点からは致命的な問題です。
さらに、返答が長くなるとなぜかJSONの形式を間違える(閉じカッコが少なくなる?)という現象まで発生しました💦。
ということで、短い内容であれば優秀だったのですが、今回は泣く泣く不採用です😢。
② Claude(Anthropic)🤖
文書生成性能に期待してトライしました。
結果 ❌
本命のChatGPTが残念な結果だったので、次はChatGPTから派生したClaudeに挑戦です!
なんと文書生成能力は本家GPTを超えるという噂まで耳にしていたので、ちょっとだけ期待しつつテストを開始。
結果は・・・ダメでした😓。
そもそもUI上でファイルを読み込ませて結果を出力してもらおうとしたのですが、Claudeさん、ファイルが重かったのか途中で固まってしまいました💦。
もしかすると、API経由で接続したり、細かくチューニングを行えば精度が改善したのかもしれませんが、
心がぽっきり折れてしまったので、ここで検証終了です…。
後で調べたところ、そもそも返答をjsonに制限する方法がプロンプトで縛ることしかできないようで、
安定性考慮すると難しかったのかなと思っています。
③ Gemini(Google)✨
ChatGPTとClaudeがダメだったので、完全に諦めモードに入りつつありました😅。
とはいえ、OCRのエンジン性能で言えばGoogleのVision APIが圧倒的に評判がいいので、ダメもとでトライしてみることに。
結果は・・・素晴らしい!!✨
なんと返答は完璧にJSON固定で、ChatGPTのように内容を勝手に省略される心配もありませんでした🎯。
(その後、OCRの枚数にして100ページ以上を検証しましたが、まったく問題なしです👏)
さらに、少し話がズレますが、Gemini APIの使い方と料金体系が非常にシンプルかつスマートでした。
ChatGPTだと画像の保存コスト(容量×時間)や質問のトークンなど、計算がちょっとややこしい感じですが、Geminiは画像をbase64で渡し、それをトークン換算してプロンプトと合わせて料金計算してくれるので、分かりやすさが抜群です。
(とはいえ、その分、ChatGPT側だと複雑な処理ができる可能性はありそうですが…🤔)
コード例はこちら👇
const model = genAI.getGenerativeModel({
model: "gemini-2.5-flash-preview-05-20",
generationConfig: {
responseMimeType: "application/json",
responseSchema: jsonSchema,
}
});
const prompt = `
投入したスキーマに合わせて画像内の情報を取得してください。
該当なしの場合は近似項目または空欄で。
省略せず全ページ出力してください。
`;
const answer = await model.generateContent([prompt, imgData]);
console.log(JSON.stringify(answer));
const result = JSON.parse(answer.response.candidates[0].content.parts[0].text);
📊 結果とまとめ
| サービス | JSON固定 | 省略問題なし | 安定性 |
|---|---|---|---|
| ChatGPT | △ | ❌ | △ |
| Claude | ❌ | ❌ | ❌ |
| Gemini | ✅ | ✅ | ✅ |
💡 現時点ではGemini一択です! 💡
Claudeは難しく、ChatGPTの省略問題は今後の改善に期待。
🔗 活用して作成したOCRアプリ(kintoneプラグイン)はこちら
👉 Qiita記事
ぜひご覧ください!😊
