このブログは?
オープンソースの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図を描くこともできます。
| プロンプト |
|---|
| スマホアプリから自然言語で問い合わせると会社の社内規定を類似性検索して自然言語で答えてくれるシステムのシーケンス図を描いてください。類似性検索のための具体的な処理や自然言語で回答を生成する処理も詳しく記載してください。 |
システム設計支援エージェントの成果物例
|
この例では、いわゆる RAG 技術を使った社内規定のQAシステムのシーケンス図を描いています。
※【まどマギ・ネタバレ注意】
エージェントの頭脳にあたる LLM は物知りなので、こんなフローチャートも描けます!
smolagents とは?
smaolagentsは、Hugging Face が公開している AIエージェント構築のための軽量な Python ライブラリです。
Hugging Face の smolagents のページにはこんな楽しいロゴが載っています。
007 の「殺しのライセンス」のパロディですね。
なぜ "Call"なのでしょう?
Hugging Face の公式見解を見たわけではないので想像ですけど、AIエージェントがAIエージェントであるゆえんは、「考えて(必要なアクションを計画して)」、「アクションを実行して」、「アクションの結果を観察して」、「次のアクションを考える」というところにあるわけですが、この「アクション」は、LLM 自体に手足耳目があるわけではないので、LLMの外のツールを呼び出す必要があるというところからエージェントの象徴として選ばれているのだと思います。そして、smolagents は、ツールの呼び出しにコードを使うコードエージェントに重きを置いているところが特徴的です。
このブログは?、再び
このブログの内容(+α)は、Oracle Code Night - Oracle AI Jam Session #28 「これからの生成AI ~ゼロからはじめるAIエージェント」 でデモを交えてご紹介させていただきました。下記にアーカイブもありますので、ぜひお立ち寄りください。
こんな方のお役に立てることを目指しています
- はじめてAIエージェントのコードを書いてみようと思っている方
- AIエージェントは知っているけどコードエージェントって何と思われた方
ノーコードでエージェント作りたいんだけどという方へ
- ご安心ください。ウェビナー Oracle AI Jam Session #28 「これからの生成AI~ゼロからはじめるAIエージェント~」ではノーコードの AIエージェントプラットフォーム n8n(エヌエイトエヌ) も扱います!
- n8n で、OCI Generative 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 で詳細に検討されています。
- コードベースは、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 の例)
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 の例)
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 の例)
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_userとoci_tenancyユーザー名やテナンシー名ではなく、必ず OCID を指定してください -
oci_keyキーファイルのパスではなく、秘密鍵の内容そのものを記述します。各行末に\nを付けて一行に連結してください -
drop_params: trueOCI Generative AI がサポートしていないパラメータ(stream_optionsなど)を LiteLLM 側で自動的に削除します。これを設定しないとException: param stream_options is not supportedなどといったエラーが発生します -
master_keyLiteLLM 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 から直接インストールする手順
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なし)の初期化
agent = CodeAgent(tools=[], model=model)
- ツールは渡さないので、tools は空です
- model には、先程、LiteLLMModel として初期化した model(OCIの xAI Grok 4) を設定します
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なし)のコード例の全体
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アクセス)の初期化
agent = CodeAgent(tools=[],
model=model,
additional_authorized_imports=['requests', 'bs4']
)
additional_authorized_imports=['requests', 'bs4'] というパラメータを追加しました。
additional_authorized_imports は、CodeAgent が生成する Pythonコードが使用できるライブラリを追加するオプションです。今回は、Webアクセスの際に利用しそうな requests と bs4 を追加しています。
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アクセス)のコード例の全体
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)指定したウェブページのコンテンツを
requestsのgetメソッドで取得して、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アクセス)と同じです。
agent = CodeAgent(tools=[],
model=model,
additional_authorized_imports=['requests', 'bs4']
)
CodeAgent(Toolなしで WebAPI)の実行
今回はプロンプトの中にある地域の天気予報を取得して欲しいという指示と、 2つの WebAPI のエンドポイントの URL とクエリーパラメータの説明を記載します。
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)のコード例の全体
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日)の天気予報を取得できています。
処理は以下のように行われました。
- 「東京ディズニーランド」という地名(施設名)から国土地理院の API で候補地域のリストを取得する Pythonコードを生成して実行。ただし、デフォルトでは許可されていない json ライブラリを import しようとしたためエラーとなる
- 「東京ディズニーランド」という地名(施設名)から国土地理院の API で候補地域のリストを取得する Pythonコードを前のステップでエラーとなった json ライブラリの import をしないようにして再度生成して実行
- 国土地理院の API の応答(リストのフォーマット)を見て、リストからターゲット地域(「東京ディズニーランド」)の緯度経度を抽出し、続けて、Open Meteo の API で天気予報を取得する Pythonコードを生成して実行
Open Metao の API は、json 形式で応答することはわかっていますので additional_authorized_imports に json を含めておくべきだったかもしれません。しかし、このような場合にも回避策があれば自動的に適用して再実行してくれるところが便利ですね。
CodeAgent(toolあり)- システム設計支援エージェント
【ソースコード(400_system_design_agent_gradio.py)】
やっとツールを使う例に辿り着きました!CodeAgent はツールがなくてもいろいろなことができますのでここまでの道のりが長かったですね。
ここで作るのは冒頭で成果物を載せているシステム設計支援エージェントです。
もう一度載せるとこんなものを作ってくれます。

