7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コードエージェント smolagents でAIエージェントを始めよう! - AI駆動開発ツール自作編

7
Last updated at Posted at 2025-09-15

このブログは?

オープンソースのAIエージェントフレームワークの smaolagents の特徴や使い方をコード例とその挙動をご紹介しながらの簡単なエージェントを作っていきます。また、エージェントの方式としては 「コードエージェント」 というタスクを処理するために必要なコードをエージェントがその場で生成して実行するタイプを使ってみます。また、 AIモデルとしては OpenAIの gpt-4o、Google の gemini-2.5-pro、OCI Generative AI の LLM(xAI の Grock 4) を使う方法をご紹介しています。

このブログのコードはすべて GitHub で公開しています。リンクはこのブログの最後に貼っておきますのでお役に立てましたら。

このブログの後半で作るエージェントは、smolagents と Gradio(UI) で作った「システム設計支援エージェント」 です。このエージェントは、言葉でシステム要件を伝えてシーケンス図やフローチャートを描いてもらったりDDLを渡して ER図を描くことができます。また、AIアシスタントとして質問に答えることもできます。さらに、MCPを通してデータベースに接続することもできますので、実際のデータベースの中を覗いて ER図を描くこともできます。

プロンプト
スマホアプリから自然言語で問い合わせると会社の社内規定を類似性検索して自然言語で答えてくれるシステムのシーケンス図を描いてください。類似性検索のための具体的な処理や自然言語で回答を生成する処理も詳しく記載してください。
システム設計支援エージェントの成果物例システム設計支援エージェント成果物.png

この例では、いわゆる RAG 技術を使った社内規定のQAシステムのシーケンス図を描いています。

※【まどマギ・ネタバレ注意】
エージェントの頭脳にあたる LLM は物知りなので、こんなフローチャートも描けます!

image.png
https://x.com/yuji_amanogawa/status/1966450242758357074

smolagents とは?

smaolagentsは、Hugging Face が公開している AIエージェント構築のための軽量な Python ライブラリです。

Hugging Face の smolagents のページにはこんな楽しいロゴが載っています。

Hugging Face smolagents

007 の「殺しのライセンス」のパロディですね。

なぜ "Call"なのでしょう?

Hugging Face の公式見解を見たわけではないので想像ですけど、AIエージェントがAIエージェントであるゆえんは、「考えて(必要なアクションを計画して)」、「アクションを実行して」、「アクションの結果を観察して」、「次のアクションを考える」というところにあるわけですが、この「アクション」は、LLM 自体に手足耳目があるわけではないので、LLMの外のツールを呼び出す必要があるというところからエージェントの象徴として選ばれているのだと思います。そして、smolagents は、ツールの呼び出しにコードを使うコードエージェントに重きを置いているところが特徴的です。

このブログは?、再び

このブログの内容(+α)は、Oracle Code Night - Oracle AI Jam Session #28 「これからの生成AI ~ゼロからはじめるAIエージェント」 でデモを交えてご紹介させていただきました。下記にアーカイブもありますので、ぜひお立ち寄りください。

こんな方のお役に立てることを目指しています

  • はじめてAIエージェントのコードを書いてみようと思っている方
  • AIエージェントは知っているけどコードエージェントって何と思われた方

ノーコードでエージェント作りたいんだけどという方へ

AIエージェントとは?

Hugging Face の smolagents のドキュメントにある What are agents? 🤔 などを読むと、「AIエージェント」とは、AIモデル(LLM)が判断・行動を選択し、その過程で外部の情報取得やツール操作を繰り返しながら、目標達成まで自律的に進めるプログラム体系 といった感じの定義になると思います。

平たく言えば、AIモデル(LLM)の出力がプログラムの処理フローに何らかの影響を与えるシステムのことと言えそうです。つまり、AIモデル(LLM)が処理フローを制御する主体であって、その意味では、AIモデルにある種の自律性(エージェンシー)があるとみなせるシステムを AIエージェントと呼ぶといったところでしょうか。

この自律性(エージェンシー)は、Hugging Face によれば 0 か 1 かのデジタルではなく連続したスペクトラムだそうです。日本的感覚ではグラデーションの方が、馴染がありますね。

これは、二元論で「これはエージェントじゃない」とか「これはエージェントだ」と片づけられるものではなく、自律性(エージェンシー)の強さによっていろいろなエージェントがあり得るということですね。要は役に立てばよく、用途にあった自律性(エージェンシー)の強さの仕組みを選べば良いということだと理解しています。

自律性(エージェンシー)のスペクトラム(グラデーション)

自律性(エージェンシー)のレベル 内容 名称
☆☆☆ AIモデル出力がワークフローに関与しない シンプルプロセッサ
★☆☆ AIモデル出力でif/else分岐 ルーター
★★☆ AIモデル出力で関数呼び出し ツールコール
★★☆ AIモデル出力でループ制御 マルチステップエージェント
★★★ エージェント同士を連携 マルチエージェント
★★★ AIモデルが自身のツールを定義したり他のエージェントを起動 コードエージェント

この表は、What are agents? 🤔より引用・和訳しています。

AIエージェントが有効な場面

あらかじめ固定的なワークフローを決められないような複雑なタスクではAIエージェントの利用が有効と言えそうです。if then else 文ですべての条件分岐を書き下すことが不可能か経済的に合理的でないような場合ですね。

AIエージェントが有効な場面
ユーザー要求やタスクの内容が多様・複雑で、人手であらかじめ書き下せる決まった手順や分岐(if・else、ルーティング)だけでは十分に対応できない場合
一般的な決定論的ワークフロー(例:固定分岐、選択肢によるパターン化)が頻繁に「うまく機能しない」・「柔軟性が必要」と感じる場合
例えば、旅行予約アプリで「到着日が遅れるかもしれない」「荷物の預かりと保険が必要」など、一つのリクエストに複数の条件や判断が絡むようなケース
避けるべき場面
タスクやリクエストごとに明確なルール・分岐が決められていて、シンプルな実装で十分に対応できる場合には、AIエージェントは過剰で、従来的なプログラムが推奨される

再び、smolagents とは?

「smolagents は、ツールの呼び出しにコードを使うコードエージェントに重きを置いているところが特徴的です」と書きましたが、そもそもツールの呼び出しにはどのような方式があるのでしょうか?

AIエージェントのツール呼び出しの2つのパラダイム

JSONベースのツール呼び出し(JSONアクション)

ほとんどのエージェントフレームワークで採用されている方式です。LLMの Function Calling(モデルプロバイダーによっては、Tool Use と呼んだり、その中間の Tool Calling と呼んだりします)を使用することが一般的です。

Function Callingへの誤解 Function Callingの真実
ツールを呼ぶのは誰? LLM LLMを呼び出している
アプリケーション
ツールを呼び出す
LLMが返すのは何? ツールの実行結果 ツールを呼び出すための
JSON データ(ツール/
関数の名前や引数と
その値をまとめたもの)
ツール実行結果をLLMへ渡すのは誰? (LLM) アプリケーション

エージェントフレームワークを使う場合はこの Function Calling の詳細は抽象化(隠蔽)されます。

Function Calling
への誤解
Function Calling
の真実
エージェント
フレームワーク
ツールを呼ぶのは誰? LLM LLMを呼び出している
アプリケーション
ツールを呼び出す
エージェント
フレームワーク
LLMが返すのは何? ツールの実行結果 ツールを呼び出すためのJSON データ(ツール/
関数の名前や引数とその値をまとめたもの)
JSONだが通常アプリケーションからは見えない
ツール実行結果をLLMへ渡すのは誰? (LLM) アプリケーション エージェント
フレームワーク

なお、JSONベースの呼び出し(JSONアクション)では、ツールは事前に定義されていて LLM に与えられる必要があります。つまり、アプリケーションコード中にあらかじめ用意されたツールで解決できない課題には対応できません。

MCPサーバーのツールを活用する場合は、MCPサーバーやツールを動的に発見できる仕組み(レジストリなど)を用意することで対応できる課題の幅を広げることは可能です。ただし、そのレジストリにも用意されていない機能は利用できません。

smolagents は、この JSONベースのツール呼び出しには ToolCallingAgent を用意しています。

コードベースのツール呼び出し(コードアクション)

ツールを呼び出したり計算を実行するために、LLMにコードを生成させる方式です。自然言語で指示してプログラムを生成するコード生成は、コードをLLMに書かせることが目的ですが、これはツール呼び出しのための手段としてコードを生成させます。また、計算などはツールがなくてもコードを生成して解決することができます。

JSONベースのツール呼び出しでは、ループや条件文の処理が難しいことがありますが、これらはプログラムコードが得意とするものです。

smolagents は、このコードベースのツール呼び出しを CodeAgents でサポートしています。

JSONベースとコードベースの比較

コードベースの優位性は、論文 arXiv:2402.01030 - Executable Code Actions Elicit Better LLM Agents で詳細に検討されています。

image.png

  • コードベースは、JSONベースに較べて成功率が 最大20%高い
  • コードベースは、JSONベースに較べて課題解決までのターン数が平均30%少なかった

また、クローズドモデルに較べて相対的に性能が劣るオープンソースモデルは、ループや条件文を必要とする複雑なタスクを JSON ベースで達成することが苦手ですが、コードベースによる性能向上が顕著であったとのことです。

JSONベースとコードベースの使いわけ

smolagents のドキュメントでは以下のような使い分けがガイドされています。

  • CodeAgent(コードベース)
    • 推論、チェイン(連鎖)、または動的な組み合わせが必要なとき
    • 組み合わせることができるツール群を使うとき(例 - 解析(パース) + 計算 + 検索)
    • エージェントが問題解決者やプログラマーであるとき
  • ToolCallingAgent(JSON ベース)
    • シンプルでアトミックな(1つで完結している)ツール(例:APIを呼び出す、文書を取得する)を使うとき
    • 高い信頼性と明確な検証を求めるとき
    • エージェントがディスパッチャーやコントローラーのような役割のとき

これを私なりに(使ってみた経験から)ざっくりまとめるとこんな感じになります。

CodeAgent
(コードベース)
ToolCallingAgent
(JSON ベース)
シンプルな処理
ツールの組み合わせなどの複雑な処理
用意されたツールの使用
ツールが用意されていないタスク ×

ツールの組み合わせを CodeAgent が生成するコードの中で解決してくれるのは、LLM の呼び出し回数削減につななって応答が早くなるので UX 向上に役立ちます。

また、最後の「ツールが用意されていないタスク」というのがとても便利です。例えば、REST API で呼び出すサービスなどは、エンドポイントの URL とクエリパラメータを教えておけば、API呼び出しのコードを書かなくても呼び出してくれます。

smolagent を使ってみる

モデル統合

smolagents の頭脳となる AIモデルは様々なものが利用可能です。

モデル統合の種類 説明
openai OpenAI の GPT-4o、GPT-5など
transformers Hugging Face Transformers モデル
vllm 推論エンジンとして VLLM を利用するモデル
mlx-lm Apple のハードウェア特化の推論エンジン MLX-LM を利用するモデル
bedrock Amazon Bedrockのモデル
litellm 様々な推論サービスに統一したインターフェースでアクセスを提供する LiteLLM を使用。OpenAI、Anthropic、Cohere、Azure OpenAI、Vertex AI、Amazon Bedrock、Mistral AI、LM Studio、Ollama、Oracle Cloud Infrastructure (OCI) などをサポート

このブログでは、litellm を利用しますので、smolagents が提供する LiteLLMModel を使います。

AIモデルの初期化(OpenAI gpt-4o の例)

OpenAI gpt-4o の初期化(LiteLLM使用)
import os
from dotenv import load_dotenv
from smolagents import CodeAgent, LiteLLMModel

openai_api_key = os.getenv("OPENAI_API_KEY")

_= load_dotenv()

model = LiteLLMModel(model_id="gpt-4o", api_key=openai_api_key, temperature=0.0)

OpenAI の APIキーは、.env ファイルに定義しておく例となっています。

AIモデルの初期化(Google gemini-2.5-pro の例)

Google gemini-2.5-pro の初期化(LiteLLM使用)
import os
from dotenv import load_dotenv
from smolagents import CodeAgent, LiteLLMModel

google_api_key = os.getenv("GOOGLE_API_KEY")

_= load_dotenv()

model = LiteLLMModel(model_id="gemini/gemini-2.5-pro", api_key=google_api_key, temperature=0.0)

Google の APIキーは、.env ファイルに定義しておく例となっています。

