AI Navigate

本番環境でAIエージェントはあなたを欺く――出荷前にそれを見抜く方法

Dev.to / 2026/3/18

💬 オピニオンDeveloper Stack & InfrastructureTools & Practical Usage

要点

  • 本番環境のAIエージェントは、LLMsが実行ごとに出力を変える可能性があり、もっともらしく正しそうに見えても誤った結果を生成し、従来の入力-出力契約を覆してしまう。
  • サーバーレスなAIパイプラインでは、プロンプトにバージョン管理がなく、リトライが失敗を隠し、沈黙的な劣化は下流の影響が現れるまで気づかれないことがある。
  • 提案されている対策は、入力/出力を固定するゴールデン・フィクスチャを用いたプロンプト回帰テストスイートで、精度が閾値を下回るとビルドを失敗させます。
  • 記事は、各デプロイ時の自動検証のため、tests/fixtures/ 配下に格納された JSON ベースのゴールデン・フィクスチャ形式を含む、具体的なセットアップを提供します。

あなたはAIエージェントをデプロイします。手動テストはすべて通ります。デモでは良さそうに見えます。

3週間後、誰かが出力を「cleaner」にするようにシステムプロンプトを編集します。エージェントはエッジケースで挙動が異なり始めます。エラーもアラートもありません。ただ、わずかに間違った出力になる — 誰かが気づくまで。

この記事は、この問題を防ぐCI/CDとプロンプト回帰設定についてです。ここにあるすべては実用的で、今日AWSで機能します。

CI/CD における AI エージェントの問題点

従来のソフトウェアには明確な契約があります。入力 X が与えられると、関数 F は出力 Y を返します。テストは Y を検証します。Y が変われば、テストは失敗し、ビルドは壊れ、あなたは調査します。

LLMベースのエージェントはこのモデルを壊します。「関数」は言語モデルです。同じ入力でも実行ごとにわずかに異なる出力を生むことがあります。そして故障モードは例外ではなく、もっともらしく見える誤答です。

サーバーレスAIパイプラインでこれをさらに悪化させる3つの要因:

1. プロンプトはコードのようにバージョン管理されていません。 エンジニアはそれらをPythonファイル内の文字列として編集します。あるいは、さらに悪いことに、バージョン管理の外部にある設定ファイルで編集します。誰もコード変更のようにプロンプト変更をレビューしません。

2. リトライは故障を覆い隠す。 Lambda はエラー時にリトライします。あなたのリトライロジックは低信頼度の応答でリトライします。悪い出力が現れる頃には、それを引き起こしたプロンプト変更を追跡するのは難しくなっています。

3. 静かな劣化。 正確さが95%の分類エージェントが80%まで落ちても、エラーは発生しません。単に間違いが多くなるだけです。ログではなく、下流の影響からそれを知ることになるでしょう。

解決策: プロンプト回帰テストスイート

考え方はシンプルです。既知の正しい出力を持つ既知の入力のセットを“ゴールデンフィクスチャ”として固定します。デプロイのたびにエージェントをそれらに対して実行します。精度が閾値を下回った場合、ビルドを失敗させます。

以下が完全なセットアップです。

ステップ 1: ゴールデンフィクスチャの形式

各フィクスチャは tests/ Fixtures/ にある JSON ファイルです。構造は以下のとおり:

{ 
  "document_id": "fixture_001",
  "input": {
    "document_text": "Policy holder: Jane Smith. Coverage: accidental damage. Item: MacBook Pro 16-inch. Purchase date: 2023-08-15. Claim date: 2025-11-03. Damage description: Screen cracked after drop.",
    "tenant_id": "test-tenant"
  },
  "expected": {
    "risk_level": "MEDIUM",
    "reminder_eligible": true,
    "confidence_min": 0.70
  }
}

20–30 件のフィクスチャを維持します。エッジケースをカバーします:境界リスクレベル、あいまいな説明、欠落したフィールド、古いクレームなど。これらはエージェントが間違える文書です。

自動的にフィクスチャを生成しないでください。手動で作成します。要点は、人間が正しい出力を決定したということです。

ステップ 2: テストランナー

# tests/test_regression.py

以下は全文です。

if "confidence_min" in expected: assert result["confidence"] >= expected["confidence_min"], ( f"[{fixture['document_id']}" f"Confidence {result['confidence']}:.2ff} below minimum " f"{expected['confidence_min']}" ) def test_overall_accuracy(): """ 別個のテスト: 集計精度が MIN_ACCURACY 未満の場合、全体のテストスイートを失敗させます。 回帰を検出するには、個々のテストが端のケースで通過してもこれを検出します。 """ fixtures = load_fixtures() passed = 0 for fixture in fixtures: result = run_classifier( document_text=fixture["input"]["document_text"], tenant_id=fixture["input"]["tenant_id"] ) if result["risk_level"] == fixture["expected"]["risk_level"]: passed += 1 accuracy = passed / len(fixtures) assert accuracy >= MIN_ACCURACY, ( f"Accuracy {accuracy:.0%} below threshold {MIN_ACCURACY:.0%}. " f"Passed {passed}/{len(fixtures)} fixtures." ) - name: AWS認証情報を設定 uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} - name: ECRにログイン id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Dockerイメージをビルドしてプッシュ run: | IMAGE_TAG=$(git rev-parse --short HEAD) docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV - name: Lambdaへデプロイ run: | aws lambda update-function-code \ --function-name $LAMBDA_FUNCTION \ --image-uri $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \ --region $AWS_REGION aws lambda wait function-updated \ --function-name $LAMBDA_FUNCTION echo "Deployed image $IMAGE_TAG to $LAMBDA_FUNCTION"

