Docker Model RunnerとAgentic Composeによるコンテキストパッキングにおけるコンテキストサイズの問題を解決する方法

投稿日 Feb 13, 2026

ローカル言語モデルを扱ったことがあるなら、特に性能の低いマシンで小さなモデルを使う際に、コンテキストウィンドウの制限に遭遇したことがあるでしょう。避けられない制約ですが、コンテキストパッキングのような技術で意外にも管理しやすくなっています。

こんにちは、私はフィリップです。Dockerの利用をサポートするプリンシパルソリューションアーキテクトです。前回の ブログ記事では、RAGを使って非常に小さなモデルを有用にする方法について書きました。文脈の長さを短くするためにメッセージ履歴を 2 に制限しました。

しかし場合によっては、履歴により多くのメッセージを残す必要があります。例えば、コードを生成するための長い会話:

- generate an http server server in golang
- add a human structure and a list of humans
- add a handler to add a human to the list
- add a handler to list all humans
- add a handler to get a human by id
- etc...

例えば、歴史にメッセージを残したい会話があるとしましょう 10 。さらに、非常に冗長なモデルを使っており(多くのトークンが使われます)、すぐにこのようなエラーに遭遇します。

error: {
    code: 400,
    message: 'request (8860 tokens) exceeds the available context size (8192 tokens), try increasing it',
    type: 'exceed_context_size_error',
    n_prompt_tokens: 8860,
    n_ctx: 8192
  },
  code: 400,
  param: undefined,
  type: 'exceed_context_size_error'
}


どうされました。

ローカルLLMにおけるコンテキストウィンドウとその制限の理解

私たちのLLMにはコンテキストウィンドウがあり、サイズは制限されています。つまり、会話が長くなりすぎたら...バグが起きます。

このウィンドウは、モデルが一度に処理できる トークン の総数であり、短期の作業記憶のように機能します。コンテキストウィンドウについて詳しく知りたい方は 、IBMの記事 をご覧ください

上記のコードスニペットの例では、このサイズはDocker Model Runner、Ollama、LlamacppなどローカルLLMを駆動するLLMエンジンのトークン 8192 に設定されています。

このウィンドウにはシステムプロンプトユーザーメッセージ履歴注入されたドキュメント、生成されたレスポンスなどすべてが含まれます。詳細はこの Redisの投稿 をご覧ください。 

例:モデルのコンテキスト 32k個の場合、合計(入力+履歴+生成出力)はkトークンのままで≤ 32なければなりません。詳しく はこちらをご覧ください。  

compose.ymlファイル内でデフォルトのコンテキストサイズ(上下)を変更することは可能です:

models:
  chat-model:
    model: hf.co/qwen/qwen2.5-coder-3b-instruct-gguf:q4_k_m
    # Increased context size for better handling of larger inputs
    context_size: 16384