AIモデルの初期化(OCI xAI Grok 4 の例)

OCI xAI Grok 4 の初期化(LiteLLM使用)
import os
from dotenv import load_dotenv
from smolagents import CodeAgent, LiteLLMModel

_= load_dotenv()
oci_user = os.getenv("OCI_USER")
oci_fingerprint = os.getenv("OCI_FINGERPRINT")
oci_tenancy = os.getenv("OCI_TENANCY")
oci_region = os.getenv("OCI_REGION")
oci_key = os.getenv("OCI_KEY")
oci_compartment_id = os.getenv("OCI_COMPARTMENT_ID")

model = LiteLLMModel(
    model_id="oci/xai.grok-4",
    oci_region=os.getenv("OCI_REGION"),                    # 例: "us-chicago-1"
    oci_user=os.getenv("OCI_USER"),                        # OCI User OCID
    oci_fingerprint=os.getenv("OCI_FINGERPRINT"),          # RSA key fingerprint
    oci_tenancy=os.getenv("OCI_TENANCY"),                  # Tenancy OCID
    oci_key=os.getenv("OCI_KEY"),                          # Private key content
    oci_compartment_id=os.getenv("OCI_COMPARTMENT_ID"),    # Compartment OCID
    temperature=0.0,
    max_tokens= 10000,
    drop_params=True
)

OCIの認証関連のパラメータは、.env ファイルに定義しておく例となっています。
OCI関連の設定ポイント

  • 認証方法 OCI Generative AI との認証には signing key のみ使用可能です(インスタンスプリンシパル等は使用できません。これは、LiteLLM の OCI統合の制約です)
  • oci_useroci_tenancy ユーザー名やテナンシー名ではなく、必ず OCID を指定してください
  • oci_key キーファイルのパスではなく、秘密鍵の内容そのものを記述します。各行末に \n を付けて一行に連結してください
  • drop_params: true OCI Generative AI がサポートしていないパラメータ(stream_options など)を LiteLLM 側で自動的に削除します。これを設定しないと Exception: param stream_options is not supported などといったエラーが発生します
  • master_key LiteLLM Proxy へのアクセスに使用する API キーです。任意の文字列を設定できます

