ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-03-14上級

LangChain と Antigravity で組むAIパイプライン — 権限境界・失敗回復・コストから逆算する設計

チェーンの繋ぎ方ではなく、途中で止まったときにどこから再開できるかからパイプラインを設計します。権限境界・チェックポイント・キャッシュを入れた前後の実測を添えて。

LangChain3AIパイプラインアーキテクチャ9本番環境6チェーン構成メモリ管理2

プレミアム記事

壁紙アプリのアセットを分類するパイプラインを初めて夜通し回した朝、画面には途中で止まったログだけが残っていました。数千枚のうち処理できたのはおよそ半分。原因は API のレート制限でしたが、本当に困ったのはそこではありません。どこまで終わったのかを、どこにも記録していなかったことです。

結局その日は最初からやり直しました。同じ画像に同じ料金を二度払ったことになります。個人開発では、この二度払いが直接そのまま利益から引かれます。

以来、私はパイプラインを設計するとき、チェーンの繋ぎ方より先に「途中で死んだとき、どこから再開できるか」を決めるようにしております。LangChain が提供するのは合成可能なコンポーネント、Antigravity が提供するのはそれを人と並べて動かすオーケストレーション層。この記事の主題は、その二つの間に置くべき三つの決定 — 権限の境界、失敗の記録、コストの可視化 — です。

以下では合成可能なチェーン設計、メモリ管理、ルーティング、レジリエンスの実装を順に見たうえで、最後に権限設計とコスト実測という、公式ドキュメントがあまり語らない領域まで踏み込みます。

AIパイプラインアーキテクチャの理解

パイプラインの抽象化

パイプラインは単なるLLM呼び出しの連鎖ではありません。専門的なコンポーネントが協調して動作する構成です:

入力 → 前処理 → ルーティング → チェーン選択 → 実行 → 後処理 → 出力
    ↓        ↓         ↓          ↓        ↓
データ検証  コンテキスト  マルチブランチ  ロギング  レスポンス
変換        取得        フォールバック  監視      フォーマット
          キャッシング  エラーハンドリング メトリクス

なぜこれが重要か: ジェネリックなパイプライン設計は本番負荷の下で破綻します。カスタムアーキテクチャは複雑性をインテリジェントに分散させます—高価な操作(検索など)をクリティカルパスの外に移動し、積極的にキャッシングして、コンポーネントに障害が発生した場合はグレースフルにデグラデします。

5つのパイプラインパターン

LangChainは5つの異なるパターンを有効にしており、各々が異なる問題に適しています:

  1. シーケンシャルパイプライン — 線形の操作チェーン(ドキュメントQ&A、要約)
  2. 分岐パイプライン — 入力の特性に基づく条件付きルーティング
  3. 再帰的パイプライン — 精緻化または分解のために自分自身を呼び出すチェーン
  4. 並列パイプライン — 複数のチェーンが同時に実行され、結果が統合される
  5. 適応的パイプライン — メトリクス、パフォーマンスデータ、または状態に基づいて実行時に決定される動作

各パターンをいつ適用するかを理解することで、システムの効率性と信頼性が劇的に向上します。

合成可能なコンポーネントでシーケンシャルパイプラインを構築

コア アーキテクチャ

本番レベルのシーケンシャルパイプラインは、関心事を個別のテスト可能なレイヤーに分離します:

# config/pipeline_config.py
from dataclasses import dataclass
from typing import Optional, Dict, Any
from enum import Enum
 
class ChainStage(Enum):
    """パイプライン実行ステージ"""
    INPUT_VALIDATION = "input_validation"
    CONTEXT_RETRIEVAL = "context_retrieval"
    PROMPT_CONSTRUCTION = "prompt_construction"
    LLM_EXECUTION = "llm_execution"
    OUTPUT_PARSING = "output_parsing"
    RESPONSE_VALIDATION = "response_validation"
 
@dataclass
class PipelineConfig:
    """集中化されたパイプライン設定"""
    # モデル設定
    model_name: str = "gpt-4"
    temperature: float = 0.7
    max_tokens: int = 2048
 
    # チェーン動作
    max_retries: int = 3
    retry_backoff: float = 2.0
    timeout_seconds: int = 30
 
    # メモリ管理
    memory_type: str = "buffer"  # buffer, summary, entity
    max_memory_tokens: int = 4000
 
    # コンテキスト検索
    enable_rag: bool = True
    retrieval_strategy: str = "similarity"  # similarity, bm25, hybrid
    top_k_docs: int = 5
 
    # ロギングと監視
    log_level: str = "INFO"
    enable_metrics: bool = True
    enable_tracing: bool = True
 
@dataclass
class StageContext:
    """パイプライン全体で共有されるコンテキスト"""
    input_text: str
    metadata: Dict[str, Any]
    retrieved_docs: list = None
    prompt_template_used: str = None
    llm_response: str = None
    execution_time: Dict[str, float] = None
    errors: list = None
💡
**設定ファーストの設計:** すべてのパイプラインパラメータを集中化された設定に保存します。これにより実験が可能になります—コードに触れずに温度、メモリタイプ、または検索戦略を切り替えます。

コンポーネント構成

各ステージは独立した合成可能なユニットです:

# pipelines/components/base.py
from abc import ABC, abstractmethod
from typing import Any, Dict
import time
from datetime import datetime
 
