LayerX エンジニアブログ

LayerX の エンジニアブログです。

LLMで長い出力を扱うときに、知っておくとちょっと楽になること

こんにちは。AI・LLM事業部 LLMグループ エンジニアのkoseiです。

AI・LLM事業部ではエンタープライズのお客様向けの文書処理業務を効率化するプロダクト「Ai Workforce」を開発・提供しています。
詳細は以下のリンクをご参考ください。
getaiworkforce.com

直近も大手リース会社のお客さまにご導入いただきました。「Ai Workforce」が実際どのように利用されているのか、ご興味のある方は是非ご覧ください。

getaiworkforce.com

さて、LLMを用いたアプリケーションを開発していると、LLMの出力が長いタスクって意外と遭遇しますよね。
長文の要約、大量のデータへのタグ付け・分類、文書に対する巨大な構造化JSONの生成など。
こうしたケースでは、出力のトークン数が自然と増えがちですが、長くなると結構困ることがあります。

  • 出力が途中で切れてしまう
  • レイテンシが長くなる
  • コストが思ったよりかさむ

前提:出力トークン長は処理コストや時間に直結

まず前提として、OpenAIの公式ドキュメントにもあるように、LLMのレイテンシには入力トークンよりも出力トークン長が大きく影響します:

cutting 50% of your output tokens may cut ~50% your latency.
OpenAI Platform

また、費用も基本的に出力トークンの方が高く、GPT-4.1では入力トークンの約4倍です