drop_params パラメータについての補足
AIサービスプロバイダーの API よって利用可能なパラメータが異なることがあります。そのため多くのAIプロバイダーをサポートするフレームワークやアプリケーションは、特定の AIサービスプロバイダーや LLM ではサポートされないパラメータを設定してしまうことがあります。
このようなパラメータが設定されていると多くの AIサービスプロバイダーのAPIはリクエストを拒否してしまいます。これを防ぐために LiteLLM にはdrop_params: true というパラメータがあります。これを設定することで、LiteLLM がこれらの非対応パラメータを自動的に削除し、エラーを回避します。
ただし、smolagents が設定したパラメータが OCI Generative AI サービス自体では利用可能なパラメータであっても、特定のモデルがサポートしていない場合、そのようなパラメータは、LiteLLM のチェックをすり抜けて OCI 側でエラーとなります(参考 smolagents issus #1765)。

Tips -smolagents における grok-3-mini, grok-3-mini-fast, grok-4, grok-code-fast-1 の利用について
2025/11/14 23:15 更新
v1.22.0 がリリースされこの問題は修正されました!

(2025/9/18 12:00 更新)
現時点(2025/9/16) においては、PyPI からインストールした smolagents 1.21.2 では、これらのモデルは動作しません。
私の方で issue #1765 を上げています。
既に PR #1766で改善策も提案されていますが、まだ、マージされていません。
この issue が解決されるまでは、以下の対応をすることで xAI Grok 3, Grok 3 mini,Grok 4 を利用可能となります。

最新の reasoning model(grok-3-mini, grok-3-mini-fast, grok-4, grok-code-fast-1)に対応していなかった問題は、smolagents ver 1.22.0.dev0 で、PR #1766 がマージされて issue #1765 が解決しました。
PyPI にある正式リリース版(2025/9/18時点では、1.21.3)にはまだ反映されていませんので、smolagents を下記の手順で GitHub から直接インストールすることで、xAI grok-3-mini, grok-3-mini-fast, grok-4, grok-code-fast-1 の利用が可能となります

smolagents を GItHub から直接インストールする手順

GitHub から直接インストール
pip install -U "smolagents[litellm,toolkit,gradio,mcp]@git+https://github.com/huggingface/smolagents.git"
インストールされたバージョンの確認例
$ pip show smolagents
Name: smolagents
Version: 1.22.0.dev0
Location: H:\lab\smolagents\mermaid-diagram-agent\.venv\Lib\site-packages
Requires: huggingface-hub, jinja2, pillow, python-dotenv, requests, rich
Required-by:

Versions が 1.22.0.dev0 以上となっていれば OK です。

CodeAgent(Toolなし)

【ソースコード(100_simple_codeagent.py)】

最初は、エージェントにツールを与えず数学的な問題を解かせる例を考えてみます。JSONベース(Function Callingベース)のエージェントは、このような問題をツールなしに解くことはできないのですが、コードベースであれば可能です。

CodeAgent(Toolなし)の初期化

CodeAgent(Toolなし)の初期化
agent = CodeAgent(tools=[], model=model)
  • ツールは渡さないので、tools は空です
  • model には、先程、LiteLLMModel として初期化した model(OCIの xAI Grok 4) を設定します

CodeAgent(Toolなし)の実行

実際にエージェントを実行する部分です。

CodeAgent(Toolなし)の実行
agent.run("1,1,2,3,5,8,...と続き10項めが55となる数列の128項めを求めてください。")

エージェントへの指示(プロンプト)を run関数の引数に設定して、起動します。
ここでは、1,1,2,3,5,8,...と続き10項めが55となる数列の 128番目の項の値を求めるように指示しています。

「1,1,2,3,5,8,...と続き10項めが55となる数列」は、TVアニメ『サイレント・ウィッチ 沈黙の魔女の隠しごと』の挿入歌『サムおじさんの豚』に登場する数列です。
YouTube アニプレックス チャンネル - 【1時間耐久】サムおじさんの豚|TVアニメ『サイレント・ウィッチ 沈黙の魔女の隠しごと』

CodeAgent(Toolなし)のコード例の全体と実行結果の例

CodeAgent(Toolなし)のコード例の全体
100_simple_codeagent.py
import os
from dotenv import load_dotenv
from smolagents import CodeAgent, LiteLLMModel

_= load_dotenv()
oci_user = os.getenv("OCI_USER")
oci_fingerprint = os.getenv("OCI_FINGERPRINT")
oci_tenancy = os.getenv("OCI_TENANCY")
oci_region = os.getenv("OCI_REGION")
oci_key = os.getenv("OCI_KEY")
oci_compartment_id = os.getenv("OCI_COMPARTMENT_ID")

model = LiteLLMModel(
    model_id="oci/xai.grok-4",
    oci_region=os.getenv("OCI_REGION"),                    # 例: "us-chicago-1"
    oci_user=os.getenv("OCI_USER"),                        # OCI User OCID
    oci_fingerprint=os.getenv("OCI_FINGERPRINT"),          # RSA key fingerprint
    oci_tenancy=os.getenv("OCI_TENANCY"),                  # Tenancy OCID
    oci_key=os.getenv("OCI_KEY"),                          # Private key content
    oci_compartment_id=os.getenv("OCI_COMPARTMENT_ID"),    # Compartment OCID
    temperature=0.0,
    max_tokens= 10000,
    drop_params=True
)

agent = CodeAgent(tools=[], model=model)

agent.run("1,1,2,3,5,8,...と続き10項めが55となる数列の128項めを求めてください。")
実行結果例
(.venv) LOCAL:mermaid-diagram-agent $ uv run 100_simple_codeagent.py 
╭──────────────────────────────────── New run ─────────────────────────────────────╮
│                                                                                  │
│ 1,1,2,3,5,8,...と続き10項めが55となる数列の128項めを求めてください。                 │
│                                                                                  │
╰─ LiteLLMModel - oci/xai.grok-4 ──────────────────────────────────────────────────╯
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ─ Executing parsed code: ───────────────────────────────────────────────────────── 
  a, b = 1, 1                                                                       
  for i in range(3, 129):                                                           
      a, b = b, a + b                                                               
  final_answer(b)                                                                   
 ────────────────────────────────────────────────────────────────────────────────── 
Final answer: 251728825683549488150424261
[Step 1: Duration 11.69 seconds| Input tokens: 2,547 | Output tokens: 104]
  • 「New Run」 にはユーザーが与えた指示(プロンプト)が表示されています
  • 「Step 1」には、CodeAgent がユーザーの指示(プロンプト)を解決するために生成した Python コードが表示されています
  • 「Final Answer」には、ユーザーが与えた指示(プロンプト)に対するエージェントの応答が表示
    されています
  • 最後の行には、実行に要した時間と AIモデルへの入力トークン数、出力トークン数が表示されています

この例では、「1,1,2,3,5,8,...と続き10項めが55となる数列の128項めを求めてください。」という指示に答えるために CodeAgent が生成したPythonコードの実行が成功して、正しい答え "251728825683549488150424261" が出力されています。

生成したコードに間違いがあった場合は、CodeAgent はエラーメッセージから間違いを特定してコードを修正して再実行します。

この例では、指示文中の数列がフィボナッチ数列であるという情報や、フィボナッチ数列を計算するツール(関数)はエージェントに与えていません。

AIモデル(Grok 4)が、"1,1,2,3,5,8,...と続き10項めが55となる数列"はフィボナッチ数列であると判定し、さらに、CodeAgent が(Grok 4 を使って)フィボナッチ数列を計算するコードを生成して問題を解決しています

CodeAgent(Toolなしで Webアクセス)

【ソースコード(200_web_access_codeagent.py)】

今度は、Webページにアクセスしてコンテンツを取得する処理をツールなしで実行できるエージェントです。JSONベース(Function Callingベース)のエージェントは、HTTPリクエストを発行するツールなしにWebページにアクセスすることはできないのですが、コードベースであれば可能です。

CodeAgent(Toolなしで Webアクセス)の初期化

CodeAgent(Toolなしで Webアクセス)の初期化
agent = CodeAgent(tools=[],
    model=model,
    additional_authorized_imports=['requests', 'bs4']
)

additional_authorized_imports=['requests', 'bs4'] というパラメータを追加しました。
additional_authorized_imports は、CodeAgent が生成する Pythonコードが使用できるライブラリを追加するオプションです。今回は、Webアクセスの際に利用しそうな requestsbs4 を追加しています。

CodeAgent(Toolなしで Webアクセス)の実行

CodeAgent(Toolなしで Webアクセス)の実行
agent.run("https://www.oracle.com/jp/news/announcement/oracle-to-offer-google-gemini-models-to-customers-2025-08-14/ のタイトルは?")

今回は、オラクルの日本語ブログの1つのタイトルを調べるように指示しています。

CodeAgent(Toolなしで Webアクセス)のコード例の全体と実行結果の例

CodeAgent(Toolなしで Webアクセス)のコード例の全体
200_web_access_codeagent.py
import os
from dotenv import load_dotenv
from smolagents import CodeAgent, LiteLLMModel

_= load_dotenv()
oci_user = os.getenv("OCI_USER")
oci_fingerprint = os.getenv("OCI_FINGERPRINT")
oci_tenancy = os.getenv("OCI_TENANCY")
oci_region = os.getenv("OCI_REGION")
oci_key = os.getenv("OCI_KEY")
oci_compartment_id = os.getenv("OCI_COMPARTMENT_ID")

model = LiteLLMModel(
    model_id="oci/xai.grok-4",
    oci_region=os.getenv("OCI_REGION"),                    # 例: "us-chicago-1"
    oci_user=os.getenv("OCI_USER"),                        # OCI User OCID
    oci_fingerprint=os.getenv("OCI_FINGERPRINT"),          # RSA key fingerprint
    oci_tenancy=os.getenv("OCI_TENANCY"),                  # Tenancy OCID
    oci_key=os.getenv("OCI_KEY"),                          # Private key content
    oci_compartment_id=os.getenv("OCI_COMPARTMENT_ID"),    # Compartment OCID
    temperature=0.0,
    max_tokens= 10000,
    drop_params=True
)

agent = CodeAgent(tools=[],
    model=model,
    additional_authorized_imports=['requests', 'bs4']
)

agent.run("https://www.oracle.com/jp/news/announcement/oracle-to-offer-google-gemini-models-to-customers-2025-08-14/ のタイトルは?")
実行結果例
(.venv) LOCAL:mermaid-diagram-agent $ uv run 200_web_access_codeagent.py 
╭──────────────────────────────────── New run ─────────────────────────────────────╮
│                                                                                  │
│ https://www.oracle.com/jp/news/announcement/oracle-to-offer-google-gemini-models │
│ -to-customers-2025-08-14/ のタイトルは?                                          │
│                                                                                  │
╰─ LiteLLMModel - oci/xai.grok-4 ──────────────────────────────────────────────────╯
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ─ Executing parsed code: ───────────────────────────────────────────────────────── 
  import requests                                                                   
  from bs4 import BeautifulSoup                                                     
                                                                                    
  url =                                                                             
  "https://www.oracle.com/jp/news/announcement/oracle-to-offer-google-gemini-model  
  s-to-customers-2025-08-14/"                                                       
  response = requests.get(url)                                                      
  soup = BeautifulSoup(response.text, 'html.parser')                                
  title = soup.title.string.strip() if soup.title else "No title found"             
  final_answer(title)                                                               
 ────────────────────────────────────────────────────────────────────────────────── 
Final answer: 
オラクル、GoogleのGeminiモデルをお客様に提供し、企業のエージェント型AI導入を加速 |  
Oracle 日本
[Step 1: Duration 10.16 seconds| Input tokens: 2,558 | Output tokens: 145]
  • 「New Run」 にはユーザーが与えた指示(プロンプト)が表示されています。今回は、"https://www.oracle.com/jp/news/announcement/oracle-to-offer-google-gemini-models
    │ -to-customers-2025-08-14/ のタイトルは?"です

  • 「Step 1」には、CodeAgent がユーザーの指示(プロンプト)を解決するために生成した Python コードが表示されています

    CodeAgentが生成したコード
    import requests                                                                   
    from bs4 import BeautifulSoup                                                     
                                                                                    
    url =                                                                             
    "https://www.oracle.com/jp/news/announcement/oracle-to-offer-google-gemini-model  
    s-to-customers-2025-08-14/"                                                       
    response = requests.get(url)                                                      
    soup = BeautifulSoup(response.text, 'html.parser')                                
    title = soup.title.string.strip() if soup.title else "No title found"             
    final_answer(title) 
    

    指定したウェブページのコンテンツをrequestsgetメソッドで取得して、BeautifulSoupを使ってパースしてタイトルを取得しています。

  • 「Final Answer」には、ユーザーが与えた指示(プロンプト)に対するエージェントの応答が表示
    されています。今回は、"オラクル、GoogleのGeminiモデルをお客様に提供し、企業のエージェント型AI導入を加速 | Oracle 日本"です

  • 最後の行には、実行に要した時間と AIモデルへの入力トークン数、出力トークン数が表示されています

この例でもツールのコード自体は与えていませんが、CodeAgent がブログにアクセスするための
適切なコードを生成して実行して、正しい答え "オラクル、GoogleのGeminiモデルをお客様に提供し、企業のエージェント型AI導入を加速 | Oracle 日本" が出力されています。

CodeAgent(Toolなしで WebAPI)

【ソースコード(300_web_api_codeagent.py)】

ここでは、ある地域の天気予報を地名だけで取得するエージェントを作ります。
今回も、エージェントに WebAPIへアクセスツールは渡しません。プロンプトの中に「地名から緯度経度を取得できる WebAPI」、「緯度経度から天気予報を取得できる WebAPI」のエンドポイントとクエリーパラメータの説明だけを与えます。この場合も、JSONベース(Function Calling ベース)のエージェントでは、これらの WebAPI を起動して結果を返すツールが必要です。コードベースでは、こうしたツールを与えなくてエージェントが必要なコードを生成、実行して課題を解決できるところを見てみましょう。

CodeAgent(Toolなしで WebAPI)の初期化

この部分は先程のCodeAgent(Toolなしで Webアクセス)と同じです。

CodeAgent(Toolなしで WebAPI)の初期化
agent = CodeAgent(tools=[],
    model=model,
    additional_authorized_imports=['requests', 'bs4']
)

CodeAgent(Toolなしで WebAPI)の実行

今回はプロンプトの中にある地域の天気予報を取得して欲しいという指示と、 2つの WebAPI のエンドポイントの URL とクエリーパラメータの説明を記載します。

CodeAgent(Toolなしで WebAPI)の実行
agent.run("以下のREST APIでqパラメータに指定した地名、もしくは住所の緯度・経度を取得できます。緯度経度取得REST API URL: https://msearch.gsi.go.jp/address-search/AddressSearch?q=地名または住所。複数の候補地の情報が返されるので注意してください。先頭が正しいとは限りません。また、次のREST API で緯度経度から天気予報を取得できます。天気予報取得REST API URL: https://api.open-meteo.com/v1/forecast?latitude=緯度&longitude=経度&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset&hourly=temperature_2m,relative_humidity_2m,weather_code&timezone=Asia%2FTokyo&forecast_days=2 。東京ディズニーランドの明日の天気はレジャー日和ですか?その理由は?")

ちょっと見にくいのでプロンプトだけ取り出してみます。

プロンプト
以下のREST APIでqパラメータに指定した地名、もしくは住所の緯度・経度を取得できます。
緯度経度取得REST API URL: https://msearch.gsi.go.jp/address-search/AddressSearch?q=地名または住所。
複数の候補地の情報が返されるので注意してください。先頭が正しいとは限りません
また、次のREST API で緯度経度から天気予報を取得できます。
天気予報取得REST API URL: https://api.open-meteo.com/v1/forecast?latitude=緯度&longitude=経度&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset&hourly=temperature_2m,relative_humidity_2m,weather_code&timezone=Asia%2FTokyo&forecast_days=2
東京ディズニーランドの明日の天気はレジャー日和ですか?その理由は?

この例では、地名や住所から緯度・経度を取得できる国土地理院の API と緯度・経度からその地域の天気予報を取得できる Open Meteo の API の情報を渡して、東京ディズニーランドの明日の天気を取得、レジャー日和かどうか判定することを求めています。

国土地理院API実行例
https://msearch.gsi.go.jp/address-search/AddressSearch?q=東京ディズニーランド

Open Meteo

CodeAgent(Toolなしで WebAPI)のコード例の全体と実行結果の例

CodeAgent(Toolなしで WebAPI)のコード例の全体
300_web_api_codeagent.py
import os
from dotenv import load_dotenv
from smolagents import CodeAgent, LiteLLMModel

_= load_dotenv()
oci_user = os.getenv("OCI_USER")
oci_fingerprint = os.getenv("OCI_FINGERPRINT")
oci_tenancy = os.getenv("OCI_TENANCY")
oci_region = os.getenv("OCI_REGION")
oci_key = os.getenv("OCI_KEY")
oci_compartment_id = os.getenv("OCI_COMPARTMENT_ID")

model = LiteLLMModel(
    model_id="oci/xai.grok-4",
    oci_region=os.getenv("OCI_REGION"),                    # 例: "us-chicago-1"
    oci_user=os.getenv("OCI_USER"),                        # OCI User OCID
    oci_fingerprint=os.getenv("OCI_FINGERPRINT"),          # RSA key fingerprint
    oci_tenancy=os.getenv("OCI_TENANCY"),                  # Tenancy OCID
    oci_key=os.getenv("OCI_KEY"),                          # Private key content
    oci_compartment_id=os.getenv("OCI_COMPARTMENT_ID"),    # Compartment OCID
    temperature=0.0,
    max_tokens= 10000,
    drop_params=True
)

agent = CodeAgent(tools=[],
    model=model,
    additional_authorized_imports=['requests', 'bs4']
)

agent.run("以下のREST APIでqパラメータに指定した地名、もしくは住所の緯度・経度を取得できます。緯度経度取得REST API URL: https://msearch.gsi.go.jp/address-search/AddressSearch?q=地名または住所。複数の候補地の情報が返されるので注意してください。先頭が正しいとは限りません。また、次のREST API で緯度経度から天気予報を取得できます。天気予報取得REST API URL: https://api.open-meteo.com/v1/forecast?latitude=緯度&longitude=経度&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset&hourly=temperature_2m,relative_humidity_2m,weather_code&timezone=Asia%2FTokyo&forecast_days=2 。東京ディズニーランドの明日の天気はレジャー日和ですか?その理由は?")

この例の実行結果は長いので折りたたんでおきます。

CodeAgent(Toolなしで WebAPI)の実行結果例
実行結果例
(.venv) LOCAL:mermaid-diagram-agent $ uv run 300_web_api_codeagent.py 
╭──────────────────────────────────── New run ─────────────────────────────────────╮
│                                                                                  │
│ 以下のREST                                                                       │
│ APIでqパラメータに指定した地名、もしくは住所の緯度・経度を取得できます。緯度経度 │
│ 取得REST API URL:                                                                │
│ https://msearch.gsi.go.jp/address-search/AddressSearch?q=地名または住所。複数の  │
│ 候補地の情報が返されるので注意してください。先頭が正しいとは限りません。また、次 │
│ のREST API で緯度経度から天気予報を取得できます。天気予報取得REST API URL:       │
│ https://api.open-meteo.com/v1/forecast?latitude=緯度&longitude=経度&daily=weathe │
│ r_code,temperature_2m_max,temperature_2m_min,sunrise,sunset&hourly=temperature_2 │
│ m,relative_humidity_2m,weather_code&timezone=Asia%2FTokyo&forecast_days=2        │
│ 。東京ディズニーランドの明日の天気はレジャー日和ですか?その理由は?             │
│                                                                                  │
╰─ LiteLLMModel - oci/xai.grok-4 ──────────────────────────────────────────────────╯
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ─ Executing parsed code: ───────────────────────────────────────────────────────── 
  import requests                                                                   
  import json                                                                       
                                                                                    
  # 住所検索APIを呼び出し                                                           
  address_query = "東京ディズニーランド"                                            
  address_url =                                                                     
  f"https://msearch.gsi.go.jp/address-search/AddressSearch?q={address_query}"       
  address_response = requests.get(address_url)                                      
  address_data = address_response.json()                                            
                                                                                    
  # 候補を表示して確認(printでObservationに残す)                                  
  print("Address search results:")                                                  
  for feature in address_data:                                                      
      title = feature['properties']['title']                                        
      lon, lat = feature['geometry']['coordinates']                                 
      print(f"Title: {title}, Lat: {lat}, Lon: {lon}")                              
                                                                                    
  # 適切なものを選ぶ: 浦安市のものを仮定(インデックスで選ぶか、文字列でフィルタ)  
  #                                                                                 
  仮に最初のものを選ぶが、実際には内容を確認(Observationで確認後、次ステップで調   
  整)                                                                              
  selected_lat = address_data[0]['geometry']['coordinates'][1]                      
  selected_lon = address_data[0]['geometry']['coordinates'][0]                      
  print(f"Selected Lat: {selected_lat}, Lon: {selected_lon}")                       
 ────────────────────────────────────────────────────────────────────────────────── 
Warning to user: Code execution failed due to an unauthorized import - Consider     
passing said import under `additional_authorized_imports` when initializing your    
CodeAgent.
Code execution failed at line 'import json' due to: InterpreterError: Import of json
is not allowed. Authorized imports are: ['statistics', 'collections', 'queue', 're',
'stat', 'itertools', 'math', 'random', 'bs4', 'requests', 'datetime', 'unicodedata',
'time'\]
[Step 1: Duration 18.59 seconds| Input tokens: 2,722 | Output tokens: 417]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 2 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ─ Executing parsed code: ───────────────────────────────────────────────────────── 
  import requests                                                                   
                                                                                    
  # Call address search API                                                         
  address_query = "東京ディズニーランド"                                            
  address_url =                                                                     
  f"https://msearch.gsi.go.jp/address-search/AddressSearch?q={address_query}"       
  address_response = requests.get(address_url)                                      
  address_data = address_response.json()                                            
                                                                                    
  # Print results for observation                                                   
  print("Address search results:")                                                  
  for feature in address_data:                                                      
      title = feature['properties']['title']                                        
      coordinates = feature['geometry']['coordinates']                              
      lon = coordinates[0]                                                          
      lat = coordinates[1]                                                          
      print(f"Title: {title}, Lat: {lat}, Lon: {lon}")                              
                                                                                    
  # Temporarily select the first one; will adjust based on observation              
  selected_lat = address_data[0]['geometry']['coordinates'][1]                      
  selected_lon = address_data[0]['geometry']['coordinates'][0]                      
  print(f"Selected Lat: {selected_lat}, Lon: {selected_lon}")                       
 ────────────────────────────────────────────────────────────────────────────────── 
Execution logs:
Address search results:
Title: 北海道札幌市東区, Lat: 43.076111, Lon: 141.363617
Title: 北海道東神楽町東, Lat: 43.681801, Lon: 142.432129
Title: 北海道佐呂間町東, Lat: 43.999634, Lon: 143.788956
Title: 北海道湧別町東, Lat: 44.20536, Lon: 143.65062
Title: 北海道長沼町東, Lat: 42.987072, Lon: 141.742905
Title: 岩手県花巻市東, Lat: 39.366413, Lon: 141.147751
Title: 宮城県大河原町東, Lat: 38.059513, Lon: 140.733246
Title: 秋田県大館市東, Lat: 40.277309, Lon: 140.599869
Title: 秋田県大潟村東, Lat: 40.014755, Lon: 139.963943
Title: 山形県米沢市東, Lat: 37.902912, Lon: 140.127655
Title: 福島県南会津町東, Lat: 37.210133, Lon: 139.55574
Title: 茨城県つくば市東, Lat: 36.061123, Lon: 140.13031
Title: 茨城県古河市東, Lat: 36.196213, Lon: 139.7117
Title: 茨城県取手市東, Lat: 35.889046, Lon: 140.067261
Title: 群馬県桐生市東, Lat: 36.414379, Lon: 139.34668
Title: 埼玉県羽生市東, Lat: 36.172943, Lon: 139.544525
Title: 埼玉県桶川市東, Lat: 36.000023, Lon: 139.568832
Title: 埼玉県幸手市東, Lat: 36.068676, Lon: 139.722061
Title: 埼玉県鴻巣市東, Lat: 36.063168, Lon: 139.515594
Title: 埼玉県新座市東, Lat: 35.809963, Lon: 139.570267
Title: 埼玉県宮代町東, Lat: 36.002327, Lon: 139.731552
Title: 埼玉県蓮田市東, Lat: 35.983562, Lon: 139.655289
Title: 千葉県鴨川市東, Lat: 35.100498, Lon: 140.037018
Title: 千葉県柏市東, Lat: 35.857388, Lon: 139.97847
Title: 東京都国立市東, Lat: 35.697777, Lon: 139.449249
Title: 東京都渋谷区東, Lat: 35.655392, Lon: 139.708405
Title: 新潟県新潟市東区, Lat: 37.924778, Lon: 139.092606
Title: 富山県砺波市東, Lat: 36.659134, Lon: 137.028915
Title: 山梨県山梨市東, Lat: 35.714169, Lon: 138.696899
Title: 長野県王滝村東, Lat: 35.811264, Lon: 137.55545
Title: 静岡県静岡市葵区東, Lat: 35.028336, Lon: 138.400177
Title: 愛知県小牧市東, Lat: 35.284931, Lon: 136.94046
Title: 愛知県名古屋市東区, Lat: 35.179325, Lon: 136.926056
Title: 大阪府貝塚市東, Lat: 34.445225, Lon: 135.362701
Title: 大阪府堺市東区, Lat: 34.538174, Lon: 135.536514
Title: 鳥取県八頭町東, Lat: 35.360226, Lon: 134.328415
Title: 岡山県岡山市東区, Lat: 34.651375, Lon: 134.029465
Title: 広島県海田町東, Lat: 34.365276, Lon: 132.555328
Title: 広島県広島市東区, Lat: 34.395329, Lon: 132.482483
Title: 福岡県糸島市東, Lat: 33.5308, Lon: 130.186356
Title: 福岡県福岡市東区, Lat: 33.617779, Lon: 130.417496
Title: 熊本県熊本市東区, Lat: 32.780514, Lon: 130.768082
Title: 沖縄県沖縄市東, Lat: 26.342495, Lon: 127.820938
Title: 沖縄県国頭郡東村, Lat: 26.633383, Lon: 128.15683
Title: 沖縄県本部町東, Lat: 26.657301, Lon: 127.900856
Title: 沖縄県粟国村東, Lat: 26.589865, Lon: 127.231392
Title: 沖縄県渡名喜村東, Lat: 26.370394, Lon: 127.146133
Title: 沖縄県東, Lat: 26.633383, Lon: 128.15683
Title: 東京ディズニーランド, Lat: 35.6352851388889, Lon: 139.880688527778
Title: 東京ディズニーランド駅, Lat: 35.6359115619444, Lon: 139.878678858889
Selected Lat: 43.076111, Lon: 141.363617

Out: None
[Step 2: Duration 11.23 seconds| Input tokens: 6,274 | Output tokens: 726]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 3 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ─ Executing parsed code: ───────────────────────────────────────────────────────── 
  # Filter address_data for the correct entry                                       
  selected_feature = None                                                           
  for feature in address_data:                                                      
      if feature['properties']['title'] == "東京ディズニーランド":                  
          selected_feature = feature                                                
          break                                                                     
                                                                                    
  if selected_feature:                                                              
      selected_lon = selected_feature['geometry']['coordinates'][0]                 
      selected_lat = selected_feature['geometry']['coordinates'][1]                 
      print(f"Correct selected Lat: {selected_lat}, Lon: {selected_lon}")           
  else:                                                                             
      print("No matching feature found")                                            
                                                                                    
  # Call weather API                                                                
  weather_url =                                                                     
  f"https://api.open-meteo.com/v1/forecast?latitude={selected_lat}&longitude={sele  
  cted_lon}&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunse  
  t&hourly=temperature_2m,relative_humidity_2m,weather_code&timezone=Asia%2FTokyo&  
  forecast_days=2"                                                                  
  weather_response = requests.get(weather_url)                                      
  weather_data = weather_response.json()                                            
                                                                                    
  # Print key parts for observation: tomorrow's daily data (index 1)                
  if 'daily' in weather_data:                                                       
      tomorrow = 1  # Index for tomorrow                                            
      print("Tomorrow's weather code:",                                             
  weather_data['daily']['weather_code'][tomorrow])                                  
      print("Tomorrow's max temp:",                                                 
  weather_data['daily']['temperature_2m_max'][tomorrow])                            
      print("Tomorrow's min temp:",                                                 
  weather_data['daily']['temperature_2m_min'][tomorrow])                            
      print("Tomorrow's sunrise:", weather_data['daily']['sunrise'][tomorrow])      
      print("Tomorrow's sunset:", weather_data['daily']['sunset'][tomorrow])        
                                                                                    
  # Also print some hourly data summary if needed, but will analyze in next step    
 ────────────────────────────────────────────────────────────────────────────────── 