class PipelineComponent(ABC):
    """すべてのパイプラインコンポーネントの基底クラス"""
 
    def __init__(self, name: str, config: Dict[str, Any] = None):
        self.name = name
        self.config = config or {}
        self.execution_metrics = {
            "total_calls": 0,
            "successful_calls": 0,
            "failed_calls": 0,
            "total_time": 0.0,
            "avg_time": 0.0,
            "errors": []
        }
 
    @abstractmethod
    async def execute(self, context: StageContext) -> StageContext:
        """コンポーネントロジックを実行します。冪等性が必須です。"""
        pass
 
    async def run(self, context: StageContext) -> StageContext:
        """タイミング、エラートラッキング、メトリクスを処理するラッパー"""
        start_time = time.time()
 
        try:
            result = await self.execute(context)
            elapsed = time.time() - start_time
 
            # メトリクスを更新
            self.execution_metrics["total_calls"] += 1
            self.execution_metrics["successful_calls"] += 1
            self.execution_metrics["total_time"] += elapsed
            self.execution_metrics["avg_time"] = (
                self.execution_metrics["total_time"] /
                self.execution_metrics["successful_calls"]
            )
 
            if context.execution_time is None:
                context.execution_time = {}
            context.execution_time[self.name] = elapsed
 
            return result
 
        except Exception as e:
            self.execution_metrics["failed_calls"] += 1
            self.execution_metrics["errors"].append({
                "timestamp": datetime.utcnow().isoformat(),
                "error": str(e),
                "component": self.name
            })
            raise
 
# pipelines/components/validators.py
import re
from typing import List, Optional
 
class InputValidator(PipelineComponent):
    """パイプライン実行前に入力を検証します"""
 
    def __init__(self, config: Dict[str, Any] = None):
        super().__init__("InputValidator", config)
        self.min_length = self.config.get("min_length", 10)
        self.max_length = self.config.get("max_length", 50000)
        self.forbidden_patterns = self.config.get("forbidden_patterns", [
            r"<script>",
            r"eval\(",
            r"__import__"
        ])
 
    async def execute(self, context: StageContext) -> StageContext:
        text = context.input_text.strip()
 
        # 長さの検証
        if len(text) < self.min_length:
            raise ValueError(f"入力が短すぎます: {len(text)}文字、最小{self.min_length}")
        if len(text) > self.max_length:
            raise ValueError(f"入力が長すぎます: {len(text)}文字、最大{self.max_length}")
 
        # セキュリティチェック
        for pattern in self.forbidden_patterns:
            if re.search(pattern, text, re.IGNORECASE):
                raise ValueError(f"禁止されたパターンが検出されました: {pattern}")
 
        # テキストを正規化
        context.input_text = self._normalize_text(text)
        context.metadata["validation_timestamp"] = datetime.utcnow().isoformat()
 
        return context
 
    def _normalize_text(self, text: str) -> str:
        """空白とエンコーディングを正規化"""
        # 余分な空白を削除
        text = " ".join(text.split())
        # ユニコードを正規化
        return text.encode('utf-8', 'replace').decode('utf-8')
 
# pipelines/components/retrieval.py
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from typing import List
 
class ContextRetriever(PipelineComponent):
    """RAGを使用して関連するコンテキストを取得"""
 
    def __init__(self, vector_store: Chroma, config: Dict[str, Any] = None):
        super().__init__("ContextRetriever", config)
        self.vector_store = vector_store
        self.top_k = self.config.get("top_k", 5)
        self.min_relevance_score = self.config.get("min_relevance_score", 0.5)
 
    async def execute(self, context: StageContext) -> StageContext:
        # 関連するドキュメントを取得
        docs_with_scores = self.vector_store.similarity_search_with_score(
            context.input_text,
            k=self.top_k * 2  # フィルタリングのため過剰取得
        )
 
        # 最小関連度スコアでフィルタリング
        relevant_docs = [
            doc for doc, score in docs_with_scores
            if (1 - score) >= self.min_relevance_score  # 距離を類似度に変換
        ][:self.top_k]
 
        if not relevant_docs:
            context.retrieved_docs = []
            context.metadata["retrieval_status"] = "no_relevant_documents"
        else:
            context.retrieved_docs = relevant_docs
            context.metadata["retrieval_status"] = "success"
            context.metadata["retrieved_doc_count"] = len(relevant_docs)
 
        return context
⚠️
**メモリとコストの影響:** 各RAG検索は遅延とコストを追加します。関連度閾値を積極的に使用し、頻繁に取得されるコンテキスト のキャッシングを実装します。過剰取得は避けてください。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
エージェントに実行権限を渡す範囲を、副作用の可逆性で三段階に切り分ける判断基準
チェックポイントとキャッシュを入れる前後で、再処理件数とAPI呼び出し回数がどう変わったかの実測値
止まった場所から再開できるパイプラインを、SQLite だけで組む完全な実装
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

Agents & Manager2026-03-25
Antigravity で5つのエージェントを並列に走らせる ― 分解・競合解決・統合の本番設計
Antigravity で最大5つのエージェントを並列実行するための設計を、個人開発の運用視点でまとめました。タスク分解、競合解決ポリシー、アーティファクト検証とマージ、そして並列化が効かない場面の見極めまでを実装コードとともに解説します。
Agents & Manager2026-06-18
並列エージェントが同じファイルに書き込むときの取り合いを防ぐ
Antigravity 2.0 で複数エージェントを同時に走らせると、同じファイルへの書き込みが衝突して片方の変更が消えることがあります。共有ワークスペースでの書き込み調停を設計します。
Agents & Manager2026-06-03
エージェントに渡す道具の粒度を決める — 粗くまとめるか、細かく分けるか
エージェントに与えるツールを細かく分けるか粗くまとめるか。判断軸を「意図1つ=ツール1つ」に置き、破壊的操作だけ粒度を下げる設計を、6アプリ運用の実コードと実数値で解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →