◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/API / SDK
◈ API / SDK/2026-04-26中級

Gemini API のレスポンスで日本語が文字化けする時、最初に見るべき4つの場所

Gemini API で日本語レスポンスが文字化けする原因は、API側ではなくクライアント側の4つのレイヤーに潜んでいます。HTTPデコード、ストリーミングのチャンク境界、出力先の encoding、サロゲートペアの順にチェックする実践ガイドです。

gemini-api288troubleshooting59japaneseencodingutf-8streaming18

Gemini API を本番アプリに組み込んだ翌日、ユーザーから「画面が あい のような英数字記号に化けて読めません」という連絡が入る — そんな経験は、API を一度でも実装したことがある方なら覚えがあるのではないでしょうか。

私自身も Gemini API を使ったチャットアプリを最初にデプロイしたとき、ローカルでは綺麗に表示されていた日本語が、本番のログには \xe3\x81\x82\xe3\x81\x84 のような16進文字列で並んでいて、原因の特定に半日を費やしました。

実は Gemini API そのものは UTF-8 で正しい日本語を返しています。文字化けが起きる場所はほとんどの場合、API レスポンス側ではなく呼び出し側のクライアントコード・ストリーミング処理・出力先にあります。ここではGemini API で日本語レスポンスが文字化けしたときに、最初にチェックすべき4つのポイントを順番に解説します。

チェックポイント1: HTTP レイヤーの Content-Type と charset を確認する

最初に疑うべきは、HTTP レスポンスのデコードを担うクライアントライブラリの挙動です。

Gemini API は application/json; charset=UTF-8 を返しますが、フレームワークによっては charset を見落として ISO-8859-1 でデコードしてしまうケースがあります。Python の requests で発生しやすいパターンです。

import requests
 
# NG: 化けるパターン
response = requests.post(url, json=payload)
print(response.text)  # 'ã\x81\x82ã\x81\x84' のように化けることがある
 
# OK: 明示的に encoding を指定する
response = requests.post(url, json=payload)
response.encoding = "utf-8"
print(response.text)  # 正しく日本語表示される

response.text は内部的にエンコーディング判定を行いますが、ヘッダーの優先順位や apparent_encoding の挙動でブレることがあります。確実なのは response.json() を使うか、response.content.decode("utf-8") で明示的にバイト列をデコードする方法です。

JavaScript / Node.js の fetch でも同様の問題が起きます。

// OK: JSON で受け取れば fetch が UTF-8 デコードを担当する
const res = await fetch(url, {
  method: "POST",
  body: JSON.stringify(payload),
});
const data = await res.json();
console.log(data.candidates[0].content.parts[0].text);
 
// NG → OK: 自前でバッファ操作する場合は TextDecoder で明示
const buf = await res.arrayBuffer();
const decoded = new TextDecoder("utf-8").decode(buf);

ここまで確認しても化けるなら、原因は次のレイヤーにあります。

チェックポイント2: ストリーミングのチャンク境界でマルチバイト文字が切れている

Gemini API のストリーミングレスポンス(generateContentStream)でだけ文字化けが起きる場合、その犯人はほぼ間違いなくチャンク境界です。

UTF-8 では日本語1文字が3バイトで表現されます。SSE(Server-Sent Events)のチャンクは任意のバイト数で区切られるため、「あ」(E3 81 82)の3バイトが2つのチャンクに分かれることがあります。各チャンクを個別に decode("utf-8") すると、不完全なシーケンスでデコードエラーや化け文字 ? が混入します。

# NG: 各チャンクを個別にデコードする実装はバグります
async for chunk in stream:
    text = chunk.decode("utf-8")  # マルチバイト境界で壊れる
 
# OK: IncrementalDecoder で状態を保持する
import codecs
decoder = codecs.getincrementaldecoder("utf-8")()
 
async for chunk in stream:
    text = decoder.decode(chunk)  # 不完全なバイトはバッファされる
    if text:
        yield text
 
# ストリーム終了後に残りをフラッシュする
final = decoder.decode(b"", final=True)

公式 SDK(google-generativeai)を使っている場合は SDK 内部で IncrementalDecoder を使っているため、自前でストリームを読んでいる時だけこの問題が起きます。Edge Runtime や Cloudflare Workers で Response.body を直接 getReader() している場合は、特に注意が必要です。

ストリーミング全般のチャンク制御については Gemini API ストリーミングレスポンスのチャンクエラーとUX設計 で詳しく扱っています。