Execution logs:
Correct selected Lat: 35.6352851388889, Lon: 139.880688527778
Tomorrow's weather code: 51
Tomorrow's max temp: 28.6
Tomorrow's min temp: 24.9
Tomorrow's sunrise: 2025-09-15T05:23
Tomorrow's sunset: 2025-09-15T17:48

Out: 1
[Step 3: Duration 17.59 seconds| Input tokens: 11,790 | Output tokens: 1,269]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 4 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ─ Executing parsed code: ───────────────────────────────────────────────────────── 
  is_suitable = "いいえ"                                                            
  reason =                                                                          
  "天気コード51(軽い霧雨)が示すように、明日は軽い雨の可能性があり、屋外レジャー   
  には適さない可能性が高いです。ただし、気温は最高28.6℃、最低24.9℃と快適です。"     
  final_answer(f"{is_suitable}。理由: {reason}")                                    
 ────────────────────────────────────────────────────────────────────────────────── 
Final answer: いいえ。理由: 
天気コード51(軽い霧雨)が示すように、明日は軽い雨の可能性があり、屋外レジャーには適
さない可能性が高いです。ただし、気温は最高28.6℃、最低24.9℃と快適です。
[Step 4: Duration 11.86 seconds| Input tokens: 18,409 | Output tokens: 1,423]

無事、東京ディズニーランドの明日(9月15日)の天気予報を取得できています。
処理は以下のように行われました。

  1. 「東京ディズニーランド」という地名(施設名)から国土地理院の API で候補地域のリストを取得する Pythonコードを生成して実行。ただし、デフォルトでは許可されていない json ライブラリを import しようとしたためエラーとなる
  2. 「東京ディズニーランド」という地名(施設名)から国土地理院の API で候補地域のリストを取得する Pythonコードを前のステップでエラーとなった json ライブラリの import をしないようにして再度生成して実行
  3. 国土地理院の API の応答(リストのフォーマット)を見て、リストからターゲット地域(「東京ディズニーランド」)の緯度経度を抽出し、続けて、Open Meteo の API で天気予報を取得する Pythonコードを生成して実行

Open Metao の API は、json 形式で応答することはわかっていますので additional_authorized_importsjson を含めておくべきだったかもしれません。しかし、このような場合にも回避策があれば自動的に適用して再実行してくれるところが便利ですね。

CodeAgent(toolあり)- システム設計支援エージェント

【ソースコード(400_system_design_agent_gradio.py)】

やっとツールを使う例に辿り着きました!CodeAgent はツールがなくてもいろいろなことができますのでここまでの道のりが長かったですね。

ここで作るのは冒頭で成果物を載せているシステム設計支援エージェントです。
もう一度載せるとこんなものを作ってくれます。
システム設計支援エージェント成果物.png
このエージェントはこんなことができます。

  • 自然言語の指示で Mermaid 形式でサポートされているダイアグラムのスクリプトと、それをレンダリングした画像を生成することができます
  • 特にダイアグラムの生成を要求しないときは、テキストベースで質問に答えることができます
  • 対話履歴を記憶しており、生成されたダイアグラムに修正を要求して再生成することができます

マーメイドスクリプトレンダリングツール

フロンティアモデルと呼ばれる高性能なAIモデルは、Mermaid スクリプトを生成することも得意です。ただし、そこそこ微妙な規約違反なスクリプトを生成してしまいます。ということは、さすがに Mermaid スクリプトをレンダリングして画像化する Python コードをその場で生成させるのはハードルが高いですね。そこで、このツールは別途用意して、CodeAgent から呼べるようにしてあげます。

マーメイドスクリプトレンダリングツールを私が自力で開発するのもハードルが天に届きそうなくらい高いのであるものを使わせていただきます。
今回は、mermaild-cli をサブプロセスとして起動する関数を作って、それをツールとして CodeAgent に渡してあげることにします。

マーメイドスクリプトレンダリングツールのコード全体
マーメイドスクリプトレンダリングツールのコード全体
@tool
def generate_mermaid_diagram_tool(mermaid_script: str) -> str:
    """
    マーメイドダイアグラムの画像ファイルを生成するツール(例外処理版)。
    
    Args:
        mermaid_script: ダイアグラムを生成するためのマーメイドスクリプト
        
    Returns:
        str: 生成されたPNGファイルのパス
        
    Raises:
        ValueError: 入力パラメータが無効な場合
        RuntimeError: ダイアグラム生成に失敗した場合
        FileNotFoundError: mmdc コマンドが見つからない場合
    
    Example usage:
        try:
            diagram_path = generate_mermaid_diagram_tool(mermaid_script)
            # diagram_path を使用して処理を続行
        except Exception as e:
            print(f"ダイアグラム生成失敗: {e}")
    """
    
    if not mermaid_script or not mermaid_script.strip():
        raise ValueError("mermaid_script is required and cannot be empty")
    
    # outputディレクトリの作成
    output_dir = "output"
    try:
        os.makedirs(output_dir, exist_ok=True)
    except Exception as e:
        raise RuntimeError(f"Failed to create output directory: {str(e)}")
    
    # ファイル名生成
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    mmd_file = f"{output_dir}/mermaid_diagram_{timestamp}.mmd"
    png_file = f"{output_dir}/mermaid_diagram_{timestamp}.png"
    
    # スクリプトファイル書き込み
    try:
        with open(mmd_file, 'w', encoding='utf-8') as f:
            f.write(mermaid_script)
    except Exception as e:
        raise RuntimeError(f"Failed to write mermaid script file: {str(e)}")
    
    # mmdc コマンド確認
    mmdc_path = shutil.which("mmdc")
    if mmdc_path is None:
        raise FileNotFoundError("mmdc command not found. Install with: npm install -g @mermaid-js/mermaid-cli")
    
    # ダイアグラム生成
    try:
        result = subprocess.run(
            [mmdc_path, '-i', mmd_file, '-o', png_file, '--width', '2048', '--height', '2048'],
            capture_output=True,
            text=True,
            shell=False,
            encoding='utf-8',
            timeout=60
        )
        
        if result.returncode != 0:
            error_msg = f"mmdc failed (exit code {result.returncode})"
            if result.stderr:
                # 制御文字を除去してエラーメッセージを安全に
                clean_stderr = result.stderr.strip().replace('\n', ' ').replace('\r', ' ')
                error_msg += f": {clean_stderr}"
            raise RuntimeError(error_msg)
        
        if not os.path.exists(png_file):
            raise RuntimeError("Diagram file was not created")
        
        return png_file
        
    except subprocess.TimeoutExpired:
        raise RuntimeError("Diagram generation timed out (60 seconds)")
    except Exception as e:
        if isinstance(e, RuntimeError):
            raise  # RuntimeErrorは再発生
        raise RuntimeError(f"Unexpected error during diagram generation: {str(e)}")
