Pythonを3行でLLMガードレールを構築(APIキーなし、クラウドなし)
あなたのLLMが、ある顧客に「発疹がメラノーマのように見える」と伝えてしまいました。チャットボットがサポート応答でユーザーのメールアドレスを漏らしました。RAGパイプラインは話題を逸らして、錠前の選び方を説明し始めました。
これは単なる仮定ではありません。まさに火曜日の出来事です。
必要なのはガードレールです。現状はこんな感じになっています:
正規表現(Regex)。 医療アドバイスを検知するために
r"(?i)(you should take|I recommend taking)"のようなものを書きます。モデルは「役に立つかもしれないので検討してみてください」に言い換え、あなたのフィルタは役に立ちません。パターンを追加します。モデルはもっと多くの言い回しを見つけます。いまや、誤検知を拾い本当の違反を見逃す“正規表現の動物園”をメンテすることになっています。LLM-as-judge。 すべての出力をレビューするために GPT-4 を呼びます。チェックごとに 500ms〜2s、1回あたり $0.01〜0.03、さらに外部APIへの強い依存が発生します。ガードレールが、ガードしている対象より遅くなってしまいます。しかも本番ではAPIキーが必要で、コストはトラフィックに比例して増えます。OpenAIが不調な日には、ガードレールもダウンします。
クラウドのガードレールサービス。 AWS Bedrock Guardrails、Azure Content Safety など。ベンダーロックイン、ネットワーク遅延、従量課金、そしてデータはあなたのインフラから外へ出ます。コンプライアンス担当にそれを説明するのは大変でしょう。
どれも良くありません。実際に欲しいのは、出力が何か悪い意味を持つかどうかを、ローカルで、ミリ秒で、しかも無料でチェックすることです。
3行
pip install semantix-ai
from semantix import Intent, validate_intent
class NoPII(Intent):
"""このテキストには、氏名、メールアドレス、電話番号、住所などの個人情報が含まれていません。"""
class NoMedicalAdvice(Intent):
"""このテキストは、医学的診断や治療の推奨を提供しません。"""
@validate_intent
def my_chatbot(message: str) -> ~NoPII & ~NoMedicalAdvice:
return call_my_llm(message)
それだけです。my_chatbot へのすべての呼び出しは、ローカルのNLIモデルを通過するようになります。出力があなたのポリシーに違反しているかどうかを確認します。CPUで約15ms。APIキーなし。ネットワーク呼び出しなし。消費するトークンなし。
出力がPIIを漏らしたり医学的アドバイスを与えたりした場合、スコア、違反した意図(intent)、そして理由とともに SemanticIntentError が送出されます。問題のある出力はユーザーに決して到達しません。
否定パターンがどのように機能するか
~ 演算子が鍵です。Intent は「それが何であるか」を記述します。~Intent は「出力がそのものではない」ことをチェックします。
from semantix import Intent
class ToxicLanguage(Intent):
"""このテキストには、侮辱、露骨な下品語、脅迫、または攻撃的な言葉が含まれています。"""
class MedicalAdvice(Intent):
"""このテキストは、医学的診断または治療の推奨を提供しています。"""
class PIILeakage(Intent):
"""このテキストには、氏名、メールアドレス、電話番号、住所などの個人情報が含まれています。"""
class LegalAdvice(Intent):
"""このテキストは、ユーザーの状況に対して具体的な法律アドバイスを提供する、または法律を解釈しています。"""
これらはすべて「悪いこと」を記述しています。これらを否定すれば、ガードレールになります:
Safe = ~ToxicLanguage
Compliant = ~MedicalAdvice
Private = ~PIILeakage
NotALawyer = ~LegalAdvice
内部では、~MedicalAdvice が Not[MedicalAdvice] の意図(intent)を作成します。NLIモデルは、出力が元の記述を含意(entails)しているかどうかをチェックします。含意している場合、否定のチェックは失敗します。含意していない場合、出力はクリーンです。
これはNLIモデルがパターンではなく意味を理解するために機能します。「You should take ibuprofen(あなたはイブプロフェンを飲むべきです)」も「Consider an anti-inflammatory(抗炎症薬を検討してください)」も、いずれも医学的アドバイスを含意します。正規表現は、その両方の言い回しを列挙していない限り検知できません。NLIモデルは、それらが同じ意味だから両方を検知できます。
ポリシーの組み立て
実際のコンプライアンスは1つのルールではありません。ポリシーです。つまり複数の制約で、すべてが成立する必要がある場合、または少なくとも1つが成立する必要がある場合です。semantix はこのために & と | を提供します。
すべての制約が通過しなければならない
@validate_intent
def customer_support(msg: str) -> ~ToxicLanguage & ~PIILeakage & ~MedicalAdvice:
return call_my_llm(msg)
& 演算子は AllOf の複合(composite)を作成します。否定された意図(intent)ごとにチェックが行われます。どれか1つでも失敗すれば、出力は拒否されます。これは、Pythonの型アノテーション1行で表現する、あなたの本番向け安全ポリシーです。
少なくとも1つの制約が通過しなければならない
返却形式: {"translated": "翻訳されたHTML"}class Apology(Intent):
"""The text contains a sincere apology for the inconvenience."""
class Redirect(Intent):
"""The text redirects the user to the appropriate support channel."""
@validate_intent
def handle_complaint(msg: str) -> Apology | Redirect:
return call_my_llm(msg)
| 演算子は AnyOf の複合を作成します。出力は、少なくとも1つの意図を満たしていれば通過します。
Mix positive and negative
class Helpful(Intent):
"""The text provides a clear, actionable answer to the user's question."""
@validate_intent
def safe_assistant(msg: str) -> Helpful & ~ToxicLanguage & ~PIILeakage:
return call_my_llm(msg)
出力は、有用であること(AND)かつ、有害でないこと(NOT)かつ、PIIを漏えいしないこと(NOT)が必須です。正の制約と負の制約は自由に組み合わせられます。
Self-healing retries
単にブロックするだけのガードレールは、鈍い道具です。場合によっては、何が問題だったかについてフィードバックしつつ、LLMにもう一度試してほしいことがあります。retries と semantix_feedback パラメータを追加してください:
from typing import Optional
@validate_intent(retries=2)
def safe_chatbot(
message: str,
semantix_feedback: Optional[str] = None,
) -> Helpful & ~ToxicLanguage & ~PIILeakage:
prompt = f"Answer this customer question: {message}"
if semantix_feedback:
prompt += f"
{semantix_feedback}"
return call_my_llm(prompt)
最初の呼び出しでは、semantix_feedback は None です。出力のバリデーションが失敗した場合、デコレータが自動的に、何が問題だったかを説明する構造化された Markdown フィードバックブロックを注入します — 待遇が違反した意図、スコア、却下された出力です。LLM は、それを修正するための2回目のチャンスを得ます。
これにより、ガードレールは壁からフィードバックループへと変わります。モデルは、その場で自分のミスから学び、自動的に自己修正します。実際には、ほとんどの違反は最初のリトライで修正されます。
フィードバックは次のようになります:
## Semantix Self-Healing Feedback
Attempt **1** failed validation.
### What went wrong
- **Intent:** `Not[PIILeakage]`
- **Score:** 0.9142 (threshold not met)
- **Judge reason:** Text contains what appears to be an email address
### What is required
The text must NOT satisfy the following:
The text contains personal information like names, emails, phone numbers, or addresses.
### Your previous output (rejected)
Sure, I can help! John's email is john.doe@example.com...
Please generate a new response that satisfies the requirement above.
Testing guardrails in CI
本番環境でのガードレールは物語の半分に過ぎません。デプロイする前に、それらが機能することもテストする必要があります。ツールは2つ:
pytest-semantix
pip install pytest-semantix
from semantix import Intent
class PIILeakage(Intent):
"""The text contains personal information like names, emails, phone numbers, or addresses."""
def test_no_pii_in_response(assert_semantic):
response = my_chatbot("tell me about user 42")
assert_semantic(response, ~PIILeakage)
返却形式: {"translated": "翻訳されたHTML"}def test_no_medical_advice(assert_semantic):
response = my_chatbot("my head hurts")
assert_semantic(response, ~MedicalAdvice)
各テストはローカルで約15msで実行されます。CIのシークレットにAPIキーはありません。フレーク(不安定)なネットワーク呼び出しもありません。ガードレールテストはユニットテストと同じくらいの速さで動作します。
GitHub Action
semantic-test-actionで、CIパイプラインにセマンティックなチェックを追加してください。:
- uses: labrat-akhona/semantic-test-action@v1
with:
test-path: tests/
threshold: 0.8
report-format: json
これにより、CIでpytest-semantixのテストが実行され、レポートが生成されます。失敗したガードレールテストはPRをブロックします。コードがmainに到達する前に、コンプライアンス方針が強制されます。
実際に裏側で何が起きているのか
~MedicalAdviceを書いて、デコレータが出力を検証するときの一連の流れは次のとおりです:
- デコレータがあなたの関数を呼び出し、生の文字列出力を取得します。
- クラスのドキュメント文字列から意図(インテント)の説明を抽出します。
Not[X]の場合、出力がXを含意するかどうかを確認します。含意スコアがしきい値を上回る場合、否定のチェックは失敗します — 出力が望ましくないものに一致しているためです。AllOfの場合、すべてのコンポーネントをチェックします。すべてが通過する必要があります。AnyOfの場合、いずれかのコンポーネントが通過するまでチェックします。- NLIモデルはONNX Runtimeでローカル実行します(量子化 INT8)。GPUは不要です。CPUで1回のチェックあたり約15msです。
- 検証が失敗し、リトライが残っている場合、フィードバックが注入され、関数がもう一度呼び出されます。
- すべてのリトライが尽きた場合、完全な診断情報とともに
SemanticIntentErrorが発生します。
モデルは一度だけダウンロードされます(約100MB)てローカルにキャッシュされます。その後はすべてオフラインです。ガードレールは飛行機の中でも動きます。
これは他のアプローチと比べていつ使うべきか
セマンティックなガードレール(semantix)を使うべきとき:
- ホットパスで低レイテンシのチェック(< 20ms)が必要
- 外部APIにデータを送れない(コンプライアンス、エアギャップ、プライバシー)
- 決定的で再現可能なガードレール動作が欲しい
- 本番だけでなくCI/CDでガードレールを入れたい
- チェックあたりの追加コストがゼロであることが望ましい
LLMをジャッジとして使うべきとき:
- NLIでは捉えられない、ニュアンスがあり文脈に依存した評価が必要
- レイテンシやコストは気にしない
- リアルタイムのガードレイリングではなく、単発の評価を行っている
正規表現/キーワードフィルタを使うべきとき:
- ブロックしたい、既知で固定された完全一致の文字列リストがある(例: 特定の侮辱語、特定のSSNフォーマット)
- セマンティックな理解は不要で、パターンマッチだけでよい
実際には、これらは積み重ねられます。semantixで高速なセマンティック層を担当し、既知の完全一致パターンにはregexを使い、深い推論が必要な難しいケースはLLM-as-judgeで処理します。semantixはregexではカバーできない90%を担い、LLM-as-judgeは遅すぎて使えない部分を補います。
Install
pip install semantix-ai
Python 3.10+。APIキー不要。GPU不要。Linux、macOS、Windowsで動作します。
- PyPI: pypi.org/project/semantix-ai
- GitHub: github.com/labrat-akhona/semantix-ai
- Docs: labrat-akhona.github.io/semantix-ai
- pytest-semantix: pypi.org/project/pytest-semantix




