Gemma 4でアプリを構築しているなら、自由形式のテキストではなく構造化出力が必要です。解析、検証し、データベースやAPIにパイプできるJSONが必要です。毎回、例外なく。
これはローカルLLMでの作業で最もトリッキーな部分の1つですが、適切な技術があればGemma 4は驚くほど信頼できます。すべての方法を見ていきましょう。
なぜ構造化出力が重要か
Gemma 4をチャットするだけでなく、より大きなシステムのコンポーネントとして使っている場合、予測可能な出力が必要です:
# これが欲しい:
{"sentiment": "positive", "confidence": 0.92, "topics": ["pricing", "support"]}
# これは欲しくない:
"The sentiment of this text is positive, with a confidence of about 92%..."最初のものは解析してプログラム的に使用できます。2番目は別の解析ラウンドが必要で、レイテンシー、コスト、障害ポイントが追加されます。
方法1:システムプロンプト技術
最もシンプルなアプローチ — システムプロンプトでモデルに正確に欲しいものを伝える:
import requests
import json
response = requests.post("http://localhost:11434/api/chat", json={
"model": "gemma4:26b",
"messages": [
{
"role": "system",
"content": """You are a JSON-only response API.
You MUST respond with valid JSON and nothing else.
No markdown, no explanation, no code blocks — just raw JSON.
Schema:
{
"sentiment": "positive" | "negative" | "neutral",
"confidence": number between 0 and 1,
"topics": string[],
"summary": string (one sentence)
}"""
},
{
"role": "user",
"content": "Analyze: 'The new update is amazing! The UI is so much cleaner and everything loads faster. Only complaint is the price went up.'"
}
],
"stream": False,
})
result = json.loads(response.json()["message"]["content"])
print(result)これはほとんどの場合動作します。しかし「ほとんどの場合」は本番環境には不十分です。モデルが時々「Here's the JSON:」のような前文を追加したり、出力をマークダウンコードブロックで包んだりする可能性があります。
方法2:Ollama formatパラメータ
Ollamaには出力を有効なJSONに制約する組み込みformatパラメータがあります:
response = requests.post("http://localhost:11434/api/chat", json={
"model": "gemma4:26b",
"messages": [
{
"role": "system",
"content": "Analyze the sentiment of the given text. Return: sentiment (positive/negative/neutral), confidence (0-1), topics (list), summary (one sentence)."
},
{
"role": "user",
"content": "The customer service was terrible but the product itself is excellent."
}
],
"format": "json",
"stream": False,
})
# これは有効なJSONが保証されている
result = response.json()["message"]["content"]
parsed = json.loads(result)format: "json"フラグは、Ollamaにトークン生成を有効なJSONのみを生成するように制約するよう指示します。これはプロンプトエンジニアリングだけよりはるかに信頼できます。
制限事項: 有効なJSON構文は保証しますが、スキーマは保証しません。モデルは期待する形式の代わりに{"answer": "positive"}を返すかもしれません。検証は依然として必要です。
方法3:Pydanticによるスキーマ定義
本番コードでは、Pydanticで期待するスキーマを定義し、それに対して検証します:
from pydantic import BaseModel, Field
from typing import Literal
import json
import requests
class SentimentResult(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
confidence: float = Field(ge=0, le=1)
topics: list[str]
summary: str
def analyze_sentiment(text: str) -> SentimentResult:
schema_str = json.dumps(SentimentResult.model_json_schema(), indent=2)
response = requests.post("http://localhost:11434/api/chat", json={
"model": "gemma4:26b",
"messages": [
{
"role": "system",
"content": f"""Respond with JSON matching this exact schema:
{schema_str}
No other text. Just valid JSON."""
},
{
"role": "user",
"content": f"Analyze this text: {text}"
}
],
"format": "json",
"stream": False,
})
raw = json.loads(response.json()["message"]["content"])
return SentimentResult.model_validate(raw)
# 使用
result = analyze_sentiment("Great product, terrible shipping time.")
print(f"Sentiment: {result.sentiment} ({result.confidence:.0%})")
print(f"Topics: {', '.join(result.topics)}")これは型安全と検証を提供します。モデルが予期しないものを返した場合、Pydanticはデータを静かに破損させる代わりに明確なエラーをスローします。
方法4:検証とリトライパターン
最大限の信頼性のために、リトライループを追加:
from pydantic import BaseModel, ValidationError
import json
import requests
import time
def get_structured_output(
prompt: str,
schema_class: type[BaseModel],
model: str = "gemma4:26b",
max_retries: int = 3,
) -> BaseModel:
schema_str = json.dumps(schema_class.model_json_schema(), indent=2)
for attempt in range(max_retries):
try:
response = requests.post("http://localhost:11434/api/chat", json={
"model": model,
"messages": [
{
"role": "system",
"content": f"Respond ONLY with JSON matching this schema:\n{schema_str}"
},
{"role": "user", "content": prompt}
],
"format": "json",
"stream": False,
"options": {
"temperature": 0.1 if attempt == 0 else 0.3,
},
})
raw = json.loads(response.json()["message"]["content"])
return schema_class.model_validate(raw)
except (json.JSONDecodeError, ValidationError) as e:
if attempt == max_retries - 1:
raise ValueError(
f"Failed to get valid output after {max_retries} attempts: {e}"
)
time.sleep(0.5)
raise ValueError("Unreachable")
# 使用
class ProductReview(BaseModel):
rating: int = Field(ge=1, le=5)
pros: list[str]
cons: list[str]
recommendation: bool
review = get_structured_output(
"Review: 'Solid laptop, great keyboard, battery could be better. 4/5 would buy again.'",
ProductReview,
)主要な設計選択:
- 一貫性のために低温度(0.1)で開始、多様性のためにリトライで増加
- 有効なJSON構文を保証するために
format: "json"を使用 - スキーマの正確性のためにPydanticで検証
- リトライを3に制限 — 3回失敗したら、プロンプトにおそらく問題がある
よくある失敗と修正
モデルがJSONをマークダウンで包む:
```json
{"key": "value"}
```修正:Ollamaのformat: "json"を使用。利用できない場合、マークダウンを除去:
def clean_json(text: str) -> str:
text = text.strip()
if text.startswith("```"):
text = text.split("\n", 1)[1] # 最初の行を削除
text = text.rsplit("```", 1)[0] # 最後のバッククォートを削除
return text.strip()モデルが追加フィールドを含める:
モデルは要求していないフィールドを返す可能性があります。Pydanticはこれを処理 — デフォルトでは追加フィールドを無視します。またはmodel_config = ConfigDict(extra="forbid")を設定して拒否します。
モデルが間違った型を使う:
時々モデルは0.92(数値)の代わりに"0.92"(文字列)を返します。Pydanticのmodel_validateはほとんどの型変換を自動的に処理します。
空またはNullフィールド:
空の可能性があるフィールドはオプションにしましょう:
class Result(BaseModel):
name: str
email: str | None = None # モデルがメールを見つけられない可能性
topics: list[str] = [] # デフォルトで空リストネストされたオブジェクト:
Gemma 4はネストされたJSONをうまく処理しますが、ネストは2-3レベル以内に:
class Address(BaseModel):
city: str
country: str
class Person(BaseModel):
name: str
age: int
address: Address # 1レベルのネスト — 問題なしパフォーマンスのヒント
- 低い温度(0.1-0.3)はより一貫したJSONを生成
- 短いスキーマはより良いコンプライアンス — 一度に20フィールドを要求しない
- システムプロンプト内のfew-shot例は信頼性を劇的に改善
- 26BモデルはE4BよりJSONが大幅に優秀 — モデル比較を参照
- Thinkingモードは複雑なスキーマに役立つ — thinkingモードガイドを参照
次のステップ
- アプリケーションでOllama APIでJSON出力を使用
- vLLM + DockerでJSON APIサーバーをデプロイ
- 特定のJSONフォーマットのためにGemma 4をファインチューニング
- 複雑な構造化タスクのためのthinkingモードについて学ぶ
Stop reading. Start building.
~/gemma4 $ Get hands-on with the models discussed in this guide. No deployment, no friction, 100% free playground.
Launch Playground />