このエージェントはこんなことができます。
- 自然言語の指示で 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あり)- システム設計支援エージェント の初期化
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あり)- システム設計支援エージェント の実行
実際にエージェントを実行する部分です。
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あり)- システム設計支援エージェント のコード全体
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の実行例(ログ)
╭─────────────────────────────────── 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の実行例(ログ)
╭─────────────────────────────────── 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]
| プロンプト(ターン3) |
|---|
| 類似性検索関連と回答生成関連を別々のサブグラフに分けて分かりやすくしてください。 |
システム設計支援エージェントの成果物例
|
なかなか良いですね。
この後に2回程調整をお願いしてできあがったものが次のシーケンス図です。

Oracleデータベース SHスキーマのER図
Oracle データべースのSHサンプルスキーマの9つのテーブルのDDLを渡して ER図を描いてもらいました。
SHスキーマのDDL
| Oracle データベース SHスキーマのER図 |
|---|
| 次のDDLのER図を描いてください。テーブルの構造も記載してください。 ・・・DDLは長いのでブログ上は省略・・・ |
システム設計支援エージェントの成果物例
|
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
上図は、Introducing MCP Server for Oracle Databaseより引用
利用可能なツールは、5つあります。
| ツール名 | 概要 |
|---|---|
| list-connections | 保存されているデータベース接続の一覧を取得します |
| connect | 指定された接続を使ってデータベースへ接続します |
| disconnect | データベース接続を切断します |
| run-sqlcl | SQLclコマンドを実行します |
| run-sql | SQLクエリを実行します |
SQLcl の設定手順は以下の記事でご紹介しています。このあたりです。
なお、MCPとは何かについては下記の記事でご紹介していますので、MCPは初めてとか、MCPでDBにつながったら何ができるの?という方は、ぜひお立ち寄りください。
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を指定しています -
MCPClientにsqlcl_server_parametersを渡して、MCPサーバーを初期化しています -
sqlcl_mcp_client.get_tools()でMCPサーバーのツールのリストを取得しています
CodeAgent の追加ツールとして 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図
|
「それ」と書いただけですが、これまでの対話を覚えていますので IMAGES のER図を描いてくれています。また、DDLは渡していませんが MCPをp使ってデータベースからテーブル構造を取得して ER図を生成しています。
| プロンプト |
|---|
| SHスキーマのER図を描いて |
ER図
|
こちらも無事 MCPで SHスキーマのテーブル一覧を取得、テーブル構造を取得して ER図を描いてくれています。
ただし、ときどきテーブル間のリレーションだけ描いてテーブルの構造を省略することがあるので、「各テーブルの構造(カラムの構成)が抜けています。」などと指摘すると直してくれます。
この「システム設計支援エージェント」は、対話履歴(コンテキスト)をメモリ中に記憶して、送信ボタンが押される都度すべての対話履歴が AIモデルへ送信されます。対話履歴には以下のようなデータが含まれます。
- ユーザーインプットや タスク情報
- 各ステップで生成・実行された Python コード(AIモデル が出力したコード、実際に実行されたコード本体)
- ツール起動指示(tool calls) とその 実行結果(observation/result)
- ステップごとのエラーや実行ログ、print 出力
- 最終回答(Final Answer)
このデータ量は急速に大きくなりますので、対話を続けるとAIモデルの入力データ長(コンテキスト長、コンテキストウィンドウ)の上限に達してしまいます。その場合は、AIモデルのAPIサービスからエラーが返って来ます。
また、AIモデルのAPIサービスでトークン単位、もしくは、キャラクター単位の課金が設定されている場合は費用もかさむこととなります。この記事の「システム設計支援エージェント」にはこの履歴をリセットする機能を実装していません(クリアボタンは画面をクリアするだけで履歴はリセットしません)。履歴をリセットするためには、アプリケーション自体を再起動してください。
実利用を想定した開発では、履歴(コンテキスト)の管理(サマライズやリセット)に配慮することが必要です。 ⇒ 最近話題のコンテキストエンジニアリングですね!
おしまい
このブログのコードは下記のリポジトリで公開しています。








