取り組み背景
業務でたくさんのPDFの内容をmarkdown化しなければならなくなったがなるべく楽をしたいので良い方法はないかと模索したところ、chatgptで全部いけそうだったので、ファイル名に渡したらmarkdownファイルをアウトプットしてくれるようなプロンプト x プログラムを作る。
サンプルPDFはこちら、京都市の児童手当に関する説明PDF。
https://www.city.kyoto.lg.jp/hagukumi/cmsfiles/contents/0000182/182951/pamphlet-japanese.pdf
WebアプリのChatGPTに丸投げしてみる
いけそう、もう完成かな?
アウトプットを見比べてみるといい感じに出来あがっちゃってる。
あとはAPI経由でPDF送信できるようにすれば終わり!
費用がかさみそうだったので、画像を直接送るのではなく、一旦ローカルでテキスト抽出->テキストAPI経由で送信->md化したものを受け取ってローカルに保存、という感じでやってみる。
PDFからテキストの抽出
zenn記事のこちらを参考にしながらやらせていただきました!
TypeScriptでpdfからテキストを抽出する(PDF.js)
bunの導入
ランタイムにはbunを利用。
typescriptがそのまま動いて早いらしい...
https://bun.sh/
curl -fsSL https://bun.sh/install | bash
source ~/.zshrc
テキスト抽出していく
PDF.jsの導入。
PDF.js
npm install --save pdfjs-dist
あ〜、bunだとbun addを使うのね。
bun add @types/node
参考にしたプログラムをちょっと修正したのが以下。
import * as fs from "fs";
import * as pdfjsLib from "pdfjs-dist";
async function extractTextFromPDF(): Promise<string> {
const pdfPath = "./childmoney.pdf";
const pdfData = new Uint8Array(fs.readFileSync(pdfPath));
const loadingTask = pdfjsLib.getDocument({ data: pdfData });
const pdf = await loadingTask.promise;
const maxPages = pdf.numPages;
let pdfText = "";
for (let pageNumber = 1; pageNumber <= maxPages; pageNumber++) {
const page = await pdf.getPage(pageNumber);
const content = await page.getTextContent({ includeMarkedContent: false });
const pageText = content.items.map((item) => ("str" in item ? item.str : "")).join("\n");
pdfText += pageText + "\n";
}
fs.writeFileSync("./output.txt", pdfText);
console.log(pdfText);
return pdfText;
}
extractTextFromPDF().catch((error) => {
console.error(error);
});
🔽 bun run pdfToMd.ts!!
ふむふむ良い感じ。
chatgptのAPIを叩く
chatgptのAPIを取得
OpenAIのSDKを使う
API reference
OpenAI Node API Library
以下で導入。
npm install openai@^4.0.0
サンプルコード。
import * as fs from 'fs';
import OpenAI from 'openai';
async function convertTextToMarkDown(): Promise<void> {
const textFilePath = "./output.txt";
const text = await fs.readFileSync(textFilePath, "utf-8");
const url = "https://api.openai.com/v1/chat/completions";
const model = "gpt-3.5-turbo";
const apiKey = process.env.OPENAI_API_KEY;
const messages = [{role: "user", content: "Please say hello."}];
const openai = new OpenAI({ apiKey });
const chatCompletion = await openai.chat.completions.create({
messages: [{role: "user", content: "Please say hello."}],
model,
});
console.log(chatCompletion.choices[0].message);
}
convertTextToMarkDown().catch((error) => {
console.error(error);
});
あとはこれでリクエストを送れば...!
const prompt = `以下のテキストをmarkdown形式に変換してください。
${text}`
const openai = new OpenAI({ apiKey });
const chatCompletion = await openai.chat.completions.create({
messages: [{role: "user", content: prompt[0]},{role: "user", content: prompt[1]}],
model,
});
あれ...?!
This model's maximum context length is 4097... というエラーが返ってきた。
なるほど、送受信できるリクエスト文字数(トークン数)に上限があるのですね。
PDFページごとにファイルを分割してファイルごとにリクエスト送信、受信するようにする
ファイル分割送受信、プロンプトも修正して以下のような形に仕上がりました。
表の情報を文字列から推測させてmd形式で再現するのが難しい...
精度は何とも言えないけど一からPDFー>Markdownするのに比べたらだいぶマシかも?プロンプトエンジニアリングの勉強にもなりました!
ここまで読んでいただきありがとうございました〜!