この記事はPogee向けのコンテンツ企画として、生成AIを用いて作成した記事です。公開プロフィールの画面を実際に確認しています。JSONサンプルはすべて架空の教材で、Pogeeの内部実装や実際の会話ログではありません。掲載コードはNode.jsとAjvで実行確認しています。
AIキャラクターの設定をJSONで扱うとき、会話の品質を調べる前に見つけられる不具合があります。必須フィールドの欠落、想定外のキー、IDの表記揺れです。今回はJSON Schemaでそれらを検出する最小例を作ります。
Pogeeの公開プロフィールを入口に考える
このテーマの具体的な背景として、2026年10月15日にPogeeの公開キャラクター「Barnaby」のプロフィールを開きました。日本語UIには「チャットを始める」ボタンがあり、プロフィール本文は英語で表示されています。公開本文には、回し車で走る小さなペットと、投資家へ資金を求める企業的な語り口が描かれています。
名前や役割が欠けていないことと、設定の意味がつながっていることは別の検査です。例えば、ペットという設定と企業的な口調の組み合わせは意図したキャラクター表現かもしれません。構造だけで「矛盾」と決めず、編集上の意図を確認する必要があります。
以下では、その問題を独立した教材用JSONで扱います。リンの設定、フィールド名、スキーマは本記事のための例であり、Barnabyの設定ファイルを取得したものでも、Pogeeの内部データ形式や公開APIを示すものでもありません。
Pogeeの公開キャラクターBarnabyの実画面(2026年10月15日、日本語UI)。プロフィール本文は英語。JSON例は独立した教材であり、製品内部の形式ではありません。
確認する範囲
対象は設定データの構造です。ID、名前、役割、話し方、守る条件を定義し、未知のキーを拒否します。JSON Schemaだけで人格の一貫性、文章の意味、長期記憶まで検証できるわけではありません。最後に、構造としては正しいのに意味の確認が必要な入力も試します。
図1:合成設定5件の実行結果と検証範囲。AI支援で作成した説明図で、製品画面のスクリーンショットではありません。
準備
空の作業ディレクトリで次を実行します。この例はCommonJSのファイルとして保存するため、拡張子を.cjsにします。実行確認環境はNode.js v25.2.1、Ajv 8.18.0です。
npm init -y
npm install --save-exact ajv@8.18.0
# 次のコードを validate-character.cjs として保存
node validate-character.cjs
Draft 2020-12のスキーマを使うので、Ajvの2020用エントリーポイントを読み込みます。既存のプロジェクトに導入するときも、スキーマの方言とバリデーターを合わせてください。
そのまま動かせるコード
const Ajv2020 = require('ajv/dist/2020.js');
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'object',
additionalProperties: false,
required: ['id', 'name', 'role', 'voice', 'boundaries'],
properties: {
id: { type: 'string', pattern: '^[a-z][a-z0-9_-]{2,31}$' },
name: { type: 'string', minLength: 1 },
role: { type: 'string', minLength: 1 },
voice: { enum: ['calm', 'cheerful', 'formal'] },
boundaries: {
type: 'array', minItems: 1, uniqueItems: true,
items: { type: 'string', minLength: 1 }
}
}
};
const valid = {
id: 'rin_library', name: 'リン', role: '夜の図書館の司書',
voice: 'calm', boundaries: ['分からないことは分からないと伝える']
};
const { voice, ...missingVoice } = valid;
const cases = [
['valid', valid],
['missing_voice', missingVoice],
['unknown_field', { ...valid, secretMemory: true }],
['invalid_id', { ...valid, id: 'Bad ID' }],
['semantic_conflict', { ...valid, role: 'とても元気な接客係。常に大声で話す' }]
];
const validate = new Ajv2020({ allErrors: true }).compile(schema);
for (const [label, data] of cases) {
const ok = validate(data);
console.log(`${label}: ${ok ? 'VALID' : 'INVALID'}`);
if (!ok) {
const e = validate.errors[0];
console.log(` ${e.instancePath || '/'} ${e.message}`);
}
}
additionalProperties: falseで未知のキーを拒否し、requiredで欠落を検出します。voiceは列挙値、boundariesは空でない文字列の配列です。minLengthは空文字を防ぎますが、空白だけの文字列を有意味な文章に変えるものではありません。入力の正規化が必要なら、その方針を別途定義します。
実行結果
valid: VALID
missing_voice: INVALID
/ must have required property 'voice'
unknown_field: INVALID
/ must NOT have additional properties
invalid_id: INVALID
/id must match pattern "^[a-z][a-z0-9_-]{2,31}$"
semantic_conflict: VALID
正常な設定はVALIDです。voiceを除くと必須項目エラーになり、未定義のsecretMemoryを加えると未知のプロパティとして拒否されます。IDに空白や大文字を使ったケースも、指定したパターンに合わず失敗します。
allErrorsを有効にしていますが、表示は各ケースの先頭のエラーだけです。運用ではvalidate.errors全体を保持して編集画面に返すと、修正箇所をまとめて提示できます。個人情報や非公開設定をログにそのまま出すかどうかは、別途判断が必要です。
VALIDでも、意味が正しいとは限らない
semantic_conflictでは、落ち着いた話し方を指定したまま、役割の説明を「常に大声で話す接客係」に変えています。両方の値はスキーマを満たすためVALIDです。実際に矛盾とみなすべきかは仕様次第ですが、少なくとも構造の検査だけでは判断できません。
設定の構造をスキーマで検査し、意味の整合性を編集レビューで確認し、生成された返答を会話テストで確かめる。この3段階を分けると、設定の欠落とモデルの応答品質を混同しにくくなります。今回の5ケースはデータ検証の確認であり、キャラクター会話の性能測定ではありません。
既存の編集工程に入れるなら
まず保存前に同じスキーマを適用し、エラーを項目名と一緒に返す小さなチェックから始めます。次に、設定ファイルの変更時にも実行し、必須項目の追加などで既存データが失敗する場合の移行方法を決めます。スキーマの版を管理すると、古い設定との互換性も追いやすくなります。
キャラクターのプロフィールと会話の関係という背景には、Pogeeの公開ガイドがあります。本記事のコードは、その説明から特定の内部構成を推定したものではなく、独立した教材です。

