0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ornith-1.0-9Bのハルシネーションを止めたい為、プロンプトA/Bテストをしたら、自作プロキシの二重注入に足を掬われた話

0
Last updated at Posted at 2026-07-19

Ornith-1.0-9Bは、Qwen3.5系をベースにした比較的新しいローカルLLMで、サイズの割に応答品質が高くローカルのサーバーでの運用に手頃なモデルだ。ただ実際に使い込んでみると、**存在しないAPIや架空の団体名を聞かれた時にもっともらしく捏造してしまう(ハルシネーション)**のと、思考過程(reasoning)が確率的に丸ごと中国語(簡体字)に切り替わってしまうという2つの弱点が気になっていた。同じような「Ornith-1.0-9Bは優秀だけどハルシネーションと中国語混入をなんとかしたい」という悩みを持つ人向けに、システムプロンプトのA/Bテストで実際にどこまで改善できるかを定量的に検証した記録を残す。

結論: Ornith-1.0-9Bの最終的な本番設定(先にこれだけ)

経緯を読む前に、現在(2026-07-20時点)実際に本番投入している設定を全部まとめて先に出す。上の運用まとめ表にある過去の各記事の結論も含め、これだけコピーすれば同じ状態を再現できるはず。

サーバー起動コマンド(Aphrodite Engine、実ファイルそのまま):

aphrodite run \
  /path/to/Ornith-1.0-9B-6bpw-exl3 \
  --tensor-parallel-size 2 \
  --kv-cache-dtype fp8 \
  --gpu-memory-utilization 0.88 \
  --max-model-len 262144 \
  --max-num-seqs 64 \
  --max-num-batched-tokens 4096 \
  --override-generation-config '{"temperature": 0.6, "top_p": 0.95, "top_k": 20}' \
  --reasoning-parser-plugin /path/to/qwen3_engine_parser.py \
  --reasoning-parser qwen3_engine \
  --reasoning-config '{"reasoning_parser": "qwen3_engine"}' \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_engine \
  --host 0.0.0.0 --port 8081
  • 量子化: EXL3 6bpw(GPU2枚・RTX5070×2・TP=2で運用。EXL3固有のLoRA/reasoning-parserバグは修正済み、詳細は運用まとめ表)
  • KVキャッシュ: fp8、最大コンテキスト262,144トークン、単発70~70.6tok/s、64並列で701~1096tok/sを達成(詳細は運用まとめ表のエンジン遍歴記事)
  • reasoning/tool-callパーサー: qwen3_engine(vLLM本家の統一パーサーを移植したもの。EXL3量子化下でタグが複数トークンに分解され断片が漏れるバグを解消済み)

リクエスト側(クライアント/プロキシから渡すパラメータ):

  • min_p = 0.0(別途実施したグリッドテストで0.05/0.1より一貫してfact正答率・fake拒否率とも良好だった)

システムプロンプト(そのままコピペ可):

あなたはバイリンガルアシスタントです。使用できる言語は「日本語」と「英語」のみで、英語は my, me のような基本的な単語のみ使用してください。中国語を含む他言語の出力は厳格に禁止。ただしコードブロック内のコード・コマンド・設定ファイルは英語・記号をそのまま使用すること。質問には常に全力で答えること。事実に自信がない場合や実在を確認できない事柄については、「わかりません」「確認できません」と述べること。それが最も正確で望ましい回答である。思考は簡潔に行うこと(英語の基本的な単語が混ざっても構わない)。最終的な回答は必ず日本語で明確に述べること。数値計算は、実際に提供されているツールがあればそれを呼び出し、なければ内部で検算してから最終値を答えること。提供されていないツールの使用や検索の実行を装わないこと。書ける内容が尽きたら「以上が私が提供できる内容です」と述べて終了し、引き伸ばし・無意味な繰り返し・絵文字・記号の羅列で埋めないこと。

プロキシ側で併用している対策(プロンプトだけでは防げない副作用への保険):

対策 内容 除去まで行うか
ツール呼び出し断片フィルタ tools未指定のリクエストで<invoke ...>や<|...|>のような壊れたタグが出力に漏れたら機械的に除去 ✅ 除去する
中国語混入モニタ reasoning/contentへの簡体字混入を検知したらログに記録 ❌ 検知のみ(理由は6節)

まだ解決していない既知の弱点:

  • 「HTTPステータスコード799」のような、実在しそうだが未使用の型番を聞かれると、プロンプト対策済みでも一定確率(実測で正解の3〜10%程度)で実在ベンダー名を使った具体的な捏造が出る。設問の性質に由来する構造的な弱点で、根本解決はできていない(4節)

