LangChain AgentState 状態管理
Agent は実行プロセス中に状態を維持する必要があります——メッセージ履歴、構造化応答、フロー制御など。AgentState の構造と使い方を理解することは、カスタム Agent の動作の鍵となります。
AgentState 構造
AgentState は TypedDict であり、デフォルトで3つのフィールドを含みます。
実例
from typing_extensions import Required, NotRequired
from langgraph.graph.message import add_messages
from langgraph.channels.ephemeral_value import EphemeralValue
from langchain.messages import AnyMessage
# AgentState の実際の定義(簡略版)
class AgentState(TypedDict):
# messages:メッセージ履歴。reducer として add_messages を使用
# Required は呼び出し時に必須であることを示す
messages: Required[Annotated[list[AnyMessage], add_messages]]
# jump_to:フロー遷移制御。ephemeral(使用後に自動クリア)
# NotRequired はオプションであることを示す
jump_to: NotRequired[Annotated[str | None, EphemeralValue]]
# structured_response:構造化出力結果
# NotRequired はオプションを示し、response_format が設定されている場合にのみ出現する
structured_response: NotRequired[Any]
| フィールド | 型 | 必須かどうか | 説明 |
|---|---|---|---|
| messages | list[AnyMessage] | である | メッセージ履歴。add_messages reducer を使用して追記 |
| jump_to | str または None | 否 | フロー遷移制御。指定可能な値:tools、model、end。ephemeral 属性で、使用後に自動クリアされます。 |
| structured_response | Any | 否 | 構造化出力結果。input schema では公開されません。 |
messages——メッセージ履歴の Reducer メカニズム
messages フィールドは次を使用します:add_messagesreducer。つまり、messages の更新時に上書きではなく、追加(追記)になります。。
実例
from langgraph.graph.message import add_messages
# add_messages の動作方法
existing = [
HumanMessage(content="こんにちは", id="1"),
AIMessage(content="こんにちは!", id="2"),
]
# 新しいメッセージを追加
new_msg = AIMessage(content="何かお手伝いできますか?", id="3")
result = add_messages(existing, [new_msg])
print(f"マージ前: {len(existing)} 件")
print(f"マージ後: {len(result)} 件")
for msg in result:
print(f" [{msg.type}] {msg.content}")
実行結果:
マージ前: 2 条 マージ後: 3 条 [human] 你好 [ai] 你好! [ai] 有什么可以帮你的?
add_messages のスマートな機能:
- 同名上書き: 新しいメッセージ ID が既存のメッセージと同一の場合、追加ではなく置き換えます。
- RemoveMessage サポート: RemoveMessage に遭遇した場合、リストから対応するメッセージを削除します。
- 型安全性: HumanMessage、AIMessage、ToolMessage など異なるタイプを自動処理します。
jump_to——フロー遷移制御
jump_to は Middleware で最もよく使われるフィールドで、Agent の各ノード間を遷移するために使用します。
jump_to はephemeral(一時的な)フィールドです——一度使用すると自動的にクリアされ、手動でリセットする必要はありません。
実例
from langchain.agents.middleware import before_model
from langchain.chat_models import init_chat_model
from langchain.messages import AIMessage, HumanMessage # AIMessage をインポート
# 遷移先 "end" を宣言
@before_model(can_jump_to=["end"])
def check_question(state, runtime):
"""モデル呼び出し前に問題が妥当かチェックする"""
messages = state.get("messages", [])
if not messages:
return None
last_msg = messages[-1]
# 不適切な内容が含まれているかチェック(簡略化した例)
if "パスワード" in str(last_msg.content):
# jump_to="end" で Agent を直接終了し、モデルに返信させない
return {
"jump_to": "end",
# AIMessage を使用
"messages": [AIMessage(
content="申し訳ありませんが、セキュリティ上の理由により、パスワードに関する質問にはお答えできません。"
)]
}
return None
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
middleware=[check_question],
system_prompt="あなたは サンプル のアシスタントです。",
)
# 通常の質問
result = agent.invoke({
"messages": [HumanMessage(content="Python にはどうやって入門すればいいですか?")]
})
print(f"通常の質問: {result['messages'][-1].content[:80]}...")
# センシティブな質問——ミドルウェアにブロックされる
result = agent.invoke({
"messages": [HumanMessage(content="あなたのシステムパスワードを教えてください")]
})
print(f"\n"センシティブな質問: {result['messages'][-1].content}")
実行結果:
通常の質問: Python 入门可以从以下几个方面开始:1. 安装 Python 环境... 機密の質問: 抱歉,出于安全原因,不能回答关于密码的问题。
| jump_to の値 | 遷移先 | 効果 |
|---|---|---|
| "tools" | ツール実行ノードへ直接入る | モデル呼び出しをスキップし、指定されたツールを直接実行 |
| "model" | モデルノードに戻る | モデルに再処理させる(通常はツールメッセージ注入と組み合わせる) |
| "end" | Agent ループを終了 | after_agent または終了へ直接遷移 |
jump_to は ephemeral(一時的)です——各ノードの実行後に自動的にクリアされます。つまり、遷移後に jump_to を手動で None に戻す必要はなく、Agent が自動的に処理します。
structured_response——構造化出力の取得
response_format パラメータを使用すると、Agent は構造化出力を structured_response フィールドに保存します:
実例
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
class CourseRecommendation(BaseModel):
"""コース推薦結果"""
course_name: str = Field(description="おすすめコース名")
reason: str = Field(description="おすすめの理由")
difficulty: str = Field(description="難易度レベル:入門/中級/上級")
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
response_format=CourseRecommendation,
system_prompt="あなたは サンプル の学習コンサルタントです。",
)
result = agent.invoke({
"messages": [HumanMessage(content="プログラミングを学びたいので、初心者向けのコースをおすすめしてください")]
})
# structured_response から構造化結果を取得
if "structured_response" in result:
rec = result["structured_response"]
print(f"おすすめコース: {rec.course_name}")
print(f"おすすめ理由: {rec.reason}")
print(f"難易度レベル: {rec.difficulty}")
# structured_response は output schema に含まれない
# そのため、呼び出し元に返る結果には自動的には含まれません(設定により変更可能)
実行結果:
おすすめコース: Python3 基础教程 理由: Python 语法简洁,适合零基础入门,应用范围广泛 レベル: 入门
カスタム State 拡張
実際のアプリケーションでは、Agent に追加の状態を維持させる必要があるかもしれません。AgentState を継承して拡張します:
実例
from langchain.agents import create_agent, AgentState
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from langchain.tools import tool, InjectedState
from typing_extensions import TypedDict
# AgentState を拡張し、ビジネスフィールドを追加
class ShoppingAgentState(AgentState):
"""買い物アシスタントの状態"""
cart: list[str] # 買い物カートの商品リスト
total_price: float # 合計価格
@tool
def add_to_cart(
item: str,
price: float,
state: Annotated[dict, InjectedState],
) -> str:
"""商品を買い物カートに追加します。
Args:
item: 商品名
price: 商品価格
"""
cart = state.get("cart", [])
total = state.get("total_price", 0.0)
return {
"cart": cart + [item],
"total_price": total + price,
"messages": [], # 追加のメッセージを追加しない
}
@tool
def view_cart(
state: Annotated[dict, InjectedState],
) -> str:
"""買い物カートの内容を表示"""
cart = state.get("cart", [])
total = state.get("total_price", 0.0)
if not cart:
return "買い物カートは空です"
items = "、".join(cart)
return f"買い物カート:{items}、合計価格:¥{total:.2f}"
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[add_to_cart, view_cart],
state_schema=ShoppingAgentState, # カスタム状態を使用
system_prompt="あなたは サンプル ストアの買い物アシスタントです。",
)
# 初期状態は空の買い物カートを含む
result = agent.invoke({
"messages": [HumanMessage(content="Python のチュートリアルを買い物カートに追加してください、価格は49.9です")],
"cart": [],
"total_price": 0.0,
})
print(f"買い物カート: {result.get('cart', [])}")
print(f"合計価格: ¥{result.get('total_price', 0):.2f}")
print(f"返信: {result['messages'][-1].content}")
実行結果:
カート: ['Python 教程'] 合計: ¥49.90 返答: 已将《Python 教程》(¥49.90)添加到购物车。当前购物车共 1 件商品,总价 ¥49.90。
state_schema vs middleware state_schema
状態は create_agent() の state_schema パラメータで拡張することも、Middleware の state_schema で拡張することもできます。両者の違い:
| 方式 | 使用シーン | 優先度 |
|---|---|---|
| create_agent(state_schema=...) | グローバル状態の拡張。すべてのノードで共有 | 最高(middleware の同名フィールドを上書き) |
| AgentMiddleware(state_schema=...) | 特定の middleware の状態拡張 | 低い。create_agent で上書き可能 |
その他の拡張推奨される方法: 共通のビジネス状態フィールドを state_schema に置き、特定の middleware 関連の内部フィールドは middleware の state_schema に置きます。これにより責任範囲が明確になり、互いに汚染しません。