まず上限を明確にする
すべてを決定する 3 つの数字:
- コンテキストの総量は100,000トークンで、プロンプトと補完の合計となります。
max_tokensはデフォルトで 2048、1 回のリクエストで最大 16,000 です。- リクエストボディは 8 MB 以下である必要があります。この上限は純粋なテキストではほぼ問題になりませんが、実際に上限に達するのはトークン数です。
予算の計算式はシンプルです:利用可能なプロンプト = 100000 - max_tokens。モデルに4,000トークンを出力させたい場合、プロンプトの最大は60,000に減ります。これを超えるとAPIは400を返し、自動的に切り捨てはしません。したがって「本全体を突っ込んでから考えよう」は戦略ではなく、単なる運試しです。
もう一つの一般的な誤解:max_tokens を大きくすれば安全だということ。実際にはそれは予約済みの出力スペースであり、16,000 を設定するとプロンプトは 48,000 しか使えません。必要に応じて設定し、無条件に最大値にするのはやめましょう。
計算の例を挙げます。30 万字のインタビュー整理稿を要約すると仮定します。1 文字あたり 1.3 トークンで概算すると約 39 万トークンとなり、上限の 6 倍以上です。どうやっても全体を送ることはできません。さらに、1 回の要約で 800 トークンの出力が必要だと仮定すると、入力ブロックの最大は 63,000 となります。実際にはさらに 10% の余裕を持たせるため、実質 5 万トークン程度です。しかしこれが最適解ではありません。ブロックが大きすぎるとモデルは中間部分への注意が薄れるため、ブロックを小さく切り、呼び出し回数を増やす方が望ましいです。
トークン概算:近似アルゴリズム
ローカルなトークナイザーがない場合は、以下の経験値で概算できます。以下はいずれも近似値であり正確な値ではありません。テキストによって1〜2割の差が生じる可能性があります:
| テキストの種類 | 近似換算 |
|---|---|
| 中国語本文 | 1 文字あたり約 1〜1.5 トークン |
| 英語 | 4 文字あたり約 1 トークン、または単語あたり約 1.3 トークン |
| コード、JSON、記号の多いテキスト | 通常テキストよりもトークンを多く消費するため、保守的な高値で見積もることを推奨します。 |
実用的な概算関数と、余裕を考慮した予算計算を以下に示します:
CONTEXT = 100000
def est_tokens(text: str) -> int:
"""粗估:中文按每字 1.3 token,其余字符按每 4 个字符 1 token。仅为近似。"""
zh = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
other = len(text) - zh
return int(zh * 1.3 + other / 4) + 1
def max_prompt_budget(max_tokens: int, margin: float = 0.1) -> int:
"""给定计划的 max_tokens,返回 prompt 最多能用多少 token(留 margin 余量)。"""
return int((CONTEXT - max_tokens) * (1 - margin))
# 例:计划让模型写 3000 token,prompt 预算
print(max_prompt_budget(3000)) # 54900校正方法:典型入力から1回リクエストを送信し、レスポンスのusage.prompt_tokensを読み取って推定値と比較し、自社のデータに対する実際の倍率を算出します。係数を測定値に変更し、推定は「分割するかどうか」の判断にのみ使用し、帳合わせにはusageを確認します。
長文書の要約:分割と結合
文書が予算を超える場合、標準的な手法は map-reduce です:まずブロックごとに要約し、それらを結合して全体の要約を作成します。以下の点に注意してください:
- 段落の境界で分割し、固定文字数で無理やり切らないでください。そうすると文が途中で切れてしまいます。
- 各ブロックの予算を十分に確保し、上限の 3 分の 1 を超えないことを推奨します。指示と出力用のスペースを残すためです。
- 分割要約では、人名、数字、重要なイベントを保持するよう指示してください。そうしないと、結合する際に情報が失われます。
- 要約の要約がまだ長い場合は、再帰的に処理します。
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.wushenchaapi.com/v1", api_key=os.environ["API_KEY"])
def split_paragraphs(text, budget):
"""按段落切块,每块估算不超过 budget token。"""
chunks, cur, used = [], [], 0
for p in text.split("\n"):
t = int(len(p) * 1.3) + 1 # 中文粗估
if used + t > budget and cur:
chunks.append("\n".join(cur))
cur, used = [], 0
cur.append(p)
used += t
if cur:
chunks.append("\n".join(cur))
return chunks
def ask(prompt, max_tokens=800):
r = client.chat.completions.create(
model="uncensored",
temperature=0.3,
max_tokens=max_tokens,
messages=[{"role": "user", "content": prompt}],
)
return r.choices[0].message.content
def summarize_long(text, budget=12000):
parts = split_paragraphs(text, budget)
partial = [ask("用不超过 200 字概括下面这段,保留人名和关键事件:\n\n" + p) for p in parts]
merged = "\n".join(f"第{i+1}部分:{s}" for i, s in enumerate(partial))
return ask("下面是分段摘要,请合并成一份 400 字以内的整体摘要:\n\n" + merged, max_tokens=900)ここでは temperature を 0.3 に設定します。要約タスクでは安定性が重要です。各ブロックの呼び出しは独立しており、並列実行できますが、API キー 1 つあたり 1 分間に 300 回のリクエスト制限があることに注意してください。数十ブロックの文書であれば、この制限に達することはありません。
ブロックのサイズに正解はありません。経験則として、1 万〜1 万 5 千トークンを 1 ブロックにすると、詳細の保持と呼び出し回数のバランスが取れます。サンプルデータで 5000、12000、25000 の 3 種類のブロックサイズで実行し、要約で欠落した重要事実の数を比較することで、あなたの文書タイプに適した値を選定できます。契約書や技術文書など情報密度の高い素材ではブロックを小さくし、会話記録など冗長性の高いデータでは大きく設定できます。
小説の続き書き:スライディングウィンドウとローリングアウトライン
10数章に達すると、全文を収めきれなくなり、また全量を収める必要もなくなります。アプローチは2層のメモリです:
- 近景:直近の2〜3章の原文。文体、会話のリズム、シーン詳細の一貫性を確保します。
- 遠景:それ以前の章をアウトラインに圧縮し、キャラクター関係、伏線、未解決の糸口を保持します。
1 章書くごとに、モデルにその章を要約させ、アウトラインに追記させます。アウトライン自体が 3000 トークンを超えた場合は、全体を再度圧縮します。
def continue_story(chapters, outline, new_hint, keep_last=3):
"""chapters: 已写章节列表。只带最近 keep_last 章原文,更早的用 outline(滚动大纲)代替。"""
recent = "\n\n".join(chapters[-keep_last:])
prompt = (
f"【全书大纲(早期章节摘要)】\n{outline}\n\n"
f"【最近章节原文】\n{recent}\n\n"
f"【下一章要求】\n{new_hint}\n\n请写下一章,约 2000 字。"
)
return ask(prompt, max_tokens=4000)伏線は最も失われやすい要素です。アウトラインに「回収されていない伏線」の項目を別途設け、続き書きの際に「本章でその伏線の 1 つを処理する」と明確に指示してください。キャラクター名や地名も固定リストとして system に配置し、ウィンドウから外れた後にモデルが勝手に名前を変更しないようにします。
続き書きでは、見落とされがちな「スタイルのドリフト」の問題もあります。モデルは直近の章に近づきすぎて、以前の章のスタイルと一致しない場合、その不一致が増幅されます。解決策として、system に「スタイルのサンプル」を記述し、最も満足している原文の一部を固定アンカーとして配置します。これはウィンドウの移動に伴って変化しません。
max_tokens と切り捨て
回答が切り捨てられるには 2 つのケースがあり、区別する必要があります:
finish_reasonがlengthの場合:設定した max_tokens に到達しました。解決策は max_tokens を増やすか、モデルに分割して書かせることです(「ここまで書いたら止めて、私が続けるように指示してから続けて」)。- リクエストが直接 400 エラーになる場合:プロンプトと max_tokens の合計が 100,000 を超えています。解決策は入力を短くするか、max_tokens を減らすことです。
実用的な予算対照表:
| タスク | 推奨 max_tokens | 利用可能なプロンプト(約) |
|---|---|---|
| 要約、抽出 | 800 | 63,200 |
| 単章の続き書き(約2,000文字) | 4000 | 60,000 |
| 万字単位の長文出力 | 16,000(上限) | 48,000 |
長文出力は「応答時間」の影響も受けるため、ストリーミング出力を併用し、生成され次第表示することをお勧めします。
続き書きが途中で切れた場合、中途半端な内容だけをモデルに渡して「続けて」と言うのは避けてください。より確実な方法は、生成済みの内容をassistantメッセージとしてmessagesに戻し、userメッセージで「前の文の続きを書いて、重複しないように」と指示することです。これにより最も自然な接続が実現します。ただし、この手順でプロンプトが長くなるため、予算を再確認してください。
長文コンテキストのチェックリスト
- リクエストごとに推定関数でプロンプトを計算し、予算を超えた場合は分割処理へ進みます。
- レスポンスごとに使用量(usage)を記録し、推定係数を継続的に校正します。
- systemメッセージとアウトラインは内容の先頭に配置し、指示内の重要な要件は最後に再確認します。
- finish_reasonを確認し、lengthの場合は警告を出か続き書きを行います。
- 履歴会話には保持上限を設定し、上限を超えた場合は最も古いメッセージを削除します。APIが400を返すのを待たないでください。
関連内容:プロンプトの構造はプロンプトの書き方、エラー処理はエラーコードトラブルシューティングガイド、課金は価格ページをご覧ください。
最後に一点だけ注意してください:長いコンテキストは長い記憶を意味しません。モデルはリクエストごとに与えられた内容を最初から読み直すため、前回の呼び出しの記憶は保持されません。連続性が必要な場合は、履歴を自ら渡す必要があります。