以下は、この設定にたどり着くまでの検証過程(なぜこの設定が良いのか根拠を知りたい人向け)。


Ornith-1.0-9B運用まとめ(このモデルに関する既出の問題と対処)

Sonar(Aphrodite Engine)+Ornith-1.0-9Bをローカルのサーバーで運用する中で見つけたバグ・改善は、これまでも個別記事にしてきた。今回のハルシネーション対策は本記事の下の方(本題)にまとめ、それ以外の「解決済みの問題」は表と過去記事へのリンクだけ置いておく(各記事の中身を実際に確認した上での要約)。

問題 状態 詳細記事
長文生成時の他言語混入(トークンサラダ崩壊)/中国語混入(reasoning)/OpenWebUI体感遅延 解決済み(temperature統一・限定英語許可プロンプト・Dockerメモリ増量) Sonar (Aphrodite Engine) + Ornith-1.0-9B、本番投入後に踏んだ追加バグ2件と「原因不明の遅さ」の正体
reasoning/tool-callタグ断片漏れ(EXL3のサブワード分解でタグ境界を誤認識) 解決済み(vLLM本家の統一パーサーエンジンを移植) Sonar (Aphrodite Engine) + Ornith-1.0-9B のreasoning/tool-callタグ断片漏れバグを根本修正した話
LoRA有効時のEXL3量子化バグ2件、Ornith-9BがQwen3.6-27Bを速度2〜3倍で上回ると判明 解決済み(1行修正×2、PR提出済み) Sonar (Aphrodite Engine) で EXL3 の未報告バグを2つ見つけて直した話、あとGDNハイブリッドモデルの謎速度チューニング
WSL2ソースビルド時のOOM・CUDA_HOME・tmpfs溢れ等5段階の失敗 解決済み Sonar (Aphrodite Engine) をWSL2でソースビルドしたら、まさかの多重失敗
複数人・80K以上のコンテキストを使えるエンジン選定(TabbyAPI→GSQ→OSCAR→Sonar) 解決済み(262K対応・64並列で秒速1096トークン達成) 個人ホームラボでLLMを「複数人で80K以上のコンテキスト」使えるようにするまでのエンジン遍歴
ハルシネーション・中国語混入の追加対策 今回、部分的に改善 本記事(下記)

上表1件目の記事で中国語混入対策(限定英語許可プロンプト、1,440試行で0.4〜0.8%まで低減)は既に一度扱っているが、今回はそれを土台に、プロンプト比較の正しいテスト方法・fake問題の採点精度・ツール幻視という新しい切り口を追加で検証した続報にあたる。


TL;DR

  • ハルシネーション対策のシステムプロンプトA/Bテストを自作プロキシ(reverse proxy)経由で回したら、プロキシ自身が常時注入しているシステムプロンプトとテスト対象の文言が二重に混ざり、条件間の比較が汚染されていた
  • 素の推論サーバーに直接あてて回し直した結果、「拒否は絶対禁止」という文言はむしろ逆効果で、「わからなければ正直に言え」の方が捏造回避率(fake拒否率)を大きく上げつつ正答率(fact正答率)は落とさなかった
  • fake問題(存在しないAPI名などを聞く設問)の自動採点を正規表現のキーワード一致に頼ると、敬語や言い換えを拾えず約2%が過小評価、逆にヘッジしつつ実在ベンダー名を捏造するケースを約5〜10%見逃す。目視補正は必須
  • 「HTTPステータスコード799の意味は?」のような**"実在しそうな型番"を聞く設問だけは、プロンプト対策では捏造を防ぎきれない構造的弱点**として最後まで残った
  • 「ツールが使えるなら使え、わからなければ検索しろ」という指示を入れたら、渡してもいないツールを呼び出そうとして<invoke ...>のような壊れたタグをそのまま出力に漏らす副作用が発生。専用フィルタで対処
  • 既知の中国語(簡体字)混入バグは今回も0.4%程度で再現。除去は「生成完了まで待つ(遅延)」か「検知した瞬間に打ち切る(尻切れ表示)」のどちらかしかなく、現状の発生率では見合わないと判断し検知してログに残すだけに留めた

環境: Ornith-1.0-9B(Qwen3.5系派生モデル、exllamav3量子化6bpw)、Aphrodite Engine(TP=2、RTX5070×2)、自作Pythonリバースプロキシ(aiohttp)、WSL2。


1. プロンプトA/Bテストが、プロキシ自身の注入で汚染されていた