関数を smolagent のツールとして使えるように宣言

tool デコレータで、ツールとして CodeAgent に渡したい関数を修飾します。下記のコードの@toolです。
tool デコレータで関数を修飾すると関数の引数や戻り値の型ヒント、関数の説明(docstring)が、AIモデルに渡されて、AIモデルは適切にツールを呼び出せる Python コードを生成できます。

ツールはデコレータを使う方法の他に Toolクラスを継承したサブクラスとして作ることもできます。

関数の説明(docstring)は、とても大切です。AIエージェントのHello World 的なチュートリアルなどでは簡単にあっさり流されていることが多いと思いますが、AIモデルはこれを元にどのツールを呼び出すのか、どのように呼びだすのか、戻り値をどのように解釈すればいいのかを判断します。ツールの呼び出しが上手く行かない(呼び出して欲しいツールを呼び出さない、呼び出しに失敗してエラーになる、間違ったパラメータを渡してしまう、戻り値の解釈を間違えてしまうなど)ときは、関数の説明(docstring)を何度でも見直しましょう。MCPサーバーのツールでも同様です。

マーメイドスクリプトレンダリングツールの関数宣言
@tool
def generate_mermaid_diagram_tool(mermaid_script: str) -> str:
    """
    マーメイドダイアグラムの画像ファイルを生成するツール(例外処理版)。
    
    Args:
        mermaid_script: ダイアグラムを生成するためのマーメイドスクリプト
        
    Returns:
        str: 生成されたPNGファイルのパス
        
    Raises:
        ValueError: 入力パラメータが無効な場合
        RuntimeError: ダイアグラム生成に失敗した場合
        FileNotFoundError: mmdc コマンドが見つからない場合
    
    Example usage:
        try:
            diagram_path = generate_mermaid_diagram_tool(mermaid_script)
            # diagram_path を使用して処理を続行
        except Exception as e:
            print(f"ダイアグラム生成失敗: {e}")
    """

コード本体は主要な部分だけご紹介します(コード全体は、上の「マーメイドスクリプトレンダリングツールのコード全体」を展開してください)。

マーメイドスクリプトのレンダリング部分
mmdc_path = shutil.which("mmdc")
...
result = subprocess.run(
    [mmdc_path, '-i', mmd_file, '-o', png_file, '--width', '2048', '--height', '2048'],
    capture_output=True,
    text=True,
    shell=False,
    encoding='utf-8',
    timeout=60
)

mmdc は、mermaid_cli のコマンドです。mmdc_path = shutil.which("mmdc")で、mmdc のパスを取得しています。
その後、subprocess.runでサブプロセスとして mmdc を起動して、マーメイドスクリプトを画像ファイルへ変換しています。

マーメイドスクリプトの注意事項ガイドツール

AIモデルは、そこそこ微妙な規約違反なスクリプトを生成してしまいますと書きました。これが結構よくあります。エージェントは、間違いがあっても mmdc コマンドのエラーメッセージを AIモデルが解釈してスクリプトを修正して、再度、マーメイドスクリプトレンダリングツールを呼びだしてmmdcで画像を再生成ということを繰り返してくれます。

そして、多くの場合は正解に辿り着いてくれるのですが、時間がかかってしまう上に従量課金のAPIサービスでAIモデルを使っている場合はその入力や出力のトークンやキャラクターへの課金もかさんでしまいます

このエージェントを使っている中で頻発するスクリプトの間違いについてはあらかじめ AIモデルに知らせておいて、極力間違いのないスクリプトを生成させたいところです。

そこで、今回は、マーメイドスクリプトの注意事項ガイドツールを用意して、AIモデルがマーメイドスクリプトを生成する必要があるときに呼び出して、マーメイドスクリプトの注意事項を取得してもらうことにしました。

このツールは短いので全文をそのまま載せます。

マーメイドスクリプトの注意事項ガイドツール
@tool
def get_mermaid_script_guidelines_tool() -> str:
    """
    Mermaid スクリプトの記法に関する注意事項を返すツール。

    Args:
        None
    
    Returns:
        Mermaid スクリプトの記法に関する注意事項
    """

    
    return """
    # Mermaid スクリプトを生成する際には、以下の注意事項を必ず守ってください。
    # Mermaid スクリプト全般の注意事項
        - ノードラベルに"<br>"は使えません。"<br>"の代わりに改行文字を使ってください。
        - ノードラベルに半角の"("")"は使えません。半角の"("")"の代わりに全角の""""を使ってください。
        - subgraph の名前に空白が含まれる場合は二重引用符で囲んでください。
        - subgraph は直接エッジでつなげることはできません。ノード間をエッジでつなげてください。
    # ER図のMermaidスクリプトの注意事項
        - テーブル構造を表す場合の属性の構成要素は、データ型 カラム名 制約(PK, "NOT NULL", UK, CK, FK のいずれか) の順で記順してください。制約がない場合は、制約欄には何も書かないでください。
        - NUMBER にスケールは指定できません。NUMBER(精度,スケール)という記述はNGです。NUMBER(精度) とするかNUMBER とだけ記述してください。
        - VECTOR にデータフォーマットは指定できません。VECTOR(次元数, データフォーマット)という記述はNGです。VECTOR(次元数) とするか VECTOR とだけ記述してください。
    """

tool デコレータで修飾することも、関数の説明(docstring)が大切なことも先程と同じです。
このツールはマーメイドスクリプト生成にあたっての注意事項を提供することが目的ですので、注意事項を記載したテキストを返すだけの簡単な実装になっています。
ここは本格的に多様な図表に対応したガイドを載せたいとなった場合はデータベースなどから図表に種類に応じて適切なガイドを取得するなどといった工夫もできます。また、既存の資産をサンプルとして活用したり、会社やプロジェクトの標準化のために活用するということも考えられますね。

ちなみに、ここで返している注意事項はこのシステムで十数回ダイアグラムを生成する中で頻発した問題を記載しています。エラーをデータベースなどに蓄積してガイドへ反映する仕組み作りも大切かもしれませんね。

CodeAgent(toolあり)- システム設計支援エージェント の初期化

CodeAgent(toolあり)- システム設計支援エージェント の初期化
agent = CodeAgent(
    tools=[get_mermaid_script_guidelines_tool, generate_mermaid_diagram_tool],  
    model=model,
    use_structured_outputs_internally=False,
    max_steps=10,
    additional_authorized_imports=["json"],
    stream_outputs=True

今度は、toolありですので、tools に先程紹介した2つのツールを設定しています。

CodeAgent(toolあり)- システム設計支援エージェント の実行

実際にエージェントを実行する部分です。

CodeAgent(toolあり)- システム設計支援エージェント の実行
task_prompt = f"""
ユーザーメッセージ:{user_message}
"""
result = agent.run(
    task=task_prompt,
    reset=False,  # 会話をリセットするかどうか。リセットする場合はTrue、しない場合はFalse
    max_steps=10   # 最大10ステップで制限
)

task_promptに、Gradio の UI で入力されたユーザーからの指示・システム要件(ユーザーメッセージ)を設定して、agent.run に渡しています。これで、UIでユーザーが指示した内容をエージェントが解決してくれます(しようとしてくれます)。
なお、新しいパラメータの reset=False は、会話の履歴をリセットしない、つまり、次に agent.runが呼び出された際に今回のユーザーメッセージやそれに対するエージェントの応答などの履歴がエージェントに渡されて文脈(コンテキスト)が維持されます。デフォルトは False なので指定は不要ですが説明のため記載しています。
また、max_step は、実行するステップの数(エラーがあった際の再生成と再実行を含む)を制限するものです。

CodeAgent(toolあり)- システム設計支援エージェント のコード全体

コードの他の部分は、主に Gradio の UI に関わる部分、ツールが生成したスクリプトファイル、画像ファイルの取り扱いに関わる部分ですので説明は割愛して、コード全体を掲載しておきます。

CodeAgent(toolあり)- システム設計支援エージェント のコード全体
400_system_design_agent_gradio.py
import datetime
import os
import re
from PIL import Image
import subprocess
import shutil
from dotenv import load_dotenv
from smolagents import CodeAgent, LiteLLMModel, tool
import gradio as gr

load_dotenv()

oci_user = os.getenv("OCI_USER")
oci_fingerprint = os.getenv("OCI_FINGERPRINT")
oci_tenancy = os.getenv("OCI_TENANCY")
oci_region = os.getenv("OCI_REGION")
oci_key = os.getenv("OCI_KEY")
oci_compartment_id = os.getenv("OCI_COMPARTMENT_ID")

model = LiteLLMModel(
    model_id="oci/xai.grok-4",
    oci_region=os.getenv("OCI_REGION"),                    # 例: "us-ashburn-1"
    oci_user=os.getenv("OCI_USER"),                        # OCI User OCID
    oci_fingerprint=os.getenv("OCI_FINGERPRINT"),          # RSA key fingerprint
    oci_tenancy=os.getenv("OCI_TENANCY"),                  # Tenancy OCID
    oci_key=os.getenv("OCI_KEY"),                          # Private key content
    oci_compartment_id=os.getenv("OCI_COMPARTMENT_ID"),    # Compartment OCID
    temperature=0.0,
    max_tokens= 10000,
    drop_params=True
)

@tool
def get_mermaid_script_guidelines_tool() -> str:
    """
    Mermaid スクリプトの記法に関する注意事項を返すツール。

    Args:
        None
    
    Returns:
        Mermaid スクリプトの記法に関する注意事項
    """

    
    return """
    # Mermaid スクリプトを生成する際には、以下の注意事項を必ず守ってください。
    # Mermaid スクリプト全般の注意事項
        - ノードラベルに"<br>"は使えません。"<br>"の代わりに改行文字を使ってください。
        - ノードラベルに半角の"("")"は使えません。半角の"("")"の代わりに全角の""""を使ってください。
        - subgraph の名前に空白が含まれる場合は二重引用符で囲んでください。
        - subgraph は直接エッジでつなげることはできません。ノード間をエッジでつなげてください。
    # ER図のMermaidスクリプトの注意事項
        - テーブル構造を表す場合の属性の構成要素は、データ型 カラム名 制約(PK, "NOT NULL", UK, CK, FK のいずれか) の順で記順してください。制約がない場合は、制約欄には何も書かないでください。
        - NUMBER にスケールは指定できません。NUMBER(精度,スケール)という記述はNGです。NUMBER(精度) とするかNUMBER とだけ記述してください。
        - VECTOR にデータフォーマットは指定できません。VECTOR(次元数, データフォーマット)という記述はNGです。VECTOR(次元数) とするか VECTOR とだけ記述してください。
    """