Key decisions:

  • needs: regression-tests — デプロイジョブはテストが失敗した場合には開始されません
  • OIDCロール想定認証(シークレットに長寿命のキーを置かない)
  • lambda wait function-updated — ジョブが完了する前に関数が実際に更新されていることを保証します

Step 4: IAM OIDC Setup for GitHub Actions (No Long-Lived Keys)

GitHub Actions に AWS へのアクセスを与える最もクリーンな方法は OIDC です — リポジトリに限定され、ジョブの後に期限切れになる一時的な認証情報です。

# infra/oidc.tf

data "aws_iam_openid_connect_provider" "github" {
  url = "https://token.actions.githubusercontent.com"
}

resource "aws_iam_role" "github_actions" {
  name = "github-actions-warrantyai"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Federated = data.aws_iam_openid_connect_provider.github.arn }
      Action    = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringLike = {
          "token.actions.githubusercontent.com:sub" = "repo:YOUR_ORG/warrantyai:ref:refs/heads/main"
        }
      }
    }]
  })
}

resource "aws_iam_role_policy" "github_actions_policy" {
  name = "github-actions-policy"
  role = aws_iam_role.github_actions.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability",
                    "ecr:PutImage", "ecr:InitiateLayerUpload",
                    "ecr:UploadLayerPart", "ecr:CompleteLayerUpload"]
        Resource = "*"
      },
      {
        Effect   = "Allow"
        Action   = ["lambda:UpdateFunctionCode", "lambda:GetFunction"
        Resource = aws_lambda_function.pipeline_processor.arn
      },
      {
        Effect   = "Allow"
        Action   = ["bedrock:InvokeModel"]
        Resource = "*"
      }
    ]
  })
}

Replace YOUR_ORG/warrantyai with your actual GitHub org and repo name. The StringLike condition locks the role to your main branch only PRs get the regression test job but not deploy permissions.

What This Catches (and What It Doesn't)

検出される内容:

  • 知っているエッジケース上で分類挙動を変えるプロンプトの編集
  • 出力構造に影響を与えるモデルバージョンの変更
  • フィールド抽出を壊す出力パーサの変更
  • 実際の作業を行っていた指示の誤って削除

検出されない内容:

  • まだ fixtures に追加していない新しいエッジケース
  • 遅延のリグレッション(別途遅延ベンチマークを追加してください)
  • プロンプト肥大化によるコストリグレッション(トークンのカウントを追加してください)

The fixture set is a living document. Every time a production bug surfaces from a new edge case, add a fixture for it. The test suite gets more valuable over time, not less.

The One Thing Worth Knowing

The first time you run this on an existing project, it will probably fail. Not because your agent is bad — but because you'll discover that your "obvious" classifications aren't as consistent as you thought.

That's the test suite doing its job. Fix the fixtures (or fix the agent), and you now have a baseline. Every future change is measured against that baseline.

That's the whole point.

if you think what its to do with warrantyAI

This is a solution which I am building to learn and implement AI systems.

\"\"

WarrantyAIの構築: AIプラットフォームエンジニアの2026年のノーススター目標 | Harish Aravindan がこのトピックに投稿 | LinkedIn

🚀 DevOps から MLOps へ: 学習の第1週 / 「Warranty AI」を構築。長年クラウドインフラとDevOpsパイプラインを管理してきた私は、正式にAIプラットフォームエンジニアリングへの移行を開始しました。私の2026年のノーススター目標は、WarrantyAIを構築することです。住宅所有者やオフィス管理者が家電の健康状態を評価し、契約の微細事項を特定し、生成AIを用いて修理費用を最適化する生産グレードの「保証対応」システムです。第1週の学習: なぜAIプラットフォームエンジニアか。AIプラットフォームエンジニアは単に「プロンプトを書く」だけではありません。AIモデルが現実世界のデータと安全かつ効率的に相互作用できる、拡張性のある「配管」を構築します。私の12か月のロードマップは以下に焦点を当てます:MLOps: モデルのライフサイクル(トレーニング、デプロイ、モニタリング)を自動化する。意味論的アーキテクチャ: AIに“長期記憶”を与えるベクトルデータベースの活用。クラウドネイティブAI: BedrockやS3 LakehousesなどAWSのリソースを活用して、コスト効率の高いスケールを実現。第1週の学習: 「ブラックボックス」を打破。今週は Retrieval-Augmented Generation(RAG)に焦点を当てました。AIに一般的な保証がどう見えるかを尋ねるだけではなく、5ページの LG 冷蔵庫保証PDFを渡し、隠れたコストを見つけるよう依頼しました。GitHub Repo: https://lnkd.in/g-hkGJ6M. コアコンセプト: 埋め込み(「翻訳者」): Amazon Titan Text Embeddings v2 を使用して、人間のテキストを数学的ベクトルに変換します。ベクトルインデックス(「ライブラリ」): FAISS を実装して、AI がキーワードではなく“意味”で検索できるようにします。メッセージAPI(「会話」): 従来の文字列ベースのプロンプトから、Ministral-3-8b のようなモデルが要求する現代的で構造化されたメッセージ形式へ移行します。結論と今後のステップ: 第1週で、AIエンジニアリングは20%のプロンプトと80%のデータエンジニアリングとインフラで成り立つと教えられました。ネイティブAPIとベクトルロジックを習得することで、単なる「おしゃべり」ではなく、正確さを備えた基盤を築きました。来週: これらのベクトルを Apache Iceberg を使って永続的な S3 Lakehouse に移行し、データを整理してクエリ可能な状態を維持しつつ、数百台の家電へとスケールさせていきます。#MLOps #AWSBedrock #AIPlatformEngineer #WarrantyAI #GenerativeAI #DevOpsToAI #ApacheIceberg https://lnkd.in/g-hkGJ6M

\"favicon\"linkedin.com