Claude と Python、MCP を活用したスキル駆動型金融分析エージェントの設計
MarkTechPost は、Anthropic の金融サービスリポジトリを解析し、Python で再現したスキル駆動型エージェントの構築手順と実装コードを公開した。
AI深層分析を開く2026年7月28日 09:10
AI深層分析
キーポイント
スキル駆動アーキテクチャの Python 実装
Anthropic の公式リポジトリに含まれるエージェント、垂直プラグイン、パートナー統合などの構造を解析し、Python で再現する手法を示す。
検索可能なスキルレジストリの構築
リポジトリ内の SKILL.md ファイルをパースして検索可能なレジストリを作成し、選択した金融プレイブックを Anthropic Messages API に注入する SkillAgent を実装する。
具体的な金融分析タスクの実行
構築されたアーキテクチャを用いて、DCF 評価、WACC と終値成長の感度ヒートマップ生成、企業比較分析による Excel 出力、私募株投資委員会メモの起草を実行する。
マネージドエージェントのデプロイ仕様検証
実際のデプロイリクエストを送信せずとも、マネージドエージェントのデプロイ仕様を検査・確認できる仕組みを提供する。
リポジトリ構造の自動マッピング
スクリプトはエージェント、垂直領域、パートナー、cookbook の各プラグインディレクトリを走査し、SKILL.md ファイルとコマンドファイルの数をカウントして DataFrame として出力する。
重要な引用
reproduce its skill-driven architecture in pure Python
parse the repository's SKILL.md files into a searchable registry and construct a reusable SkillAgent
execute a synthetic discounted cash flow valuation, generate a WACC and terminal-growth sensitivity heatmap
"Loaded {len(registry.skills)} unique skills."
編集コメントを表示
編集コメント
本記事は、単なる API の呼び出しを超え、複雑な金融ドメインを処理するためのエージェントアーキテクチャをコードレベルで解明している。開発者にとって、Anthropic の公式リポジトリから実用的なパターンを抽出し、自社のシステムに組み込むための貴重な実践例となる。
本チュートリアルでは、Anthropic の金融サービスリポジトリを基盤とした高度なワークフローを構築し、そのスキル駆動型アーキテクチャを純粋な Python で再現します。まず必要なライブラリのインストールとリポジトリのクローンを行い、エージェント、垂直分野向けプラグイン、パートナー統合、管理型エージェントのカックブック、そして金融分析スキルをプログラムでマッピングします。
次に、リポジトリ内の SKILL.md ファイルを解析して検索可能なレジストリを作成し、選択した金融プレイブックを Anthropic Messages API に注入できる再利用可能な SkillAgent を構築します。これにより、Python による計算やファイル生成のための反復的なツール使用ループもサポートされます。
このアーキテクチャを用いて、合成された割引キャッシュフロー(DCF)バリュエーションの実行、WACC とターミナル成長率の感応度ヒートマップの生成、フォーマット済み Excel 出力を伴う同業他社分析、プライベート・エクイティ投資委員会向けメモの草案作成、そして実際のデプロイメントリクエストを送信せずに管理型エージェントの展開仕様を検証する一連のタスクを実行します。
まず、必要なライブラリをインストールします。以下のコマンドを実行してください。
import subprocess, sys, os, io, re, json, glob, textwrap, contextlib, pathlib
def sh(cmd):
print(f"$ {cmd}")
r = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if r.returncode != 0:
print(r.stderr[-1500:])
return r
sh(f"{sys.executable} -m pip install -q anthropic pandas openpyxl pyyaml matplotlib")
import pandas as pd
import yaml
import matplotlib.pyplot as plt
REPO_URL = "https://github.com/anthropics/financial-services.git"
REPO_DIR = "financial-services"
if not os.path.isdir(REPO_DIR):
sh(f"git clone --depth 1 {REPO_URL} {REPO_DIR}")
else:
print("Repo already cloned — skipping.")
def get_api_key():
try:
from google.colab import userdata
k = userdata.get("ANTHROPIC_API_KEY")
if k:
return k
except Exception:
pass
if os.environ.get("ANTHROPIC_API_KEY"):
return os.environ["ANTHROPIC_API_KEY"]
from getpass import getpass
return getpass("Enter your Anthropic API key: ")
os.environ["ANTHROPIC_API_KEY"] = get_api_key()
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-6"
print("SDK ready. Model:", MODEL)
必要な Python ライブラリをインストールし、Anthropic の金融サービス用リポジトリをクローンして、Google Colab 環境で実行準備を整えます。Anthropic API キーは、Colab シークレット、環境変数、または安全な対話型プロンプトから取得します。その後、公式の Anthropic SDK を初期化し、金融分析ワークフローを支える Claude モデルを選択します。
def repo_map(root=REPO_DIR):
rows = []
for kind, pattern in [
("agent", f"{root}/plugins/agent-plugins/*"),
("vertical",f"{root}/plugins/vertical-plugins/*"),
("partner", f"{root}/plugins/partner-built/*"),
("cookbook",f"{root}/managed-agent-cookbooks/*"),
]:
for p in sorted(glob.glob(pattern)):
if not os.path.isdir(p):
continue
skills = glob.glob(f"{p}/**/SKILL.md", recursive=True)
commands = glob.glob(f"{p}/commands/*.md")
rows.append({"type": kind, "name": os.path.basename(p),
"skills": len(skills), "commands": len(commands)})
return pd.DataFrame(rows)
print("\n=== REPO MAP ===")
repo_df = repo_map()
print(repo_df.to_string(index=False))
mcp_files = glob.glob(f"{REPO_DIR}/plugins/**/.mcp.json", recursive=True)
for f in mcp_files[:1]:
print(f"\n=== MCP CONNECTORS ({f}) ===")
try:
cfg = json.load(open(f))
for name, srv in cfg.get("mcpServers", cfg).items():
print(f" {name: {srv.get('url', srv)}")
except Exception as e:
print(" (could not parse:", e, ")")
FRONTMATTER = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.S)
class Skill:
def __init__(self, path):
self.path = path
raw = open(path, encoding="utf-8", errors="replace").read()
m = FRONTMATTER.match(raw)
meta = {}
if m:
try:
meta = yaml.safe_load(m.group(1)) or {}
except Exception:
meta = {}
self.name = str(meta.get("name") or pathlib.Path(path).parent.name)
self.description = str(meta.get("description", ""))[:300]
self.body = raw[m.end():] if m else raw
def __repr__(self):
return f""
class SkillRegistry:
def __init__(self, root=REPO_DIR):
paths = sorted(glob.glob(f"{root}/plugins/vertical-plugins/**/SKILL.md", recursive=True))
paths += sorted(glob.glob(f"{root}/plugins/**/SKILL.md", recursive=True))
self.skills = {}
for p in paths:
s = Skill(p)
self.skills.setdefault(s.name.lower(), s)
def find(self, query):
q = query.lower()
hits = [s for k, s in self.skills.items() if q in k]
if not hits:
hits = [s for s in self.skills.values() if q in s.description.lower()]
return hits
def get(self, query):
hits = self.find(query)
if not hits:
raise KeyError(f"No skill matching '{query}'. "
f"Available: {sorted(self.skills)[:40]}")
return hits[0]
registry = SkillRegistry()
print(f"\nLoaded {len(registry.skills)} unique skills.")
print("Sample:", sorted(registry.skills)[:12], "...")
リポジトリの構造を検証し、エージェントプラグイン、垂直領域向けプラグイン、パートナー統合機能、管理型エージェント用レシピ、および利用可能なコマンドを特定します。MCP 設定ファイルの場所を確認し、リポジトリ内で定義されている外部金融データコネクタを表示します。その後、各 SKILL.md ファイルを解析して YAML メタデータと手法を抽出し、検索可能アクセスのためにすべての固有スキルを登録します。
os.makedirs("outputs", exist_ok=True)
TOOLS = [
{
"name": "run_python",
"description": ("Execute Python code and return stdout. pandas as pd "
"and numpy as np are pre-imported. Use print() to "
"return results. State persists across calls."),
"input_schema": {
"type": "object",
"properties": {"code": {"type": "string"}},
"required": ["code"],
},
},
{
"name": "save_file",
"description": "Save text content to outputs/.",
"input_schema": {
"type": "object",
"properties": {"filename": {"type": "string"},
"content": {"type": "string"}},
"required": ["filename", "content"],
},
},
]
_PY_NS = {}
def _tool_run_python(code):
import numpy as np
_PY_NS.setdefault("pd", pd); _PY_NS.setdefault("np", np)
buf = io.StringIO()
try:
with contextlib.redirect_stdout(buf):
exec(code, _PY_NS)
out = buf.getvalue()
return out[:6000] if out else "(no stdout — use print())"
except Exception as e:
return f"ERROR: {type(e).__name__}: {e}"
def _tool_save_file(filename, content):
safe = os.path.basename(filename)
path = os.path.join("outputs", safe)
open(path, "w", encoding="utf-8").write(content)
return f"Saved {path} ({len(content)} chars)"
DISPATCH = {"run_python": lambda i: _tool_run_python(i["code"]),
"save_file": lambda i: _tool_save_file(i["filename"], i["content"])}
BASE_SYSTEM = """You are a financial analyst assistant operating with the
skill playbooks provided below (from Anthropic's financial-services repo).
Follow the skill's methodology, conventions, and output format closely.
Use the run_python tool for all numerical work — never do arithmetic in
your head. Use save_file for final deliverables. All work is a DRAFT for
human review; do not present it as investment advice."""
class SkillAgent:
"""Minimal reproduction of Cowork's skill-firing: chosen skills are
concatenated into the system prompt; the agent then runs a standard
tool-use loop against the Messages API until the model stops."""
def __init__(self, skill_queries, max_skill_chars=12000, verbose=True):
self.skills = [registry.get(q) for q in skill_queries]
blocks = []
for s in self.skills:
blocks.append(f"\n\n===== SKILL: {s.name} =====\n"
f"{s.description}\n{s.body[:max_skill_chars]}")
self.system = BASE_SYSTEM + "".join(blocks)
self.verbose = verbose
def run(self, prompt, max_turns=12):
messages = [{"role": "user", "content": prompt}]
for turn in range(max_turns):
resp = client.messages.create(
model=MODEL, max_tokens=8000,
system=self.system, tools=TOOLS, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use":
final = "".join(b.text for b in resp.content
if b.type == "text")
return final, messages
results = []
for block in resp.content:
if block.type == "tool_use":
if self.verbose:
print(f" [turn {turn}] tool: {block.name}")
out = DISPATCHblock.name
results.append({"type": "tool_result",
"tool_use_id": block.id,
"content": str(out)})
messages.append({"role": "user", "content": results})
return "(hit max_turns)", messages
Claude が Colab 環境内で Python の計算を実行し、生成された成果物を保存できるようにツールを定義します。これにより、数値モデルや表、中間変数がエージェントの複数回のターンにわたって保持される永続的な Python ネームスペースが構築されます。その後、SkillAgent クラスを作成し、システムプロンプトに必要な金融プレイブックを注入して、Anthropic Messages API のツール使用ループを管理します。
サンプルデータ:「Meridian Software」(架空企業)の2025年度実績(単位:百万ドル)
売上高850(前年比18%増)、EBITDAマージン27%、減価償却費は売上の4%
設備投資は売上の5%、運転資本の変化は売上成長率の1%、実効税率24%
純有利子負債320百万ドル、希薄化後発行株式数9,200万株
前提条件:売上成長率は5年間で18%から6%へ直線的に低下、EBITDAマージンは合計100bp拡大、WACCは9.5%、ターミナル成長率は2.5%
"""
print("\n" + "="*76 + "\nデモA — DCF(dcf-model スキル)\n" + "="*76)
try:
dcf_agent = SkillAgent(["dcf"])
dcf_answer, _ = dcf_agent.run(
"この企業に対して、スキルプレイブックに従い5年間の無レバレッジDCFを実行してください:\n"
+ SAMPLE_CO +
"\nrun_pythonを用いて企業価値、株式価値、および内包株価を計算し、WACC(8.5%〜10.5%、50bp刻み)とターミナル成長率(1.5%〜3.5%、50bp刻み)の感応度グリッドをJSON形式で出力してください。出力はマーカー SENS_JSON: の下に格納し、簡潔な要約も添えてください。")
print("\n--- DCF 結果 ---\n", dcf_answer[:3000])
m = re.search(r"SENS_JSON:\s*(\{.*\})", dcf_answer, re.S)
if m:
grid = json.loads(m.group(1))
sens = pd.DataFrame(grid)
sens = sens.apply(pd.to_numeric, errors="coerce")
fig, ax = plt.subplots(figsize=(7, 4))
im = ax.imshow(sens.values, cmap="RdYlGn", aspect="auto")
ax.set_xticks(range(len(sens.columns)), sens.columns)
ax.set_yticks(range(len(sens.index)), sens.index)
ax.set_xlabel("ターミナル成長率"); ax.set_ylabel("WACC")
ax.set_title("内包株価の感応度(ドル)")
for i in range(sens.shape[0]):
for j in range(sens.shape[1]):
v = sens.values[i, j]
if pd.notna(v):
ax.text(j, i, f"{v:,.0f}", ha="center", va="center",
fontsize=8)
fig.colorbar(im); plt.tight_layout(); plt.show()
except Exception as e:
print("デモAはスキップされました:", e)
合成された事業仮定を提示し、DCF スキルエージェントに 5 カ年間の無レバレッジ・キャッシュフローに基づく企業価値評価の構築を指示します。Python 実行ツールを用いて、企業価値、株式価値、内包株価、および 2 次元感度行列を算出します。その後、構造化された感度結果を抽出し、WACC(加重平均資本コスト)とターミナル成長率、そして内包評価額との関係をヒートマップで可視化します。
Copy CodeCopiedUse a different Browser
PEERS = """
合成比較対象企業セット(株価除く、1株あたり指標は除く):
ティッカー 株価 発行株式数 純負債 売上高 (NTM) EBITDA (NTM) EPS (NTM)
ALFA 64.2 210 450 2900 820 3.10
BRVO 28.7 540 -120 4100 980 1.45
CHRL 112.5 95 760 1850 610 5.60
DLTA 41.9 330 210 2600 700 2.05
"""
print("\n" + "="*76 + "\nデモ B — 比較分析 (comps-analysis スキル) -> Excel\n" + "="*76)
try:
comps_agent = SkillAgent(["comps"])
comps_answer, _ = comps_agent.run(
"比較分析スキルに基づき、以下の企業について EV(企業価値)、EV/売上高、EV/EBITDA、P/E レシオ (すべて NTM) を計算してください。run_python を使用します:\n" + PEERS +
"\nその後、COMPS_JSON マーカーの下に、完全な比較分析表と最小値・第 25 パーセンタイル・中央値・第 75 パーセンタイル・最大値の要約統計を JSON 形式で出力してください。キーは 'table' (行辞書のリスト) と 'stats' (辞書の辞書) です。")
print("\n--- COMPS ナラティブ ---\n", comps_answer[:1500])
m = re.search(r"COMPS_JSON:\s*(\{.*\})", comps_answer, re.S)
if m:
payload = json.loads(m.group(1))
table = pd.DataFrame(payload["table"])
stats = pd.DataFrame(payload["stats"])
xlsx = "outputs/comps_analysis.xlsx"
with pd.ExcelWriter(xlsx, engine="openpyxl") as xl:
table.to_excel(xl, sheet_name="Comps", index=False)
stats.to_excel(xl, sheet_name="Summary Stats")
from openpyxl import load_workbook
from openpyxl.styles import Font, PatternFill
wb = load_workbook(xlsx)
for ws in wb.worksheets:
for cell in ws[1]:
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill("solid", start_color="1F4E79")
for col in ws.columns:
w = max(len(str(c.value)) for c in col if c.value is not None)
ws.column_dimensions[col[0].column_letter].width = w + 3
wb.save(xlsx)
print("Wrote", xlsx)
print(table.to_string(index=False))
except Exception as e:
print("Demo B skipped:", e)
同業他社を合成したデータセットを用意し、企業価値(EV)や EV/売上高倍率、EV/EBITDA 倍率、株価収益率(P/E レシオ)などのバリュエーション指標を算出します。エージェントが出力する構造化された JSON データを解析し、比較対象企業の詳細データと集計統計を含む DataFrame を生成。その後、分析結果を複数シートを持つ Excel ワークブックとして書き出し、プロフェッショナルな見出しフォーマットや自動列幅調整を適用して完成させます。
コードの複製 | 別のブラウザで使用する
print("\n" + "="*76 + "\nDEMO C — IC MEMO (ic-memo skill)\n" + "="*76)
try:
ic_agent = SkillAgent(["ic-memo"])
ic_answer, _ = ic_agent.run(
"Draft a first-round IC memo per the skill for a hypothetical "
"buyout of Meridian Software (see facts below). Base the valuation "
"framing on ~11x EV/EBITDA entry, 45% leverage, 5-yr hold. Use "
"run_python for any quick math (e.g., rough MOIC/IRR math) and "
"save the memo with save_file as ic_memo_meridian.md.\n" + SAMPLE_CO)
print("\n--- IC MEMO (first 1500 chars of reply) ---\n", ic_answer[:1500])
memo_path = "outputs/ic_memo_meridian.md"
if os.path.exists(memo_path):
print(f"\nMemo saved -> {memo_path} "
f"({os.path.getsize(memo_path)} bytes)")
except Exception as e:
print("Demo C skipped:", e)
print("\n" + "="*76 + "\nPART 7 — MANAGED AGENT COOKBOOK (dry run)\n" + "="*76)
yamls = sorted(glob.glob(f"{REPO_DIR}/managed-agent-cookbooks/*/agent.yaml")) \
+ sorted(glob.glob(f"{REPO_DIR}/managed-agent-cookbooks/*/*.yaml"))
if yamls:
path = yamls[0]
print("Inspecting:", path)
try:
spec = yaml.safe_load(open(path))
print(json.dumps(spec, indent=2, default=str)[:2500])
print("\nDeploy flow: resolve file refs -> upload skills -> create "
"leaf-worker subagents -> POST orchestrator to /v1/agents "
"(see scripts/orchestrate.py for the handoff_request event loop).")
except Exception as e:
print("Could not parse yaml:", e)
else:
print("No cookbook yaml found on this branch — see "
"managed-agent-cookbooks/ READMEs on GitHub.")
print("\n" + "="*76)
print("DONE. Artifacts in ./outputs/:", os.listdir("outputs"))
print("""
Where to go next:
- Swap SAMPLE_CO / PEERS for real data via the repo's MCP connectors
(Daloopa, FactSet, S&P, Morningstar, PitchBook...) — subscriptions apply.
- Load other skills: SkillAgent(["lbo"]), ["merger"], ["earnings"],
["rebalance"], ["tlh"], ["kyc"] ... — see sorted(registry.skills).
- Stack skills: SkillAgent(["comps", "dcf"]) for a football-field workflow.
- For production, install as a Cowork plugin or deploy via Managed Agents
instead of this Colab loop — same skills, governed runtime.
All outputs are drafts for qualified human review — not investment advice.
"")
投資委員会メモのスキルを、架空のソフトウェア買収案件に適用し、Python で裏付けとなる収益指標を計算します。生成されたメモは Markdown 形式で保存され、出力ディレクトリにファイルが正しく存在するか検証します。その後、管理型エージェントのクックブックを確認し、デプロイ仕様の表示を行います。最後に、生成された成果物と、本番環境への拡張可能性についてレビューして締めくくります。
このチュートリアルを完了することで、Anthropic の金融サービス向けスキルおよびエージェントフレームワークの実用的な Colab 版を実装できます。同時に、リポジトリが持つ手法駆動型の財務分析アプローチも維持されます。構造化されたスキル発見、動的システムプロンプトの構築、永続的な Python 実行環境、API を介したツールオーケストレーション、そして自動化された成果物生成を、単一の再利用可能なワークフローに統合しました。
また、同一のエージェントアーキテクチャが DCF バリュエーション、トレーディング・コンプ分析、感応度テスト、Excel レポート作成、投資委員会メモの準備など、多様な金融ユースケースに対応できることも示しています。今後はさらにスキルを追加して読み込み、複数のバリュエーションプレイブックを組み合わせ、MCP 連携を通じてライセンスされた金融データプロバイダーへ接続し、Claude Code や Cowork、Managed Agents における統制された本番ランタイムへとチュートリアル用サンドボックスを置き換えることで、システムを拡張できます。
完全なコードはこちらをご覧ください。また、Twitter でフォローしていただくと幸いです。15 万人以上の ML 関連メンバーが参加する Reddit サブコミュニティや、ニュースレターへの登録もぜひご検討ください。Telegram をご利用の方にも、今なら Telegram チャンネルへのご参加が可能です。
GitHub リポジトリの紹介、Hugging Face ページの宣伝、製品リリース、ウェビナーなど、パートナーシップをご希望の場合はご連絡ください。
原文を表示
In this tutorial, we build an advanced workflow around Anthropic’s financial-services repository and reproduce its skill-driven architecture in pure Python. We begin by installing the required libraries, cloning the repository, and programmatically mapping its agents, vertical plugins, partner integrations, managed-agent cookbooks, and financial analysis skills. We then parse the repository’s SKILL.md files into a searchable registry and construct a reusable SkillAgent that injects selected financial playbooks into the Anthropic Messages API while supporting an iterative tool-use loop for Python calculations and file generation. Using this architecture, we execute a synthetic discounted cash flow valuation, generate a WACC and terminal-growth sensitivity heatmap, perform comparable-company analysis with formatted Excel output, draft a private-equity investment committee memo, and inspect a managed-agent deployment specification without sending a live deployment request.
Copy CodeCopiedUse a different Browser
import subprocess, sys, os, io, re, json, glob, textwrap, contextlib, pathlib
def sh(cmd):
print(f"$ {cmd}")
r = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if r.returncode != 0:
print(r.stderr[-1500:])
return r
sh(f"{sys.executable} -m pip install -q anthropic pandas openpyxl pyyaml matplotlib")
import pandas as pd
import yaml
import matplotlib.pyplot as plt
REPO_URL = "https://github.com/anthropics/financial-services.git"
REPO_DIR = "financial-services"
if not os.path.isdir(REPO_DIR):
sh(f"git clone --depth 1 {REPO_URL} {REPO_DIR}")
else:
print("Repo already cloned — skipping.")
def get_api_key():
try:
from google.colab import userdata
k = userdata.get("ANTHROPIC_API_KEY")
if k:
return k
except Exception:
pass
if os.environ.get("ANTHROPIC_API_KEY"):
return os.environ["ANTHROPIC_API_KEY"]
from getpass import getpass
return getpass("Enter your Anthropic API key: ")
os.environ["ANTHROPIC_API_KEY"] = get_api_key()
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-6"
print("SDK ready. Model:", MODEL)
We install the required Python libraries, clone Anthropic’s financial-services repository, and prepare the Google Colab runtime for execution. We retrieve the Anthropic API key from Colab secrets, environment variables, or a secure interactive prompt. We then initialize the official Anthropic SDK and select the Claude model that powers the financial-analysis workflows.
Copy CodeCopiedUse a different Browser
def repo_map(root=REPO_DIR):
rows = []
for kind, pattern in [
("agent", f"{root}/plugins/agent-plugins/*"),
("vertical",f"{root}/plugins/vertical-plugins/*"),
("partner", f"{root}/plugins/partner-built/*"),
("cookbook",f"{root}/managed-agent-cookbooks/*"),
]:
for p in sorted(glob.glob(pattern)):
if not os.path.isdir(p):
continue
skills = glob.glob(f"{p}/**/SKILL.md", recursive=True)
commands = glob.glob(f"{p}/commands/*.md")
rows.append({"type": kind, "name": os.path.basename(p),
"skills": len(skills), "commands": len(commands)})
return pd.DataFrame(rows)
print("\n=== REPO MAP ===")
repo_df = repo_map()
print(repo_df.to_string(index=False))
mcp_files = glob.glob(f"{REPO_DIR}/plugins/**/.mcp.json", recursive=True)
for f in mcp_files[:1]:
print(f"\n=== MCP CONNECTORS ({f}) ===")
try:
cfg = json.load(open(f))
for name, srv in cfg.get("mcpServers", cfg).items():
print(f" {name:<14} -> {srv.get('url', srv)}")
except Exception as e:
print(" (could not parse:", e, ")")
FRONTMATTER = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.S)
class Skill:
def __init__(self, path):
self.path = path
raw = open(path, encoding="utf-8", errors="replace").read()
m = FRONTMATTER.match(raw)
meta = {}
if m:
try:
meta = yaml.safe_load(m.group(1)) or {}
except Exception:
meta = {}
self.name = str(meta.get("name") or pathlib.Path(path).parent.name)
self.description = str(meta.get("description", ""))[:300]
self.body = raw[m.end():] if m else raw
def __repr__(self):
return f"<Skill {self.name}>"
class SkillRegistry:
def __init__(self, root=REPO_DIR):
paths = sorted(glob.glob(f"{root}/plugins/vertical-plugins/**/SKILL.md", recursive=True))
paths += sorted(glob.glob(f"{root}/plugins/**/SKILL.md", recursive=True))
self.skills = {}
for p in paths:
s = Skill(p)
self.skills.setdefault(s.name.lower(), s)
def find(self, query):
q = query.lower()
hits = [s for k, s in self.skills.items() if q in k]
if not hits:
hits = [s for s in self.skills.values() if q in s.description.lower()]
return hits
def get(self, query):
hits = self.find(query)
if not hits:
raise KeyError(f"No skill matching '{query}'. "
f"Available: {sorted(self.skills)[:40]}")
return hits[0]
registry = SkillRegistry()
print(f"\nLoaded {len(registry.skills)} unique skills.")
print("Sample:", sorted(registry.skills)[:12], "...")
We inspect the repository structure and identify its agent plugins, vertical plugins, partner integrations, managed-agent cookbooks, and available commands. We locate MCP configuration files and display the external financial data connectors defined in the repository. We then parse each SKILL.md file, extract its YAML metadata and methodology, and register every unique skill for searchable access.
Copy CodeCopiedUse a different Browser
os.makedirs("outputs", exist_ok=True)
TOOLS = [
{
"name": "run_python",
"description": ("Execute Python code and return stdout. pandas as pd "
"and numpy as np are pre-imported. Use print() to "
"return results. State persists across calls."),
"input_schema": {
"type": "object",
"properties": {"code": {"type": "string"}},
"required": ["code"],
},
},
{
"name": "save_file",
"description": "Save text content to outputs/<filename>.",
"input_schema": {
"type": "object",
"properties": {"filename": {"type": "string"},
"content": {"type": "string"}},
"required": ["filename", "content"],
},
},
]
_PY_NS = {}
def _tool_run_python(code):
import numpy as np
_PY_NS.setdefault("pd", pd); _PY_NS.setdefault("np", np)
buf = io.StringIO()
try:
with contextlib.redirect_stdout(buf):
exec(code, _PY_NS)
out = buf.getvalue()
return out[:6000] if out else "(no stdout — use print())"
except Exception as e:
return f"ERROR: {type(e).__name__}: {e}"
def _tool_save_file(filename, content):
safe = os.path.basename(filename)
path = os.path.join("outputs", safe)
open(path, "w", encoding="utf-8").write(content)
return f"Saved {path} ({len(content)} chars)"
DISPATCH = {"run_python": lambda i: _tool_run_python(i["code"]),
"save_file": lambda i: _tool_save_file(i["filename"], i["content"])}
BASE_SYSTEM = """You are a financial analyst assistant operating with the
skill playbooks provided below (from Anthropic's financial-services repo).
Follow the skill's methodology, conventions, and output format closely.
Use the run_python tool for all numerical work — never do arithmetic in
your head. Use save_file for final deliverables. All work is a DRAFT for
human review; do not present it as investment advice."""
class SkillAgent:
"""Minimal reproduction of Cowork's skill-firing: chosen skills are
concatenated into the system prompt; the agent then runs a standard
tool-use loop against the Messages API until the model stops."""
def __init__(self, skill_queries, max_skill_chars=12000, verbose=True):
self.skills = [registry.get(q) for q in skill_queries]
blocks = []
for s in self.skills:
blocks.append(f"\n\n===== SKILL: {s.name} =====\n"
f"{s.description}\n{s.body[:max_skill_chars]}")
self.system = BASE_SYSTEM + "".join(blocks)
self.verbose = verbose
def run(self, prompt, max_turns=12):
messages = [{"role": "user", "content": prompt}]
for turn in range(max_turns):
resp = client.messages.create(
model=MODEL, max_tokens=8000,
system=self.system, tools=TOOLS, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use":
final = "".join(b.text for b in resp.content
if b.type == "text")
return final, messages
results = []
for block in resp.content:
if block.type == "tool_use":
if self.verbose:
print(f" [turn {turn}] tool: {block.name}")
out = DISPATCHblock.name
results.append({"type": "tool_result",
"tool_use_id": block.id,
"content": str(out)})
messages.append({"role": "user", "content": results})
return "(hit max_turns)", messages
We define tools that allow Claude to execute Python calculations and save generated deliverables inside the Colab environment. We build a persistent Python namespace so numerical models, tables, and intermediate variables remain available across multiple agent turns. We then create the SkillAgent class, inject selected financial playbooks into its system prompt, and manage the Anthropic Messages API tool-use loop.
Copy CodeCopiedUse a different Browser
SAMPLE_CO = """
Target: 'Meridian Software' (synthetic). FY2025 actuals, $mm:
Revenue 850 (grew 18% y/y) | EBITDA margin 27% | D&A 4% of rev
CapEx 5% of rev | NWC change 1% of rev growth | Tax rate 24%
Net debt 320 | Diluted shares 92mm
Assumptions: revenue growth fades 18% -> 6% linearly over 5 yrs,
EBITDA margin expands 100bps total, WACC 9.5%, terminal growth 2.5%.
"""
print("\n" + "="*76 + "\nDEMO A — DCF (dcf-model skill)\n" + "="*76)
try:
dcf_agent = SkillAgent(["dcf"])
dcf_answer, _ = dcf_agent.run(
"Run a 5-year unlevered DCF per the skill playbook on this company:\n"
+ SAMPLE_CO +
"\nCompute enterprise value, equity value, and implied share price "
"with run_python. Then print a WACC (8.5%-10.5%, 50bp steps) x "
"terminal growth (1.5%-3.5%, 50bp steps) sensitivity grid of implied "
"share price as a JSON object under the marker SENS_JSON:, and give "
"a concise summary.")
print("\n--- DCF RESULT ---\n", dcf_answer[:3000])
m = re.search(r"SENS_JSON:\s*(\{.*\})", dcf_answer, re.S)
if m:
grid = json.loads(m.group(1))
sens = pd.DataFrame(grid)
sens = sens.apply(pd.to_numeric, errors="coerce")
fig, ax = plt.subplots(figsize=(7, 4))
im = ax.imshow(sens.values, cmap="RdYlGn", aspect="auto")
ax.set_xticks(range(len(sens.columns)), sens.columns)
ax.set_yticks(range(len(sens.index)), sens.index)
ax.set_xlabel("Terminal growth"); ax.set_ylabel("WACC")
ax.set_title("Implied share price sensitivity ($)")
for i in range(sens.shape[0]):
for j in range(sens.shape[1]):
v = sens.values[i, j]
if pd.notna(v):
ax.text(j, i, f"{v:,.0f}", ha="center", va="center",
fontsize=8)
fig.colorbar(im); plt.tight_layout(); plt.show()
except Exception as e:
print("Demo A skipped:", e)
We provide synthetic operating assumptions and instruct the DCF skill agent to construct a five-year unlevered cash-flow valuation. We use the Python execution tool to calculate enterprise value, equity value, implied share price, and a two-dimensional sensitivity matrix. We then extract the structured sensitivity results and visualize the relationship between WACC, terminal growth, and implied valuation through a heatmap.
Copy CodeCopiedUse a different Browser
PEERS = """
Synthetic peer set ($mm except per-share):
Ticker Price Shares NetDebt Rev_NTM EBITDA_NTM EPS_NTM
ALFA 64.2 210 450 2900 820 3.10
BRVO 28.7 540 -120 4100 980 1.45
CHRL 112.5 95 760 1850 610 5.60
DLTA 41.9 330 210 2600 700 2.05
"""
print("\n" + "="*76 + "\nDEMO B — COMPS (comps-analysis skill) -> Excel\n" + "="*76)
try:
comps_agent = SkillAgent(["comps"])
comps_answer, _ = comps_agent.run(
"Per the comps skill, compute EV, EV/Revenue, EV/EBITDA and P/E "
"(all NTM) for these peers with run_python:\n" + PEERS +
"\nThen print the full comps table plus min/25th/median/75th/max "
"summary stats as JSON under the marker COMPS_JSON: with keys "
"'table' (list of row dicts) and 'stats' (dict of dicts).")
print("\n--- COMPS NARRATIVE ---\n", comps_answer[:1500])
m = re.search(r"COMPS_JSON:\s*(\{.*\})", comps_answer, re.S)
if m:
payload = json.loads(m.group(1))
table = pd.DataFrame(payload["table"])
stats = pd.DataFrame(payload["stats"])
xlsx = "outputs/comps_analysis.xlsx"
with pd.ExcelWriter(xlsx, engine="openpyxl") as xl:
table.to_excel(xl, sheet_name="Comps", index=False)
stats.to_excel(xl, sheet_name="Summary Stats")
from openpyxl import load_workbook
from openpyxl.styles import Font, PatternFill
wb = load_workbook(xlsx)
for ws in wb.worksheets:
for cell in ws[1]:
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill("solid", start_color="1F4E79")
for col in ws.columns:
w = max(len(str(c.value)) for c in col if c.value is not None)
ws.column_dimensions[col[0].column_letter].width = w + 3
wb.save(xlsx)
print("Wrote", xlsx)
print(table.to_string(index=False))
except Exception as e:
print("Demo B skipped:", e)
We supply a synthetic peer set and calculate enterprise value, EV-to-revenue, EV-to-EBITDA, and price-to-earnings multiples. We convert the agent’s structured JSON response into detailed comparable-company and summary-statistics DataFrames. We then export the analysis to a multi-sheet Excel workbook and apply professional header formatting and automatic column sizing.
Copy CodeCopiedUse a different Browser
print("\n" + "="*76 + "\nDEMO C — IC MEMO (ic-memo skill)\n" + "="*76)
try:
ic_agent = SkillAgent(["ic-memo"])
ic_answer, _ = ic_agent.run(
"Draft a first-round IC memo per the skill for a hypothetical "
"buyout of Meridian Software (see facts below). Base the valuation "
"framing on ~11x EV/EBITDA entry, 45% leverage, 5-yr hold. Use "
"run_python for any quick math (e.g., rough MOIC/IRR math) and "
"save the memo with save_file as ic_memo_meridian.md.\n" + SAMPLE_CO)
print("\n--- IC MEMO (first 1500 chars of reply) ---\n", ic_answer[:1500])
memo_path = "outputs/ic_memo_meridian.md"
if os.path.exists(memo_path):
print(f"\nMemo saved -> {memo_path} "
f"({os.path.getsize(memo_path)} bytes)")
except Exception as e:
print("Demo C skipped:", e)
print("\n" + "="*76 + "\nPART 7 — MANAGED AGENT COOKBOOK (dry run)\n" + "="*76)
yamls = sorted(glob.glob(f"{REPO_DIR}/managed-agent-cookbooks/*/agent.yaml")) \
+ sorted(glob.glob(f"{REPO_DIR}/managed-agent-cookbooks/*/*.yaml"))
if yamls:
path = yamls[0]
print("Inspecting:", path)
try:
spec = yaml.safe_load(open(path))
print(json.dumps(spec, indent=2, default=str)[:2500])
print("\nDeploy flow: resolve file refs -> upload skills -> create "
"leaf-worker subagents -> POST orchestrator to /v1/agents "
"(see scripts/orchestrate.py for the handoff_request event loop).")
except Exception as e:
print("Could not parse yaml:", e)
else:
print("No cookbook yaml found on this branch — see "
"managed-agent-cookbooks/ READMEs on GitHub.")
print("\n" + "="*76)
print("DONE. Artifacts in ./outputs/:", os.listdir("outputs"))
print("""
Where to go next:
- Swap SAMPLE_CO / PEERS for real data via the repo's MCP connectors
(Daloopa, FactSet, S&P, Morningstar, PitchBook...) — subscriptions apply.
- Load other skills: SkillAgent(["lbo"]), ["merger"], ["earnings"],
["rebalance"], ["tlh"], ["kyc"] ... — see sorted(registry.skills).
- Stack skills: SkillAgent(["comps", "dcf"]) for a football-field workflow.
- For production, install as a Cowork plugin or deploy via Managed Agents
instead of this Colab loop — same skills, governed runtime.
All outputs are drafts for qualified human review — not investment advice.
""")
We apply the investment-committee memo skill to a hypothetical software buyout and calculate supporting return metrics with Python. We save the resulting memo as a Markdown deliverable and verify that the generated file exists in the output directory. We then inspect a managed-agent cookbook, display the deployment specification, and conclude by reviewing the generated artifacts and possible production extensions.
By completing this tutorial, we implement a practical Colab-based approximation of Anthropic’s financial-services skill and agent framework while preserving the repository’s methodology-driven approach to financial analysis. We combine structured skill discovery, dynamic system-prompt construction, persistent Python execution, API-based tool orchestration, and automated deliverable generation in a single reusable workflow. We also demonstrate how the same agent architecture supports multiple finance use cases, including DCF valuation, trading-comps analysis, sensitivity testing, Excel reporting, and investment committee memo preparation. From here, we extend the system by loading additional skills, combining multiple valuation playbooks, connecting to licensed financial data providers via MCP integrations, and replacing the tutorial sandbox with a governed production runtime in Claude Code, Cowork, or Managed Agents.
Check out the Full Code here. Also, feel free to follow us on Twitter and don’t forget to join our 150k+ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us
The post Designing Skill-Driven Financial Analysis Agents with Claude, Python, MCP Connectors, and Automated Deliverables appeared first on MarkTechPost.
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み