Dockerでも以下のコマンドでこれが可能です:docker model configure –context-size 8192 ai/qwen2。5-コーダー`

こうして問題は解決しますが、問題 の一部に過ぎません。実際、モデルがより大きなコンテキストサイズ( 16384)をサポートしている保証はなく、たとえ対応できたとしても、モデルのパフォーマンスを非常に早く低下させる可能性があります。

したがって、hf.co/qwen/qwen2と 。5-coder-3b-instruct-gguf:q4_k_m,コンテキスト内のトークン数が 16384 トークンに近づくと、生成は(少なくとも私のマシンでは )かなり遅くなります 。これもモデルの容量によります(ドキュメントを読んでください)。そして、モデルが小さいほど、大きな文脈を扱い集中し続けるのが難しくなります。

ヒント: 必ずアプリケーション内で、メッセージリストを空にするか縮小するかのオプション(例えば/clearコマンド)を用意してください。オートマチックかマニュアルか。ただし、初期のシステム指示はそのままにしてください。

つまり、行き詰まっている。小型モデルでさらに進めるにはどうすればいいのでしょうか?

しかし、まだ解決策はあります。それが コンテキストパッキングです。

コンテキストパッキングを使って限られたコンテキストウィンドウにより多くの情報を収めようとします

コンテキストサイズを無限に増やすことはできません。さらに多くの情報をコンテキストに収めるために 、「コンテキストパッキング」という手法を用いられます。これはモデル自身が過去のメッセージを要約したり(または別のモデルにタスクを託したり)し、 履歴を この要約に置き換えることでコンテキスト内のスペースを確保できます。

そこで、あるトークンの制限から過去のメッセージの履歴をまとめ、生成された要約に置き換えることに決めます。

そこで、文 脈パッキング ステップを追加するために例を修正しました。演習では、別のモデルを使って要約を行うことにしました。

compose.ymlファイルの修正

compose.ymlファイルに新しいモデルを追加しました:ai/qwen2。5:1。5B-F16

models:
  chat-model:
    model: hf.co/qwen/qwen2.5-coder-3b-instruct-gguf:q4_k_m

  embedding-model:
    model: ai/embeddinggemma:latest

  context-packing-model:
    model: ai/qwen2.5:1.5B-F16

そうしたら:

  • プログラムを動かすサービスのモデルセクションにモデルを追加しました。
  • 履歴のメッセージ数を以前2から10に増やしました。
  • コンテキスト圧縮をトリガーする前に 5120 にトークン制限を設定しています。
  • 最後に、「コンテキストパッキング」モデルの指示を定義し、過去のメッセージを要約するよう求めました。

式典の抜粋:

golang-expert-v3:
build:
    context: .
    dockerfile: Dockerfile
environment:

    HISTORY_MESSAGES: 10
    TOKEN_LIMIT: 5120
    # ...
   
configs:
    - source: system.instructions.md
    target: /app/system.instructions.md
    - source: context-packing.instructions.md
    target: /app/context-packing.instructions.md

models:
    chat-model:
    endpoint_var: MODEL_RUNNER_BASE_URL
    model_var: MODEL_RUNNER_LLM_CHAT

    context-packing-model:
    endpoint_var: MODEL_RUNNER_BASE_URL
    model_var: MODEL_RUNNER_LLM_CONTEXT_PACKING

    embedding-model:
    endpoint_var: MODEL_RUNNER_BASE_URL
    model_var: MODEL_RUNNER_LLM_EMBEDDING

ファイルの完全版はこちらでご覧いただけます: compose.yml

コンテキストパッキングモデルのシステム命令

compose.ymlファイルの中に、context-packing.instructions.md ファイルに「コンテキストパッキング」モデルの新しいシステム命令を追加しました:

context-packing.instructions.md:
content: |\
    You are a context packing assistant.
    Your task is to condense and summarize provided content to fit within token limits while preserving essential information.
    Always:
    - Retain key facts, figures, and concepts
    - Remove redundant or less important details
    - Ensure clarity and coherence in the condensed output
    - Aim to reduce the token count significantly without losing critical information

    The goal is to help fit more relevant information into a limited context window for downstream processing.

あとはアシスタントのコードにコンテキストパッキングロジックを実装するだけです。

 アシスタントのコードにコンテキストパッキングを適用する

まず、アシスタントのセットアップ部分でコンテキストパッキングモデルとの接続を定義します:

const contextPackingModel = new ChatOpenAI({
  model: process.env.MODEL_RUNNER_LLM_CONTEXT_PACKING || `ai/qwen2.5:1.5B-F16`,
  apiKey: "",
  configuration: {
    baseURL: process.env.MODEL_RUNNER_BASE_URL || "http://localhost:12434/engines/llama.cpp/v1/",
  },
  temperature: 0.0,
  top_p: 0.9,
  presencePenalty: 2.2,
});

また、このモデルで定義したシステム命令とトークン制限も取得します。

let contextPackingInstructions = fs.readFileSync('/app/context-packing.instructions.md', 'utf8');

let tokenLimit = parseInt(process.env.TOKEN_LIMIT) || 7168

会話ループに入ったら、 過去のメッセージで消費されたトークンの数を推定し、この数が 定義された上限を超えた場合は、コンテキストパッキングモデルを呼び出して過去のメッセージの履歴を要約し、生成された要約(アシスタントタイプのメッセージ:["assistant", summary])に置き換えます。その後、メインモデルを使って応答を生成し続けます。

会話ループからの抜粋:

 let estimatedTokenCount = messages.reduce((acc, [role, content]) => acc + Math.ceil(content.length / 4), 0);
  console.log(` Estimated token count for messages: ${estimatedTokenCount} tokens`);

  if (estimatedTokenCount >= tokenLimit) {
    console.log(` Warning: Estimated token count (${estimatedTokenCount}) exceeds the model's context limit (${tokenLimit}). Compressing conversation history...`);

    // Calculate original history size
    const originalHistorySize = history.reduce((acc, [role, content]) => acc + Math.ceil(content.length / 4), 0);

    // Prepare messages for context packing
    const contextPackingMessages = [
      ["system", contextPackingInstructions],
      ...history,
      ["user", "Please summarize the above conversation history to reduce its size while retaining important information."]
    ];

    // Generate summary using context packing model
    console.log(" Generating summary with context packing model...");
    let summary = '';
    const summaryStream = await contextPackingModel.stream(contextPackingMessages);
    for await (const chunk of summaryStream) {
      summary += chunk.content;
      process.stdout.write('\x1b[32m' + chunk.content + '\x1b[0m');
    }
    console.log();

    // Calculate compressed size
    const compressedSize = Math.ceil(summary.length / 4);
    const reductionPercentage = ((originalHistorySize - compressedSize) / originalHistorySize * 100).toFixed(2);

    console.log(` History compressed: ${originalHistorySize} tokens → ${compressedSize} tokens (${reductionPercentage}% reduction)`);

    // Replace all history with the summary
    conversationMemory.set("default-session-id", [["assistant", summary]]);

    estimatedTokenCount = compressedSize

    // Rebuild messages with compressed history
    messages = [
      ["assistant", summary],
      ["system", systemInstructions],
      ["system", knowledgeBase],
      ["user", userMessage]
    ];
  }

