こんにちは、HolySheep AI 技術チームです。本稿では、LangGraph で 상태機械(State Machine)ベースの Agent を構築し、HolySheep AI のプロキシ API とシームレスに統合する実践的な開発教程をお届けします。LangChain が提供する ReAct パターンを超えた、より複雑なタスクフロー制御が必要な方はぜひ最後まで読んでください。

前提条件と環境構築

筆者が実際にゼロからプロジェクトを構築した経験を基に説明します。まずは開発 환경을整えます。

# 必要なパッケージのインストール(筆者の実測環境:macOS 14, Python 3.11)
pip install langgraph langchain-core langchain-openai python-dotenv aiohttp

バージョン確認(2025年6月時点で動作確認済み)

langgraph==0.2.x, langchain-core==0.3.x

プロジェクト構造

mkdir langgraph-holysheep-agent && cd langgraph-holysheep-agent touch .env main.py agent.py tools.py

HolySheep API とは

HolySheep AI は、OpenAI / Anthropic / Google / DeepSeek などの主要LLMプロバイダーを единыйエンドポイント経由で呼び出せるAIプロキシ基盤です。筆者が本番環境に導入した決め手は3つあります:

LangGraph 状態機械 Agent の設計思想

なぜ状態機械なのか

従来の ReAct Agent は「思考→行動→観察」の単純なループですが、の実ビジネスシナリオでは 以下のような複雑なフローが求められます:

LangGraph の StateGraph はこれらの要件を状態に焦点を当てて модель化するため、ReAct より予測可能でデバッグしやすいエージェント構築が可能になります。

状態設計

# agent.py
from typing import TypedDict, Annotated, Sequence
from langgraph.graph import StateGraph, END
import operator

class AgentState(TypedDict):
    """LangGraph で管理するAgentの状態定義"""
    messages: list[dict]           # 会話履歴
    current_step: str              # 現在のステップ (init, research, validate, respond, error)
    task_type: str | None          # タスク分類 (research, coding, analysis)
    retrieved_context: str | None  # 検索済みコンテキスト
    validation_result: bool | None # バリデーション結果
    retry_count: int               # リトライ回数
    error_message: str | None       # エラー内容


def create_agent_graph(holysheep_api_key: str) -> StateGraph:
    """
    HolySheep API を使った LangGraph 状態機械Agent を構築
   筆者が初めて構築した際は StateGraph の nodes/edges 設計で3回リファクタリングしました
    """
    from langchain_openai import ChatOpenAI

    # HolySheep API エンドポイントに定向
    # 重要:api.openai.com や api.anthropic.com は絶対に使用しない
    llm = ChatOpenAI(
        model="gpt-4.1",
        api_key=holysheep_api_key,
        base_url="https://api.holysheep.ai/v1",  # ← 必ずこのエンドポイントを使用
        timeout=30.0,
    )

    workflow = StateGraph(AgentState)

    # ノード定義
    workflow.add_node("init", init_node)
    workflow.add_node("classify", classify_node)
    workflow.add_node("research", research_node)
    workflow.add_node("validate", validate_node)
    workflow.add_node("respond", respond_node)
    workflow.add_node("error_handler", error_handler_node)

    # 開始ノード
    workflow.set_entry_point("init")

    # エッジ定義(状態機械の核心)
    workflow.add_edge("init", "classify")
    workflow.add_conditional_edges(
        "classify",
        route_by_task,
        {
            "research": "research",
            "unknown": "respond",
        }
    )
    workflow.add_edge("research", "validate")
    workflow.add_conditional_edges(
        "validate",
        route_by_validation,
        {
            "pass": "respond",
            "fail": "error_handler",
        }
    )
    workflow.add_edge("error_handler", END)
    workflow.add_edge("respond", END)

    return workflow.compile()

コアノードの實現

① 初期化ノード

# agent.py — 続き

def init_node(state: AgentState) -> AgentState:
    """タスク入力の初期処理"""
    return {
        "messages": state["messages"],
        "current_step": "init",
        "task_type": None,
        "retrieved_context": None,
        "validation_result": None,
        "retry_count": 0,
        "error_message": None,
    }


def classify_node(state: AgentState, llm) -> AgentState:
    """LLMでタスクを分類(HolySheep API経由)"""
    user_message = state["messages"][-1]["content"]

    classification_prompt = f"""次のタスクを分類してください:
タスク: {user_message}

分類:
- research: 調査・リサーチ系タスク
- coding: コード生成タスク
- analysis: 分析タスク

JSON形式で回答: {{"task_type": "xxx", "confidence": 0.x}}"""

    response = llm.invoke([{"role": "user", "content": classification_prompt}])
    # 実際のアプリでは JSON パース処理を追加
    task_type = "research"  # 簡略化
    return {**state, "task_type": task_type, "current_step": "classify"}


def route_by_task(state: AgentState) -> str:
    """タスク分類に応じたルート分岐"""
    return state.get("task_type", "unknown")

② リサーチ・バリデーション・レスポンスノード

# agent.py — 続き(続き)

def research_node(state: AgentState, llm) -> AgentState:
    """Deep Research 風のリサーチを実行"""
    user_message = state["messages"][-1]["content"]

    research_prompt = f"""あなたは専門リサーチャーです。以下のタスクを調査してください:

タスク: {user_message}

調査結果を提供してください。出典があれば明記してください。"""

    response = llm.invoke([
        {"role": "system", "content": "あなたは有用的なリサーチアシスタントです。"},
        {"role": "user", "content": research_prompt}
    ])

    return {
        **state,
        "retrieved_context": response.content,
        "current_step": "research",
    }


def validate_node(state: AgentState, llm) -> AgentState:
    """リサーチ結果の品質バリデーション"""
    context = state.get("retrieved_context", "")

    validation_prompt = f"""以下の調査結果の品質をバリデーションしてください:

- 情報が正確か
- 回答がタスクに関連しているか
- 十分詳細か

結果: {context[:500]}

true/false で回答理由をつけてください。"""

    response = llm.invoke([{"role": "user", "content": validation_prompt}])
    passed = "true" in response.content