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

## 取り組み背景

業務でたくさんのPDFの内容をmarkdown化しなければならなくなったがなるべく楽をしたいので良い方法はないかと模索したところ、chatgptで全部いけそうだったので、ファイル名に渡したらmarkdownファイルをアウトプットしてくれるようなプロンプト x プログラムを作る。

サンプルPDFはこちら、京都市の児童手当に関する説明PDF。

![](https://storage.googleapis.com/zenn-user-upload/2e0f85195790-20231201.png)
https://www.city.kyoto.lg.jp/hagukumi/cmsfiles/contents/0000182/182951/pamphlet-japanese.pdf

## WebアプリのChatGPTに丸投げしてみる

いけそう、もう完成かな？

![](https://storage.googleapis.com/zenn-user-upload/f4d8b5b2db3f-20231201.png)

アウトプットを見比べてみるといい感じに出来あがっちゃってる。

![](https://storage.googleapis.com/zenn-user-upload/16bcef5399ee-20231201.png)

あとはAPI経由でPDF送信できるようにすれば終わり！
費用がかさみそうだったので、画像を直接送るのではなく、一旦ローカルでテキスト抽出->テキストAPI経由で送信->md化したものを受け取ってローカルに保存、という感じでやってみる。

## PDFからテキストの抽出

zenn記事のこちらを参考にしながらやらせていただきました！
[TypeScriptでpdfからテキストを抽出する（PDF.js）](https://zenn.dev/ptna/articles/63df4a8007f9d3)

### bunの導入

ランタイムにはbunを利用。
typescriptがそのまま動いて早いらしい...
https://bun.sh/

```bash
curl -fsSL https://bun.sh/install | bash
source ~/.zshrc
```

### テキスト抽出していく
PDF.jsの導入。
[PDF.js](https://www.npmjs.com/package/pdfjs-dist)

```bash
npm install --save pdfjs-dist
```

あ〜、bunだとbun addを使うのね。

```bash
bun add @types/node
```

参考にしたプログラムをちょっと修正したのが以下。

```ts
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！！

ふむふむ良い感じ。

![](https://storage.googleapis.com/zenn-user-upload/461d009979b6-20231202.png)

## chatgptのAPIを叩く

### chatgptのAPIを取得

![](https://storage.googleapis.com/zenn-user-upload/5b290ba94da7-20231203.png)

## OpenAIのSDKを使う

[API reference](https://platform.openai.com/docs/api-reference/authentication)
[OpenAI Node API Library](https://www.npmjs.com/package/openai)

以下で導入。

```bash
npm install openai@^4.0.0
```

サンプルコード。

```ts
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);
});
```

いけた〜！
![](https://storage.googleapis.com/zenn-user-upload/36355f07b96e-20231203.png)

あとはこれでリクエストを送れば...！

```ts
    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...**　というエラーが返ってきた。

![](https://storage.googleapis.com/zenn-user-upload/0a1ffa3029ea-20231203.png)

なるほど、送受信できるリクエスト文字数（トークン数）に上限があるのですね。

## PDFページごとにファイルを分割してファイルごとにリクエスト送信、受信するようにする

ファイル分割送受信、プロンプトも修正して以下のような形に仕上がりました。

https://github.com/pistachiyoda/pdfToMarkdown/tree/main

表の情報を文字列から推測させてmd形式で再現するのが難しい...
精度は何とも言えないけど一からPDFー>Markdownするのに比べたらだいぶマシかも？プロンプトエンジニアリングの勉強にもなりました！

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