コードの完全なバージョンはこちらでご覧いただけます: index.js

あとはアシスタントをテストして長い会話をしてもらい、コンテキストの構築が実際に機能している様子を見るだけです。

docker compose up --build -d
docker compose exec golang-expert-v3 node index.js

会話が進むと、トークン制限に関する警告メッセージが表示され、その後コンテキストパッキングモデルで生成された要約、そして最後に履歴内のトークン数の減少が表示されます。

Estimated token count for messages: 5984 tokens
Warning: Estimated token count (5984) exceeds the model's context limit (5120). Compressing conversation history...
Generating summary with context packing model...
Sure, here's a summary of the conversation:

1. The user asked for an example in Go of creating an HTTP server.
2. The assistant provided a simple example in Go that creates an HTTP server and handles GET requests to display "Hello, World!".
3. The user requested an equivalent example in Java.
4. The assistant presented a Java implementation that uses the `java.net.http` package to create an HTTP server and handle incoming requests.

The conversation focused on providing examples of creating HTTP servers in both Go and Java, with the goal of reducing the token count while retaining essential information.
History compressed: 4886 tokens → 153 tokens (96.87% reduction)

これにより、アシスタントが長時間の会話を処理しつつ、良好な生成パフォーマンスを維持できるようにしています。

概要

コンテキストウィンドウは、特に小規模なモデルやリソースが限られたマシンでローカル言語モデルを扱う際に避けられない制約です。しかし、コンテキストパッキングのような手法を使うことで、この制限を簡単に回避できます。Docker Model RunnerとAgentic Composeを使えば、長く冗長な会話でもモデルを圧倒せずにサポートできます。

すべてのソースコードはCodebergで利用可能です: コンテキストパッキング。ぜひ試してみてください! 

関連記事