チェックポイント3: 出力先(コンソール・ファイル・DB)の encoding 設定

API から正しく日本語が取れていても、それを書き出す側で化けるパターンも非常に多いです。

特に Windows 環境のコンソール出力は要注意です。Python のデフォルト標準出力は cp932(Shift_JIS)が選ばれることがあり、print(response_text) で UnicodeEncodeError が発生するか、化け文字に置き換えられます。

import sys
import io
 
# Windows でコンソールに日本語を出すための定石
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
print(gemini_response)  # 正しく出力される
 
# ファイル書き出しも同じく encoding を明示する
with open("output.txt", "w", encoding="utf-8") as f:
    f.write(gemini_response)

データベースに保存する場合は、テーブルの文字コードが utf8mb4 になっているかも必ず確認してください。MySQL の古いデフォルト utf8(実体は3バイトUTF-8)では、絵文字や一部の漢字(𠮷野家の「𠮷」など)を含むレスポンスが切り捨てられて文字化けします。

チェックポイント4: 絵文字とサロゲートペアの取り扱い

Gemini はカジュアルな会話文を生成する際に、絵文字(🎉 や 😊)を自然に挿入することがあります。これらは UTF-16 サロゲートペアで2要素として表現されるため、文字数カウントや切り詰め処理で破綻しやすい部分です。

const text = "おめでとう🎉";
console.log(text.length);  // 7 — 絵文字が2文字としてカウントされる
 
// NG: 後ろから切ると絵文字が割れて化ける
text.slice(0, 6);  // "おめでとう\uD83C" のような不正な孤立サロゲート
 
// OK: Array.from でコードポイント単位に分解してから処理する
const points = Array.from(text);
points.slice(0, 5).join("");  // "おめでとう"

私の経験では、Gemini のレスポンスを Web Push 通知のタイトルに使う場面で、200文字制限のために slice(0, 200) をしていたら、ちょうど絵文字の途中で切れて配信エラーになったことがあります。ユーザー向けに表示する文字列を切り詰める時は、必ずコードポイント単位で扱うのが鉄則です。

日本語と英語が混在するレスポンスのコントロールについては、Gemini API の言語制御 — 出力が混在する問題の修正 で別途まとめています。出力品質そのものを底上げしたい方は、Gemini API 日本語タスク実践ガイド も合わせてご覧ください。

まずはこの順番でログに息を吹き込んでみてください

文字化けは原因の切り分けが命です。まずはログ出力を print(repr(response_text)) のように repr で囲んで、実際にどの段階のバイト列・文字列が壊れているかを可視化しましょう。'\xe3\x81\x82' のように生バイトが見えていればデコード前、'あ' のように Unicode コードポイントなら正しくデコードできています。

Gemini API はサーバー側ではきちんと UTF-8 を返してくれます。原因はかならずクライアント側の4つのレイヤー(HTTP デコード・ストリームチャンク・出力 encoding・サロゲートペア)のどこかにあるはずです。今日のうちに repr で1行ログを足しておくと、次に同じ事故が起きた時に5分で犯人を捕まえられるようになります。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

◈ API / SDK2026-05-20
Gemini API のストリーミングが本番だけまとめて返ってくる — Cloud Run・Vercel・Cloudflare のバッファリング対処
ローカル開発では一文字ずつ流れるのに本番デプロイ後だけ応答が一括で返ってくる — Cloud Run / Vercel / Cloudflare Workers / Nginx で発生するストリーミングバッファリング問題を、原因と対処パターン別に整理しました。
◈ API / SDK2026-10-16
models は 200 なのに generateContent だけ 403 になる日 — 切り分けの順番を先に決めておく
モデル一覧の取得は 200 で通るのに、生成リクエストだけが 403 permission_denied で止まるときの切り分け手順です。キー・プロジェクト・請求の順に確かめる小さな診断スクリプトと、リトライしてはいけない理由、サポートへ渡す材料の揃え方をまとめました。
◈ API / SDK2026-05-17
Gemini API で画像バッチ分析の INVALID_ARGUMENT エラーを診断する
Gemini API で複数画像を一括分析するとき INVALID_ARGUMENT エラーが出て詰まった経験はありませんか。MIMEタイプ・サイズ制限・リクエスト構造という3つの原因を診断し、File API と inline data を使い分けて根本解決する実践ガイドです。