@tool
def generate_mermaid_diagram_tool(mermaid_script: str) -> str:
    """
    マーメイドダイアグラムの画像ファイルを生成するツール(例外処理版)。
    
    Args:
        mermaid_script: ダイアグラムを生成するためのマーメイドスクリプト
        
    Returns:
        str: 生成されたPNGファイルのパス
        
    Raises:
        ValueError: 入力パラメータが無効な場合
        RuntimeError: ダイアグラム生成に失敗した場合
        FileNotFoundError: mmdc コマンドが見つからない場合
    
    Example usage:
        try:
            diagram_path = generate_mermaid_diagram_tool(mermaid_script)
            # diagram_path を使用して処理を続行
        except Exception as e:
            print(f"ダイアグラム生成失敗: {e}")
    """
    
    if not mermaid_script or not mermaid_script.strip():
        raise ValueError("mermaid_script is required and cannot be empty")
    
    # outputディレクトリの作成
    output_dir = "output"
    try:
        os.makedirs(output_dir, exist_ok=True)
    except Exception as e:
        raise RuntimeError(f"Failed to create output directory: {str(e)}")
    
    # ファイル名生成
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    mmd_file = f"{output_dir}/mermaid_diagram_{timestamp}.mmd"
    png_file = f"{output_dir}/mermaid_diagram_{timestamp}.png"
    
    # スクリプトファイル書き込み
    try:
        with open(mmd_file, 'w', encoding='utf-8') as f:
            f.write(mermaid_script)
    except Exception as e:
        raise RuntimeError(f"Failed to write mermaid script file: {str(e)}")
    
    # mmdc コマンド確認
    mmdc_path = shutil.which("mmdc")
    if mmdc_path is None:
        raise FileNotFoundError("mmdc command not found. Install with: npm install -g @mermaid-js/mermaid-cli")
    
    # ダイアグラム生成
    try:
        result = subprocess.run(
            [mmdc_path, '-i', mmd_file, '-o', png_file, '--width', '2048', '--height', '2048'],
            capture_output=True,
            text=True,
            shell=False,
            encoding='utf-8',
            timeout=60
        )
        
        if result.returncode != 0:
            error_msg = f"mmdc failed (exit code {result.returncode})"
            if result.stderr:
                # 制御文字を除去してエラーメッセージを安全に
                clean_stderr = result.stderr.strip().replace('\n', ' ').replace('\r', ' ')
                error_msg += f": {clean_stderr}"
            raise RuntimeError(error_msg)
        
        if not os.path.exists(png_file):
            raise RuntimeError("Diagram file was not created")
        
        return png_file
        
    except subprocess.TimeoutExpired:
        raise RuntimeError("Diagram generation timed out (60 seconds)")
    except Exception as e:
        if isinstance(e, RuntimeError):
            raise  # RuntimeErrorは再発生
        raise RuntimeError(f"Unexpected error during diagram generation: {str(e)}")

agent = CodeAgent(
    tools=[get_mermaid_script_guidelines_tool, generate_mermaid_diagram_tool],  
    model=model,
    use_structured_outputs_internally=False,
    max_steps=10,
    additional_authorized_imports=["json"],
    stream_outputs=True
)

def process_user_message_with_agent(user_message):
    if not user_message.strip():
        return "システム要件を入力してください。", "ステータス: 入力待ち", "", "", None, ""
    
    try:   
        task_prompt = f"""
        ユーザーメッセージ:{user_message}
        """
        result = agent.run(
            task=task_prompt,
            reset=False,  # 会話をリセットするかどうか。リセットする場合はTrue、しない場合はFalse
            max_steps=10   # 最大10ステップで制限
        )
        
        # agent.runの戻り値からファイルパス名を抽出
        result_str = str(result)
        
        # エージェントの応答は純粋な結果文字列を使用
        response_text = result_str

        # CodeAgent の戻り値テキストから .png パスを抽出し、拡張子置換で .mmd を導出(ディレクトリ探索はしない)
        png_pattern = r'output[/\\]mermaid_diagram_\d{8}_\d{6}\.png'

        png_match = re.search(png_pattern, result_str)

        if png_match:
            image_file = png_match.group(0)
            # mmd は result には含まれないため、png の拡張子を置換して導出
            script_file = re.sub(r'\.png$', '.mmd', image_file)

            if os.path.exists(image_file):
                generated_image = Image.open(image_file)

                script_content = ""
                if script_file and os.path.exists(script_file):
                    try:
                        with open(script_file, 'r', encoding='utf-8') as f:
                            script_content = f.read()
                    except Exception as e:
                        script_content = f"スクリプトファイル読み込みエラー: {str(e)}"

                status_text = "ダイアグラムが正常に生成されました。"
                if not script_file:
                    status_text += " スクリプトファイルが見つかりません。"

                return (
                    response_text,
                    status_text,
                    script_file,
                    image_file,
                    generated_image,
                    script_content
                )
            else:
                return (
                    response_text,
                    f"エラー: 画像ファイル '{image_file}' が存在しません。",
                    script_file if 'script_file' in locals() else "",
                    image_file,
                    None,
                    ""
                )
        else:
            # 画像ファイルの記載がエージェント応答に無い場合はテキスト応答を返す
            return (
                response_text,
                "タスク完了: テキスト応答",
                "",
                "",
                None,
                ""
            )
        
    except Exception as e:
        error_msg = f"エラーが発生しました: {str(e)}"
        status_msg = f"エラーステータス: {type(e).__name__}"
        return error_msg, status_msg, "", "", None, ""

def clear_all():
    return "", "", "", "", "", None, ""

with gr.Blocks(title="システム設計支援エージェント") as interface:
    gr.Markdown("# システム設計支援エージェント")
    
    with gr.Row():
        with gr.Column():
            user_message = gr.Textbox(
                label="要件入力",
                placeholder="システム要件を入力してください...",
                lines=10,
                max_lines=10,
                show_copy_button=True
            )
            with gr.Row():
                send_btn = gr.Button("送信", variant="primary")
                clear_btn = gr.Button("クリア", variant="secondary")
            
        with gr.Column():
            
            result_output = gr.Textbox(
                label="エージェントの応答",
                lines=5,
                max_lines=25,
                show_copy_button=True
            )
            status_output = gr.Textbox(
                label="ステータス",
                lines=1,
                max_lines=1,
                show_copy_button=True
            )
            with gr.Accordion("成果物", open=False):
                script_file_output = gr.Textbox(
                    label="スクリプトファイル名",
                    lines=1,
                    max_lines=1,
                    show_copy_button=True
                )
                image_file_output = gr.Textbox(
                    label="画像ファイル名",
                    lines=1,
                    max_lines=1,
                    show_copy_button=True
                )
                script_output = gr.Textbox(
                    label="生成されたスクリプト",
                    lines=25,
                    max_lines=25,
                    show_copy_button=True
                )
    
    with gr.Row():
        image_output = gr.Image(
            label="ダイアグラム",
            type="pil",
            height=1024,
            show_download_button=True
        )
    
    send_btn.click(
        fn=process_user_message_with_agent,
        inputs=[user_message],
        outputs=[result_output, status_output, script_file_output, image_file_output, image_output, script_output]
    )
    
    clear_btn.click(
        fn=clear_all,
        inputs=[],
        outputs=[user_message, result_output, status_output, script_file_output, image_file_output, image_output, script_output]
    )

interface.launch(share=False)

CodeAgent(toolあり)- システム設計支援エージェント の実行例

(.venv) LOCAL:mermaid-diagram-agent $ uv run 400_system_design_agent_gradio.py 
* Running on local URL:  http://127.0.0.1:7861
* To create a public link, set `share=True` in `launch()`.
╭─────────────────────────────────── New run ───────────────────────────────────╮
│                                                                               │
│ ユーザーメッセージ:スマホアプリから自然言語で問い合わせると会社の社内規定を  │
│ 類似性検索して自然言語で答えてくれるシステムのシーケンス図を描いてください。  │
│                                                                               │
╰─ LiteLLMModel - oci/xai.grok-4 ───────────────────────────────────────────────╯
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


Thought:
ユーザーのリクエストは、スマホアプリから自然言語で問い合わせると、会社の社内規定 
を類似性検索して自然言語で答えてくれるシステムのシーケンス図を描くことです。Merma
idを使ってシーケンス図を作成します。まず、get_mermaid_script_guidelines_toolを使 
ってMermaidスクリプトのガイドラインを確認します。これにより、正しい構文を確保しま
す。  guidelines = get_mermaid_script_guidelines_tool() print(guidelines)        
 ─ Executing parsed code: ────────────────────────────────────────────────────── 
  guidelines = get_mermaid_script_guidelines_tool()                              
  print(guidelines)                                                              
 ─────────────────────────────────────────────────────────────────────────────── 
Execution logs:

    # Mermaid スクリプトを生成する際には、以下の注意事項を必ず守ってください。   
    # Mermaid スクリプト全般の注意事項
        -
ノードラベルに"<br>"は使えません。"<br>"の代わりに改行文字を使ってください。     
        -
ノードラベルに半角の"("")"は使えません。半角の"("")"の代わりに全角の"("")
"を使ってください。
        - subgraph の名前に空白が含まれる場合は二重引用符で囲んでください。      
        - subgraph
は直接エッジでつなげることはできません。ノード間をエッジでつなげてください。     
    # ER図のMermaidスクリプトの注意事項
        - テーブル構造を表す場合の属性の構成要素は、データ型 カラム名 制約(PK,   
"NOT NULL", UK, CK, FK のいずれか)
の順で記順してください。制約がない場合は、制約欄には何も書かないでください。     
        - NUMBER
にスケールは指定できません。NUMBER(精度,スケール)という記述はNGです。NUMBER(精度)
とするかNUMBER とだけ記述してください。
        - VECTOR にデータフォーマットは指定できません。VECTOR(次元数,
データフォーマット)という記述はNGです。VECTOR(次元数) とするか VECTOR
とだけ記述してください。


Out: None
[Step 1: Duration 10.42 seconds| Input tokens: 2,111 | Output tokens: 181]       
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 2 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Thought:
ガイドラインを確認しました。シーケンス図を作成するために、MermaidのsequenceDiagra
m構文を使います。システムの参加者を定義:User (スマホアプリ)、AppServer
(アプリサーバー)、VectorDB (類似性検索のためのベクトルDB)、LLM
(自然言語生成)。シーケンスは、問い合わせの送信、検索、回答生成、返信です。Mermaid
スクリプトを作成し、generate_mermaid_diagram_toolで画像を生成します。
mermaid_script = """ sequenceDiagram participant User as ユーザー(スマホアプリ)
participant AppServer as アプリサーバー participant VectorDB as
ベクトルDB(社内規定) participant LLM as LLM(自然言語生成)


 User->>AppServer: 自然言語問い合わせを送信
 AppServer->>VectorDB: 問い合わせを埋め込み、類似性検索
 VectorDB->>AppServer: 関連規定ドキュメントを返す
 AppServer->>LLM: ドキュメントと問い合わせで回答生成を依頼
 LLM->>AppServer: 生成された自然言語回答を返す
 AppServer->>User: 自然言語回答を送信



""" image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)
final_answer(image)
 ─ Executing parsed code: ────────────────────────────────────────────────────── 
  mermaid_script = """                                                           
  sequenceDiagram                                                                
      participant User as ユーザー(スマホアプリ)                               
      participant AppServer as アプリサーバー                                    
      participant VectorDB as ベクトルDB(社内規定)                             
      participant LLM as LLM(自然言語生成)                                     
                                                                                 
      User->>AppServer: 自然言語問い合わせを送信                                 
      AppServer->>VectorDB: 問い合わせを埋め込み、類似性検索                     
      VectorDB->>AppServer: 関連規定ドキュメントを返す                           
      AppServer->>LLM: ドキュメントと問い合わせで回答生成を依頼                  
      LLM->>AppServer: 生成された自然言語回答を返す                              
      AppServer->>User: 自然言語回答を送信                                       
  """                                                                            
  image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)           
  final_answer(image)                                                            
 ─────────────────────────────────────────────────────────────────────────────── 
Final answer: output/mermaid_diagram_20250915_005459.png
[Step 2: Duration 14.18 seconds| Input tokens: 4,929 | Output tokens: 605]

ステップ1で get_mermaid_script_guidelines_toolを呼んでスクリプトのガイドを取にいってくれています。
エラーが発生することもなく無事にレンダリングできているようですね。

プロンプト(ターン1)
スマホアプリから自然言語で問い合わせると会社の社内規定を類似性検索して自然言語で答えてくれるシステムのシーケンス図を描いてください。
システム設計支援エージェントの成果物例システム設計支援エージェント成果物2.png

良い感じではありますが、もう少し詳しく書いてもらおうと思います。要件入力欄に「類似性検索のための具体的な処理や自然言語で回答を生成する処理も詳しく記載してください。」と入力して、送信してみます。

