LoginSignup
2
3

PDFファイルを渡したらmarkdown化したものをアウトプットしてくれるプログラムを作りたい!

Posted at

取り組み背景

業務でたくさんの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するのに比べたらだいぶマシかも?プロンプトエンジニアリングの勉強にもなりました!

ここまで読んでいただきありがとうございました〜!

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3