動かざることバグの如し

近づきたいよ 君の理想に

llama.cpp JSONスキーマでZodのdescribeが効かない問題

環境

  • llama.cpp b7971

困っていること

llama.cppもJSONスキーマに対応している。

github.com

しかし、Zodと組み合わせた場合、describeで書いた説明文がLLMに渡っていないようだ。

以下が検証コードで、Zodスキーマのdescribeに特定の文字列を指定し、OpenAI APIとllama.cppの両方で同じスキーマを使わせることで、describeの内容が反映されているかを確認している。

import { openai } from '@ai-sdk/openai';
import { createOpenAI } from '@ai-sdk/openai';
import { Output, generateText } from 'ai';
import { z } from 'zod';
const localOpenAI = createOpenAI({
  baseURL: 'http://192.168.16.21:8000/v1'
});

const run = async () => {
  const testSchema = z.object({
    secretCode: z.string().describe("必ず 'PINEAPPLE' という文字列を入れてください")
  });

  const { output } = await generateText({
    // model: localOpenAI.chat('main'),
    model: openai('gpt-4.1'),
    output: Output.object({
      schema: testSchema
    }),
    prompt: 'こんにちは'
  });

  console.log(output);
};

run();

OpenAIのAPI(openai('gpt-4.1'))ではレスポンスが「PINEAPPLE」となり、describeが正しく渡っていることが確認できた。 一方、llama.cpp(localOpenAI.chat('main'))を使うと「12345」など、describeの内容が反映されていない出力になった。

解決策

Zod v4で toJSONSchema が実装された。これを使うことで、describeの内容を含む正しいJSON Schemaを生成できる。

${JSON.stringify(z.toJSONSchema(testSchema))}

上記のように記述すると、以下のようなJSON Schemaが生成される。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "secretCode": {
      "type": "string",
      "description": "必ず 'PINEAPPLE' という文字列を入れてください"
    }
  },
  "required": [
    "secretCode"
  ],
  "additionalProperties": false
}

このJSON Schemaをプロンプトに含めることで、describeの内容がLLMに正しく伝わるようになる。

なんでllama.cppはdescribeが抜け落ちるの?

describeの内容が無視される理由は、llama.cppのがJSONスキーマを「文法制約(Grammar)」としてのみ使用し、「プロンプト(指示)」としてモデルに伝えていないためだ。

llama.cppは渡されたJSONスキーマをGBNF(Grammar-Based Normalization Form)という文法定義に変換する。しかし、describeやdescriptionに書かれた意味的な指示は文法定義には含まれない。