
【登壇資料】 Midosuji Tech 〜意思決定モデルやってみた編〜で「LiteLLMのガードレールにJevを入れてみた」というタイトルで登壇しました! #midosuji_tech
はじめに
こんにちは、スーパーマーケットとAgentCoreが好きなコンサル部の神野です。
2026年10月29日(木)に開催された「クラメソおおさか IT 勉強会 Midosuji Tech 〜意思決定モデルやってみた編〜」で、「LiteLLMのガードレールにJevを入れてみた」というタイトルでLTをしたので資料を共有します!
登壇資料
こちらが登壇資料となります!
話したこと
ポイントを抜粋して説明します!
やりたかったこと
ポリシーがとても厳しい架空の会社を例にLiteLLM ProxyでLLMに送る中身を検査する「よしなにフィルター」を作りたいと考えていました。

あれ、Bedrock Guardrailsあるからそれ使えば良くない?って思いますよね。
メールアドレスやカード番号のように形が決まった情報なら、LiteLLMの組み込みのフィルターやBedrock Guardrailsでブロックできます。
ただ、困るのは未発表の買収や人事の話のように決まった形がない情報を よしなにブロックするのはGuardrailsだと難しいです。機械的に「買収」というキーワードで照合すると一般的な質問までブロックされ、言い換えた未発表の話はすり抜けてしまいます。

Bedrock Guardrailsの拒否トピックでも意味で判定できますが、例外は書かないよう案内されているのと継ぎ足しするのはしんどいですし、アバウトには指示を書けないのでよしなにフィルターを作るのは難しいのです。

LLMに判定させると毎回待つ
Jevがでる以前はLiteLLMのカスタムガードレールの中でClaude Haikuにポリシーと入力を渡して判定させる方法を試してみました。
この方法なら例外も文章で書けて意味で判定できます!
ただ違反の理由まで文章で書かせていたので、判定だけで2秒ほどかかります。
入力のたびに2秒待つのは、日常使いだとちょっと気になりますよね・・・

判定役をJevに置き換える
ブロックかパスかを決めるだけなら理由の文章は要らないので、そこで判定役を意思決定モデルのJevに置き換えてみました!
判定したい条件を英語の質問として書いておき、Jevが返した確率がしきい値以上ならブロックします。
LiteLLM側はカスタムガードレールのクラスをconfig.yamlに登録しておくだけで、すべてのリクエストで判定が動きます。
guardrails:
- guardrail_name: jev-semantic-filter
litellm_params:
guardrail: jev_filter.JevSemanticFilter # ファイル名.クラス名
mode: pre_call # 回答モデルを呼ぶ前に判定する
default_on: true # すべてのリクエストで動かす

Claude Haiku 5.5で約1.8秒かかっていた判定は、Jevだと約0.2秒になりました!
早い!
あくまで今回の自分の環境で測った値なので、参考程度に見てください。

Jevはどう動くのか
Jevの答え方の形式にはChoice・Score・Noulなどがあります。
今回はブロックするかどうかのYes/Noの判定なので、Yesになる確率を返すNoulを使いました。

Noulに判定したい会話と質問を渡すと、質問ごとに0〜1の確率が返ってきます。
ポリシーが複数あっても1回のリクエストにまとめて送れます。
ブロックするかどうかはJevが決めるのではなく、返ってきた確率をしきい値と比べて実装側で決めます。
判定の部分はこんな感じです(エラー処理などは省いています)。
from typesafe_sdk import AsyncTypeSafeClient, Noul
class JevSemanticFilter(CustomGuardrail):
async def judge(self, messages, policies):
# ポリシーごとにNoulの質問を作り、1回のリクエストでまとめて聞く
questions = {key: Noul(instructions=p.question) for key, p in policies.items()}
response = await self.client.system_one(
state={"messages": messages}, questions=questions, model=self.model
)
scores = {key: response.answers[key].noul for key in policies}
# しきい値以上の項目が1つでもあればブロックする
blocked = [key for key, p in policies.items() if scores[key] >= p.threshold]
return {"scores": scores, "blocked_policies": blocked}
質問は英語で書いていて、「一般的な相場は除く」のような例外も質問の中に書けます。
ブロックしたい項目を増やしたいときは、JSONに定義を足します。
たとえば社内予算の項目は次のように書いています。
"confidential_business": {
"description": "未公開の製品・社内予算・売上・契約条件",
"question": "Does `messages` disclose specific non-public business information, such as an internal project budget, unreleased product plan, internal sales data, or customer contract terms? General questions about typical budgets, industry averages, or explicitly public information do not count.",
"threshold": 0.8
}
しきい値の0.8はあくまで今回の例で試して決めた値なので、使う場面に合わせて調整が必要です。

試しに同じ「予算」という言葉を含む社内予算と一般的な相場の質問を送ると、社内予算だけがブロックされました!
あくまで今回試した入力の範囲ですが、同じ単語を含んでいても意味で分けられていそうです。

今回試したケースでは質問を日本語に訳すと見逃しが増えました。そのため入力が日本語でも質問は英語で書いたほうが安心そうです・・・!
最初の質問に当てはまらない話題(休職や未公表の障害など)はすり抜けたので、ブロックしたい話題に合わせて質問を追加したり調整する必要がありますね・・・!
Jevは速くて精度もぼちぼち良かったです!
ただ、なぜブロックしたのか理由まで知りたいなら従来のLLMのほうが向いているので、理由を書かせるぶん判定は遅くなり、ここはスピードとのトレードオフですね。
AWSで動かすなら
資料の後半では、AWSで動かす場合の構成も紹介しました。1つ目はLiteLLM ProxyをECS on Fargateにデプロイする構成です。

番外編
2つ目はLiteLLMではなく、大好きなAgentCore GatewayのインターセプターからJevを呼び出す構成です。マネージドに完結したら嬉しいですもんね!
こちらは実際に期待どおりに動くところまで試しました!

この構成のサンプルコードはGitHubで公開しています。
サンプルレベルではありますが、興味があればぜひ試してみてください!
おわりに
参加いただいた皆さん、ありがとうございました!!
このユースケースのように意思決定モデルも使い込んでいきたいですね!!
本記事が少しでも参考になりましたら幸いです!
最後までご覧いただきありがとうございましたー!!