ターン2の実行例(ログ)
╭─────────────────────────────────── New run ───────────────────────────────────╮
│                                                                               │
│ ユーザーメッセージ:類似性検索のための具体的な処理や自然言語で回答を生成する  │
│ 処理も詳しく記載してください。                                                │
│                                                                               │
╰─ LiteLLMModel - oci/xai.grok-4 ───────────────────────────────────────────────╯
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Thought:
ユーザーのリクエストは、前のシーケンス図を拡張して、類似性検索の具体的な処理(例:
埋め込みベクトルの生成)と自然言語回答生成の処理を詳しく記載することです。参加者 
を追加(例:
EmbeddingModel)してステップを細分化し、Mermaidスクリプトを更新します。ガイドライ
ンに従い、全角括弧を使い、改行でラベルを扱います。  mermaid_script = """
sequenceDiagram participant User as ユーザー(スマホアプリ) participant
AppServer as アプリサーバー participant EmbeddingModel as 埋め込みモデル
participant VectorDB as ベクトルDB(社内規定) participant LLM as
LLM(自然言語生成)


 User->>AppServer: 自然言語問い合わせを送信
 AppServer->>EmbeddingModel: 問い合わせテキストを送信して埋め込みベクトルを生成  
 EmbeddingModel->>AppServer: 問い合わせの埋め込みベクトルを返す
 AppServer->>VectorDB: 埋め込みベクトルを使って類似性検索を実行(例: cosine      
 similarity)
 VectorDB->>AppServer: 類似度の高い社内規定ドキュメントを返す
 AppServer->>LLM:
 ドキュメントと問い合わせをプロンプトとして送信し、\n自然言語回答を生成依頼(例: 
 要約や説明生成)
 LLM->>AppServer: 生成された自然言語回答を返す(例: コンテキストに基づく応答)   
 AppServer->>User: 自然言語回答を送信



""" image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)
final_answer(image)
 ─ Executing parsed code: ────────────────────────────────────────────────────── 
  mermaid_script = """                                                           
  sequenceDiagram                                                                
      participant User as ユーザー(スマホアプリ)                               
      participant AppServer as アプリサーバー                                    
      participant EmbeddingModel as 埋め込みモデル                               
      participant VectorDB as ベクトルDB(社内規定)                             
      participant LLM as LLM(自然言語生成)                                     
                                                                                 
      User->>AppServer: 自然言語問い合わせを送信                                 
      AppServer->>EmbeddingModel:                                                
  問い合わせテキストを送信して埋め込みベクトルを生成                             
      EmbeddingModel->>AppServer: 問い合わせの埋め込みベクトルを返す             
      AppServer->>VectorDB: 埋め込みベクトルを使って類似性検索を実行(例:        
  cosine similarity)                                                            
      VectorDB->>AppServer: 類似度の高い社内規定ドキュメントを返す               
      AppServer->>LLM:                                                           
  ドキュメントと問い合わせをプロンプトとして送信し、\n自然言語回答を生成依頼(   
  例: 要約や説明生成)                                                           
      LLM->>AppServer: 生成された自然言語回答を返す(例:                         
  コンテキストに基づく応答)                                                     
      AppServer->>User: 自然言語回答を送信                                       
  """                                                                            
  image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)           
  final_answer(image)                                                            
 ─────────────────────────────────────────────────────────────────────────────── 
Code execution failed at line 'image = 
generate_mermaid_diagram_tool(mermaid_script=mermaid_script)' due to: 
RuntimeError: mmdc failed (exit code 1): Error: Parse error on line 14: 
...して送信し、自然言語回答を生成依頼(例: 要約や説明生成)    LLM-> 
----------------------^ Expecting 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW',       
'SOLID_ARROW', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 
'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT',      
'DOTTED_POINT', got 'TXT' Parser3.parseError 
(C:\Users\yujim\AppData\Roaming\npm\node_modules\@mermaid-js\mermaid-cli\node_mod
ules\mermaid\dist\mermaid.js:88557:28)     at #evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/cdp/ExecutionContext.js:388:19)    
 at async ExecutionContext.evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/cdp/ExecutionContext.js:275:16)    
 at async IsolatedWorld.evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/cdp/IsolatedWorld.js:97:16)     at 
async CdpJSHandle.evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/JSHandle.js:146:20)     at     
async CdpElementHandle.evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/ElementHandle.js:340:20)     at
async CdpElementHandle.$eval 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/ElementHandle.js:494:24)     at
async CdpFrame.$eval 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/Frame.js:450:20)     at async  
CdpPage.$eval 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/Page.js:450:20)     at async   
renderMermaid 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
src/index.js:265:22)     at fromText 
(C:\Users\yujim\AppData\Roaming\npm\node_modules\@mermaid-js\mermaid-cli\node_mod
ules\mermaid\dist\mermaid.js:151976:21)
[Step 3: Duration 14.90 seconds| Input tokens: 8,562 | Output tokens: 1,171]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 2 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Thought:
エラーの原因はMermaidスクリプトのメッセージラベルに改行が入っていたため、パースエ
ラーが発生した。メッセージを1行に収め、改行を削除してスクリプトを修正する。ガイド
ラインに従い、全角括弧を使い、ノードラベルは適切に扱う。  mermaid_script = """   
sequenceDiagram participant User as ユーザー(スマホアプリ) participant
AppServer as アプリサーバー participant EmbeddingModel as 埋め込みモデル
participant VectorDB as ベクトルDB(社内規定) participant LLM as
LLM(自然言語生成)


 User->>AppServer: 自然言語問い合わせを送信
 AppServer->>EmbeddingModel: 問い合わせテキストを送信して埋め込みベクトルを生成  
 EmbeddingModel->>AppServer: 問い合わせの埋め込みベクトルを返す
 AppServer->>VectorDB: 埋め込みベクトルを使って類似性検索を実行(例: cosine      
 similarity)
 VectorDB->>AppServer: 類似度の高い社内規定ドキュメントを返す
 AppServer->>LLM:
 ドキュメントと問い合わせをプロンプトとして送信し、自然言語回答を生成依頼(例:   
 要約や説明生成)
 LLM->>AppServer: 生成された自然言語回答を返す(例: コンテキストに基づく応答)   
 AppServer->>User: 自然言語回答を送信



""" image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)
final_answer(image)
 ─ Executing parsed code: ────────────────────────────────────────────────────── 
  mermaid_script = """                                                           
  sequenceDiagram                                                                
      participant User as ユーザー(スマホアプリ)                               
      participant AppServer as アプリサーバー                                    
      participant EmbeddingModel as 埋め込みモデル                               
      participant VectorDB as ベクトルDB(社内規定)                             
      participant LLM as LLM(自然言語生成)                                     
                                                                                 
      User->>AppServer: 自然言語問い合わせを送信                                 
      AppServer->>EmbeddingModel:                                                
  問い合わせテキストを送信して埋め込みベクトルを生成                             
      EmbeddingModel->>AppServer: 問い合わせの埋め込みベクトルを返す             
      AppServer->>VectorDB: 埋め込みベクトルを使って類似性検索を実行(例:        
  cosine similarity)                                                            
      VectorDB->>AppServer: 類似度の高い社内規定ドキュメントを返す               
      AppServer->>LLM:                                                           
  ドキュメントと問い合わせをプロンプトとして送信し、自然言語回答を生成依頼(例:  
  要約や説明生成)                                                               
      LLM->>AppServer: 生成された自然言語回答を返す(例:                         
  コンテキストに基づく応答)                                                     
      AppServer->>User: 自然言語回答を送信                                       
  """                                                                            
  image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)           
  final_answer(image)                                                            
 ─────────────────────────────────────────────────────────────────────────────── 
Final answer: output/mermaid_diagram_20250915_005803.png
[Step 4: Duration 20.43 seconds| Input tokens: 13,984 | Output tokens: 1,687]

最初のステップでは、マーメイドスクリプトのメッセージラベルに改行を入れてしまってレンダリングに失敗していますが、再度、スクリプトを生成しなおして、無事、画像の生成にも成功しています。
(スクリプトのガイドの強化にも役立ちそうな事例です)

プロンプト(ターン2)
類似性検索のための具体的な処理や自然言語で回答を生成する処理も詳しく記載してください。
システム設計支援エージェントの成果物例システム設計支援エージェント成果物3.png

埋め込みモデルも追加されてかなり良いですね。でも、ちょっと混雑してきましたのでわかりやすくしてもらいましょう。

要件入力欄に「類似性検索関連と回答生成関連を別々のサブグラフに分けて分かりやすくしてください。」と入力して、送信してみます。

ターン3の実行例(ログ)
╭─────────────────────────────────── New run ───────────────────────────────────╮
│                                                                               │
│ ユーザーメッセージ:類似性検索関連と回答生成関連を別々のサブグラフに分けて分  │
│ かりやすくしてください。                                                      │
│                                                                               │
╰─ LiteLLMModel - oci/xai.grok-4 ───────────────────────────────────────────────╯
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Thought:
ユーザーのリクエストは、シーケンス図をsubgraphを使って類似性検索関連と回答生成関 
連に分けることです。MermaidのsequenceDiagramでsubgraphを使って参加者をグループ化 
します。AppServerは両方のプロセスに関わるので、subgraphの外に置き、シーケンスは変
更せずに視覚的に分離します。ガイドラインに従い、subgraph名に空白がないようにし( 
必要なら引用符)、メッセージラベルは1行に収めます。  mermaid_script = """        
sequenceDiagram participant User as ユーザー(スマホアプリ) participant
AppServer as アプリサーバー participant EmbeddingModel as 埋め込みモデル
participant VectorDB as ベクトルDB(社内規定) participant LLM as
LLM(自然言語生成)


 User->>AppServer: 自然言語問い合わせを送信

 subgraph 類似性検索関連
     AppServer->>EmbeddingModel:
 問い合わせテキストを送信して埋め込みベクトルを生成
     EmbeddingModel->>AppServer: 問い合わせの埋め込みベクトルを返す
     AppServer->>VectorDB: 埋め込みベクトルを使って類似性検索を実行(例: cosine  
 similarity)
     VectorDB->>AppServer: 類似度の高い社内規定ドキュメントを返す
 end

 subgraph 回答生成関連
     AppServer->>LLM:
 ドキュメントと問い合わせをプロンプトとして送信し、自然言語回答を生成依頼(例:   
 要約や説明生成)
     LLM->>AppServer: 生成された自然言語回答を返す(例:
 コンテキストに基づく応答)
 end
                                                                                 
 AppServer->>User: 自然言語回答を送信
                                                                                 


""" image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)
final_answer(image)
 ─ Executing parsed code: ────────────────────────────────────────────────────── 
  mermaid_script = """                                                           
  sequenceDiagram                                                                
      participant User as ユーザー(スマホアプリ)                               
      participant AppServer as アプリサーバー                                    
      participant EmbeddingModel as 埋め込みモデル                               
      participant VectorDB as ベクトルDB(社内規定)                             
      participant LLM as LLM(自然言語生成)                                     
                                                                                 
      User->>AppServer: 自然言語問い合わせを送信                                 
                                                                                 
      subgraph 類似性検索関連                                                    
          AppServer->>EmbeddingModel:                                            
  問い合わせテキストを送信して埋め込みベクトルを生成                             
          EmbeddingModel->>AppServer: 問い合わせの埋め込みベクトルを返す         
          AppServer->>VectorDB: 埋め込みベクトルを使って類似性検索を実行(例:    
  cosine similarity)                                                            
          VectorDB->>AppServer: 類似度の高い社内規定ドキュメントを返す           
      end                                                                        
                                                                                 
      subgraph 回答生成関連                                                      
          AppServer->>LLM:                                                       
  ドキュメントと問い合わせをプロンプトとして送信し、自然言語回答を生成依頼(例:  
  要約や説明生成)                                                               
          LLM->>AppServer: 生成された自然言語回答を返す(例:                     
  コンテキストに基づく応答)                                                     
      end                                                                        
                                                                                 
      AppServer->>User: 自然言語回答を送信                                       
  """                                                                            
  image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)           
  final_answer(image)                                                            
 ─────────────────────────────────────────────────────────────────────────────── 
Code execution failed at line 'image = 
generate_mermaid_diagram_tool(mermaid_script=mermaid_script)' due to: 
RuntimeError: mmdc failed (exit code 1): Error: Parse error on line 10: ...      
subgraph 類似性検索関連        AppServer-> -----------------------^ Expecting    
'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 
'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW',       
'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'NEWLINE'      
Parser3.parseError 
(C:\Users\yujim\AppData\Roaming\npm\node_modules\@mermaid-js\mermaid-cli\node_mod
ules\mermaid\dist\mermaid.js:88557:28)     at #evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/cdp/ExecutionContext.js:388:19)    
 at async ExecutionContext.evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/cdp/ExecutionContext.js:275:16)    
 at async IsolatedWorld.evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/cdp/IsolatedWorld.js:97:16)     at 