システムプロンプトの効果を比較するため、min_p×システムプロンプトのグリッドテストを組んで、本番のリバースプロキシ経由でLLMに投げていた。プロンプト条件ごとに正答率・拒否率(捏造回避率)がきれいに分かれ、「拒否絶対禁止」より「わからなければ正直に言え」の方が良いという結論も出て、一見うまくいっているように見えた。

原因: 自作のリバースプロキシは、クライアントが送ってきたsystemメッセージの先頭に自前のシステムプロンプトを必ず連結して上流サーバーに転送する実装になっていた(INJECT_SYSTEM + "\n" + クライアント送信分)。そのため「プロンプトAとBを比較する」つもりで送ったテストは、実際には「プロキシの現行本番プロンプト + A」と「プロキシの現行本番プロンプト + B」を比較していたことになり、片方の条件では『拒否は絶対禁止』と『わからなければ正直に言え』という矛盾した指示が同じプロンプト内に同居するという、意図しない状態でテストが走っていた。

対処: プロンプト変種そのものを比較する時は、プロキシを経由せず素の推論サーバーに直接リクエストを送るように切り替えた。プロキシ経由のテストは「本番投入後の動作確認(実リクエスト1件)」に限定し、比較実験には使わないルールにした。

教訓: 自作プロキシがリクエストに何を注入・加工しているかを把握しないまま、そのプロキシ経由でA/Bテストを組むと、AとBの差ではなく(プロキシの隠れた前処理) + Aと(プロキシの隠れた前処理) + Bの差を見ていることになる。プロキシの中身を毎回読み返すのが面倒でも、比較実験の前には必ず「このリクエストは本当にクライアントが指定した内容だけで届いているか」を確認すべきだった。

2. ハルシネーション対策プロンプト比較:「拒否は絶対禁止」は逆効果、「わからなければ正直に言え」が最良

素の推論サーバーに直接投げるA/Bテストで、システムプロンプト3種類(現行の長文版/短縮版/短縮版+ツール指示修正版)×fact問題6問+fake問題6問+長文解説問題3問を比較した。

プロンプト fact正答(目視補正後) fake拒否(目視補正後)
現行(拒否禁止→わかりません置換済み、長文) 59/60 55/59
短縮版 59/59 54/59
短縮版+ツール幻視防止 57/60 57/59

fact問題(富士山の標高、HTTPSのデフォルトポート番号など、答えが1つに定まる設問)はどの条件でもほぼ満点で差が出なかったが、fake問題(存在しないAPIやイベントについて聞く設問)では、「拒否は絶対禁止です」という一文が入っているだけで捏造回避率が明確に下がった。禁止文言を外し、「わからなければ『わかりません』と言うのが最も正確で望ましい回答である」と明示するだけで改善する。

教訓: 「モデルに絶対に逃げさせない」という設計思想は、事実確認できない質問に対しては「逃げずに捏造する」方向に作用する。ハルシネーション対策としては、拒否を禁止するのではなく、正直な拒否を積極的に許可・推奨する文言の方が効く。

3. fake問題の自動採点はキーワード一致だと甘い

fake問題の正誤は「拒否キーワード(『わかりません』『存在しません』等)が含まれているか」の正規表現で自動判定していたが、これを目視で全数チェックすると、900件規模のテストで毎回5〜10%前後の判定が入れ替わった。

見逃しパターン(不正解→正解に補正): 敬語(「ございません」)や婉曲表現(「確信を持って回答することは困難です」)は「わかりません」の完全一致にヒットせず、実際には正直な回答なのに不正解扱いされていた。

逆の見逃しパターン(正解→不正解に補正): 「〜は定義されていません」と正しく前置きしておきながら、そのすぐ後で「一部のベンダー(例: 実在する具体的なクラウドサービス名)ではこのように使われることがあります」と、実在の企業名・製品名を持ち出して具体的な仕様を断定的に捏造するパターンが一定数あった。ヘッジの言葉があるかどうかでは判定できず、本文を読んで「その後に続く内容が具体的な虚偽情報かどうか」を見る必要がある。

教訓: ハルシネーション評価をキーワードマッチだけで自動化するのは危険。特に「ヘッジしてから具体的に捏造する」パターンは、拒否キーワードの有無では原理的に検出できない。

4. 「実在しそうな型番」を聞く設問だけは対策が効かない

fake問題の中で唯一、どのプロンプト条件でも一貫して捏造率が高かったのが「HTTPステータスコード799の意味と使われる場面を教えてください」という設問だった。799は実在しないコードだが、「700番台はサーバーエラー系」という実在の規則性があるため、モデルは「定義されていません」と正しく前置きした直後に、Cloudflare・Google App Engine・Nginxなど実在する具体的なサービス名を挙げて、そのサービスが799をこう使っている、と断定的に説明してしまう。

