よくある問題とその解決策
原因: スライス名がレジストリに登録されていない
解決策:
# オプション1: スライスをレジストリに追加
registry.add_valid_slice("your_slice_name")
# オプション2: タイポを確認
# "shoping" → "shopping" かも?
予防策:
# スライス名を定数として定義
SLICE_ORDERS = "orders"
SLICE_WORKFLOW = "workflow"
# コントラクトで定数を使用
reads=[SLICE_ORDERS]
原因: コントラクトにrequires_llm=TrueがあるがLLMが注入されていない
解決策:
# インスタンス化時にLLMを提供
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4")
node = MyNode(llm=llm)
# またはグラフ構築時
graph = build_graph_from_registry(
registry=registry,
llm=llm, # 全ノードに渡される
)
原因: servicesで宣言されたサービスが利用できない
解決策:
# 必要なサービスをすべて提供
db_service = DatabaseService()
cache_service = CacheService()
node = MyNode(
llm=llm,
db_service=db_service,
cache_service=cache_service,
)
予防策:
# 既知のサービスでバリデーション
validator = ContractValidator(
registry,
known_services={"db_service", "cache_service"},
)
考えられる原因と解決策:
# 確認: 'when'条件は正しい?
when={"request.action": "serch"} # タイポ! "search" が正しい
# 優先度が高い別のノードが先にマッチしている
# decide_with_trace()でデバッグ
decision = await supervisor.decide_with_trace(state)
print(decision.reason.matched_rules)
# 確認: 正しいスーパーバイザーに登録されている?
supervisor="main" # build_graph_from_registryのsupervisors=と一致が必要
# トリガー条件を追加
trigger_conditions=[
TriggerCondition(priority=10, when={"request.action": "my_action"})
]
トレーサブルルーティングでデバッグ:
decision = await supervisor.decide_with_trace(state)
print(f"選択: {decision.selected_node}")
print(f"タイプ: {decision.reason.decision_type}")
print(f"マッチしたルール:")
for rule in decision.reason.matched_rules:
print(f" P{rule.priority}: {rule.node} - {rule.condition}")
一般的な修正方法:
when条件をより具体的にwhen_notを追加して不要なマッチを除外解決策:
# 悪い例
llm_hint="検索"
# 良い例
llm_hint="ユーザーが明示的に商品を検索したい時に使用。閲覧やレコメンドには使用しない。"
# アクションが明示的ならLLMの代わりにルールを使用
when={"request.action": "search"} # 明確な意図
priority=100 # LLMが決定する前に強制選択
原因: ノードがENDに到達せずルーティングを繰り返す
解決策:
# response typeがterminal_statesに含まれていることを確認
terminal_response_types={"question", "results", "error"}
# ノードの出力が一致するタイプであること
return NodeOutputs(
response={
"response_type": "results",
"response_data": {"items": [1, 2, 3]},
}
)
class ResultNode(ModularNode):
CONTRACT = NodeContract(
name="result",
description="最終結果を返してフローを終了",
reads=["request"],
writes=["response"],
supervisor="main",
trigger_conditions=[TriggerCondition(priority=10)],
is_terminal=True, # このノードの後にENDを強制
)
supervisor = GenericSupervisor(
max_iterations=50, # 問題を見つけるために増加
)
原因: ノード出力がコントラクトのwritesと一致しない
解決策:
# コントラクトで宣言
writes=["orders"]
# executeで一致するスライスを返す
return NodeOutputs(
orders={"cart": [...]}, # ✅ 正しい
# Not: response={"cart": [...]} # ❌ 間違ったスライス
)
原因: コントラクトのreadsに必要なスライスが含まれていない
解決策:
# 'context'スライスからデータが必要な場合
CONTRACT = NodeContract(
name="my_node",
description="contextスライスが必要な例",
reads=["request", "context"], # 'context'を含める
writes=["response"],
supervisor="main",
)
async def execute(self, inputs, config=None):
context = inputs.get_slice("context") # これで利用可能
ファイルパスを確認:
from agent_contracts.config import load_config, set_config
# 絶対パス
config = load_config("/path/to/agent_config.yaml")
# または作業ディレクトリからの相対パス
config = load_config("./config/agent_config.yaml")
set_config(config)
YAML構文を確認:
# 有効なYAML
supervisor:
max_iterations: 10
response_types:
terminal_states:
- interview
- results
設定を確認:
# agent_config.yaml内
response_types:
terminal_states:
- question # response_typeと完全一致が必要
- results
- error
response_typeのフォーマットを確認:
# 完全一致が必要
return NodeOutputs(
response={
"response_type": "question", # 完全一致
# "Question" や "QUESTION" ではない
}
)
Undeclared slice read / Undeclared slice write(s) の警告が出る場合、ノードが NodeContract.reads/writes に含まれないスライスにアクセスしています。
対応:
NodeContract にスライスを追加するio:
strict: false # true: ContractViolationError で停止
warn: true # 警告ログ
drop_undeclared_writes: true # コントラクト外writeを破棄
pytest-asyncioを使用:
import pytest
@pytest.mark.asyncio
async def test_node_execution():
node = MyNode(llm=mock_llm)
inputs = NodeInputs(request={"action": "test"})
result = await node.execute(inputs)
assert result.response is not None
pytestを設定:
# pyproject.toml
[tool.pytest.ini_options]
asyncio_mode = "strict"
fixtureでレジストリをリセット:
import pytest
from agent_contracts import reset_registry
@pytest.fixture(autouse=True)
def clean_registry():
reset_registry()
yield
reset_registry()
解決策:
# スーパーバイザーにはGPT-3.5、ノードにはGPT-4
routing_llm = ChatOpenAI(model="gpt-3.5-turbo")
execution_llm = ChatOpenAI(model="gpt-4")
supervisor = GenericSupervisor(llm=routing_llm)
# アクションが明示的ならLLMは不要
when={"request.action": "search"}
# 純粋なルールベース、LLMオーバーヘッドなし
supervisor = GenericSupervisor(
llm=None, # ルールベースのみ
)
原因: ステートスライス内の大きなデータ(特にbase64画像)がルーティング判断のためにLLMに送信されている
症状:
解決策:
# GenericSupervisorは自動的にサニタイズ:
# - 画像データ → "[IMAGE_DATA]"
# - 長い文字列 → 先頭を保持してトリミング
# デフォルトのmax_field_lengthは10000文字
supervisor = GenericSupervisor(
supervisor_name="main",
llm=llm,
max_field_length=10000, # 必要に応じて調整
)
# ステートにbase64画像を保存している場合:
request = {
"action": "analyze",
"image": "image/png;base64,iVBORw0KG..." # 自動的にサニタイズされる
}
def my_context_builder(state, candidates):
# 大きなデータを含むスライスは含めない
return {
"slices": {"request", "response", "_internal"}, # 最小限のコンテキスト
# 避ける: {"request", "response", "raw_data", "images"}
}
# LLMに送信される内容を確認するためにデバッグログを有効化
import logging
logging.getLogger("agent_contracts").setLevel(logging.DEBUG)
予防策:
context_builderで最小限のコンテキストを使用原因: evaluate_triggers()を直接使用しているコードがv0.3.xのtuple形式を前提としている
症状:
# v0.3.x形式のコード
matches = registry.evaluate_triggers("main", state)
priority, node_name = matches[0] # エラー!
解決策:
# v0.4.0形式に更新
matches = registry.evaluate_triggers("main", state)
match = matches[0]
priority = match.priority
node_name = match.node_name
condition_index = match.condition_index # 新機能!
影響を受けるコード:
evaluate_triggers()を直接呼び出しているregistry.evaluate_triggers()の結果を処理している影響を受けないコード:
GenericSupervisorのみを使用decide()やdecide_with_trace()のみを使用困ったときは:
examples/ ディレクトリContractValidator.validate()decide_with_trace()import logging
logging.getLogger("agent_contracts").setLevel(logging.DEBUG)