金融AIエージェントの作り方:LLM × 計算エンジン × オーケストレーション実装ガイド

金融領域でLLMを使うとき、最初にぶつかる壁はシンプルです。
- 文章の要約や説明はLLMが強い
- でも 数値計算・論理整合性・事実確認はLLMが弱い(幻覚・計算ミス)
金融は「間違えたらアウト」な領域なので、LLMを単体で使うのではなく、LLMを司令塔(Brain)にして、計算は確定的ツールに任せるのが王道になっています。
本記事では、金融AIエージェントの全体像を アーキテクチャ〜実装パターン〜プロンプト設計〜運用まで、実装目線で網羅的に整理します。
1. なぜ「エージェント型」なのか(LLM単体の限界)
LLMは確率的に文章を生成するため、以下が起きます。
- 幻覚(Hallucination):それっぽい嘘を混ぜる
- 計算の不安定さ:単純な算術でミスることがある
- 再現性の低さ:同じ質問でも微妙に答えが変わる
金融実務では、これらは致命的です。
そこで、LLMを「テキスト生成器」ではなく 推論と意思決定を担当する司令塔(Brain) として使い、計算は Python/C++の計算モジュールで実行する構成が主流になります。
2. 全体アーキテクチャ:モノリスではなくモジュール分割が基本
金融AIエージェントは「全部入りの巨大モデル」ではなく、役割分担したモジュールを疎結合で接続するのが安定します。
(人間の金融組織が、分析・クオンツ・リスク・コンプライアンスで分業しているのと同じ発想です)
2.1 4層アーキテクチャ(FinRobot的な分解)
実装を考えると、次の4層に分けると整理しやすいです。
① エージェント層(Financial AI Agents Layer)
ユーザーと会話し、タスクを実行する最上位レイヤー。典型的に次の3モジュールで回します。
- Perception(知覚):市場データ、ニュース、財務データを取り込み、LLMが扱える形に整形
- 例:板情報の生データはトークン的に無理 → 「売り圧力が強い」など特徴量に要約
- Brain(推論):どう解くかを決める(ツール選択、パラメータ決定、手順計画)
- ここでは“答え”を出すより 「次に何を実行するか」を決めるのが重要
- Action(行動):Python実行、外部API呼び出し、DBクエリ、注文発注などを実行
- 成功/失敗をPerceptionに戻して、自己修正ループを作る
② アルゴリズム層(Financial LLM Algorithms Layer)
「脳の専門性」を作るレイヤー。
- 金融向けに追加学習したLLM(例:FinGPT系)
- センチメント分類、時系列予測など非LLMモデル(TSモデル、分類器)もここに置く
③ DataOps / LLMOps 層(Infrastructure Layer)
信頼性を支える土台。
- DataOps:リアルタイムデータの収集、欠損処理、正規化、特徴量ストア
- LLMOps:モデルのバージョニング、推論APIのレイテンシ監視、微調整パイプライン
④ 基盤モデル層(Foundation Model Layer)
特定ベンダーにロックインしないための抽象化レイヤー。
- OpenAI / Anthropic / Llama / Mistral などを統一IFで扱う
- 「推論重視」「コスト重視」など、タスクに応じてモデルを切り替えられる設計が強い
3. 複雑タスクは「マルチエージェント + スケジューラ」で回す
「リバランスして、根拠を説明して、レポート化して」といった業務は、単一エージェントでやると破綻しやすいです。
そこで上位に Director(司令官) を置く構成が有効です。
- Director Agent:要求をサブタスクに分解し、依存関係を整理(DAG化)
- Agent Registry:利用可能エージェントの能力・負荷を管理
- Agent Adaptor:タスクに合わせてプロンプト/権限/ツールを動的に付与
例:
市場分析 → クオンツ計算 → リスク検証 → 文章化(レポート)
という“専門家チーム”を再現できます。
4. 重要なのは「ツール(計算エンジン)」:LLMの弱点を殺す
LLMの知能は「判断」に使い、数値は必ずツールで計算します。ここが品質の分かれ目です。
4.1 Python × C++ のハイブリッドが現実解
- Python:エージェント実装、データ処理、オーケストレーション(LangChain/LangGraph)
- C++:高速計算(デリバティブ、リスク、モンテカルロ、HFTなど)
統合パターンは2つが定番です。
- 既製:QuantLib(C++)を Python Binding で使う
- 自作:C++コアを pybind11 でPythonモジュール化
- PythonのGILを避けて並列化しやすく、モンテカルロで効きやすい
実務Tip:LLMに直接QuantLibの複雑なAPIを触らせるのは事故りやすいので、Python側で「引数を減らしたFacade関数」を用意すると安定します。
5. 実装例①:ブラック・ショールズ(ベクトル化)を“ツール化”する
オプション価格をLLMに計算させるのはNG。計算ツールに一本化します。
import numpy as np
from scipy.stats import norm
from pydantic import BaseModel, Field
class OptionPricingInput(BaseModel):
spot_prices: list[float] = Field(..., description="原資産価格のリスト")
strike_prices: list[float] = Field(..., description="権利行使価格のリスト")
time_to_maturity: float = Field(..., description="満期までの期間(年換算)")
risk_free_rate: float = Field(..., description="無リスク金利(例: 0.05)")
volatility: float = Field(..., description="ボラティリティ(例: 0.2)")
option_type: str = Field("call", description="'call' または 'put'")
def black_scholes_vectorized(data: OptionPricingInput):
S = np.array(data.spot_prices)
K = np.array(data.strike_prices)
T = data.time_to_maturity
r = data.risk_free_rate
sigma = data.volatility
with np.errstate(divide='ignore', invalid='ignore'):
d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
if data.option_type == "call":
prices = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
else:
prices = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
return {"prices": np.nan_to_num(prices).tolist()}
ポイントは3つです。
- Pydanticで入力を型定義(LLMに安全な引数生成を強制)
- NumPyでベクトル化(大量銘柄でも速い)
- LLMに返すのは JSON(構造化)(“文字列返し”は後で壊れます)
6. 実装例②:モンテカルロ(VaR/CVA)はジョブキューで非同期処理
リスク計量は重いので、エージェントのリクエストと計算を分離します。
典型構成
- LLMエージェント:パラメータを整えて ジョブを投入
- ワーカー:Celery等で計算実行
- 結果:要約統計(平均、標準偏差、95%/99% VaR)だけ返す
- 全パス返却(巨大データ)はNG
計算モデル(GBM)の基本式
- 価格パス生成:
実装の勘所:
- 乱数の品質(PRNG)が結果の信頼性に直結
- 分散減少法(反変変数、制御変数)を入れると収束が速い
- GPUを使うなら CuPy/PyTorch でGPUメモリ上で回す
7. 実装例③:最適化(配分決定)は「LLM=翻訳役」にする
ポートフォリオ最適化は、LLMに“解”を出させず、制約を数式に落とす役にします。
フロー
- Translation:LLMが自然言語制約を整理(例:「テック株は最大30%」)
- Formulation:Pythonが目的関数・制約式を組み立て
- Solving:cvxpy / PuLP + ソルバ(OSQP/ECOS/Gurobi等)
- Explanation:LLMが「なぜこの配分か」を説明文にする
これで 数値はソルバ、説明はLLM と役割が分離できます。
8. LangGraphで作る「状態を持つ」金融ワークフロー
金融タスクは直線処理だとすぐ詰まります。
- データ不足 → 再収集
- 異常値 → パラメータ修正して再計算
- レポート不十分 → 差し戻し
こういうループ・分岐を扱うには、LangGraphのような グラフ(DAG)型オーケストレーションが向いています。
8.1 State(共有状態)の設計
from typing import TypedDict, Annotated
from langchain_core.messages import BaseMessage
import operator
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], operator.add] # 会話履歴(追記型)
financial_data: dict # 取得データ
analysis_results: dict # 計算結果(JSON)
errors: list[str] # エラー
next_step: str # ルーティング用
8.2 Router / Tool / Reviewer の3点セット
- Router(Supervisor):次にどのノードへ行くかをLLMが判断
- Tool Node:計算ツールを実行し、結果は state.analysis_results に格納
- Reviewer(Reflector):整合性チェックして不足があれば差し戻し
コツ:計算結果は必ず構造化してStateに保存し、最終出力でLLMに文章化させる。これで“数字のねつ造”が激減します。
9. Function Calling × Pydantic(スキーマ駆動)
LLMとツールの接続は、自由記述にすると必ず壊れます。
「型」を渡して、型に沿ったJSONを出させるのが安定解です。
from pydantic import BaseModel, Field
from langchain.tools import tool
from typing import List
class PortfolioOptimizationInput(BaseModel):
tickers: List[str] = Field(..., description="最適化対象の銘柄リスト")
target_return: float | None = Field(None, description="目標リターン(小数)")
max_risk: float | None = Field(None, description="許容最大リスク(標準偏差)")
constraints: List[str] = Field(default_factory=list, description="追加制約(自然言語可)")
@tool("optimize_portfolio", args_schema=PortfolioOptimizationInput)
def optimize_portfolio(tickers: List[str], target_return: float = None,
max_risk: float = None, constraints: List[str] = None):
# 内部でcvxpy等を呼ぶ
return {"weights": {"AAA": 0.2, "BBB": 0.8}, "notes": ["tech<=30% を満たすため..."]}
10. レガシー統合:Wrapper Agent と「仕様書RAG」
金融機関はレガシー(COBOL/古いJava/C++)が普通にあります。
LLMを入れるときは、いきなり置き換えずに ラップして接続が現実的です。
- API Gateway:機能をREST/gRPCでラップ
- Documentation RAG:仕様書・DBスキーマ・OpenAPIを埋め込み検索
- エージェントが「どのAPIを叩けばデータが取れるか」を引けるようになる
11. プロンプトは“お願い”ではなく「制御コード」
金融は幻覚を許せないので、プロンプトは強めに制御します。
11.1 Financial CoT(手順固定で整合性を上げる)
おすすめは「手順テンプレ」を持たせることです。
- Data Validation(欠損・異常値)
- Hypothesis(仮説)
- Quantitative Analysis(ツールで計算)
- Risk Assessment(リスク列挙・定量化)
- Conclusion(統合結論)
- 禁止事項:データがないのに補完しない、根拠なしの断定をしない
11.2 PoT(Program of Thought)で計算はコードに逃がす
計算をLLMにやらせず、Pythonコードを書かせて実行結果を採用します。
これで算術ミスを回避できます。
11.3 JSON Schemaで出力形式を固定する
DB格納やシステム連携なら、自然文ではなくJSONを強制します。
{
"type": "object",
"properties": {
"ticker": {"type": "string"},
"sentiment_score": {"type": "number", "minimum": -1.0, "maximum": 1.0},
"key_drivers": {"type": "array", "items": {"type": "string"}}
},
"required": ["ticker", "sentiment_score", "key_drivers"]
}
12. DataOps / MLOps:金融は「データ鮮度」が性能の半分
モデルより先に、データが崩れていると全部崩れます。
- ストリーミング:Kafka、kdb+、時系列DB
- 特徴量ストア:移動平均、RSI、ニュース埋め込みをRedis等へ
- 金融向けInstruction Tuning:データは Input/Instruction/Output 形式で作る
- 学習はLoRAで軽量化するのが現実的
13. 実装シナリオ(イメージを掴む)
13.1 Equity Research Agent(調査レポート)
- 収集担当 → 分析担当 → 執筆担当のマルチエージェント
- 10-K/有報をRAGで参照し、必要セグメントを抽出して説明文を生成
13.2 Quant Trading Agent(戦略立案・自己改善)
- マクロ→ミクロ→リスクの階層
- 過去結果をMemoryに保存し、Reflectionで改善
- バックテストしてから実弾投入(Backtrader等)
まとめ:金融AIエージェントの設計原則
- LLMは司令塔、計算はツール(幻覚と計算ミスを潰す)
- モジュール分割 + スキーマ駆動(Pydantic/Function Calling)
- 状態管理 + ループ(LangGraphで“実務の試行錯誤”を表現)
- 重い計算は非同期(ジョブキューで分離)
- レガシーはラップ + 仕様RAG(現実的に繋ぐ)