async CdpJSHandle.evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/JSHandle.js:146:20)     at     
async CdpElementHandle.evaluate 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/ElementHandle.js:340:20)     at
async CdpElementHandle.$eval 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/ElementHandle.js:494:24)     at
async CdpFrame.$eval 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/Frame.js:450:20)     at async  
CdpPage.$eval 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
node_modules/puppeteer-core/lib/esm/puppeteer/api/Page.js:450:20)     at async   
renderMermaid 
(file:///C:/Users/yujim/AppData/Roaming/npm/node_modules/@mermaid-js/mermaid-cli/
src/index.js:265:22)     at fromText 
(C:\Users\yujim\AppData\Roaming\npm\node_modules\@mermaid-js\mermaid-cli\node_mod
ules\mermaid\dist\mermaid.js:151976:21)
[Step 5: Duration 19.73 seconds| Input tokens: 20,462 | Output tokens: 2,297]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Step 2 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Thought:
前回のエラーはsequenceDiagram内でsubgraphがサポートされていないため発生した。Merm
aidのsequenceDiagramではsubgraphを使えないので、代わりにNote
overを使ってセクションを視覚的に分離する。これで類似性検索関連と回答生成関連を分 
かりやすく区切れる。メッセージラベルは1行に収め、ガイドラインを守る。
mermaid_script = """ sequenceDiagram participant User as ユーザー(スマホアプリ)
participant AppServer as アプリサーバー participant EmbeddingModel as
埋め込みモデル participant VectorDB as ベクトルDB(社内規定) participant LLM as 
LLM(自然言語生成)


 User->>AppServer: 自然言語問い合わせを送信

 Note over AppServer,VectorDB: 類似性検索関連
 AppServer->>EmbeddingModel: 問い合わせテキストを送信して埋め込みベクトルを生成  
 EmbeddingModel->>AppServer: 問い合わせの埋め込みベクトルを返す
 AppServer->>VectorDB: 埋め込みベクトルを使って類似性検索を実行(例: cosine      
 similarity)
 VectorDB->>AppServer: 類似度の高い社内規定ドキュメントを返す

 Note over AppServer,LLM: 回答生成関連
 AppServer->>LLM:
 ドキュメントと問い合わせをプロンプトとして送信し、自然言語回答を生成依頼(例:   
 要約や説明生成)
 LLM->>AppServer: 生成された自然言語回答を返す(例: コンテキストに基づく応答)   

 AppServer->>User: 自然言語回答を送信
                                                                                 


""" image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)
final_answer(image)
 ─ Executing parsed code: ────────────────────────────────────────────────────── 
  mermaid_script = """                                                           
  sequenceDiagram                                                                
      participant User as ユーザー(スマホアプリ)                               
      participant AppServer as アプリサーバー                                    
      participant EmbeddingModel as 埋め込みモデル                               
      participant VectorDB as ベクトルDB(社内規定)                             
      participant LLM as LLM(自然言語生成)                                     
                                                                                 
      User->>AppServer: 自然言語問い合わせを送信                                 
                                                                                 
      Note over AppServer,VectorDB: 類似性検索関連                               
      AppServer->>EmbeddingModel:                                                
  問い合わせテキストを送信して埋め込みベクトルを生成                             
      EmbeddingModel->>AppServer: 問い合わせの埋め込みベクトルを返す             
      AppServer->>VectorDB: 埋め込みベクトルを使って類似性検索を実行(例:        
  cosine similarity)                                                            
      VectorDB->>AppServer: 類似度の高い社内規定ドキュメントを返す               
                                                                                 
      Note over AppServer,LLM: 回答生成関連                                      
      AppServer->>LLM:                                                           
  ドキュメントと問い合わせをプロンプトとして送信し、自然言語回答を生成依頼(例:  
  要約や説明生成)                                                               
      LLM->>AppServer: 生成された自然言語回答を返す(例:                         
  コンテキストに基づく応答)                                                     
                                                                                 
      AppServer->>User: 自然言語回答を送信                                       
  """                                                                            
  image = generate_mermaid_diagram_tool(mermaid_script=mermaid_script)           
  final_answer(image)                                                            
 ─────────────────────────────────────────────────────────────────────────────── 
Final answer: output/mermaid_diagram_20250915_010537.png
[Step 6: Duration 16.15 seconds| Input tokens: 28,790 | Output tokens: 2,872]
おっと!私が「サブグラフ」と指示したせいでステップ1で「sequenceDiagram内でsubgraphがサポートされていない」というエラーになっていますね。でも、エージェントがちゃんと代替案を考えて再生成してくれています。
プロンプト(ターン3)
類似性検索関連と回答生成関連を別々のサブグラフに分けて分かりやすくしてください。
システム設計支援エージェントの成果物例システム設計支援エージェント成果物4.png

なかなか良いですね。
この後に2回程調整をお願いしてできあがったものが次のシーケンス図です。
システム設計支援エージェント成果物5.png

Oracleデータベース SHスキーマのER図

Oracle データべースのSHサンプルスキーマの9つのテーブルのDDLを渡して ER図を描いてもらいました。
SHスキーマのDDL

Oracle データベース SHスキーマのER図
次のDDLのER図を描いてください。テーブルの構造も記載してください。
・・・DDLは長いのでブログ上は省略・・・
システム設計支援エージェントの成果物例ER図.png

CodeAgent(tool + DB MCPサーバー利用あり)- システム設計支援エージェント(DB接続版)

【ソースコード(500_system_design_agent_gradio_with_MCP.py)】

smolagents は、MCPにも対応しています。ここでは、Oracle Database の MCP サーバー(SQLcl MCP Server)を CodeAgent にツールとして追加登録して、smolagents がデータベースへ接続できるようにしてみようと思います。

SQLcl MCP Server とは?

Oracle Database のコマンドラインインターフェース (CLI) の Oracle SQLcl が MCPサーバーとして動作するようになりました。SQLcl は、sql コマンドに -mcp オプションを付けて起動すると STDIO トランスポートで MCP クライアントと通信する MCP サーバーになり、データベース操作をツールとして公開します。

Using the Oracle SQLcl MCP Server

image.png

上図は、Introducing MCP Server for Oracle Databaseより引用

利用可能なツールは、5つあります。

ツール名 概要
list-connections 保存されているデータベース接続の一覧を取得します
connect 指定された接続を使ってデータベースへ接続します
disconnect データベース接続を切断します
run-sqlcl SQLclコマンドを実行します
run-sql SQLクエリを実行します

SQLcl の設定手順は以下の記事でご紹介しています。このあたりです。

なお、MCPとは何かについては下記の記事でご紹介していますので、MCPは初めてとか、MCPでDBにつながったら何ができるの?という方は、ぜひお立ち寄りください。

MCPクライアントの設定

MCPクライアントの設定
sqlcl_server_parameters = StdioServerParameters(
    command="D:\\tools\\sqlcl\\bin\\sql.exe",
    args=["-mcp"],
)
sqlcl_mcp_client = MCPClient(
    server_parameters=sqlcl_server_parameters
)
sqlcl_tools = sqlcl_mcp_client.get_tools()
  • sqlcl_server_parameters は、SQLcl MCPサーバーの設定です。SQLcl MCPサーバーは、SQLcl のsqlコマンドを-mcp引数付きで起動すると MCPサーバーとして機能するという仕様になっていますがので、MCPサーバーを起動するコマンドとして sqlコマンドのパスを、引数には、-mcpを指定しています
  • MCPClientsqlcl_server_parameters を渡して、MCPサーバーを初期化しています
  • sqlcl_mcp_client.get_tools()でMCPサーバーのツールのリストを取得しています

CodeAgent の追加ツールとして SQLcl MCPサーバーのツール群を渡す

SQLcl MCPサーバーのツール群追加
agent = CodeAgent(
    tools=[get_mermaid_script_guidelines_tool, generate_mermaid_diagram_tool, *sqlcl_tools],  
    model=model,
    use_structured_outputs_internally=False,
    max_steps=10,
    additional_authorized_imports=["json"],
    stream_outputs=True
)

ここで、MCP非対応版から変わったのは、toolsパラメータに *sqlcl_toolsを追加していることだけです。なお、sqlcl_mcp_client.get_tools()P はリストを返すため、ここではsqlcl_tools*でアンパックしています。

MCPサーバーのツールを利用可能にするためのコードは以上ですべてです!(なんと簡単!)

利用可能なデータベース接続を調べてみる

プロンプト エージェントの応答
利用可能なDB接続を教えて 利用可能なDB接続: Dev Day Tokyo orasejapan - wksp_apexdev, vector_adb23aai01_private, Dev Day Tokyo orasejapan - devday25user, Dev Day orasejapan - devday25user, Dev Day Tokyo orasejapan - admin, docuser_adb23ai01_private, adb23ai01_high, Dev Day Tokyo orasejapan - labuser, docuser2_adb23ai02_private, docuser2_adb23ai01_private, adb23ai02_high, Dev Day orasejapan - admin, Dev Day Tokyo orasejapan - opuser

UIは工夫の余地がありありですが、機能はちゃんと動作していますね。SQL Developer で定義しているデータベース接続がすべて見えています。

データベースに接続してみる

プロンプト エージェントの応答
Dev Day Tokyo orasejapan - labuser につないで Dev Day Tokyo orasejapan - labuser に接続しました。

テーブルの一覧を取得する

プロンプト エージェントの応答
名前に$記号が入っていないテーブルのリストを
1行1テーブルで出して
AI_PROFILE_VECTOR_SUMMARY
IMAGES
VECTOR_DOCUMENTS

テーブルの構造を調べる

プロンプト エージェントの応答
IMAGES の構造は? "名前","Nullかどうか","タイプ"
"IMAGE_ID","NOT NULL","NUMBER"
"FILE_NAME"," ","VARCHAR2(255)"
"CAPTION"," ","VARCHAR2(4000)"
"CAPTION_EMBEDDING"," ","VECTOR(,,DENSE)"
"IMAGE_DATA"," ","BLOB"
"IMAGE_EMBEDDING"," ","VECTOR(,,DENSE)"
"UPLOAD_DATE"," ","TIMESTAMP(6)"

出力コンポーネントを Markdown コンポーネントにしておけばよかったというのは置いておいて、テーブル構造も得られています。ログを見てみると SQLcl MCP サーバーの run_sqlclツールを使ってDESCコマンドを実行していました。パチパチ!

ER図を描く

プロンプト
それのER図を描いて
ER図
ER図images.png

「それ」と書いただけですが、これまでの対話を覚えていますので IMAGES のER図を描いてくれています。また、DDLは渡していませんが MCPをp使ってデータベースからテーブル構造を取得して ER図を生成しています。

プロンプト
SHスキーマのER図を描いて
ER図
ER図sh.png

こちらも無事 MCPで SHスキーマのテーブル一覧を取得、テーブル構造を取得して ER図を描いてくれています。
ただし、ときどきテーブル間のリレーションだけ描いてテーブルの構造を省略することがあるので、「各テーブルの構造(カラムの構成)が抜けています。」などと指摘すると直してくれます。

この「システム設計支援エージェント」は、対話履歴(コンテキスト)をメモリ中に記憶して、送信ボタンが押される都度すべての対話履歴が AIモデルへ送信されます。対話履歴には以下のようなデータが含まれます。

  • ユーザーインプットや タスク情報
  • 各ステップで生成・実行された Python コード(AIモデル が出力したコード、実際に実行されたコード本体)
  • ツール起動指示(tool calls) とその 実行結果(observation/result)
  • ステップごとのエラーや実行ログ、print 出力
  • 最終回答(Final Answer)

このデータ量は急速に大きくなりますので、対話を続けるとAIモデルの入力データ長(コンテキスト長、コンテキストウィンドウ)の上限に達してしまいます。その場合は、AIモデルのAPIサービスからエラーが返って来ます。
また、AIモデルのAPIサービスでトークン単位、もしくは、キャラクター単位の課金が設定されている場合は費用もかさむこととなります。この記事の「システム設計支援エージェント」にはこの履歴をリセットする機能を実装していません(クリアボタンは画面をクリアするだけで履歴はリセットしません)。履歴をリセットするためには、アプリケーション自体を再起動してください。

実利用を想定した開発では、履歴(コンテキスト)の管理(サマライズやリセット)に配慮することが必要です。 ⇒ 最近話題のコンテキストエンジニアリングですね!

おしまい

このブログのコードは下記のリポジトリで公開しています。

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?