はじめに
前回の記事では、LangGraphの State と Conditional Edge をブラウザで見える化し、AIエージェントの処理の流れを理解しやすくする方法を書きました。
前回記事:
今回はその続編として、同じ考え方を 経営サポートAI に応用しました。
目的は、AIに経営や仕事の相談をするときにありがちな、
- PCやセッションによって答えが変わる
- どのファイルを根拠に答えたのかわからない
- 「本日やること」のような定型質問でも回答がブレる
- AIが雰囲気で判断しているように見える
という問題を減らすことです。
結論から言うと、経営サポートAIも State / Node / Conditional Edge に分けることで、かなり扱いやすくなります。
作ったもの
今回作ったのは、ai-board という経営サポートAIのための小さなCLIです。
.venv/bin/python board_runner.py コマンドを教えて
.venv/bin/python board_runner.py 本日やること
.venv/bin/python board_runner.py 朝会お願いします。
.venv/bin/python board_runner.py 再起動する
たとえば、
.venv/bin/python board_runner.py 本日やること
と実行すると、daily-log.md の当日エントリーを読み、Today's Top3 を抜き出して返します。
つまり、AIが毎回なんとなく「今日やること」を考えるのではなく、
本日やること
-> daily-log.mdを読む
-> 当日エントリーを探す
-> Today's Top3を抽出する
-> それを正として返す
というルートを固定しました。
なぜ経営AIにGraphが必要なのか
経営や仕事の相談では、AIの自由な発想は役に立ちます。
一方で、毎日使う経営サポートAIでは、自由すぎることが問題になります。
たとえば「本日やること」と聞いたときに、あるPCでは、
1. 新規提案向け4コースの目次を作る
2. 共通40分コンテンツの生成指示を作る
3. Zoomで見せる提案メモを作る
と答え、別のPCでは、
1. 資料作成を完了する
2. 定例グルコン後処理を完了する
3. 新規提案準備を進める
と答えることがあります。
どちらも文脈としては間違っていなくても、仕事で使うには困ります。
経営サポートAIでは、特に次のような処理は決定論的にしたいです。
- どのファイルを正とするか
- どの順番で読むか
- どの条件なら質問するか
- どの条件なら実行するか
- どの条件なら保留するか
- どの結果をログに残すか
ここにGraph構造が効きます。
State / Node / Conditional Edgeへの分解
今回の実装では、処理中の状態を BoardState として定義しました。
class BoardState(TypedDict, total=False):
user_input: str
today: str
repo_root: str
command_type: CommandType
required_files: list[str]
files: dict[str, str]
current_top3: list[str]
missing_info: list[str]
risk_flags: list[str]
recent_writes: list[str]
next_action: str
response: str
trace: list[str]
State には、ユーザー入力、今日の日付、コマンド種別、読み込むファイル、抽出したTop3、最終回答などを入れています。
次に、処理を Node に分けます。
classify_command
determine_required_files
load_required_files
extract_today_top3
answer_today_tasks
show_commands
run_morning_meeting
restart_board
それぞれのNodeは、Stateを受け取り、Stateを更新して返します。
たとえば classify_command は、入力を見てコマンド種別を決めます。
def classify_command(state: BoardState) -> BoardState:
text = normalize_text(state["user_input"])
if "コマンドを教えて" in text or "コマンド教えて" in text:
state["command_type"] = "commands"
state["next_action"] = "show_commands"
elif "本日やること" in text or "今日やること" in text:
state["command_type"] = "today_tasks"
state["next_action"] = "load_required_files"
elif "朝会お願いします" in text:
state["command_type"] = "morning_meeting"
state["next_action"] = "load_required_files"
else:
state["command_type"] = "unknown"
state["next_action"] = "unknown"
return state
ここでは、AIに「これは何の依頼ですか?」と毎回考えさせず、文字列ベースでルートを固定しています。
Conditional Edgeで処理を固定する
分類後は、Stateを見て次のNodeへ分岐します。
def route_after_classification(state: BoardState) -> str:
next_action = state["next_action"]
if next_action == "show_commands":
return "show_commands"
if next_action == "load_required_files":
return "determine_required_files"
return "answer_unknown"
さらに、コマンド種別ごとに次の処理を分けます。
def route_by_command(state: BoardState) -> str:
command = state["command_type"]
if command == "today_tasks":
return "extract_today_top3"
if command == "morning_meeting":
return "run_morning_meeting"
if command == "recent_writes":
return "inspect_recent_writes"
if command == "restart":
return "restart_board"
return "answer_unknown"
これにより、
コマンドを教えて
-> classify_command
-> show_commands
本日やること
-> classify_command
-> determine_required_files
-> load_required_files
-> extract_today_top3
-> answer_today_tasks
という処理ルートになります。
LangGraphでつなぐ
LangGraphが入っている環境では、以下のように StateGraph として組み立てます。
def build_langgraph_app() -> Any:
from langgraph.graph import END, StateGraph
workflow = StateGraph(BoardState)
workflow.add_node("classify_command", classify_command)
workflow.add_node("determine_required_files", determine_required_files)
workflow.add_node("load_required_files", load_required_files)
workflow.add_node("extract_today_top3", extract_today_top3)
workflow.add_node("show_commands", show_commands)
workflow.add_node("answer_today_tasks", answer_today_tasks)
workflow.add_node("run_morning_meeting", run_morning_meeting)
workflow.add_node("restart_board", restart_board)
workflow.set_entry_point("classify_command")
workflow.add_conditional_edges(
"classify_command",
route_after_classification,
{
"show_commands": "show_commands",
"determine_required_files": "determine_required_files",
"answer_unknown": "answer_unknown",
},
)
workflow.add_edge("determine_required_files", "load_required_files")
return workflow.compile()
ポイントは、LLMに全部任せるのではなく、
- 入力分類
- ファイル読込
- Top3抽出
- コマンド一覧表示
- 再起動処理
を明示的なNodeとして分けたことです。
LangGraph未導入環境でも動くようにした
実務で使う場合、別PCで依存関係が入っていないこともあります。
そこで、LangGraphが未導入でも同じロジックで動くフォールバックを入れました。
def run_board(user_input: str, repo_root: Path, date_text: str | None = None) -> BoardState:
initial_state: BoardState = {
"user_input": user_input,
"today": date_text or today_jst(),
"repo_root": str(repo_root),
"missing_info": [],
"risk_flags": [],
"trace": [],
}
try:
app = build_langgraph_app()
except ImportError:
return run_without_langgraph(initial_state)
return app.invoke(initial_state)
これにより、LangGraphがある環境では StateGraph を使い、ない環境では同じ分岐関数を順番に呼ぶ形で動きます。
実行結果
たとえば、
.venv/bin/python board_runner.py コマンドを教えて --trace
を実行すると、コマンド一覧と処理ルートが返ります。
--- trace ---
classify_command -> show_commands
また、
.venv/bin/python board_runner.py 本日やること --trace
では、次のようなルートになります。
--- trace ---
classify_command -> determine_required_files -> load_required_files -> extract_today_top3 -> answer_today_tasks
この trace があるだけで、AIがどの判断経路を通ったのかがかなり見えやすくなります。
テストも書いた
定型コマンドは、ブレると困ります。
そこで最低限のテストも書きました。
def test_today_tasks_come_from_daily_log():
state = board_runner.run_board(
"本日やること",
Path(__file__).resolve().parents[1],
"2026-07-19",
)
assert "資料作成を完了する" in state["response"]
assert "日曜午前中の定例グルコンの後処理を完了する" in state["response"]
assert "新規提案準備を進める" in state["response"]
実行結果:
5 passed
何が便利になったか
今回の実装で便利になったことは、主に3つです。
1. 返答のブレが減る
「本日やること」と聞かれたら、必ず daily-log.md の当日 Today's Top3 を見に行きます。
これにより、AIが古い文脈や別ファイルの未完了タスクを拾って、違うTop3を返す問題を減らせます。
2. 処理ルートが追える
--trace を付けると、
classify_command -> determine_required_files -> load_required_files -> extract_today_top3 -> answer_today_tasks
のように、どのNodeを通ったか確認できます。
これは経営AIにとって重要です。
経営判断では、結論だけでなく、
- 何を根拠にしたか
- どこで分岐したか
- どのファイルを読んだか
- どこで人間確認が必要になったか
が見える必要があるからです。
3. Codex内からも使える
このCLIをCodex内から呼ぶ運用にしたので、ユーザーは普通に、
コマンドを教えて
と入力するだけでよくなりました。
内部では、
.venv/bin/python board_runner.py コマンドを教えて
を実行し、その結果を返します。
経営AIとしての意味
今回作ったものは、まだ小さなCLIです。
ただし、考え方としてはかなり重要です。
経営サポートAIでは、AIに自由に考えさせる部分と、ルールで固定する部分を分ける必要があります。
AIに任せる部分:
- 要約
- 提案
- 文章化
- 選択肢の整理
プログラムで固定する部分:
- どのファイルを読むか
- どの情報を正とするか
- どの条件で分岐するか
- どのタイミングで人間確認に戻すか
- どのログに記録するか
この分離ができると、AIは「気分で答える相談相手」から、「決められた経営フローに沿って判断を支援するシステム」に近づきます。
今後やりたいこと
今後は、次のように発展させたいです。
- ブラウザで現在のNodeを見える化する
- Stateの中身を画面に表示する
-
daily-log.mdへの追記もNode化する - CEO / CMO / CFO / CTO / CPO / CSOをそれぞれNode化する
- 売上、顧客対応、提案期限、リスクによってConditional Edgeを増やす
- 人間確認が必要な判断を自動で止める
最終的には、
朝会開始
-> 状況確認
-> 売上確認
-> 顧客対応確認
-> リスク判定
-> CEO判断
-> Top3決定
-> daily-log.mdへ記録
のような経営フローを、Graphとして管理したいと考えています。
まとめ
前回は、LangGraphの State と Conditional Edge を理解するために、ブラウザで処理の流れを見える化しました。
今回は、その考え方を実際の仕事に応用し、経営サポートAIの定型コマンドをGraph構造で処理するようにしました。
ポイントは次の3つです。
- AIの返答を安定させるには、Stateを明示する
- 業務処理はNodeに分ける
- 重要な分岐はConditional Edgeで固定する
経営AIを作るうえで大事なのは、AIに全部任せることではありません。
AIが得意なところはAIに任せ、ブレてはいけないところはGraphで制御する。
この考え方が、実用的なAIエージェントを作るうえでかなり重要だと感じました。