(画像出典:https://openai.com/api/pricing/

そのため、LLMの出力トークン長はできるだけ節約できると嬉しいです。
今回はそのような「長い出力」を扱うときに、事前に知っておくと少し楽になるTipsをご紹介します。

Tips 1: 続きを生成させる

出力トークン数制限に対する言わずもがなな方法です。長文の途中で止まってしまった場合、"Continue from here"のように続きを出力させる。
ただし、構造化データを扱っている場合は注意が必要です。中途半端なところで分割されるとパースしづらくなるため、意味的に区切りの良いところで分割する等気をつけましょう。

Tips 2:日本語ではなく英語で出力させる

こちらは小技系です。
日本語は1文字が1トークンになることが多い一方で、英語では単語単位でトークン化されることが多いため、同じ内容でも英語の方がトークン数を節約できます(だいたい2割ほど削減できることも)。
特に Chain of Thought(CoT)を使った推論系タスクで効果的です。


https://platform.openai.com/tokenizer でカウント)

Tips 3: unpretty-printed jsonを用いる

こちらのMNTSQさんの発表資料で記載されている方法で、手軽に試せてかつ削減幅も大きいのでおすすめです。
LT資料 - MNTSQの契約書解析を LLMに置き換える話 - Speaker Deck


// pretty print
{
    "user": {
      "id": 123,
      "name": "Taro Tanaka",
      "email": "taro@example.com"
    },
    "items": [
      {
        "id": 1,
        "name": "Book",
        "price": 1200
      }
    ]
  }

// un-pretty print
{"user":{"id":123,"name":"Taro Tanaka","email":"taro@example.com"},"items":[{"id":1,"name":"Book","price":1200}]}

このように、スペースや改行を省いたJSON形式で出力させることで、出力トークンを大幅に削減(上記発表資料では4割以上)できます。

Tips 4: 小さいモデルとの併用

小さなモデルのほうが出力可能な最大トークン数が多いケースがあります。
高精度が必要な部分だけ大きいモデルに任せて、補助的な処理は小さいモデルで分担する、という方法も有効です。例えば、大きいモデルで項目抽出した結果(長いため途中で途切れている)を小さいモデルに入力して残りを抽出させるなど。

(出典:Azure AI Foundry公式ドキュメント

Tips 5: 入力の部分ごとに出力する

たとえば、「文書全体を要約したい」というケース。
文書全体を一度に処理すると出力が長くなりすぎます(もしくは不十分)が、章やセクションごとに出力させることで、各リクエストの出力をスリムに保てます。

一般的なRAGシステム構築の際に文書の分割自体はよく行われますが、「長い出力をさせたい」ケースにおいては必ずしも入力資料を厳密に分割する必要はありません。
例えば文書の章ごとにある程度のボリュームのある要約を出力したい場合、事前に章ごとに入力文書を分割する必要はなく、①まず章立てをLLMで取得する、②その後各章ごとにLLMにリクエストを送り、要約させる、という流れで十分です。

Tips 6: idを事前に付与し、idをLLMに回答させる

個人的にはこの方法が汎用性高く扱いやすいです。
特に複数の要素に対して、LLMの処理結果を付与する場合はidを事前に付与しておくと便利です。
例えば商品データに対してLLMで特定のカテゴリーを付与する場合、以下のような入出力が考えられます。


# 商品データの入力例
["軽量撥水ジャケット", "オーガニックコットンTシャツ", "コンパクト電動歯ブラシ", ...]

# 商品データの出力例(LLMでカテゴリーを付与)
[
  {
    "name": "軽量撥水ジャケット",
    "llm_category": "レインウェア"
  },
  {
    "name": "オーガニックコットンTシャツ",
    "llm_category": "トップス"
  },
  {
    "name": "コンパクト電動歯ブラシ",
    "llm_category": "家電 > オーラルケア"
  },
  ...
]

全ての商品にカテゴリーを付与した結果を得る方法はいくつか考えられますが、以下のようにidを事前に付与しておくと扱いやすく、出力トークン数を節約できます。


# 商品データの入力例(事前にidを付与しておく)
[
  {"id": "1", "name": "軽量撥水ジャケット"},
  {"id": "2", "name": "オーガニックコットンTシャツ"}, 
  {"id": "3", "name": "コンパクト電動歯ブラシ"},
  ...
]

# 商品データの出力例(LLMでカテゴリーを付与)
{
  "1": "レインウェア",
  "2": "トップス",
  "3": "家電 > オーラルケア",
  ...
}

他の処理方法(商品ごとにリクエストを送る方法・nameを含めて出力させる方法・categoryのリストを出力させる方法等)に比べて、LLMがサボった時にもどの商品がカテゴリー付与されていないか判別しやすく、トークン数も日本語を出力するより少なく済みます。

さらには、以下のように事前定義可能なカテゴリーはidにするともっと節約できますが、要件と精度に応じて調整が必要です。例えば文書の章タイトルを扱う際にはLLMがidと章番号を混同してしまうケースもあり、idの付け方にも注意です。


category_id_mapping = {
	"cat-1": "家電 > オーラルケア",
	"cat-2": "レインウェア",
	"cat-3": "トップス"
}

# LLMの出力例
llm_response = {
	"1": "cat-2",
	"2": "cat-3",
	"3": "cat-1"
}

とはいえ…今後は全く工夫せずとも良くなるかも

モデルのバージョンアップ含め、出力トークン数は長くなってきています。例えばGPT-4oのmax_tokensは4096(2024-05-13)から16384(2024-07-18)になりました。近いうちに本記事の小技も全く必要なくなるかもしれません。

まとめ

一つ一つは細かいですが、出力トークン数の取り回しをよくする方法をいくつかご紹介しました。
「出力が長くなりそう」とわかった時点で、こうした選択肢を知っておくだけで、設計がしやすくなり、無駄なトライ&エラーを避けられることも多いです。
要件に応じて使い分けつつ、設計・プロンプト・後処理の引き出しとして活用してみてください。

LayerX AI・LLM事業部では歴史あるエンタープライズのお客さまの課題をLLMを用いて解決するチャレンジを共にする仲間を募集しています。
直近では以下のチャレンジに取り組んでいます。ご興味ある方は他の記事を読んでみてください。

少しでも面白そう、と思っていただけたならご興味あるトピックについてカジュアルにお話しできますと嬉しいです。ブログよりもさらに踏み込んだお話しができます!

こちらからカジュアル面談をご予約ください!