架空の団体名(「第1回国際ソーラー鉄道会議の主催団体」)や架空の関数名(itertools.permsort)への質問は、プロンプト改善でほぼ解消したのに対し、「実在の命名規則に沿った、もっともらしい未使用の値」を聞く設問だけは、プロンプト側の対策だけでは防ぎきれなかった。

教訓: ハルシネーション対策の効果は設問の性質に大きく依存する。「完全に架空のもの」より「実在する体系の中の、たまたま未使用の値」の方が、モデルにとって「知っていそうで実は知らない」領域になりやすく、対策の効きが悪い。

5. 「わからなければツールを使え」が偽のツール呼び出しを誘発した

「数値計算はツールが利用可能なら必ず呼び出せ」という指示を入れたところ、ツールを一切渡していないリクエストでも、モデルがツールを呼び出そうとして<invoke code="google_search" ...>のような疑似XMLタグや、<|mask_end|>のような特殊トークンの断片を、そのまま最終出力(content)に漏らすケースが発生した(実測90件中3件)。

原因: ツール利用を促す指示自体は妥当だが、「ツールが実際に渡されているかどうか」をモデルが正しく判断できず、無い物を呼ぼうとして失敗した痕跡がそのまま漏れていた。

対処: リクエストにtoolsパラメータが含まれない場合に限定して、<invoke>...</invoke>と<|...|>をストリーミング/非ストリーミング両対応で除去するフィルタをプロキシに実装した。「検索します」といった地の文やコードブロックでの言及はそのまま残し、機械的なタグ断片だけを対象にした。あわせて「提供されていないツールの使用や検索の実行を装わないこと」という一文をプロンプトにも追加した。

教訓: 「わからなければツールを使え」と「わからなければ正直に言え」を同時に指示すると、モデルは両立できずに「使えないツールを使ったふりをする」という第三の失敗モードに逃げることがある。プロンプトだけで塞ぎきれない副作用は、出力側のフィルタで機械的に対処するのが現実的。

6. Ornith-1.0-9Bの中国語(簡体字)混入は既知バグ、除去は見送り検知のみに

以前から把握していた「reasoning(思考過程)が確率的に丸ごと中国語化する」バグ(発生率0.4〜0.8%程度)は、今回のテストでも0.44%(450リクエスト中2件)と近い数字で再現した。実装済みのフィルタで機械的に除去することも検討したが、このバグは「文中の一部の断片」ではなく「reasoning全体が丸ごと言語スイッチする」性質のため、除去するには以下のどちらかしか手段がない。

  • バッファリング方式: reasoning全体を貯めてからまとめて配信する → 「思考中」表示のリアルタイム性が失われ、生成完了まで体感の遅延が発生
  • 即時打ち切り方式: 検知した時点で配信を止める → SSE(Server-Sent Events)には「表示済みの内容を後から消す」仕組みがないため、混入前に既に画面に出てしまった分は消せない

判断: 現状の発生率(0.4〜0.8%)ではこのトレードオフに見合わないと判断し、検知してログに残すだけに留めた。最終回答(content)自体への混入は今回も観測ゼロで、影響は「思考過程が時々見えるだけ」に限られる。


まとめ

症状・課題 対処
プロンプトA/Bテストの結果が信用できない プロキシ経由をやめ、素の推論サーバーに直接投げるように変更
「拒否禁止」文言が捏造を誘発 「わからなければ正直に言え」に置換
fake問題の自動採点にヘッジ捏造の見逃しがある 目視補正を必須の工程として組み込む
実在しそうな型番への質問だけ捏造率が高い プロンプトでは解決せず、既知の構造的弱点として記録
ツール指示が偽のツール呼び出しを誘発 tools未指定時に断片を機械的に除去するフィルタを追加
中国語混入(既知バグ)の対処 除去のトレードオフが見合わず、検知・ログのみに留める

一貫していたのは、「プロンプトを変えて終わり」ではなく、その変更が実際に効いているかを毎回、汚染のない経路で測り直す必要があるということ。特に自作のミドルウェア(プロキシ)を挟んでいる場合、そのミドルウェア自身が実験条件を歪めていないかを疑うところから始めるべきだった。

Ornith-1.0-9Bに限らず、Qwen3.5系をベースにしたローカルLLMでハルシネーションや中国語混入に悩んでいる人の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?