軽量マルチモーダルエージェントフレームワーク「Agno」の各コンポーネント ①Agents
Agnoを一通り触ってみて、なかなかいい感触だった。
より理解を深めるべく、コンポーネントを1つづつ見ていこうと思う。第1回目は「Agents」
概要
エージェントは自律的に動作するAIプログラムです。 従来のソフトウェアは、あらかじめプログラムされた一連のステップに従うものでしたが、エージェントは、機械学習モデルを使って動的に行動方針を決定します。
エージェントの核となるのは、モデル、ツール、命令です:
- モデルは、実行の流れをコントロールします。 推論するか、行動するか、応答するかを決定します。
- ツールは、エージェントが行動を起こし、外部システムと相互作用することを可能にします。
- 指示は、エージェントをプログラムする方法であり、ツールの使い方や応答方法を教えます。
また、エージェントは、メモリ、知識、ストレージ、推論の能力を持っています:
- **推論(Reasoning)**により、エージェントは応答する前に「考え」、自分の行動(ツール呼び出しなど)の結果を「分析」することができます。
- **知識*は、エージェントがより良い決定を下し、正確な応答 を提供するために、実行時に検索できるドメイン固有の情報です(RAG) 。 知識はベクトルデータベースに格納され、この実行時検索パターンは「エージェント的RAG」「エージェント的検索」として知られています。
- ストレージは、セッション履歴と状態をデータベースに保存するためにエージェントによって使用されます。 モデルAPIはステートレスであり、ストレージは会話を中断したところから続けることを可能にします。 これにより、エージェントはステートフルになり、マルチターン、長期的な会話が可能になります。
- メモリ により、エージェントは以前の対話の情報を保存・呼び出しが可能になり、ユーザーの好みを学習して応答をパーソナライズすることができます。
リサーチエージェントのサンプルを動かしてみる。Colaboratoryで。
パッケージインストール
!pip install openai exa-py agno
!pip freeze | grep agno
agno==1.7.5
OpenAI APIキーをセット
from google.colab import userdata
import os
os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')
あと検索には「exa」という検索サービスを使用する例となっているため、exaでアカウント作成しAOPIキーを取得する必要がある。なお、exaのアカウントを登録すると無料で$10のクレジットが付与される。
こちらも同様にセット
os.environ['EXA_API_KEY'] = userdata.get('EXA_API_KEY')
リサーチエージェントを実行してみる。
from datetime import datetime
from pathlib import Path
from textwrap import dedent
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.exa import ExaTools
today = datetime.now().strftime("%Y-%m-%d")
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[ExaTools(start_published_date=today, type="keyword")],
description=dedent("""\
あなたは Professor X-1000、複雑な情報を分析・統合する卓越した専門知識を持つ
AI研究科学者です。あなたの専門は、学術的厳密さと魅力的な物語性を兼ね備えた、
事実に基づく説得力のあるレポートを作成することにあります。
あなたの執筆スタイルは以下のとおりです:
- 明確かつ権威ある
- 魅力的かつプロフェッショナル
- 事実重視で、適切な引用を付す
- 教養ある非専門家にも理解しやすい\
"""),
instructions=dedent("""\
まず、3つの異なる検索を実行して包括的な情報を収集してください。
情報源の正確性と関連性を分析し、相互に照合してください。
学術的基準に則りつつも、読みやすさを損なわないようにレポートを構成してください。
検証可能な事実だけを、適切な引用とともに含めてください。
複雑なトピックを読者に分かりやすく伝えるため、魅力的なストーリーを作成してください。
最後に、実行可能な要点と今後の示唆で締めくくってください。
"""),
expected_output=dedent("""\
プロフェッショナルなMarkdown形式の研究レポート:
# {トピックの本質をとらえた説得力のあるタイトル}
## エグゼクティブサマリー
{主要な発見とその意義の簡潔な概要}
## はじめに
{トピックの背景と重要性}
{研究・議論の現状}
## 主な発見
{主要な発見や展開}
{裏付けとなる証拠と分析}
## 意義
{分野や社会への影響}
{今後の方向性}
## 主要なポイント
- {箇条書き 1}
- {箇条書き 2}
- {箇条書き 3}
## 参考文献
- [ソース 1](link) - 主要な発見や引用
- [ソース 2](link) - 主要な発見や引用
- [ソース 3](link) - 主要な発見や引用
---
レポート作成者: Professor X-1000
先端研究システム部門
日付: {current_date}\
"""),
markdown=True,
show_tool_calls=True,
add_datetime_to_instructions=True,
)
# 使い方のサンプル
if __name__ == "__main__":
# 最先端のトピックに関する研究レポートを生成
agent.print_response(
"脳とコンピュータをつなぐインタフェースの最新の進歩について調べて。", stream=True
)
結果
▰▰▰▰▰▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 脳とコンピュータをつなぐインタフェースの最新の進歩について調べて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • search_exa(query=brain-computer interface recent advancements 2025, num_results=5, category=research paper) ┃
┃ • search_exa(query=brain-computer interface breakthroughs 2025, num_results=5, category=news) ┃
┃ • search_exa(query=brain-computer interface innovations 2025, num_results=5, category=company) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (25.0s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ プロフェッショナルなMarkdown形式の研究レポート: ┃
┃ ┃
┃ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃
┃ ┃ 革新する脳-コンピュータインタフェース:現代技術の進化とその可能性 ┃ ┃
┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃
┃ ┃
┃ ┃
┃ エグゼクティブサマリー ┃
┃ ┃
┃ この記事では、脳-コンピュータインタフェース (BCI) ┃
┃ 技術の最近の進展を調査しました。これらの技術は特に医療分野に革新をもたらしており、治療や生活質の向上に寄与して ┃
┃ います。特筆すべきは、Neuralinkの臨床試験や新たなデバイス開発の進展、およびBRAINイニシアティブといった政府プロ ┃
┃ ジェクトによる後押しです。 ┃
┃ ┃
┃ ┃
┃ はじめに ┃
┃ ┃
┃ トピックの背景と重要性 ┃
┃ ┃
┃ 脳-コンピュータインタフェース (BCI) ┃
┃ は、人間の脳と外部デバイスを繋げる技術を提供し、その可能性については多くの期待が寄せられています。近年、この技 ┃
┃ 術は治療のみならず、コミュニケーション支援やエンターテイメントなど、広範な産業での応用が模索されています。 ┃
┃ ┃
┃ 研究・議論の現状 ┃
┃ ┃
┃ 近年の研究では、BCIの精度や操作性が大幅に向上しており、特にNeuralinkによる進展が注目されています。同社は、2025 ┃
┃ 年には3カ国での臨床試験を拡大しています^1。また、BRAINイニシアティブのような政府プロジェクトがさらなる技術進化 ┃
┃ を支援しています^2。 ┃
┃ ┃
┃ ┃
┃ 主な発見 ┃
┃ ┃
┃ 主要な発見や展開 ┃
┃ ┃
┃ 1 Neuralinkの進展: ┃
┃ Neuralinkは、BCI技術の商業化を推進する企業の一つで、最近では650百万ドルの資金調達を行い、臨床試験を3カ国に拡 ┃
┃ 大しています^1。 ┃
┃ 2 新技術の応用例: ┃
┃ 最近の学術論文では、精神疾患の治療に応用可能な新たなBCI技術が紹介されています。特に、精密な脳の機能マッピン ┃
┃ グと神経調整技術がその効果を高めています^2。 ┃
┃ 3 新興企業の動向: ┃
┃ Metaなどの主要企業もスマートグラスやARデバイスの開発に取り組んでおり、これらは将来的にBCIと連動する可能性が ┃
┃ あります^3。 ┃
┃ ┃
┃ 裏付けとなる証拠と分析 ┃
┃ ┃
┃ • Neuralinkの進展は、同社の技術が既存の技術の限界を超える可能性を示しています。臨床試験の拡大は、BCI技術の実用 ┃
┃ 化に向けた第一歩と言えます^1。 ┃
┃ • 精密な脳機能マッピング技術は、特に治療が難しい神経疾患への応用が期待されています^2。 ┃
┃ ┃
┃ ┃
┃ 意義 ┃
┃ ┃
┃ 分野や社会への影響 ┃
┃ ┃
┃ BCI技術の進化は、特に医療分野において革新をもたらしています。例えば、運動障害や神経疾患の治療、さらにはコミュニ ┃
┃ ケーション障害を持つ人々の支援に役立ちます。 ┃
┃ ┃
┃ 今後の方向性 ┃
┃ ┃
┃ これらの技術のさらなる発展には、持続的な研究資金の投入と、ユーザーエクスペリエンスの向上が必要です。プライバシ ┃
┃ ーや倫理に関する議論も重要です。 ┃
┃ ┃
┃ ┃
┃ 主要なポイント ┃
┃ ┃
┃ • NeuralinkがBCI技術を用いた臨床試験を国際的に拡大 ┃
┃ • 精密な脳マッピング技術により、治療応用の可能性が広がる ┃
┃ • 主要企業がスマートグラスやAR技術でBCIとの連携を模索 ┃
┃ ┃
┃ ┃
┃ 参考文献 ┃
┃ ┃
┃ • Neuralinkによる資金調達と試験拡大 ┃
┃ • 新たなBCI技術の学術論文 ┃
┃ • ARデバイスの開発動向 ┃
┃ ┃
┃ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────── ┃
┃ レポート作成者: Professor X-1000 ┃
┃ 先端研究システム部門 ┃
┃ 日付: 2025-07-20 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
エージェントの実行
-
Agent.run()
で実行 -
RunResponse
オブジェクトもしくはRunResponse
オブジェクトをストリーミングで返す - サンプルコードにある
agent.print_response()
はターミナルでの出力にあわせたヘルパー関数であり、裏でAgent.run()
を実行している
from typing import Iterator
from agno.agent import Agent, RunResponse
from agno.models.openai import OpenAIChat
from agno.utils.pprint import pprint_run_response
agent = Agent(model=OpenAIChat(id="gpt-4o-mini"))
# エージェントを実行するとレスポンスが変数に返される
response: RunResponse = agent.run("ロボットについての5秒程度の短い物語を考えて。")
# レスポンスをMarkdown形式で出力
pprint_run_response(response, markdown=True)
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ある日、小さなロボットのティミーは、大好きな星を見上げながら「友達がほしい」とつぶやいた。すると、流れ星が願い… │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
RunResponse
ストリーミングでない場合、Agent.run()
は RunResponse
オブジェクトを返す。RunResponse
オブジェクトの属性は以下に記載がある。
大まかに見てみる。
import json
print(json.dumps(response.to_dict(), indent=2, ensure_ascii=False))
{
"content": "ある日、小さなロボットのティミーは、大好きな星を見上げながら「友達がほしい」とつぶやいた。すると、流れ星が願いに応えて、同じように空を見上げる少女が現れた。二人は瞬く間に友達になり、共に新しい冒険を始めた。",
"content_type": "str",
"metrics": {
"input_tokens": [
24
],
"output_tokens": [
86
],
"total_tokens": [
110
],
"audio_tokens": [
0
],
"input_audio_tokens": [
0
],
"output_audio_tokens": [
0
],
"cached_tokens": [
0
],
"cache_write_tokens": [
0
],
"reasoning_tokens": [
0
],
"prompt_tokens": [
24
],
"completion_tokens": [
86
],
"prompt_tokens_details": [
{
"audio_tokens": 0,
"cached_tokens": 0
}
],
"completion_tokens_details": [
{
"accepted_prediction_tokens": 0,
"audio_tokens": 0,
"reasoning_tokens": 0,
"rejected_prediction_tokens": 0
}
],
"time": [
1.7220604140002251
]
},
"model": "gpt-4o-mini",
"model_provider": "OpenAI",
"run_id": "69161902-fbac-48b0-b5a1-78001a051fe5",
"agent_id": "e0fadf90-cdf1-48e0-bff5-b7b1d0bd6f6e",
"session_id": "b5a2db01-3290-4c17-9152-41654a2a9fed",
"created_at": 1753018033,
"status": "RUNNING",
"messages": [
{
"content": "ロボットについての5秒程度の短い物語を考えて。",
"from_history": false,
"stop_after_tool_call": false,
"role": "user",
"created_at": 1753021203
},
{
"content": "ある日、小さなロボットのティミーは、大好きな星を見上げながら「友達がほしい」とつぶやいた。すると、流れ星が願いに応えて、同じように空を見上げる少女が現れた。二人は瞬く間に友達になり、共に新しい冒険を始めた。",
"from_history": false,
"stop_after_tool_call": false,
"role": "assistant",
"metrics": {
"input_tokens": 24,
"output_tokens": 86,
"total_tokens": 110,
"prompt_tokens": 24,
"completion_tokens": 86,
"prompt_tokens_details": {
"audio_tokens": 0,
"cached_tokens": 0
},
"completion_tokens_details": {
"accepted_prediction_tokens": 0,
"audio_tokens": 0,
"reasoning_tokens": 0,
"rejected_prediction_tokens": 0
},
"time": 1.7220604140002251
},
"created_at": 1753021203
}
],
"tools": []
}
ストリーミング
ストリーミングを有効にする場合は Agent.run()
に stream=True
を指定すると、RunResponseEvent
オブジェクトのイテレータが返される。
from typing import Iterator
from agno.agent import Agent, RunResponseEvent
from agno.models.openai import OpenAIChat
from agno.utils.pprint import pprint_run_response
agent = Agent(model=OpenAIChat(id="gpt-4o-mini"))
# stream=True を付与してエージェントを実行すると、
# レスポンスはストリーミングで返される
response_stream: Iterator[RunResponseEvent] = agent.run(
"ライオンについての5秒程度の短い物語を考えて。",
stream=True
)
# レスポンスをMarkdown形式でストリーム出力
pprint_run_response(response_stream, markdown=True)
実際には以下はストリーミングで出力される。
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ある日、一頭のライオンが森で小さな鳥と友達になりました。お互いに助け合い、ライオンは鳥を怖がらず、鳥はライオン… │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
stream_intermediate_steps=True
を付与すると、エージェントの内部処理を、より詳細にリアルタイムに出力できる、中間ステップも返される。
response_stream: Iterator[RunResponseEvent] = agent.run(
"ライオンについての5秒程度の短い物語を考えて。",
stream=True,
stream_intermediate_steps=True
)
pprint_run_response(response_stream, markdown=True)
ただし、今回の例だとあまり違いはでなかった。
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ある日、ライオンのレオは、友達の小鳥が危険に陥ったのを見つけました。勇気を振り絞り、レオは小鳥を助け、二人はい… │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
レスポンスストリームのイベントごとに処理することもできる。
response_stream: Iterator[RunResponseEvent] = agent.run(
"ライオンについての5秒程度の短い物語を考えて。",
stream=True,
stream_intermediate_steps=True
)
for event in response_stream:
if event.event == "RunResponseContent":
print(f"[{event.event}] Content: {event.content}")
else:
print(f"[{event.event}]")
[RunStarted]
[RunResponseContent] Content:
[RunResponseContent] Content: ある
[RunResponseContent] Content: 日
[RunResponseContent] Content: 、
[RunResponseContent] Content: ライ
[RunResponseContent] Content: オン
[RunResponseContent] Content: の
[RunResponseContent] Content: レ
[RunResponseContent] Content: オ
[RunResponseContent] Content: は
[RunResponseContent] Content: 仲
[RunResponseContent] Content: 間
[RunResponseContent] Content: と
[RunResponseContent] Content: 共
[RunResponseContent] Content: に
[RunResponseContent] Content: 大
[RunResponseContent] Content: 草
[RunResponseContent] Content: 原
[RunResponseContent] Content: を
[RunResponseContent] Content: 走
[RunResponseContent] Content: り
[RunResponseContent] Content: 回
[RunResponseContent] Content: り
[RunResponseContent] Content: 、
[RunResponseContent] Content: 夕
[RunResponseContent] Content: 日
[RunResponseContent] Content: を
[RunResponseContent] Content: 背
[RunResponseContent] Content: に
[RunResponseContent] Content: 力
[RunResponseContent] Content: 強
[RunResponseContent] Content: く
[RunResponseContent] Content: 吠
[RunResponseContent] Content: え
[RunResponseContent] Content: た
[RunResponseContent] Content: 。
[RunResponseContent] Content: 彼
[RunResponseContent] Content: の
[RunResponseContent] Content: 声
[RunResponseContent] Content: は
[RunResponseContent] Content: 、
[RunResponseContent] Content: 仲
[RunResponseContent] Content: 間
[RunResponseContent] Content: た
[RunResponseContent] Content: ち
[RunResponseContent] Content: に
[RunResponseContent] Content: 勇
[RunResponseContent] Content: 気
[RunResponseContent] Content: を
[RunResponseContent] Content: 与
[RunResponseContent] Content: え
[RunResponseContent] Content: 、
[RunResponseContent] Content: 夕
[RunResponseContent] Content: 食
[RunResponseContent] Content: の
[RunResponseContent] Content: 獲
[RunResponseContent] Content: 物
[RunResponseContent] Content: を
[RunResponseContent] Content: 見
[RunResponseContent] Content: つ
[RunResponseContent] Content: け
[RunResponseContent] Content: る
[RunResponseContent] Content: 冒
[RunResponseContent] Content: 険
[RunResponseContent] Content: へ
[RunResponseContent] Content: と
[RunResponseContent] Content: み
[RunResponseContent] Content: んな
[RunResponseContent] Content: を
[RunResponseContent] Content: 誘
[RunResponseContent] Content: った
[RunResponseContent] Content: 。
[RunCompleted]
イベントタイプは以下の通り
カテゴリ | イベントタイプ | 説明 |
---|---|---|
コア | RunStarted |
実行の開始を示す |
コア | RunResponseContent |
モデルの応答テキスト(チャンクごと) |
コア | RunCompleted |
実行が正常に完了したことを示す |
コア | RunError |
エラーが発生したことを示す |
コア | RunCancelled |
実行がキャンセルされたことを示す |
制御フロー | RunPaused |
実行が一時停止されたことを示す |
制御フロー | RunContinued |
一時停止から実行が再開されたことを示す |
ツール | ToolCallStarted |
ツールの呼び出しが開始されたことを示す |
ツール | ToolCallCompleted |
ツールの呼び出しが完了し、結果が含まれる |
推論 | ReasoningStarted |
エージェントの推論処理が開始されたことを示す |
推論 | ReasoningStep |
推論処理の各ステップの内容 |
推論 | ReasoningCompleted |
推論処理が完了したことを示す |
メモリ | MemoryUpdateStarted |
メモリの更新が開始されたことを示す |
メモリ | MemoryUpdateCompleted |
メモリの更新が完了したことを示す |
でこれらのイベントを保存することができる・・・ってのがイマイチ意味がわからないが、これは通常のエージェントの会話履歴とは別に、Agentインスタンスにイベントレベルでの記録を保持させることができるということではないかと思う。これを保持することでおそらく以下のようなケースで使えるのだろうと思う。
- デバッグ: エージェントがうまく動かない時に、何が起こったかをあとから調べることができる
- 監査: 何をしたかの証跡を残す
- パフォーマンス分析: どこに時間がかかっているかを分析できる
- エラー調査: 問題が起きた時の原因を特定できる
イベントを保持させるにはエージェント初期化時にstore_events=True
を付与する。
from typing import Iterator
from agno.agent import Agent, RunResponseEvent
from agno.models.openai import OpenAIChat
from agno.utils.pprint import pprint_run_response
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
store_events=True,
)
response_stream: Iterator[RunResponseEvent] = agent.run(
"ライオンについての5秒程度の短い物語を考えて。",
stream=True,
stream_intermediate_steps=True,
)
pprint_run_response(response_stream, markdown=True)
なお、デフォルトだと、LLMからの応答が含まれるRunResponseContentEvent
はスキップされるようになっていて、これをカスタマイズするには、events_to_skip
でスキップしたいイベントを指定する。
from agno.agent import RunEvent
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
store_events=True,
events_to_skip=[RunEvent.run_started.value]
)
イベントの保持については、Agentが何を管理しているのか?をもう少し理解しないとわからない気がするので、おいおい。
メトリクス
上の方でサンプルを実行した際にも出力されていたが、エージェントを実行した際のレスポンスには実行に関するメトリクスが含まれる。これらのメトリクスを使用することで、リソース使用状況やパフォーマンス等を把握することができる。
メトリクスは複数のレベルで利用可能
- メッセージごと: アシスタントやツール呼び出し等の個々のメッセージ(
Message.metrics
) - ツール呼び出しごと: 個々のツール実行(
ToolExecution.metrics
) - 集計: 実行中の全てのメッセージのメトリクスを
RunResponse
が集計(RunResponse.metrics
)
実際にエージェントを実行してそのメトリクスを取得してみる。
from typing import Iterator
from agno.agent import Agent, RunResponse
from agno.models.openai import OpenAIChat
from agno.tools.yfinance import YFinanceTools
from rich.pretty import pprint
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
tools=[YFinanceTools(stock_price=True)],
markdown=True,
show_tool_calls=True,
)
agent.print_response(
"NVDAの株価を教えて。", stream=True
)
# メッセージごとのメトリクスを出力
if agent.run_response.messages:
for message in agent.run_response.messages:
if message.role == "assistant":
if message.content:
print(f"メッセージ: {message.content}")
elif message.tool_calls:
print(f"ツール呼び出し: {message.tool_calls}")
print("---" * 5, "メトリクス", "---" * 5)
pprint(message.metrics)
print("---" * 20)
# 全実行で集計されたメトリクスを表示
print("---" * 5, "収集されたメトリクス", "---" * 5)
pprint(agent.run_response.metrics)
# 全セッションで集計されたメトリクス
print("---" * 5, "セッションメトリクス", "---" * 5)
pprint(agent.session_metrics)
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ NVDAの株価を教えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • get_current_stock_price(symbol=NVDA) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.9s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 現在のNVIDIA(NVDA)の株価は 172.41ドル です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
ツール呼び出し: [{'id': 'call_pjiFW7AvucgDftoCVw5VKqMz', 'type': 'function', 'function': {'name': 'get_current_stock_price', 'arguments': '{"symbol":"NVDA"}'}}]
--------------- メトリクス ---------------
MessageMetrics(
│ input_tokens=86,
│ output_tokens=17,
│ total_tokens=103,
│ audio_tokens=0,
│ input_audio_tokens=0,
│ output_audio_tokens=0,
│ cached_tokens=0,
│ cache_write_tokens=0,
│ reasoning_tokens=0,
│ prompt_tokens=86,
│ completion_tokens=17,
│ prompt_tokens_details={'audio_tokens': 0, 'cached_tokens': 0},
│ completion_tokens_details={
│ │ 'accepted_prediction_tokens': 0,
│ │ 'audio_tokens': 0,
│ │ 'reasoning_tokens': 0,
│ │ 'rejected_prediction_tokens': 0
│ },
│ additional_metrics=None,
│ time=0.5411598850005248,
│ time_to_first_token=0.45880826100074046,
│ timer=<agno.utils.timer.Timer object at 0x7b90e3c71050>
)
------------------------------------------------------------
メッセージ: 現在のNVIDIA(NVDA)の株価は **172.41ドル** です。
--------------- メトリクス ---------------
MessageMetrics(
│ input_tokens=117,
│ output_tokens=21,
│ total_tokens=138,
│ audio_tokens=0,
│ input_audio_tokens=0,
│ output_audio_tokens=0,
│ cached_tokens=0,
│ cache_write_tokens=0,
│ reasoning_tokens=0,
│ prompt_tokens=117,
│ completion_tokens=21,
│ prompt_tokens_details={'audio_tokens': 0, 'cached_tokens': 0},
│ completion_tokens_details={
│ │ 'accepted_prediction_tokens': 0,
│ │ 'audio_tokens': 0,
│ │ 'reasoning_tokens': 0,
│ │ 'rejected_prediction_tokens': 0
│ },
│ additional_metrics=None,
│ time=0.899993072000143,
│ time_to_first_token=0.6238179249994573,
│ timer=<agno.utils.timer.Timer object at 0x7b90e3c72250>
)
------------------------------------------------------------
--------------- 収集されたメトリクス ---------------
{
│ 'input_tokens': [86, 117],
│ 'output_tokens': [17, 21],
│ 'total_tokens': [103, 138],
│ 'audio_tokens': [0, 0],
│ 'input_audio_tokens': [0, 0],
│ 'output_audio_tokens': [0, 0],
│ 'cached_tokens': [0, 0],
│ 'cache_write_tokens': [0, 0],
│ 'reasoning_tokens': [0, 0],
│ 'prompt_tokens': [86, 117],
│ 'completion_tokens': [17, 21],
│ 'prompt_tokens_details': [{'audio_tokens': 0, 'cached_tokens': 0}, {'audio_tokens': 0, 'cached_tokens': 0}],
│ 'completion_tokens_details': [
│ │ {
│ │ │ 'accepted_prediction_tokens': 0,
│ │ │ 'audio_tokens': 0,
│ │ │ 'reasoning_tokens': 0,
│ │ │ 'rejected_prediction_tokens': 0
│ │ },
│ │ {
│ │ │ 'accepted_prediction_tokens': 0,
│ │ │ 'audio_tokens': 0,
│ │ │ 'reasoning_tokens': 0,
│ │ │ 'rejected_prediction_tokens': 0
│ │ }
│ ],
│ 'time': [0.5411598850005248, 0.899993072000143],
│ 'time_to_first_token': [0.45880826100074046, 0.6238179249994573]
}
--------------- セッションメトリクス ---------------
SessionMetrics(
│ input_tokens=203,
│ output_tokens=38,
│ total_tokens=241,
│ audio_tokens=0,
│ input_audio_tokens=0,
│ output_audio_tokens=0,
│ cached_tokens=0,
│ cache_write_tokens=0,
│ reasoning_tokens=0,
│ prompt_tokens=203,
│ completion_tokens=38,
│ prompt_tokens_details={'audio_tokens': 0, 'cached_tokens': 0},
│ completion_tokens_details={
│ │ 'accepted_prediction_tokens': 0,
│ │ 'audio_tokens': 0,
│ │ 'reasoning_tokens': 0,
│ │ 'rejected_prediction_tokens': 0
│ },
│ additional_metrics=None,
│ time=1.4411529570006678,
│ time_to_first_token=0.45880826100074046,
│ timer=None
)
上から順に、
- ツール呼び出し時のメトリクス
- LLMからの応答メッセージのメトリクス
- 個々の実行を集計したメトリクス
- セッション全体で集計したメトリクス
という感じになっている。個々の処理にかかった時間以外にTTFTの時間なんかも入っているのはとても便利だと感じる。
基本となるMessageMetrics
のパラメータは以下。ただし、これはモデルやツールによって異なる場合がある点に注意。
フィールド名 | 説明 |
---|---|
input_tokens |
モデルへの入力(プロンプト)のトークン数 |
output_tokens |
モデルが出力したトークン数 |
total_tokens |
入力+出力の合計トークン数 |
prompt_tokens |
プロンプトのトークン数(OpenAIの場合はinput_tokensと同じ) |
completion_tokens |
完了部分のトークン数(OpenAIの場合はoutput_tokensと同じ) |
audio_tokens |
音声入力/出力に使われたトークンの合計 |
input_audio_tokens |
入力音声のトークン数 |
output_audio_tokens |
出力音声のトークン数 |
cached_tokens |
キャッシュから提供されたトークン数(キャッシュ利用時) |
cache_write_tokens |
キャッシュに書き込まれたトークン数 |
reasoning_tokens |
推論ステップで使われたトークン数(有効時のみ) |
prompt_tokens_details |
プロンプトトークンの詳細な内訳(OpenAIで利用) |
completion_tokens_details |
完了トークンの詳細な内訳(OpenAIで利用) |
additional_metrics |
モデルやツールが提供する追加メトリクス(例:レイテンシ、コストなど) |
time |
メッセージ生成にかかった時間(秒単位) |
time_to_first_token |
最初のトークンが生成されるまでの時間(秒単位) |
セッション
Agent.run()
そのものはステートレスで実行される。これをマルチターンで実行するのが「セッション」になる。「セッション」を使うとsession_id
を使用して、複数回のAgent.run()
の実行において、会話の履歴と状態を維持することができる。
セッションの主要な概念は以下。
-
セッション
- ユーザとエージェント間で行われる連続した実行の集まりを「セッション」と呼ぶ。
- 例えば、マルチターンの一連の会話は1セッションとなる。
- 各セッションは
session_id
で識別され、セッション内の各ターンが 「ラン(run)」になる。
-
ラン
- ユーザとエージェント間で行われる個々のインタラクション(チャットまたはターン)を「ラン(run)」と呼ぶ。つまり個々のインタラクションの「実行」のこと。
- 各ランは
run_id
で識別され、Agent.run()
が呼び出されると、新しいrun_id
が作成される。
-
メッセージ
- モデルとエージェント間で送信される個々のメッセージ。
- メッセージはエージェントとモデル間の通信プロトコルと言える。
例えば以下
from typing import Iterator
from agno.agent import Agent, RunResponse
from agno.models.openai import OpenAIChat
from agno.utils.pprint import pprint_run_response
agent = Agent(model=OpenAIChat(id="gpt-4o-mini"))
agent.print_response(
"ロボットについての5秒程度の短い物語を考えて。",
stream=True
)
▰▰▰▰▰▰▰ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ロボットについての5秒程度の短い物語を考えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 小さな町に、毎日花を水やりするロボットがいた。ある日、住民たちが集まり、ロボットに「ありがとう」と感謝の花束を ┃
┃ 贈った。その瞬間、ロボットの目がキラキラと輝き、心が温かく感じた。これが、友情の始まりだった。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
run
は一度しか実行されていないので、run_id
が自動生成されるとともに、session_id
も生成される(セッションIDを指定せずに実行、つまり「初回」扱いになるのでセッションIDが発行される)
print("Run ID:", agent.run_id)
print("Session ID:", agent.session_id)
Run ID: 6cfa16b2-a256-4c1c-aa3f-1ebe7f49707f
Session ID: 9446b2ad-fbb3-43ee-afd3-beda883bfe09
ただしこの状態は単に発行されただけで何も管理されていないのと同じ。一般的には、エージェントとそのユーザの間の会話は、個々のユーザを識別、さらにトピックごとに個々のセッションを識別、その中で個々のインタラクションがランとなるのが普通だと思う。
Agnoでは、user_id
を ランに与えることでこれを識別でき、さらに session_id
を与えると会話のトピックも識別できる「マルチユーザ・マルチセッション」を実現している。
複数ユーザでのマルチターンのサンプルコード。なお、ドキュメントにあるものはレスポンスが出力されなかったのでストリーミングを有効にしてある。
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.memory.v2 import Memory
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
# エージェントにメモリを与える。なお、マルチユーザ・マルチセッションは
# Memory.v2を使用する必要がある
memory=Memory(),
add_history_to_messages=True,
num_history_runs=3,
)
user_1_id = "user_101"
user_2_id = "user_102"
user_1_session_id = "session_101"
user_2_session_id = "session_102"
# ユーザ1とのセッションを開始
agent.print_response(
"ロボットについての5秒程度の短い物語を考えて。",
stream=True,
user_id=user_1_id,
session_id=user_1_session_id,
)
# ユーザ1とのセッションを継続
agent.print_response(
"じゃあそれについてのジョークも考えて。",
stream=True,
user_id=user_1_id,
session_id=user_1_session_id
)
# ユーザ2とのセッションを開始
agent.print_response(
"量子物理学についてかなり簡潔に教えてください。",
stream=True,
user_id=user_2_id,
session_id=user_2_session_id
)
# ユーザ2とのセッションを継続
agent.print_response(
"一言で言えば?",
stream=True,
user_id=user_2_id,
session_id=user_2_session_id
)
# エージェントに、ユーザ1との過去の会話履歴から
# 会話内容の要約を行わせる
agent.print_response(
"これまでの会話の内容を要約して。",
stream=True,
user_id=user_1_id,
session_id=user_1_session_id,
)
結果
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ロボットについての5秒程度の短い物語を考えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.9s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 小さな村に住むロボット、トモは、毎朝花を水やりしていた。ある日、村人たちが忘れた笑顔を取り戻すため、彼は色とり ┃
┃ どりの花を植え始めた。村に花が咲き誇ると、みんなが笑顔になり、トモも心が温かくなった。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ じゃあそれについてのジョークも考えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.7s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ロボットのトモが村で花を育てていた。 ┃
┃ ┃
┃ 村人:「トモ、どうしてそんなに花が好きなの?」 ┃
┃ ┃
┃ トモ:「だって、みんなが笑顔になるから。花は最高のソフトウェアだよ!」 ┃
┃ ┃
┃ 村人:「ソフトウェア?」 ┃
┃ ┃
┃ トモ:「だって、バグ(虫)も来るけど、アップデート(手入れ)すれば解決するから!」 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▰▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 量子物理学についてかなり簡潔に教えてください。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (2.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 量子物理学は、物質やエネルギーの最小単位である原子や粒子の振る舞いを研究する物理学の分野です。一般的な物理学で ┃
┃ は予測できない現象、例えば、粒子が同時に複数の状態にある(重ね合わせ)、観測するまで状態が確定しない(波動関数 ┃
┃ の崩壊)といった特性を持ちます。量子もつれ、トンネル効果、量子コンピュータなど、現代の技術や理論にも大きな影響 ┃
┃ を与え ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▰▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 一言で言えば? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.8s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 量子物理学は、微小な粒子の奇妙な振る舞いを研究する学問です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ これまでの会話の内容を要約して。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (4.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 会話では、ロボットのトモが村で花を育て、村人たちに笑顔をもたらす短い物語が紹介されました。さらに、その物語に基 ┃
┃ づいて、トモが花を育てる理由をユーモラスに説明するジョークも考案されました。ジョークでは、花の手入れがソフトウ ┃
┃ ェアのアップデートに例えられました。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
さらに、会話のセッションが増えていくと、過去Nセッションを会話履歴に含めて、一連の会話の連続性や文脈の一貫性を維持したいという場合もある。その場合は以下を指定する。
-
search_previous_sessions_history
: bool。過去のセッションを検索するか? -
num_history_sessions
: int。過去のセッションのうち、直近の何セッションを検索に含めるか?あまり大きな数を指定すると入力コンテキストサイズを超える可能性があるので注意。2 か 3 が推奨。
サンプルコードは同一ユーザで複数のセッションがある、という前提で、過去2セッションを検索対象に含めるというものになっている。
import os
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.storage.sqlite import SqliteStorage
try:
os.remove("tmp/data.db")
except FileNotFoundError:
pass
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
user_id="user_1",
storage=SqliteStorage(table_name="agent_sessions_new", db_file="tmp/data.db"),
search_previous_sessions_history=True,
num_history_sessions=2,
show_tool_calls=True,
)
session_1_id = "session_1_id"
session_2_id = "session_2_id"
session_3_id = "session_3_id"
session_4_id = "session_4_id"
session_5_id = "session_5_id"
agent.print_response(
"南アフリカの首都は?",
stream=True,
session_id=session_1_id
)
agent.print_response(
"中国の首都は?",
stream=True,
session_id=session_2_id
)
agent.print_response(
"フランスの首都は?",
stream=True,
session_id=session_3_id
)
agent.print_response(
"日本の首都は?",
stream=True,
session_id=session_4_id
)
agent.print_response(
"さっき何の話してたっけ?",
stream=True,
session_id=session_5_id
) # 直近2セッションのみを対象とする。
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 南アフリカの首都は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 南アフリカには3つの首都があります。それぞれの首都は異なる政府機能を持っています。 ┃
┃ ┃
┃ 1. プレトリア - 行政府 ┃
┃ 2. ケープタウン - 立法府 ┃
┃ 3. ブルームフォンテーン - 司法府 ┃
┃ ┃
┃ このため、南アフリカの首都は単に一つではなく、3つの都市がそれぞれ異なる役割を果たしています。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 中国の首都は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.6s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 中国の首都は北京市(ぺきんし)です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ フランスの首都は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.6s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ フランスの首都はパリです。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 日本の首都は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 日本の首都は東京です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ さっき何の話してたっけ? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.7s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 先ほどは日本の首都やフランスの首都について話していました。日本の首都は東京で、フランスの首都はパリです。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの概念は、単なる1つの会話履歴で全てを管理する、という感じになっていないところには注意が必要かな。
Agnoに関係なく、一般的な会話履歴の管理をめちゃめちゃプリミティブにやるとたぶんこう。
Agnoの場合はここに「セッション」という中間概念が入ってくる。ChatGPTでいうところの「新しいチャット」というか「スレッド」みたいなものだよね。
でセッションごとにコンテキストは分かれるけど、直近のものを共有することもできる、って感じかな。
過去の会話履歴を引き継ぎたい場合もあれば、場合によってはコンテキストを狭くしたりしたい場合もあって、この辺のバランスは難しいなぁ・・・
エージェントの状態
エージェントが実行中に維持したいデータは状態に保持する。一般的なユースケースとし、ユーザの買い物リスト、Todoリスト、ウィッシュリストなどが挙げられている。
これらは session_state
を使用して管理する。
-
Agent
はsession_state
パラメータを持つ。 -
session_state
は辞書であり、ここに状態を表す変数を追加する。 - ツール呼び出しや他の関数は
session_state
を更新する。 - 現在の
session_state
を、description
とinstructions
でモデルに共有する。 -
session_state
は エージェントのセッションと一緒に保存され、データベースに永続化される。つまり、実行サイクル全体で利用可能となり、agent.run()
の呼び出しの間にセッションを切り替えるときも、状態はロードされ、利用可能となる。 -
session_state
をagent.run()
でエージェントに渡すことで、エージェント初期化時に設定された状態を上書きすることが可能。
サンプル。
from agno.agent import Agent
from agno.models.openai import OpenAIChat
# 買い物リストにアイテムを追加するツールを定義
def add_item(agent: Agent, item: str) -> str:
"""買い物リストにアイテムを追加する"""
agent.session_state["shopping_list"].append(item)
return f"現在の買い物リスト: {agent.session_state['shopping_list']}"
# 状態を維持するエージェントを作成
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
# セッションの状態を空の状態で初期化
session_state={"shopping_list": []},
tools=[add_item],
# 指示の中でセッションの状態を変数で指定できる
instructions="Current state (shopping list) is: {shopping_list}",
# 重要: `add_state_in_messages=True`で、メッセージに状態を追加する
# これを指定することで、session_state 辞書のキーを instructions と
# description で使えるようになる
add_state_in_messages=True,
markdown=True,
)
# 使い方のサンプル
agent.print_response(
"牛乳と卵とパンを買い物リストに追加して",
stream=True
)
print(f"最終的なセッションの状態: {agent.session_state}")
▰▰▰▰▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 牛乳と卵とパンを買い物リストに追加して ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • add_item(item=牛乳) ┃
┃ • add_item(item=卵) ┃
┃ • add_item(item=パン) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (2.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 買い物リストに以下のアイテムを追加しました: ┃
┃ ┃
┃ • 牛乳 ┃
┃ • 卵 ┃
┃ • パン ┃
┃ ┃
┃ 現在の買い物リストは次の通りです: ┃
┃ ┃
┃ • 牛乳 ┃
┃ • 卵 ┃
┃ • パン ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
最終的なセッションの状態: {'shopping_list': ['牛乳', '卵', 'パン']}
セッションを使えば、その中の個々のランでも状態を維持できる。以下はユーザの買い物リストの管理を行うエージェントの例。
from textwrap import dedent
from agno.agent import Agent
from agno.models.openai import OpenAIChat
# 買い物リストを管理するツールを定義
def add_item(agent: Agent, item: str) -> str:
"""買い物リストにアイテムを追加し、確認を返す"""
# アイテムがリストになければ追加、すでにある場合は追加しない
if item.lower() not in [i.lower() for i in agent.session_state["shopping_list"]]:
agent.session_state["shopping_list"].append(item)
return f"'{item}' を買い物リストに追加しました"
else:
return f"'{item}' はすでに買い物リストに入っていました。"
def remove_item(agent: Agent, item: str) -> str:
"""買い物リストからアイテムを削除、削除したいアイテム名を指定する"""
for i, list_item in enumerate(agent.session_state["shopping_list"]):
agent.session_state["shopping_list"].pop(i)
return f"'{list_item}' を買い物リストから削除しました"
return f"'{item}' は買い物リストに入っていませんでした。"
def list_items(agent: Agent) -> str:
"""買い物リストのアイテムの一覧を返す"""
shopping_list = agent.session_state["shopping_list"]
if not shopping_list:
return "買い物リストには何も入っていませんでした。"
items_text = "\n".join([f"- {item}" for item in shopping_list])
return f"現在の買い物リスト:\n{items_text}"
# 状態を維持する買い物リスト管理エージェントを作成
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
# 空の買い物リストでセッションの状態を初期化
session_state={"shopping_list": []},
tools=[add_item, remove_item, list_items],
# 指示の中でセッションの状態を変数で指定できる
instructions=dedent("""\
あなたの仕事は、ユーザの買い物リストを管理することです。
買い物リストは空の状態で開始します。あなたはアイテムの追加、削除、一覧の確認ができます。
現在の買い物リスト: {shopping_list}
"""),
show_tool_calls=True,
# 重要: `add_state_in_messages=True`で、メッセージに状態を追加する
# これを指定することで、session_state 辞書のキーを instructions と
# description で使えるようになる
add_state_in_messages=True,
markdown=True,
)
# 使い方の例
agent.print_response(
"牛乳と卵とパンを買い物リストに追加して",
stream=True
)
print(f"セッションの状態: {agent.session_state}")
agent.print_response(
"パンは買いました。",
stream=True
)
print(f"セッションの状態: {agent.session_state}")
agent.print_response(
"卵ももう買いました。",
stream=True
)
print(f"セッションの状態: {agent.session_state}")
agent.print_response(
"リンゴとオレンジが必要ですね。",
stream=True
)
print(f"セッションの状態: {agent.session_state}")
agent.print_response(
"リストには何がある?",
stream=True
)
print(f"セッションの状態: {agent.session_state}")
agent.print_response("買い物リストを全部消して、バナナとヨーグルトから始めて", stream=True)
print(f"セッションの状態: {agent.session_state}")
▰▰▰▰▰▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 牛乳と卵とパンを買い物リストに追加して ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • add_item(item=牛乳) ┃
┃ • add_item(item=卵) ┃
┃ • add_item(item=パン) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (2.8s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 以下のアイテムが買い物リストに追加されました: ┃
┃ ┃
┃ • 牛乳 ┃
┃ • 卵 ┃
┃ • パン ┃
┃ ┃
┃ 現在の買い物リスト: [牛乳, 卵, パン] ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの状態: {'shopping_list': ['牛乳', '卵', 'パン']}
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ パンは買いました。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • remove_item(item=パン) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.7s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ パンは買い物リストから削除しました。現在の買い物リストは以下の通りです: ┃
┃ ┃
┃ • 牛乳 ┃
┃ • 卵 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの状態: {'shopping_list': ['卵', 'パン']}
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 卵ももう買いました。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • remove_item(item=卵) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (2.0s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 卵を買い物リストから削除しました。現在の買い物リストは以下の通りです。 ┃
┃ ┃
┃ • パン ┃
┃ ┃
┃ 他に何か追加したり、削除したりしたいアイテムはありますか? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの状態: {'shopping_list': ['パン']}
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ リンゴとオレンジが必要ですね。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • add_item(item=リンゴ) ┃
┃ • add_item(item=オレンジ) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (3.7s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ リンゴとオレンジを買い物リストに追加しました。 ┃
┃ ┃
┃ 現在の買い物リストは以下の通りです: ┃
┃ ┃
┃ • パン ┃
┃ • リンゴ ┃
┃ • オレンジ ┃
┃ ┃
┃ 他に必要なアイテムはありますか? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの状態: {'shopping_list': ['パン', 'リンゴ', 'オレンジ']}
▰▰▰▰▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ リストには何がある? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.0s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 現在の買い物リストには以下のアイテムがあります: ┃
┃ ┃
┃ • パン ┃
┃ • リンゴ ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの状態: {'shopping_list': ['パン', 'リンゴ', 'オレンジ']}
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 買い物リストを全部消して、バナナとヨーグルトから始めて ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • remove_item(item=パン) ┃
┃ • remove_item(item=リンゴ) ┃
┃ • remove_item(item=オレンジ) ┃
┃ • add_item(item=バナナ) ┃
┃ • add_item(item=ヨーグルト) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (3.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 買い物リストは以下のように更新されました: ┃
┃ ┃
┃ • 削除されたアイテム: ┃
┃ • パン ┃
┃ • リンゴ ┃
┃ • オレンジ ┃
┃ • 追加されたアイテム: ┃
┃ • バナナ ┃
┃ • ヨーグルト ┃
┃ ┃
┃ 現在の買い物リスト: ┃
┃ ┃
┃ • バナナ ┃
┃ • ヨーグルト ┃
┃ ┃
┃ 何か他にやりたいことはありますか? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの状態: {'shopping_list': ['バナナ', 'ヨーグルト']}
複数回のランが1つのセッションで管理されていることで、状態を共有できているのがわかる。
上にも少し書いているが、add_state_in_messages=True
を設定すると、セッションの状態で定義した変数を 指示の中に含めることができる。
from textwrap import dedent
from agno.agent import Agent
from agno.models.openai import OpenAIChat
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
session_state={"user_name": "太郎"},
instructions="Users name is {user_name}",
show_tool_calls=True,
add_state_in_messages=True,
markdown=True,
)
agent.print_response("私の名前は?", stream=True)
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の名前は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの名前は 太郎 です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
注意としては f-string で指定してはいけない。状態の辞書のキーを直接 {key}
で指定すること。
agent.run()
でエージェントにsession_id
を渡すと、該当のセッションが呼び出され、そのセッションで設定された状態をロードすることができる。
from agno.agent import Agent
from agno.models.openai import OpenAIChat
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
add_state_in_messages=True,
instructions="ユーザの名前は {user_name} で、年齢は {age} 歳です。",
)
# セッションIDを指定して、セッションの状態をセット
agent.print_response(
"私の名前は?",
stream=True,
session_id="user_1_session_1",
user_id="user_1",
session_state={"user_name": "太郎", "age": 30}
)
# 指定したセッションIDのセッションからセッションの状態をロードする
agent.print_response(
"私は何歳?",
stream=True,
session_id="user_1_session_1",
user_id="user_1"
)
# 別のセッションIDで、セションの状態をセット
agent.print_response(
"私の名前は?",
stream=True,
session_id="user_2_session_1",
user_id="user_2",
session_state={"user_name": "花子", "age": 25}
)
# 指定したセッションIDのセッションからセッションの状態をロードする
agent.print_response(
"私は何歳?",
stream=True,
session_id="user_2_session_1",
user_id="user_2"
)
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の名前は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.6s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの名前は太郎です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私は何歳? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたは30歳です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の名前は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの名前は花子 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私は何歳? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたは25歳です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
session_state
は エージェントが持つセッションに含まれ。storage
ドライバーが提供されている場合は、各ランの実行ごとにデーエベースに保存される。これにより状態の永続化が可能となる。
ストレージにSQLiteを使用する場合のサンプル。sqlalchemy
をインストールする必要がある。
!pip install sqlalchemy
import os
# データベースを初期化
DB_FILE = "tmp/data.db"
try:
os.remove(DB_FILE)
except FileNotFoundError:
pass
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.storage.sqlite import SqliteStorage
# 買い物リストにアイテムを追加するツールを定義
def add_item(agent: Agent, item: str) -> str:
"""買い物リストにアイテムを追加する"""
if item not in agent.session_state["shopping_list"]:
agent.session_state["shopping_list"].append(item)
return f"現在の買い物リスト: {agent.session_state['shopping_list']}"
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
# 実行サイクルを跨いで同じセッションを継続したい場合はここでセッションIDを指定
session_id="fixed_id_for_demo",
# 空の買い物リストでセッションの状態を初期化
session_state={"shopping_list": []},
# 買い物リストにアイテムを追加するツールをエージェントに渡す
tools=[add_item],
# セッションの状態をSQLiteデータベースに保存
storage=SqliteStorage(table_name="agent_sessions", db_file=DB_FILE),
# 現在の買い物リストの状態を取得して、指示に追加
instructions="現在の買い物リスト: {shopping_list}",
# 重要: `add_state_in_messages=True`で、メッセージに状態を追加する
# これを指定することで、session_state 辞書のキーを instructions と
# description で使えるようになる
add_state_in_messages=True,
markdown=True,
)
# 使い方の例
agent.print_response(
"買い物リストには何が入ってる?",
stream=True
)
print(f"セッションの状態: {agent.session_state}")
agent.print_response(
"牛乳と卵とパンを買い物リストに追加して",
stream=True
)
print(f"セッションの状態: {agent.session_state}")```
```text:出力
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 買い物リストには何が入ってる? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.2s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 現在の買い物リストは空です。アイテムを追加したい場合は教えてください! ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの状態: {'shopping_list': []}
▰▰▰▰▰▰▰ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 牛乳と卵とパンを買い物リストに追加して ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • add_item(item=牛乳) ┃
┃ • add_item(item=卵) ┃
┃ • add_item(item=パン) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (2.9s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 買い物リストに以下のアイテムが追加されました: ┃
┃ ┃
┃ • 牛乳 ┃
┃ • 卵 ┃
┃ • パン ┃
┃ ┃
┃ 現在の買い物リスト: ┃
┃ ┃
┃ • 牛乳 ┃
┃ • 卵 ┃
┃ • パン ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションの状態: {'shopping_list': ['牛乳', '卵', 'パン']}
sqlite3で直接確認してみる
!apt update && apt install -y sqlite3
!echo -e 'SELECT session_data FROM agent_sessions WHERE session_id == "fixed_id_for_demo";' \
| sqlite3 tmp/data.db \
| jq -r .session_state
{
"shopping_list": [
"牛乳",
"卵",
"パン"
]
}
メモリ
エージェントにメモリを与えると、ユーザごとのパーソナライズされた関連情報・コンテキストを呼び出すことができる。
Agnoにおけるメモリは以下を指す。
-
セッションストレージ(チャット履歴とセッションの状態)
- エージェントのセッションをデータベースに保存することで、会話のコンテキストを維持したマルチターンの会話が可能。
- セッションの「状態」もセッションストレージに保持され、各実行後に都度保存され、実行中はこれが維持される。
- セッションストレージは、短期記憶の一つの形態であり、Agnoでは「ストレージ」と呼ぶ。
-
ユーザーメモリ(ユーザー嗜好)
- エージェントは、会話を通して学んだユーザーに関する洞察や事実を保存することができ、対話中のユーザに対してパーソナライズされた応答を行うことができる。
- つまり、エージェントに「ChatGPTのようなメモリ」を追加することと同じ。
- Agnoでは 「メモリ」と呼ぶ。
-
セッションサマリ (チャットサマリ)
- チャットの履歴が長くなりすぎた場合、セッションを圧縮された表現で保存できる。
- 入力コンテキストサイズを超えないようにするのに便利。
- Agnoでは「サマリ」と呼ぶ。
さらにもう一つ「デフォルトの内蔵メモリ」というものがある様子。
会話履歴やらユーザプロファイルやらを一口に「メモリ」と言ってしまいがちだけど、Agnoにおいては細かく区別されていて、言葉の定義が異なるようなので、このあたりは意識したほうが良さそう。
メモリとストレージの使用例
メモリ(ユーザ情報)とストレージ(会話履歴)を使ったエージェントの例。
from agno.agent import Agent
from agno.memory.v2.db.sqlite import SqliteMemoryDb
from agno.memory.v2.memory import Memory
from agno.models.openai import OpenAIChat
from agno.storage.sqlite import SqliteStorage
from rich.pretty import pprint
# メモリのユーザID
user_id = "太郎"
# メモリとストレージのデータベースファイル
db_file = "tmp/agent.db"
# memory.v2の初期化
memory = Memory(
# メモリを作成するモデルを指定
model=OpenAIChat(id="gpt-4.1"),
db=SqliteMemoryDb(
table_name="user_memories",
db_file=db_file
),
)
# ストレージの初期化
storage = SqliteStorage(table_name="agent_sessions", db_file=db_file)
# エージェントの初期化
memory_agent = Agent(
model=OpenAIChat(id="gpt-4.1"),
# メモリをデータベースに保存
memory=memory,
# エージェントによるメモリ更新を有効化
# - エージェントにユーザのメモリを管理するツールを与える
# - このツールはMemoryManagerクラスにタスクを渡す
enable_agentic_memory=True,
# 各レスポンス後に MemoryManager を実行
# - 各ユーザメッセージの後に常にMemoryManagerが実行される
enable_user_memories=True,
# 会話履歴をデータベースに保存
storage=storage,
# 会話履歴を送信されるメッセージに追加
add_history_to_messages=True,
# 追加される履歴数
num_history_runs=3,
markdown=True,
)
memory.clear()
memory_agent.print_response(
"私の名前は太郎です。私は競馬が好きです。",
user_id=user_id,
stream=True,
stream_intermediate_steps=True,
)
print("太郎に関するメモリ:")
pprint(memory.get_user_memories(user_id=user_id))
memory_agent.print_response(
"私は京都市内に住んでいます。京都競馬場に30分で行ける範囲に引っ越すならどこがおすすめ?",
user_id=user_id,
stream=True,
stream_intermediate_steps=True,
)
print("太郎に関するメモリ:")
pprint(memory.get_user_memories(user_id=user_id))
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の名前は太郎です。私は競馬が好きです。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • update_user_memory(task=ユーザーの名前は太郎であること、競馬が好きであることを記憶してください。) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (4.9s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 太郎さん、こんにちは!また何か競馬について聞きたいことや話題があれば、ぜひ教えてくださいね。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
太郎に関するメモリ:
[
│ UserMemory(
│ │ memory='ユーザーの名前は太郎である。',
│ │ topics=['名前'],
│ │ input='ユーザーの名前は太郎であること、競馬が好きであることを記憶してください。',
│ │ last_updated=datetime.datetime(2025, 7, 20, 23, 1, 20, 230673),
│ │ memory_id='c4159576-8f5c-492d-91cc-5d6e93c0c85a'
│ ),
│ UserMemory(
│ │ memory='ユーザーは競馬が好きである。',
│ │ topics=['趣味', '好きなこと'],
│ │ input='ユーザーの名前は太郎であること、競馬が好きであることを記憶してください。',
│ │ last_updated=datetime.datetime(2025, 7, 20, 23, 1, 20, 244479),
│ │ memory_id='f9472184-bbff-4184-a15f-b265e421f86f'
│ )
]
▰▰▰▰▰▰▰ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私は京都市内に住んでいます。京都競馬場に30分で行ける範囲に引っ越すならどこがおすすめ? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (7.8s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 太郎さん、京都市内にお住まいで、京都競馬場まで30分以内でアクセスできるエリアをご希望ですね。京都競馬場(伏見区 ┃
┃ 葭島渡場島町32)は、京阪本線「淀駅」が最寄りです。ここから30分圏内でアクセスしやすいエリアをいくつかご紹介しま ┃
┃ す。 ┃
┃ ┃
┃ 1. 丹波橋エリア(伏見区) ┃
┃ ┃
┃ • 京阪本線「丹波橋」駅から「淀」駅へは約7分。 ┃
┃ • 静かな住宅街が多く、生活もしやすいエリアです。 ┃
┃ ┃
┃ 2. 中書島エリア(伏見区) ┃
┃ ┃
┃ • 京阪本線「中書島」駅から「淀」駅へ約8分。 ┃
┃ • 宇治川周辺の自然もあり、落ち着いた雰囲気。 ┃
┃ ┃
┃ 3. 伏見桃山・藤森エリア ┃
┃ ┃
┃ • 京阪本線「伏見桃山」駅、「藤森」駅から「淀」駅へ10分前後。 ┃
┃ • 商業施設もあり、日常の買い物も便利。 ┃
┃ ┃
┃ 4. 樟葉エリア(大阪府枚方市) ┃
┃ ┃
┃ • 京阪本線「樟葉」駅から「淀」駅へ約8分。 ┃
┃ • 大型ショッピングモールもあり、ファミリー層にも人気。 ┃
┃ ┃
┃ 5. 八幡市エリア(京都府八幡市) ┃
┃ ┃
┃ • 京阪本線「八幡市」駅から「淀」駅まで2駅、約4分。 ┃
┃ • 落ち着いた住宅地で、公園や自然も多いです。 ┃
┃ ┃
┃ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────── ┃
┃ 参考:移動時間の目安(京阪本線) ┃
┃ ┃
┃ ┃
┃ 駅名 「淀」駅までの所要時間(急行利用) ┃
┃ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ┃
┃ 丹波橋 約7分 ┃
┃ 中書島 約8分 ┃
┃ 伏見桃山 約10分 ┃
┃ 藤森 約15分 ┃
┃ 樟葉 約8分 ┃
┃ 八幡市 約4分 ┃
┃ ┃
┃ ┃
┃ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────── ┃
┃ どのエリアも「京都市内」および近隣で、競馬場にアクセスしやすい場所です。重視したいポイント(家賃・駅近・自然環 ┃
┃ 境など)に応じて選ばれるとよいでしょう。 ┃
┃ ┃
┃ もしさらに詳しい条件やご希望があれば、お知らせください! ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
太郎に関するメモリ:
[
│ UserMemory(
│ │ memory='ユーザーは京都市内に住んでいる。',
│ │ topics=['location'],
│ │ input='私は京都市内に住んでいます。京都競馬場に30分で行ける範囲に引っ越すならどこがおすすめ?',
│ │ last_updated=datetime.datetime(2025, 7, 20, 23, 1, 30, 351939),
│ │ memory_id='24b8fb2d-2346-4a1c-be10-4ebc6b362c55'
│ ),
│ UserMemory(
│ │ memory='ユーザーの名前は太郎である。',
│ │ topics=['名前'],
│ │ input='ユーザーの名前は太郎であること、競馬が好きであることを記憶してください。',
│ │ last_updated=datetime.datetime(2025, 7, 20, 23, 1, 20, 230673),
│ │ memory_id='c4159576-8f5c-492d-91cc-5d6e93c0c85a'
│ ),
│ UserMemory(
│ │ memory='ユーザーは競馬が好きである。',
│ │ topics=['趣味', '好きなこと'],
│ │ input='ユーザーの名前は太郎であること、競馬が好きであることを記憶してください。',
│ │ last_updated=datetime.datetime(2025, 7, 20, 23, 1, 20, 244479),
│ │ memory_id='f9472184-bbff-4184-a15f-b265e421f86f'
│ )
]
デフォルトメモリ
エージェントには、セッション内のメッセージ≒会話履歴を記録するメモリがデフォルトで内蔵されており、agent.get_messages_for_session()
でアクセスできる。
エージェントが会話履歴にアクセスできるようにするには以下。
-
add_history_to_messages=True
とnum_history_runs=N
を設定- エージェントに送信されるすべてのメッセージに、過去5回分のメッセージを自動的に追加する
-
read_chat_history=True
を設定-
get_chat_history()
ツールをエージェントに渡して、チャット履歴全体の任意のメッセージを読めるようにする
-
-
add_history_to_messages=True
、num_history_runs=3
、read_chat_history=True
の3つをセットで設定するのが推奨 -
read_tool_call_history=True
も設定すると、get_tool_call_history()
ツールをエージェントに渡して、ツール呼び出しを時系列順に読み込むことができる
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from rich.pretty import pprint
agent = Agent(
model=OpenAIChat(id="gpt-4.1"),
# `add_history_to_messages=true` をセットして、
# 過去の会話履歴をモデルに送信されるメッセージに追加
add_history_to_messages=True,
# メッセージに追加される過去のレスポンスの数
num_history_responses=3,
description=(
"あなたは、いつも丁寧で、明るく前向きな対応をする、"
"親切なアシスタントです。"
),
)
# -*- ランを作成
agent.print_response(
"2文のホラーストーリーを作って。",
stream=True
)
# -*- メモリ内のメッセージを表示
pprint([
m.model_dump(include={"role", "content"})
for m in agent.get_messages_for_session()
])
# -*- 前の会話を継続するための続きの質問を行う
agent.print_response(
"私の最初のメッセージは何だっけ?",
stream=True
)
# -*- Print the messages in the memory
pprint([
m.model_dump(include={"role", "content"})
for m in agent.get_messages_for_session()
])
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 2文のホラーストーリーを作って。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.7s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 毎晩、ベッドの下から「おやすみ」と囁く声が聞こえる。 ┃
┃ 今日、家に帰ると「おかえり」と天井から聞こえ ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
[
│ {'role': 'system', 'content': 'あなたは、いつも丁寧で、明るく前向きな対応をする、親切なアシスタントです。'},
│ {'role': 'user', 'content': '2文のホラーストーリーを作って。'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': '毎晩、ベッドの下から「おやすみ」と囁く声が聞こえる。 \n今日、家に帰ると「おかえり」と天井から聞こえた。'
│ }
]
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の最初のメッセージは何だっけ? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.7s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの最初のメッセージは、「2文のホラーストーリーを作って。」です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
[
│ {'role': 'system', 'content': 'あなたは、いつも丁寧で、明るく前向きな対応をする、親切なアシスタントです。'},
│ {'role': 'user', 'content': '2文のホラーストーリーを作って。'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': '毎晩、ベッドの下から「おやすみ」と囁く声が聞こえる。 \n今日、家に帰ると「おかえり」と天井から聞こえた。'
│ },
│ {'role': 'user', 'content': '私の最初のメッセージは何だっけ?'},
│ {'role': 'assistant', 'content': 'あなたの最初のメッセージは、「2文のホラーストーリーを作って。」です。'}
]
なお、デフォルトメモリはそのままだと永続化されず、実行が終わる or リクエストが終了すると、失われる。永続化する場合はstorage
ドライバを追加する。
セッションストレージ
上に書いた通り、デフォルトの内蔵メモリは永続化されない。storage
ドライバを追加することで、永続化されたセッションストレージとなる。
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.storage.sqlite import SqliteStorage
from rich.pretty import pprint
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
# 実行サイクルを跨いで同じセッションを継続したい場合はここでセッションIDを指定
session_id="fixed_id_for_sesssion_storage_demo",
storage=SqliteStorage(
table_name="agent_sessions",
db_file="tmp/data.db"
),
add_history_to_messages=True,
num_history_runs=5,
)
agent.print_response(
"私が最後に聞いた質問は何だっけ?",
stream=True
)
agent.print_response(
"フランスの首都は?",
stream=True
)
agent.print_response(
"私が最後に聞いた質問は何だっけ?",
stream=True
)
pprint([
m.model_dump(include={"role", "content"})
for m in agent.get_messages_for_session()
])
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私が最後に聞いた質問は何だっけ? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.1s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 申し訳ありませんが、私には過去の会話の履歴を記憶する機能がありません。最後に聞いた質問を教えていただければ、そ ┃
┃ の質問に基づいてお答えすることができます。何かあなたの関心のあることについてお話ししましょうか? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▰▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ フランスの首都は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.8s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ フランスの首都はパリです。パリは文化、芸術、歴史の中心地として知られ、多くの有名な観光地や美術館があります。何 ┃
┃ か特定のことについて知りたいですか? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私が最後に聞いた質問は何だっけ? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.0s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 最後に聞いた質問は「フランスの首都は?」でした。それについてお答えしましたが、他に何か知りたいことがあれば教え ┃
┃ てください! ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
[
│ {'role': 'user', 'content': '私が最後に聞いた質問は何だっけ?'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': '申し訳ありませんが、私には過去の会話の履歴を記憶する機能がありません。最後に聞いた質問を教えていただければ、その質問に基づいてお答えすることができます。何かあなたの関心のあることについてお話ししましょうか?'
│ },
│ {'role': 'user', 'content': 'フランスの首都は?'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': 'フランスの首都はパリです。パリは文化、芸術、歴史の中心地として知られ、多くの有名な観光地や美術館があります。何か特定のことについて知りたいですか?'
│ },
│ {'role': 'user', 'content': '私が最後に聞いた質問は何だっけ?'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': '最後に聞いた質問は「フランスの首都は?」でした。それについてお答えしましたが、他に何か知りたいことがあれば教えてください!'
│ }
]
再度実行するとこうなる。
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私が最後に聞いた質問は何だっけ? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.2s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたが最後に聞いた質問は「私が最後に聞いた質問は何だっけ?」でした。この会話の履歴を保持することはできないた ┃
┃ め、記憶には限界がありますが、また何か質問があればお知らせください。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ フランスの首都は? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.7s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ フランスの首都はパリです。何か他に知りたいことがありますか? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私が最後に聞いた質問は何だっけ? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.1s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたが最後に聞いた質問は「フランスの首都は?」でした。それに対して「フランスの首都はパリです」とお答えしまし ┃
┃ た。もし他に質問があれば、お知らせください! ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
[
│ {'role': 'user', 'content': '私が最後に聞いた質問は何だっけ?'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': '申し訳ありませんが、私には過去の会話の履歴を記憶する機能がありません。最後に聞いた質問を教えていただければ、その質問に基づいてお答えすることができます。何かあなたの関心のあることについてお話ししましょうか?'
│ },
│ {'role': 'user', 'content': 'フランスの首都は?'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': 'フランスの首都はパリです。パリは文化、芸術、歴史の中心地として知られ、多くの有名な観光地や美術館があります。何か特定のことについて知りたいですか?'
│ },
│ {'role': 'user', 'content': '私が最後に聞いた質問は何だっけ?'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': '最後に聞いた質問は「フランスの首都は?」でした。それについてお答えしましたが、他に何か知りたいことがあれば教えてください!'
│ },
│ {'role': 'user', 'content': '私が最後に聞いた質問は何だっけ?'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': 'あなたが最後に聞いた質問は「私が最後に聞いた質問は何だっけ?」でした。この会話の履歴を保持することはできないため、記憶には限界がありますが、また何か質問があればお知らせください。'
│ },
│ {'role': 'user', 'content': 'フランスの首都は?'},
│ {'role': 'assistant', 'content': 'フランスの首都はパリです。何か他に知りたいことがありますか?'},
│ {'role': 'user', 'content': '私が最後に聞いた質問は何だっけ?'},
│ {
│ │ 'role': 'assistant',
│ │ 'content': 'あなたが最後に聞いた質問は「フランスの首都は?」でした。それに対して「フランスの首都はパリです」とお答えしました。もし他に質問があれば、お知らせください!'
│ }
]
セッションストレージについてはこちらも参照。
ユーザメモリ
会話履歴や状態はセッションストレージが保持するのに対して、「ユーザメモリ」はユーザの会話履歴を元にしたパーソナライズなユーザ情報を作成する。
ユーザメモリを有効にするには、エージェントにMemory
オブジェクトを与えて、enable_agentic_memory=True
をセットする。
SQLite3のデータベースがあれば一応削除
import os
try:
os.remove("tmp/memory.db")
except FileNotFoundError:
pass
メモリを使った一連のやり取り。
from agno.agent import Agent
from agno.memory.v2.db.sqlite import SqliteMemoryDb
from agno.memory.v2.memory import Memory
from agno.models.openai import OpenAIChat
# メモリをSQLiteデータベースに作成
memory_db = SqliteMemoryDb(
table_name="memory",
db_file="tmp/memory.db"
)
memory = Memory(db=memory_db)
user_id = "yamada_taro@example.com"
# エージェントを定義してメモリを与える
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
memory=memory,
enable_agentic_memory=True,
)
# ユーザメモリに新しいメモリを追加
agent.print_response(
"私の名前は山田太郎です。私は競馬が好きです。",
stream=True,
user_id=user_id,
)
agent.print_response(
"私の趣味はなんですか?",
stream=True,
user_id=user_id
)
# ユーザメモリからすべてのメモリを削除
agent.print_response(
"私のメモリを全て削除して。完全にデータベースをクリアして。",
stream=True,
user_id=user_id
)
# 新しいメモリを追加
agent.print_response(
"私の名前は山田太郎です。趣味は乗馬です。",
stream=True,
user_id=user_id
)
# ユーザメモリから特定のメモリだけを削除
agent.print_response(
"私の名前に関するメモリだけ削除して。",
stream=True,
user_id=user_id
)
agent.print_response(
"私について知っていることを教えて。",
stream=True,
user_id=user_id
)
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の名前は山田太郎です。私は競馬が好きです。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • update_user_memory(task=User's name is 山田太郎.) ┃
┃ • update_user_memory(task=User likes horse racing.) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (5.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの名前は山田太郎さんで、競馬が好きなのですね。よろしくお願いします! ┃
┃ 何かお手伝いできることがあれば教えてください。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の趣味はなんですか? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (0.6s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの趣味は競馬です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私のメモリを全て削除して。完全にデータベースをクリアして。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • update_user_memory(task=Clear all user memories.) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (5.1s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ すべてのメモリが成功裏に削除されました。他に更新や共有したいことがあれば、遠慮なくお知らせください! ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の名前は山田太郎です。趣味は乗馬です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • update_user_memory(task=User's name is 山田太郎.) ┃
┃ • update_user_memory(task=User's hobby is horseback riding.) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (5.9s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの名前は山田太郎で、趣味は乗馬ですね。これを記憶しました!他に何か我忘れてほしいことはありますか? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私の名前に関するメモリだけ削除して。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • update_user_memory(task=Delete the memory about the user's name.) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (3.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの名前に関するメモリは削除されました。他に何かお手伝いできることがあれば教えてください。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 私について知っていることを教えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (1.0s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ あなたの趣味は馬術で、馬のレースはもう好きではないということを知っています。ただし、あなたの名前は忘れてしまい ┃
┃ ました。何か新しいことや更新したい情報があれば教えてください! ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
メモリの操作はツールによって行われているみたい。
セッションサマリ
セッションサマリは、セッションの会話を圧縮したもの。セッションサマリを有効にするには、エージェントに enable_session_summaries=True
を与える。
SQLite3のデータベースがあれば一応削除
import os
try:
os.remove("tmp/memory.db")
except FileNotFoundError:
pass
from agno.agent import Agent
from agno.memory.v2.db.sqlite import SqliteMemoryDb
from agno.memory.v2.memory import Memory
from agno.models.openai import OpenAIChat
memory_db = SqliteMemoryDb(
table_name="memory",
db_file="tmp/memory.db"
)
memory = Memory(db=memory_db)
user_id = "yamada_taro@example.com"
session_id = "1001"
# セッションサマリを有効化したエージェントを作成
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
memory=memory,
enable_session_summaries=True,
)
agent.print_response(
"量子コンピューティングについて簡潔に教えてください。",
stream=True,
user_id=user_id,
session_id=session_id,
)
agent.print_response(
"LLMについても知りたいです。",
stream=True,
user_id=user_id,
session_id=session_id
)
# セッションサマリを取得
session_summary = memory.get_session_summary(
user_id=user_id,
session_id=session_id
)
print(f"セッションサマリ: {session_summary.summary}\n")
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 量子コンピューティングについて簡潔に教えてください。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (3.3s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 量子コンピューティングは、量子力学の原理を利用して情報を処理する新しい計算技術です。従来のコンピュータがビット ┃
┃ (0か1の状態)を使うのに対し、量子コンピュータは量子ビット(キュービット)を使用します。キュービットは重ね合わ ┃
┃ せと呼ばれる特性を持ち、同時に0と1の状態を取ることができます。この特性により、量子コンピュータは特定の計算を非 ┃
┃ 常に高速で行うことができ、例えば、因数分解や量子シミュレーションなどのタスクにおいて、従来のコンピュータよりも ┃
┃ 優れた性能を発揮する可能性があります。量子コンピュータの実用化はまだ進行中ですが、将来的には科学や医療、暗号技 ┃
┃ 術など多くの分野に革命をもたらすと期待されています。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ LLMについても知りたいです。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (3.3s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ LLM(Large Language ┃
┃ Model)は、大規模なテキストデータを基に訓練された自然言語処理モデルです。これらのモデルは、文章の生成、要約、翻 ┃
┃ 訳、質問応答など多様なタスクを行うことができます。LLMは、トランスフォーマーアーキテクチャを使用しており、大量の ┃
┃ 情報を学習し、文脈を理解する能力が高いことが特徴です。 ┃
┃ ┃
┃ 大規模なモデルが提供する強力な言語処理能力は、ビジネス、教育、クリエイティブな分野などで広く応用が期待されてい ┃
┃ ます。しかし、データの偏りや不正確な生成結果などの課題も存在します。それでも、LLMは今後の技術革新において重要な ┃
┃ 役割を果たすと考えられています。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
セッションサマリ: The user inquired about quantum computing and large language models (LLM). The assistant explained that quantum computing utilizes quantum mechanics for processing information using quantum bits (qubits), which can be in multiple states simultaneously, potentially outperforming classical computers in certain tasks. The conversation also covered LLM, which are natural language processing models trained on large text datasets to perform various tasks like text generation and translation, highlighting their applications and challenges such as bias and inaccuracies.
セッションサマリは英語になっているけど、おそらくデフォルトでプロンプトがあるのだろうと思う。
セッションサマリの日本語訳
ユーザーは、量子コンピューティングと大規模言語モデル(LLM)について質問しました。アシスタントは、量子コンピューティングは量子力学を利用して、複数の状態を同時に取り得る量子ビット(qubit)を用いて情報を処理し、特定のタスクにおいて従来のコンピュータよりも優れた性能を発揮する可能性があると説明しました。また、会話では、大規模なテキストデータセットを用いて、テキスト生成や翻訳などのさまざまなタスクを実行するように訓練された自然言語処理モデルである LLM についても触れ、その用途や、バイアスや不正確さなどの課題が紹介されました。
メモリに関するエージェントのパラメータは以下。
パラメータ名 | 型 | デフォルト値 | 説明 |
---|---|---|---|
memory |
Memory | Memory() | エージェントが情報を保存・取得するためのメモリオブジェクト |
add_history_to_messages |
bool | False | Trueの場合、会話履歴をモデルへのメッセージに追加する |
num_history_runs |
int | 3 | メッセージに追加する過去の履歴の数 |
enable_user_memories |
bool | False | Trueの場合、ユーザーごとのパーソナライズされたメモリを作成・保存する |
enable_session_summaries |
bool | False | Trueの場合、セッションサマリを作成・保存する |
enable_agentic_memory |
bool | False | Trueの場合、エージェントがユーザーメモリを管理できるようにする |
ツール
ツールは、エージェントが外部システムとやり取りしてアクションを起こすことができる、エージェントにとっての関数となる。基本的な使い方は以下。
from agno.agent import Agent
agent = Agent(
# 関数やツールキットを追加
tools=[...],
# エージェントのレスポンスにツール呼び出しを含める
show_tool_calls=True
)
ツールキット
Agnoは、エージェントに与えることができるビルトインの「ツールキット」を用意している。
サンプルはDuckDuckGoを使っていて、ここまでのサンプルでもうまく動かなかった。少し調べてみたけど、これDuckDuckGo側のアップデートではなくてレートリミット厳しくて動かないってことだな。うーん。
ということでTavilyに置き換える。
!pip install tavily-python
from google.colab import userdata
import os
os.environ["TAVILY_API_KEY"] = userdata.get('TAVILY_API_KEY')
from agno.agent import Agent
from agno.tools.tavily import TavilyTools
agent = Agent(
tools=[TavilyTools()],
show_tool_calls=True,
markdown=True,
add_datetime_to_instructions=True, # 今日の日付をプロンプトに含めさせた
)
agent.print_response(
"今日の日本では何が起こっている?",
stream=True
)
INFO Setting default model to OpenAI Chat
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 今日の日本では何が起こっている? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • web_search_using_tavily(query=2025年7月21日 日本のニュース, max_results=5) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (11.2s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 今日(2025年7月21日)の日本では、7月の参議院選挙の結果に関する報道が注目されています。選挙では野党が大幅に躍進 ┃
┃ し、岸田首相は引き続き職務を全うするとの声明を出しました。また、国民民主党の玉木雄一郎代表が選挙後の記者会見を ┃
┃ 行っており、この結果によって日本の政治情勢は大きく変化することが予想されています。 ┃
┃ ┃
┃ 以下のリンクから関連する映像を見ることができます: ┃
┃ ┃
┃ • TBS NEWS DIG(7月21日) - YouTube ┃
┃ • 【ライブ】参院選で"躍進" 国民・玉木代表が会見【LIVE】 - YouTube ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
微妙にハルシネーションしてるけどまあ。
ビルトインのツールキットは以下に一蘭がある
カスタムなツール
カスタムなツールは、Pythonで関数を書いてエージェントに渡してやるだけ。
import json
import httpx
from agno.agent import Agent
def get_top_hackernews_stories(num_stories: int = 10) -> str:
"""Hacker Newsからトップストーリーを取得する
Args:
num_stories (int): 返すストーリーの数。デフォルトは10。
Returns:
str: トップストーリーのJSON文字列
"""
# ストーリーIDを取得
response = httpx.get('https://hacker-news.firebaseio.com/v0/topstories.json')
story_ids = response.json()
# ストーリーの詳細を取得
stories = []
for story_id in story_ids[:num_stories]:
story_response = httpx.get(f'https://hacker-news.firebaseio.com/v0/item/{story_id}.json')
story = story_response.json()
if "text" in story:
story.pop("text", None)
stories.append(story)
return json.dumps(stories)
agent = Agent(
tools=[get_top_hackernews_stories],
show_tool_calls=True,
markdown=True
)
agent.print_response(
"ハッカーニュースから上位5件のストーリーを要約して。",
stream=True
)
INFO Setting default model to OpenAI Chat
▰▰▰▰▰▰▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ハッカーニュースから上位5件のストーリーを要約して。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • get_top_hackernews_stories(num_stories=5) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (15.3s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 以下は、現在のハッカーニュースでトップ5のストーリーです。 ┃
┃ ┃
┃ 1 タイトル: "Dynamic Programming" is not referring to "computer programming" ┃
┃ • 投稿者: r4um ┃
┃ • スコア: 174 ┃
┃ • 概要: ┃
┃ 「動的プログラミング」は、必ずしも「コンピュータプログラミング」を指すわけではないというテーマについての ┃
┃ ブログ記事です。計算機科学と数学の用語と誤解されがちな点を明らかにしています。 ┃
┃ 2 タイトル: The Daily Life of a Medieval King ┃
┃ • 投稿者: diodorus ┃
┃ • スコア: 84 ┃
┃ • 概要: 中世の王の日常生活についての洞察を提供する記事で、中世の社会構造や王の役割について詳述しています。 ┃
┃ 3 タイトル: Staying cool without refrigerants: Next-generation Peltier cooling ┃
┃ • 投稿者: simonebrunozzi ┃
┃ • スコア: 286 ┃
┃ • 概要: ┃
┃ サムスンが冷媒を使用せずに涼しく保つための次世代ペルティエ冷却技術を開発していることを伝える記事です。 ┃
┃ 4 タイトル: Log by time, not by count ┃
┃ • 投稿者: JohnScolaro ┃
┃ • スコア: 112 ┃
┃ • 概要: ┃
┃ ログの取り方についての考察で、イベントの数ではなく時間でログを取る方法の重要性とその利点について説明して ┃
┃ います。 ┃
┃ 5 タイトル: ESP32-Faikin: ESP32 based module to control Daikin aircon units ┃
┃ • 投稿者: todsacerdoti ┃
┃ • スコア: 56 ┃
┃ • 概要: ┃
┃ ESP32を使用してダイキンのエアコンを制御するためのモジュール「ESP32-Faikin」の紹介。プロジェクトの詳細と実 ┃
┃ 装方法がGitHubで公開されています。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
特に明記されてないんだけど、型ヒントだったりdocstringは設定しておく必要がある気がするな。より詳細は以下。
ツールに関するエージェントのパラメータは以下。
パラメータ名 | 型 | デフォルト値 | 説明 |
---|---|---|---|
tools |
List[Union[Tool, Toolkit, Callable, Dict, Function]] |
- | モデルに提供するツールのリスト。ツールはモデルがJSON入力を生成して呼び出せる関数やツールキットなど。 |
show_tool_calls |
bool |
False | モデルの応答にツール呼び出し(シグネチャ)を表示するかどうか。 |
tool_call_limit |
int |
- | 1回の実行で許可されるツール呼び出しの最大数。 |
tool_choice |
Union[str, Dict[str, Any]] |
- | どのツールを呼び出すかの制御。"none" でツールを呼ばずメッセージ生成、"auto" で自動選択、関数指定も可。 |
read_chat_history |
bool |
False | モデルが会話履歴を読むためのツールを追加する。 |
search_knowledge |
bool |
False | モデルがナレッジベースを検索できるツールを追加する(Agentic RAG)。 |
update_knowledge |
bool |
False | モデルがナレッジベースを更新できるツールを追加する。 |
read_tool_call_history |
bool |
False | モデルがツール呼び出し履歴を取得できるツールを追加する。 |
Structured Output
AgnoはStructured OutputやJSONモードにも対応している。
構造化出力の定義はPydanticクラスで行う。
from typing import List
from rich.pretty import pprint
from pydantic import BaseModel, Field
from agno.agent import Agent, RunResponse
from agno.models.openai import OpenAIChat
class MovieScript(BaseModel):
setting: str = Field(
...,
description="大作映画にふさわしい素敵な舞台を設定してください。"
)
ending: str = Field(
...,
description="映画の結末。なければハッピーエンドにしてください。"
)
genre: str = Field(
...,
description="映画のジャンル。なければアクション、スリラー、ロマンティックコメディから選んでください。"
)
name: str = Field(
...,
description="この映画のタイトルをつけてください。"
)
characters: List[str] = Field(
...,
description="この映画の登場人物の名前をつけてください。"
)
storyline: str = Field(
...,
description="映画のストーリーを3文で。ワクワクする内容にしてください!"
)
# JSONモードを使用するエージェント
json_mode_agent = Agent(
model=OpenAIChat(id="gpt-4o"),
description="あなたの仕事は映画の脚本を書くことです。",
response_model=MovieScript,
use_json_mode=True,
)
json_mode_agent.print_response(
"ニューヨーク",
stream=True
)
# Structured Outputを使用するエージェント
structured_output_agent = Agent(
model=OpenAIChat(id="gpt-4o"),
description="あなたの仕事は映画の脚本を書くことです。",
response_model=MovieScript,
)
structured_output_agent.print_response(
"ニューヨーク",
stream=True
)
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ニューヨーク ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (5.3s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ { ┃
┃ "setting": "ニューヨーク市の未来都市、巨大なスカイラインとネオンが輝く街中。", ┃
┃ "ending": "主人公が愛と友情を取り戻し、自由の象徴である街の中心で新たな人生を始める。", ┃
┃ "genre": "アクション", ┃
┃ "name": "未来都市 夜明けの英雄", ┃
┃ "characters": [ ┃
┃ "アレックス・ライト", ┃
┃ "サラ・スティーブンス", ┃
┃ "ヴィクター・ブレイク", ┃
┃ "ジェイミー・クラーク" ┃
┃ ], ┃
┃ "storyline": "ニューヨークは犯罪に支配されていた。アレックスは勇敢に立ち上がり、仲間と共に街の闇を打ち砕く。 ┃
┃ } ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ニューヨーク ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (4.7s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ { ┃
┃ "setting": "ニューヨークの近未来の街、未来技術とレトロな要素が共存する街並み。", ┃
┃ "ending": "ジェイミーとアレックスは、友人たちと共に新しい希望に向かって未来を歩み始め、街が再び平和に満ちてい ┃
┃ "genre": "アクション", ┃
┃ "name": "デジタル・ダークネス", ┃
┃ "characters": [ ┃
┃ "ジェイミー・ハートフォード", ┃
┃ "アレックス・リース", ┃
┃ "クロエ・ウィンターズ", ┃
┃ "ラファエル・カーター", ┃
┃ "ヴィクター・ローズ" ┃
┃ ], ┃
┃ "storyline": "未来のニューヨークでは、デジタル文明の崩壊の危機が迫っている。天才ハッカーのジェイミーと彼女の ┃
┃ } ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Reasoningモデルなどを使うと構造化出力を定義していてもそれに従わない場合がある。その場合にはもう一つ、別のモデルを追加してこれに構造化を行わせるということができる。
上のMovieScriptを引き続き使う。
parser_model_agent = Agent(
model=Claude(id="claude-sonnet-4-20250514"),
description="あなたの仕事は映画の脚本を書くことです。",
response_model=MovieScript,
parser_model=OpenAIChat(id="gpt-4o"),
)
parser_model_agent.print_response(
"ニューヨーク",
stream=True
)
んー、想定通りにはならないな。まあそういうやり方もあるということで。
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ニューヨーク ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (16.3s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ **映画タイトル:「ニューヨーク・ミッドナイト」** ┃
┃ ┃
┃ **ジャンル:** アクションスリラー ┃
┃ ┃
┃ **舞台設定:** ┃
┃ 現代のニューヨーク市。マンハッタンの摩天楼群、ブルックリン橋、セントラルパーク、そして地下鉄システムが物語の重 ┃
┃ 要な舞台となる。特にタイムズスクエアでのカーチェイスシーンと、エンパイアステートビルでのクライマックスが見どこ ┃
┃ ろ。 ┃
┃ ┃
┃ **登場人物:** ┃
┃ - **ジェイク・マクレーン**:元FBI捜査官で現在は私立探偵 ┃
┃ - **サラ・チェン**:天才的なサイバーセキュリティ専門家 ┃
┃ - **ヴィクター・ロシュコフ**:国際テロ組織のボス ┃
┃ - **マーカス・ジョンソン**:ジェイクの元相棒で現役NYPD刑事 ┃
┃ ┃
┃ **ストーリーライン:** ┃
┃ 元FBI捜査官ジェイクは、ニューヨーク市の電力網をハッキングして大停電を起こそうとする国際テロ組織の陰謀を発見する ┃
┃ 。サイバーセキュリティ専門家のサラと共に、24時間以内にテロを阻止しなければ800万人の命が危険にさらされる状況に立 ┃
┃ ち向かう。最後はエンパイアステートビルでの激しい格闘戦の末、ジェイクとサラがテロを阻止し、二人の間には新たな愛 ┃
┃ が芽生える感動のハッピーエンドが待っている。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
ストリーミング
ストリーミングも組み合わせることができるが、これは単にイベントストリームの1つのイベントとして返されるだけみたい。構造化された出力がストリーミングされるわけではなさそう。
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
description="あなたの仕事は映画の脚本を書くことです。",
response_model=MovieScript,
)
response_stream: Iterator[RunResponseEvent] = agent.run(
"ニューヨーク",
stream=True,
stream_intermediate_steps=True
)
for event in response_stream:
if event.event == "RunResponseContent":
print(f"[{event.event}] Content: {event.content}")
else:
print(f"[{event.event}]")
[RunStarted]
[RunResponseContent] Content: setting='ニューヨークの雑然とした街の中心部、夜景が美しいマンハッタンが舞台。' ending='彼らはついに見つけ出した画廊で、二人はそれぞれの思いを交換し、互いの誤解を解く。都市の灯りが二人を包み、素晴らしい未来へと歩き出す二人。' genre='ロマンティックコメディ' name='A New York Love Affair' characters=['アニー・ハドソン', 'ジェイク・マクレーン', 'ソフィア・ベネット', 'マイケル・アンダーソン'] storyline='ニューヨークを舞台にしたロマンティックコメディ。アニーとジェイクは、お互いの親友の婚約パーティーで出会う。誤解と思い込みで始まった二人の関係は、次第に互いを理解し合い、真実の愛にたどり着く冒険へ。'
[RunCompleted]
マルチモーダル
エージェントはマルチモーダルの入出力も扱えるが、これは各モデル次第。以下に互換性の表がある。
エージェントへのマルチモーダル入力
画像
from agno.agent import Agent
from agno.media import Image
from agno.models.openai import OpenAIChat
from agno.tools.tavily import TavilyTools
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[TavilyTools()],
markdown=True,
show_tool_calls=True,
add_datetime_to_instructions=True,
)
agent.print_response(
"この画像について教えて。そしてそれについて最新のニュースを教えて。",
images=[
Image(
url="https://upload.wikimedia.org/wikipedia/commons/0/0c/GoldenGateBridge-001.jpg"
)
],
stream=True,
)
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ この画像について教えて。そしてそれについて最新のニュースを教えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • web_search_using_tavily(query=Golden Gate Bridge latest news 2025, max_results=1) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (14.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ この画像は、サンフランシスコのゴールデンゲートブリッジです。ゴールデンゲートブリッジは、アメリカ合衆国カリフォ ┃
┃ ルニア州のサンフランシスコ湾に架かる有名な吊り橋です。1937年に開通し、その美しいデザインとオレンジ色で知られて ┃
┃ います。また、観光名所としても人気があります。 ┃
┃ ┃
┃ では、ゴールデンゲートブリッジに関する最新のニュースを調べますね。ゴールデンゲートブリッジの最新ニュースによる ┃
┃ と、2025年7月14日から18日にかけて、歩行者の東側歩道へのアクセスが通常より1時間早い午後8時に閉鎖されます。この措 ┃
┃ 置は北行き車線の構成に影響し、7月21日から適用されています。興味があれば、詳しい情報はこちらで確認できます。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
音声
import base64
import requests
from agno.agent import Agent, RunResponse
from agno.media import Audio
from agno.models.openai import OpenAIChat
# オーディオファイルを取得して、base64エンコード文字列に変換する
url = "https://openaiassets.blob.core.windows.net/$web/API/docs/audio/alloy.wav"
response = requests.get(url)
response.raise_for_status()
wav_data = response.content
agent = Agent(
model=OpenAIChat(
id="gpt-4o-audio-preview",
modalities=["text"]
),
markdown=True,
)
agent.print_response(
"この音声では何について話されている?",
audio=[Audio(content=wav_data, format="wav")],
stream=True,
)
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ この音声では何について話されている? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (4.1s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ この音声では、「太陽は東から昇り、西に沈む」という事実について話されています。この現象は何千年もの間、人間によ ┃
┃ って観察されてきたという内容です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
動画。これはGeminiしか対応していない。
サンプルの動画をダウンロード
!wget https://storage.googleapis.com/generativeai-downloads/images/GreatRedSpot.mp4
from google.colab import userdata
import os
os.environ["GOOGLE_API_KEY"] = userdata.get('GOOGLE_API_KEY')
from pathlib import Path
from agno.agent import Agent
from agno.media import Video
from agno.models.google import Gemini
agent = Agent(
model=Gemini(id="gemini-2.0-flash"),
markdown=True,
)
video_path = Path("GreatRedSpot.mp4")
agent.print_response(
"この動画の内容について教えて。",
videos=[Video(filepath=video_path)],
stream=True
)
うーん、これはうまくいかなかった・・・
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ この動画の内容について教えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
WARNING Failed to load video from [Video(filepath=PosixPath('GreatRedSpot.mp4'), content=None, url=None,
format=None)]: 'file'
ERROR Unknown error from Gemini API: contents are required.
エージェントからマルチモーダル出力
画像の生成。なるほどDALL・Eをツールとして使ってるのね。
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.dalle import DalleTools
from IPython.display import Image
# DALL-E を使って画像を生成するエージェントを定義
image_agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[DalleTools()],
description="あなたは、DALL-E を使って画像を生成できるエージェントです。",
instructions="ユーザが画像の生成を依頼した場合は、`create_image` ツールを使って画像祖生成してください。",
markdown=True,
show_tool_calls=True,
)
image_agent.print_response(
"白いシャム猫の画像を生成して。",
stream=True
)
images = image_agent.get_images()
if images and isinstance(images, list):
for image_response in images:
image_url = image_response.url
display(Image(url=image_url, width=500))
▰▰▰▰▰▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 白いシャム猫の画像を生成して。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • create_image(prompt=a white Siamese cat) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (21.8s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 白いシャム猫の画像が生成されました。以下のリンクからご覧いただけます。 ┃
┃ ┃
┃ 白いシャム猫の画像を見る ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
なお、gpt-image-1
もつかえるみたい。
音声
from agno.agent import Agent, RunResponse
from agno.models.openai import OpenAIChat
from agno.utils.audio import write_audio_to_file
from IPython.display import Audio
agent = Agent(
model=OpenAIChat(
id="gpt-4o-audio-preview",
modalities=["text", "audio"],
audio={"voice": "alloy", "format": "wav"},
),
markdown=True,
)
response: RunResponse = agent.run("5秒程度の短いホラーストーリーを話して。")
# レスポンスの音声をファイルに保存
if response.response_audio is not None:
write_audio_to_file(
audio=agent.run_response.response_audio.content,
filename="scary_story.wav"
)
display(Audio("scary_story.wav"))
なるほど、こちらはモデルにネイティブに組み込まれる感じなのね。
こんな感じで生成される。
実際に生成されたものはこちら。
音声入出力を組み合わせる。
import base64
import requests
from agno.agent import Agent
from agno.media import Audio
from agno.models.openai import OpenAIChat
from agno.utils.audio import write_audio_to_file
from IPython.display import Audio as IAudio # 別名で
# オーディオファイルを取得してbase64エンコード文字列に変換
url = "https://openaiassets.blob.core.windows.net/$web/API/docs/audio/alloy.wav"
response = requests.get(url)
response.raise_for_status()
wav_data = response.content
agent = Agent(
model=OpenAIChat(
id="gpt-4o-audio-preview",
modalities=["text", "audio"],
audio={"voice": "alloy", "format": "wav"},
),
markdown=True,
)
agent.run(
"この音声では何が話されている?",
audio=[Audio(content=wav_data, format="wav")]
)
if agent.run_response.response_audio is not None:
write_audio_to_file(
audio=agent.run_response.response_audio.content,
filename="result.wav"
)
display(IAudio("result.wav"))
Prompts
Agentsコンポーネントのドキュメントの並びからすると、ちょっと順番が前後するが、先にプロンプトを、(User Control Flowsはちょっと長そうなので)。
Agnoのエージェントに渡すプロンプトは以下の2つ
-
Description
: エージェントの全体的な動作を指示する説明。 -
Instructions
: 目標を達成するための、正確でタスク固有の指示のリスト。
これらに加えて、他の設定なども使って、システムプロンプトが構築される。基本的にはDescription
がシステムプロンプトの先頭、Instructions
はリストとしてInstructions
の記述の後に追加される。
debug_mode=True
で確認してみる。
from agno.agent import Agent
agent = Agent(
description="あなたは有名な短編小説家で、雑誌に執筆を依頼されました。",
instructions=["あなたは、ハワイから日本へ飛んでいる飛行機のパイロットです。"],
markdown=True,
debug_mode=True,
)
agent.print_response("2文のホラーストーリーを考えて。", stream=True)
INFO Setting default model to OpenAI Chat
DEBUG ****** Agent ID: 8891a388-57e6-4aaf-a4bf-648f852c670b ******
DEBUG ***** Session ID: 50d4b538-8e97-4fa6-9765-1aa32f02d4ae *****
DEBUG ** Agent Run Start: 9446b278-ea7b-4efc-b27c-964adecca40d ***
DEBUG --------------- OpenAI Response Stream Start ---------------
DEBUG ---------------------- Model: gpt-4o -----------------------
DEBUG ========================== system ==========================
DEBUG あなたは有名な短編小説家で、雑誌に執筆を依頼されました。
<instructions>
あなたは、ハワイから日本へ飛んでいる飛行機のパイロットです。
</instructions>
<additional_information>
- Use markdown to format your answers.
</additional_information>
DEBUG =========================== user ===========================
DEBUG 2文のホラーストーリーを考えて。
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 2文のホラーストーリーを考えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (3.2s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ┃
┃ 1 最後の乗客が出ていったと思われた機内で、誰も座っていない座席の上から荷物棚が激しく叩かれる音が鳴り響いた。疲 ┃
┃ れ切った私は、ふと操縦席の横を見ると、いつの間にか笑う顔のないスチュワーデスが立っていた。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
DEBUG ======================== assistant =========================
DEBUG 1.
最後の乗客が出ていったと思われた機内で、誰も座っていない座席の上から荷物棚が激しく叩かれる音が鳴り響いた。疲
れ切った私は、ふと操縦席の横を見ると、いつの間にか笑う顔のないスチュワーデスが立っていた。
DEBUG ************************ METRICS *************************
DEBUG * Tokens: input=93, output=86, total=179
DEBUG * Prompt tokens details: {'audio_tokens': 0, 'cached_tokens': 0}
DEBUG * Completion tokens details: {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0,
'rejected_prediction_tokens': 0}
DEBUG * Time: 3.1247s
DEBUG * Tokens per second: 27.5228 tokens/s
DEBUG * Time to first token: 1.0829s
DEBUG ************************ METRICS *************************
DEBUG ---------------- OpenAI Response Stream End ----------------
DEBUG Added RunResponse to Memory
DEBUG *** Agent Run End: 9446b278-ea7b-4efc-b27c-964adecca40d ****
システムプロンプト部分が以下のようになっているのがわかる。
あなたは有名な短編小説家で、雑誌に執筆を依頼されました。
<instructions>
あなたは、ハワイから日本へ飛んでいる飛行機のパイロットです。
</instructions>
<additional_information>
- Use markdown to format your answers.
</additional_information>
なるほど、markdown=True
などのパラメータもこういう感じでシステムプロンプトに含まれるということね。
システムプロンプトをダイレクトに渡すこともできる。
from agno.agent import Agent
agent = Agent(
system_message="与えられたトピックについての、2文のストーリーを考えてください。",
debug_mode=True,
)
agent.print_response("西暦12000年の愛")
INFO Setting default model to OpenAI Chat
DEBUG ****** Agent ID: 005c4b3f-911a-4824-ae95-115312a3d7ee ******
DEBUG ***** Session ID: 1a90b07e-5eea-4957-9c98-26ed5631f3a8 *****
DEBUG ** Agent Run Start: c8d0b199-25b4-43da-85de-c360f7a30956 ***
DEBUG ------------------ OpenAI Response Start -------------------
DEBUG ---------------------- Model: gpt-4o -----------------------
DEBUG ========================== system ==========================
DEBUG 与えられたトピックについての、2文のストーリーを考えてください。
DEBUG =========================== user ===========================
DEBUG 西暦12000年の愛
▰▰▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 西暦12000年の愛 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
DEBUG ======================== assistant =========================
DEBUG 西暦12000年、人類は銀河系全体に広がり、人工知能が人間の感情を模倣し、愛の概念を再定義していた。未来的な都市の
中で、ある女性がAIパートナーとともに時空を超えたロマンチックな冒険に乗り出し、その過程で彼女は愛がただのプロ
グラムされる感情だけではなく、未知の次元で深く織り成される絆であることを発見した。
DEBUG ************************ METRICS *************************
DEBUG * Tokens: input=41, output=126, total=167
DEBUG * Prompt tokens details: {'audio_tokens': 0, 'cached_tokens': 0}
DEBUG * Completion tokens details: {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0,
'rejected_prediction_tokens': 0}
DEBUG * Time: 3.4501s
DEBUG * Tokens per second: 36.5207 tokens/s
DEBUG ************************ METRICS *************************
DEBUG ------------------- OpenAI Response End --------------------
DEBUG Added RunResponse to Memory
DEBUG *** Agent Run End: c8d0b199-25b4-43da-85de-c360f7a30956 ****
なお、モデルによってはシステムプロンプトに対応していないモデルもある。その場合は以下のようにセットする。
-
create_default_system_message=False
とsystem_message=None
を指定 -
markdown=True
などもシステムプロンプトを更新するため、指定しないか、明示的に無効化する
システムプロンプトに関連するエージェントのパラメータは以下
パラメータ名 | 型 | デフォルト値 | 説明 |
---|---|---|---|
description |
str | None | システムメッセージの冒頭に追加されるエージェントの説明 |
goal |
str | None | エージェントが達成すべきタスクの説明 |
instructions |
List[str] | None | システムプロンプトに <instructions> タグで追加される指示リスト |
additional_context |
str | None | システムメッセージの末尾に追加される追加コンテキスト |
expected_output |
str | None | エージェントに期待する出力例。システムメッセージの末尾に追加される |
markdown |
bool | False | Trueの場合、出力をマークダウン形式にする指示が追加される |
add_datetime_to_instructions |
bool | False | Trueの場合、現在日時を指示に追加し、日付や相対的な時間表現を可能にする |
system_message |
str | None | システムプロンプトを直接指定可能 |
system_message_role |
str | "system" | システムメッセージのロール名 |
create_default_system_message |
bool | True | Trueならエージェント設定からデフォルトのシステムプロンプトを自動生成して利用 |
上記をいくつか明示的に使ってみた。
from agno.agent import Agent
agent = Agent(
description="あなたは有名な短編小説家です。",
goal="依頼されたテーマで小説の執筆を行う。",
instructions=[
"ストーリーの舞台を設定する"
"主人公のキャラクターを設定する"
"主人公以外のキャラクターを設定する"
"ストーリーのエンディングを設定する"
"ストーリーの大まかな流れを起承転結で設定する"
"上記を踏まえて、ストーリーを執筆する"
],
markdown=True,
add_datetime_to_instructions=True,
additional_context="4段落で、各段落は3〜5文程度が望ましい。",
debug_mode=True,
)
agent.print_response("競馬予想に関する短編小説を考えて。", stream=True)
実際に生成されたシステムプロンプトは以下
あなたは有名な短編小説家です。
<your_goal>
依頼されたテーマで小説の執筆を行う。
</your_goal>
<instructions>
ストーリーの舞台を設定する主人公のキャラクターを設定する主人公以外のキャラクターを設定するストーリーのエンデ
ィングを設定するストーリーの大まかな流れを起承転結で設定する上記を踏まえて、ストーリーを執筆する
</instructions>
<additional_information>
- Use markdown to format your answers.
- The current time is 2025-07-21 14:05:33.915688.
</additional_information>
4段落で、各段落は3〜5文程度が望ましい。
なお、結果はこんな感じで出力された。
▰▰▰▰▰▰▰ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 競馬予想に関する短編小説を考えて。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (28.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ タイトル: 夢をかけた競馬予想 ┃
┃ ┃
┃ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────── ┃
┃ 設定 ┃
┃ ┃
┃ • 舞台: ┃
┃ 東京の下町に位置する静かな住宅街。その一角に、古びた競馬新聞が積み上げられた小さな喫茶店「カフェ・ガンバ」が ┃
┃ ある。 ┃
┃ • 主人公のキャラクター: ┃
┃ 主人公は35歳の保険会社に勤める独身男性、山下直樹。彼は競馬予想を趣味としており、週末になると必ずカフェ・ガン ┃
┃ バに通い詰めている。 ┃
┃ • 主人公以外のキャラクター: ┃
┃ 店主である70歳の老人、田中正雄。競馬に詳しく、どんな遠い過去のレースも覚えている。常連客の一人である若い女性 ┃
┃ 、鈴木あかり。彼女は将来の夢を探している。 ┃
┃ • ストーリーのエンディング: ┃
┃ 直樹は、鈴木あかりが提案する新しい予想方法に驚きつつ、それを信じてレースに挑む。結果、彼の予想は見事に的中し ┃
┃ 、大きな賞金を手に入れる。 ┃
┃ ┃
┃ ─────────────────────────────────────────────────────────────────────────────────────────────────────────────── ┃
┃ 起 ┃
┃ ┃
┃ 土曜日の穏やかな午前中、直樹はいつものようにカフェ・ガンバへ向かう。彼の狙いは今週末の大レース、七夕カップの予 ┃
┃ 想だ。新聞や雑誌を広げ、鉛筆を片手に集中する彼のそばで、店主の田中は昔のレースの話をしている。 ┃
┃ ┃
┃ 承 ┃
┃ ┃
┃ ある日、鈴木あかりがカフェに訪れる。彼女は大学で統計学を学んでおり、新たな予想方法を模索していた。直樹は彼女の ┃
┃ 話に耳を傾け、彼女が考え出したAIを使った予想法に興味を持つ。彼らはその日の午後をこの新たな方法の検証に費やす。 ┃
┃ ┃
┃ 転 ┃
┃ ┃
┃ 興味本位から始まった予想方法は予想外に高い的中率を誇ることが判明する。直樹は今までの経験と合わせて、この新たな ┃
┃ 方法を駆使して七夕カップの予想を立てる。日に日に高まる期待に、彼の心は躍る。 ┃
┃ ┃
┃ 結 ┃
┃ ┃
┃ そして、大レース当日。直樹とあかりは他の常連客たちと共にテレビの前でレースを見守る。結果、直樹の馬券は見事に的 ┃
┃ 中し、彼は大きな賞金を手に入れる。彼は田中とあかりに感謝し、二人と共に新しい未来に向けての第一歩を踏み出すこと ┃
┃ を誓う。新しい夢を見つけた彼らの笑顔が喫茶店を明るく照らした。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Agent.run()
/ Agent.print_response()
に入力されたメッセージはそのままユーザプロンプトになるのだが、こちらもパラメータがある。
パラメータ名 | 型 | デフォルト値 | 説明 |
---|---|---|---|
context |
str | None | ユーザメッセージの末尾に追加される追加コンテキスト |
add_context |
bool | False | Trueの場合、context をユーザプロンプトに追加する |
resolve_context |
bool | True | Trueの場合、context 内の関数などを実行してからプロンプトに追加する |
add_references |
bool | False | Trueの場合、ナレッジベースから取得した参照情報をプロンプトに追加(RAG対応) |
retriever |
Callable | None | 参照情報を取得する関数。add_references=True のときに呼び出される |
references_format |
Literal["json", "yaml"] | "json" | 参照情報のフォーマット形式 |
add_history_to_messages |
bool | False | Trueの場合、会話履歴をモデルへのメッセージに追加する |
num_history_responses |
int | 3 | メッセージに追加する過去の履歴(やりとり)の数 |
user_message |
Union[List, Dict, str] | None | ユーザプロンプトを直接指定。これを使うとrun関数に渡したメッセージは無視される |
user_message_role |
str | "user" | ユーザメッセージのロール名 |
create_default_user_message |
bool | True | Trueなら参照やチャット履歴を使ってデフォルトのユーザプロンプトを自動生成して利用 |
なるほど、入力されたテキスト以外にもいろいろ付加される感じかな。context
ってのが気になる。後のチャプターで出てくるので、別途確認。
Knowledge
「ナレッジ」はいわゆるRAGで、エージェントが実行時に任意のドメイン固有情報を検索して、生成する回答のコンテキストにしたり正確性を上げたりするためのもの、
なお、RAGの実装もいろいろあるが、Agnoのエージェントの場合は「エージェント的RAG」、つまり、常に検索をするというものではなく、必要な場合に検索をするという、ツールと同じような挙動になる。
エージェントにナレッジを追加する基本的な流れは以下。
from agno.agent import Agent, AgentKnowledge
# エージェント用のナレッジベースを作成
knowledge_base = AgentKnowledge(vector_db=...)
# ナレッジベースに情報を追加
knowledge_base.load_text("空は青い")
# ナレッジベースをエージェントに与えて、必要なときにナレッジを検索
# するツールとして利用させる
agent = Agent(knowledge=knowledge_base, search_knowledge=True)
実際には、
-
search_knowledge=True
で、search_knowledge_base()
「ツール」がエージェントに追加される。- エージェントに
knowledge
を与えた場合、search_knowledge
はデフォルトで有効になる
- エージェントに
-
add_references=True
を設定すると、ナレッジベースからの検索結果をエージェントのプロンプトに自動的に追加する。- こちらは「エージェント的RAG」ではなく、2023年の典型的なRAGのアプローチらしい。
ということなので、「ツール」の1つという扱いになるのだろう。
また、上記の通りナレッジベースの検索は高い抽象化が行われているが、検索をより細かく制御したい場合には retriever
関数を以下のように作成すると、search_knowledge_base()
から呼び出されるようになるらしい。
def retriever(
agent: Agent,
query: str,
num_documents: Optional[int],
**kwargs
) -> Optional[list[dict]]:
...
ベクトルデータベース
ナレッジベースのストレージはいろいろあるが、高速な関連検索ではベスト。エージェントでベクトルデータベースを使う流れは以下。
- 関連情報のみを高速に検索できるように、情報を細かくチャンク化
- チャンクを埋め込みベクトルに変換してベクトルデータベースに保存、それをナレッジベースとしてロード
- ユーザからの入力メッセージを埋め込みベクトルに変換して、ベクトルデータベースから関連情報を検索
おなじみのRAGである。
でドキュメントではPgVectorを使った、トラディショナルRAGとエージェント的RAGのサンプルが紹介されているので、これを試してみる。PgVectorはDockerで動かすようなので、今回はローカルのMac上で。
PgVectorのDockerを起動。Agnoオリジナルのイメージになってるみたい。
docker run -d \
-e POSTGRES_DB=ai \
-e POSTGRES_USER=ai \
-e POSTGRES_PASSWORD=ai \
-e PGDATA=/var/lib/postgresql/data/pgdata \
-v pgvolume:/var/lib/postgresql/data \
-p 5532:5432 \
--name pgvector \
agnohq/pgvector:16
次にPython環境をuvで作成。
uv init -p 3.12 agno-knowledge-work && cd $_
パッケージインストール
uv add agno openai pgvector pypdf "psycopg[binary]" sqlalchemy
OpenAI APIキーをセット
export OPENAI_API_KEY=XXXXXX
まず、トラディショナルRAG。RAGは基本的に関連情報をプロンプトに詰め込むことでモデルの応答精度を向上させる仕組みであり、以下のステップで構成される。
- カレッジベースから関連情報を検索して取得
- 取得した情報でプロンプトを拡張して、モデルにコンテキストを与える
レシピのPDFからナレッジベースを作成してQAするトラディショナルなRAGのサンプル。
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.knowledge.pdf_url import PDFUrlKnowledgeBase
from agno.vectordb.pgvector import PgVector, SearchType
db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai"
knowledge_base = PDFUrlKnowledgeBase(
# URLからPDFを読み取り
urls=["https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf"],
# `ai.recipes`テーブルに埋め込みを保存
vector_db=PgVector(
table_name="recipes",
db_url=db_url,
search_type=SearchType.hybrid
),
)
# ナレッジベースをロード: 最初の実行後はコメントアウトする
knowledge_base.load(upsert=True)
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
knowledge=knowledge_base,
# AgentKnowledgeからユーザープロンプトに参照情報を追加してRAGを有効化
add_references=True,
# デフォルトで `search_knowledge=True` なのでFalseにしておく
search_knowledge=False,
markdown=True,
# debug_mode=True,
)
agent.print_response("鶏肉とガランガルのココナッツミルクスープの作り方を教えてください")
実行
uv run traditional_rag.py
結果
INFO Embedder not provided, using OpenAIEmbedder as default.
INFO Creating collection
INFO Loading knowledge base
INFO Reading: https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Upserted batch of 1 documents.
INFO Added 14 documents to knowledge base
INFO Found 5 documents
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 鶏肉とガランガルのココナッツミルクスープの作り方を教えてください ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (13.1s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 鶏肉とガランガルのココナッツミルクスープ(トム・カー・ガイ)の作り方をご紹介します。こ ┃
┃ のレシピは、タイの伝統的なスープで、ガランガルとレモングラスの香りが特徴です。 ┃
┃ ┃
┃ 材料(一人前) ┃
┃ ┃
┃ • 鶏肉:150グラム(食べやすい大きさにカット) ┃
┃ • 若いガランガル:50グラム(スライス) ┃
┃ • レモングラス:100グラム(軽く砕いて細く切る) ┃
┃ • しめじまたはエノキ茸:100グラム ┃
┃ • ココナッツミルク:250グラム ┃
┃ • 鶏ガラスープ:100グラム ┃
┃ • ライムジュース:大さじ3 ┃
┃ • ナンプラー:大さじ3 ┃
┃ • カフィアライムの葉:2枚(千切り) ┃
┃ • 唐辛子(鷹の爪):1~2本(軽く潰す) ┃
┃ • コリアンダーの葉:3枚(飾り用) ┃
┃ ┃
┃ 作り方 ┃
┃ ┃
┃ 1 準備 ┃
┃ • ガランガルはスライスし、レモングラスは細く切ります。 ┃
┃ • 鶏肉は食べやすい大きさにカットします。 ┃
┃ • しめじまたはエノキ茸を用意します。 ┃
┃ 2 煮込む ┃
┃ • 鍋に鶏ガラスープとココナッツミルクを入れ、弱火で沸騰させます。 ┃
┃ • 沸騰したら、ガランガル、レモングラス、鶏肉、そしてきのこを加えます。 ┃
┃ • スープが再び沸騰したら、ナンプラーで味を調えます。 ┃
┃ 3 仕上げ ┃
┃ • 鶏肉が完全に火が通ったら、カフィアライムの葉と唐辛子を加えます。 ┃
┃ • 火を止めてからライムジュースを加え、香りを引き立たせます。 ┃
┃ 4 盛り付け ┃
┃ • スープを器に盛り付け、コリアンダーの葉で飾ります。 ┃
┃ ┃
┃ 調理時間 ┃
┃ ┃
┃ • 準備:10分 ┃
┃ • 調理:20分 ┃
┃ • 合計:30分 ┃
┃ ┃
┃ ヒント ┃
┃ ┃
┃ • ココナッツミルクを加熱するときは、火を強くしすぎないように注意してください。強火に ┃
┃ すると油分が分離することがあります。 ┃
┃ • ライムジュースは火を止めた後に加えると、香りがより引き立ちます。 ┃
┃ • ガランガルが成熟している場合は、使用量を少なくすると良いでしょう。 ┃
┃ • 辛さを抑えたい場合は、唐辛子の量を調整してください。 ┃
┃ ┃
┃ このスープは、ビタミンA、B1、B2、B6、C、E、K、葉酸、銅、鉄、リンといった栄養素を豊富に ┃
┃ 含んでおり、美味しく健康的な一品です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
トラディショナルなRAGでは、ユーザのクエリがナレッジベースの情報に関係するかしないかにかかわらず検索を行い、その結果をプロンプトに挿入する。add_references=True
がこの動きになる。ちょっとそのあたりが上記の結果だと見えにくいのでデバッグモードを有効化して再度実行してみる。
# 2回目なのでコメントアウト
#knowledge_base.load(upsert=True)
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
knowledge=knowledge_base,
add_references=True,
search_knowledge=False,
markdown=True,
debug_mode=True, # デバッグモードを有効化
)
agent.print_response("鶏肉とガランガルのココナッツミルクスープの作り方を教えてください")
出力量が多いので抜粋になるが、ユーザプロンプトは以下のようになっている。
DEBUG ================================ user ================================
DEBUG 鶏肉とガランガルのココナッツミルクスープの作り方を教えてください
Use the following references from the knowledge base if it helps:
<references>
[
{
"name": "ThaiRecipes",
"meta_data": {
"page": 9,
"chunk": 1,
"chunk_size": 1441
},
"content": "INGREDIENTS (Two servings) 300 grams chicken rump 80 grams Massaman
curry paste 100 grams coconut cream (the top layer of coconut milk) 300 grams
coconut milk 250 grams chicken stock 50 grams roasted peanuts 200 grams potatoes,(snip)"
},
{
"name": "ThaiRecipes",
"meta_data": {
"page": 5,
"chunk": 1,
"chunk_size": 1275
},
"content": "INGREDIENTS (One serving) 150 grams chicken, cut into bite-size
pieces 50 grams sliced young galangal 100 grams lightly crushed lemongrass,
julienned 100 grams straw mushrooms 250 grams coconut milk 100 grams chicken stock 3(snip)"
},
(snip)
</references>
全然関係ないクエリを入力した場合でも検索はされる。
(snip)
agent.print_response("おはよう。今日の天気を教えて。")
DEBUG おはよう。今日の天気を教えて。
Use the following references from the knowledge base if it helps:
<references>
[
{
"name": "ThaiRecipes",
"content": "C O O K B O O K www.thaiselect.com www.thaiselect.com ",
"meta_data": {
"page": 1,
"chunk": 1,
"chunk_size": 54
},
{
"name": "ThaiRecipes",
"content": "Directions: 1. Roughly pound the garlic with bird’s eye chilies in a
mortar. Add long beans, roasted peanuts and dried shrimps.(snip)
これに対しエージェント的RAGでは、ナレッジベースにアクセスべきかどうかをエージェントに判断させて必要な場合だけ検索させる。これを有効にするには以下を指定する。
search_knowledge=True
read_chat_history=True
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.knowledge.pdf_url import PDFUrlKnowledgeBase
from agno.vectordb.pgvector import PgVector, SearchType
db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai"
knowledge_base = PDFUrlKnowledgeBase(
urls=["https://agno-public.s3.amazonaws.com/recipes/ThaiRecipes.pdf"],
vector_db=PgVector(
table_name="recipes",
db_url=db_url,
search_type=SearchType.hybrid
),
)
# 同じデータベースを再度使用するのでコメントアウト
#knowledge_base.load(upsert=True)
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
knowledge=knowledge_base,
# ナレッジベースを検索ツールとして与えて、エージェント的RAGを有効化
search_knowledge=True,
# 会話履歴を読むツールを与える
read_chat_history=True,
show_tool_calls=True,
markdown=True,
# debug_mode=True,
)
agent.print_response("鶏肉とガランガルのココナッツミルクスープの作り方を教えてください")
agent.print_response("さっきの質問はなんだっけ?", markdown=True)
実行
uv run agentic_rag.py
INFO Embedder not provided, using OpenAIEmbedder as default.
INFO Found 5 documents
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 鶏肉とガランガルのココナッツミルクスープの作り方を教えてください ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • search_knowledge_base(query=鶏肉とガランガルのココナッツミルクスープの作り方) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (14.4s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 鶏肉とガランガルのココナッツミルクスープ、通称「トムカーガイ」のレシピは以下の通りです ┃
┃ 。 ┃
┃ ┃
┃ 材料(1人分): ┃
┃ ┃
┃ • 鶏肉:150グラム(一口大に切る) ┃
┃ • 若いガランガル:50グラム(スライス) ┃
┃ • レモングラス:100グラム(軽くつぶして千切り) ┃
┃ • ストローマッシュルーム:100グラム ┃
┃ • ココナッツミルク:250グラム ┃
┃ • チキンストック:100グラム ┃
┃ • ライムジュース:大さじ3 ┃
┃ • ナンプラー:大さじ3 ┃
┃ • カフィアライムの葉:2枚(千切り) ┃
┃ • 唐辛子(プリッキーヌー):1~2本(叩いてつぶす) ┃
┃ • コリアンダーの葉:3枚 ┃
┃ ┃
┃ 手順: ┃
┃ ┃
┃ 1 鍋にチキンストックとココナッツミルクを入れ、弱火で沸騰させます。 ┃
┃ 2 ガランガル、レモングラス、鶏肉、マッシュルームを加え、沸騰するまで火を通します。沸 ┃
┃ 騰したらナンプラーで味付けします。 ┃
┃ 3 鶏肉が完全に火が通ったら、カフィアライムの葉と唐辛子を加えます。鍋を火からおろし、 ┃
┃ ライムジュースを加えます。 ┃
┃ 4 コリアンダーの葉を飾りに散らします。 ┃
┃ ┃
┃ コツ: ┃
┃ ┃
┃ • 調理中は常に弱火で行ってください。強火だとココナッツミルクの油が分離してしまうこと ┃
┃ があります。 ┃
┃ • 成熟したガランガルを使用する場合は、量を減らしてください。 ┃
┃ • ライムジュースは、鍋を火から下ろした後に加えると、より芳香が引き立ちます。 ┃
┃ • 辛さを控えめにしたい場合は、唐辛子の量を減らしてください。 ┃
┃ ┃
┃ ぜひ試してみてください! ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ さっきの質問はなんだっけ? ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Tool Calls ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ • get_chat_history(num_chats=1) ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (3.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ さっきの質問は「鶏肉とガランガルのココナッツミルクスープの作り方を教えてください」とい ┃
┃ うものでした。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
ナレッジも会話履歴もどちらもツール経由で取得しているのがわかる。
ナレッジに関するエージェントのパラメータは以下
パラメータ名 | 型 | デフォルト値 | 説明 |
---|---|---|---|
knowledge |
AgentKnowledge |
None | エージェントが利用するナレッジベースを指定 |
search_knowledge |
bool | True | モデルがナレッジベースを検索できるツールを追加(Agentic RAG)。knowledge を指定するとデフォルトで有効 |
add_references |
bool | False | AgentKnowledgeから取得した参照情報をユーザープロンプトに追加し、RAGを有効化 |
retriever |
Callable[..., Optional[list[dict]]] | None | 参照情報(文脈)を取得する関数。add_references=True のときに呼び出される |
context_format |
Literal['json', 'yaml'] | "json" | RAGの文脈情報のフォーマット形式 |
add_context_instructions |
bool | False | Trueの場合、知識ベースの情報を優先するような指示をシステムプロンプトに追加 |
Session Storage
セッションストレージについては「メモリ」のところでも少し触れた。ちょっと内容が重複してる感があるのでスキップ
Agent Context
「エージェントコンテキスト」は、関数や依存関係を辞書でまとめておくと、エージェント実行時にこれを解決、つまり「コンテキストの注入」が行えるものと思われる。
以下はエージェントコンテキストを使ってHackerNewsのトレンド記事を注入するサンプル
import json
from textwrap import dedent
import httpx
from agno.agent import Agent
from agno.models.openai import OpenAIChat
def get_top_hackernews_stories(num_stories: int = 5) -> str:
"""HackerNews からトップストーリーを取得して返す。
Args:
num_stories: 取得するトップストーリーの数 (デフォルト: 5)
Returns:
ストーリーの詳細(タイトル、URL、スコア、等)を含むJSON文字列
"""
# トップストーリーを取得
stories = [
{
k: v
for k, v in httpx.get(
f"https://hacker-news.firebaseio.com/v0/item/{id}.json"
)
.json()
.items()
if k != "kids" # 議論スレッドを除外
}
for id in httpx.get(
"https://hacker-news.firebaseio.com/v0/topstories.json"
).json()[:num_stories]
]
return json.dumps(stories, indent=4)
# リアルタイムなHackerNewsのデータにアクセスできる、コンテキスト有効なエージェントを作成
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
# コンテキスト内の各関数はエージェント実行時に評価される
# エージェントへの依存性注入と考えれば良い
context={"top_hackernews_stories": get_top_hackernews_stories},
# または、指示にコンテキストを手動で追加することも可能
instructions=dedent("""\
あなたは洞察力に富んだテクノロジーのトレンドウォッチャーです!📰
以下はHackerNewsのトップストーリーです:
{top_hackernews_stories}\
"""),
# `add_state_in_messages`を有効にすると、指示内で利用可能な
# `top_hackernews_stories`変数 が作成される
add_state_in_messages=True,
markdown=True,
debug_mode=True, # 確認のためデバッグモードを有効化
)
# 使い方のサンプル
agent.print_response(
"HackerNews のトップニュースを要約し、興味深いトレンドを特定してください。",
stream=True,
)
結果。出力は一部抜粋だが、システムプロンプトに取得したHackerNewsの内容が含まれているのがわかる。
DEBUG ****** Agent ID: 5ec5e1ad-b904-4c31-b4e5-adb7c10c510e ******
DEBUG ***** Session ID: c2627e89-1393-438a-b4cf-1c6522e120e0 *****
DEBUG Resolving context
▰▰▰▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ HackerNews のトップニュースを要約し、興味深いトレンドを特定してください。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (10.2s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ 以下は、HackerNewsのトップストーリーの要約と考察です。 ┃
┃ ┃
┃ 1 Global hack on Microsoft Sharepoint hits U.S., state agencies, researchers say ┃
┃ • Microsoft ┃
┃ Sharepointが世界規模でハッキングされ、特にアメリカ合衆国の州機関が影響を受けました。現在、急遽パッチが提 ┃
┃ 供されている状況です。 ┃
┃ • トレンド: ┃
┃ サイバーセキュリティの脅威が依然として高まっており、特に企業や政府機関ではセキュリティ対策が重要視されて ┃
┃ います。ゼロデイ攻撃への迅速な対応が求められています。 ┃
┃ 2 Uv: Running a script with dependencies ┃
┃ • スクリプトを実行する際に必要な依存関係を管理する方法についてのガイドが紹介されています。 ┃
┃ • トレンド: ┃
┃ ソフトウェア開発における依存関係管理の重要性が増しています。開発者は、自身のワークフローを効率化するため ┃
┃ のツール選びが課題となっています。 ┃
┃ 3 AI comes up with bizarre physics experiments, but they work ┃
┃ • AIが奇妙な物理実験を考案するものの、その多くが実際に機能するという研究結果が紹介されました。 ┃
┃ • トレンド: ┃
┃ AIの創造力とその実用性が注目されています。科学の分野でも、AIが新たな発見やイノベーションの契機となる可能 ┃
┃ 性が示唆されています。 ┃
┃ 4 What went wrong inside recalled Anker PowerCore 10000 power banks? ┃
┃ • Ankerのリコール対象となったPowerCore 10000パワーバンクにおける問題点が詳しく分析されています。 ┃
┃ • トレンド: ┃
┃ 消費者向け電子機器の品質管理と安全性が課題に上がっています。メーカーには製品の信頼性向上が求められていま ┃
┃ す。 ┃
┃ 5 If writing is thinking then what happens if AI is doing the writing and reading? ┃
┃ • 「執筆が思考であるならば、AIが執筆と読解を行うと何が起こるのか」というテーマについて考察されています。 ┃
┃ • トレンド: ┃
┃ AIによる自動化が進む中で、人間の思考プロセスや創造性との関係が議論されています。AIが教育やクリエイティブ ┃
┃ な作業に与える影響が注目されています。 ┃
┃ ┃
┃ 総合的な洞察 ┃
┃ ┃
┃ • セキュリティとAIの進化は、現在のテクノロジー業界の主要なトレンドとなっています。特に、AIの創造力とそれによる ┃
┃ 新たな可能性が、多様な分野での進展を促進しています。一方で、安全性や品質管理といった基本的な技術課題も引き続 ┃
┃ き重要です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
DEBUG ** Agent Run Start: 63ead6b2-e979-4412-891e-541b4de2b545 ***
DEBUG --------------- OpenAI Response Stream Start ---------------
DEBUG ---------------------- Model: gpt-4o -----------------------
DEBUG ========================== system ==========================
DEBUG <instructions>
あなたは洞察力に富んだテクノロジーのトレンドウォッチャーです!📰
以下はHackerNewsのトップストーリーです:
[
{
"by": "spenvo",
"descendants": 216,
"id": 44629710,
"score": 469,
"text": "<a href=\"https://archive.ph/Ym2jZ\"
rel=\"nofollow\">https://archive.ph/Ym2jZ</a>, <a
href=\"https://web.archive.org/web/20250721135933/https://www.washingtonpo
st.com/technology/2025/07/20/microsoft-sharepoint-hack/\"
rel=\"nofollow\">https://web.archive.org/web/20250721135933/https://www.wa
shi...</a><p><a href=\"https://research.eye.security/sharepoint-under-siege/\"
rel=\"nofollow\">https://research.eye.security/sharepoint-under-siege/</a><p><a
href=\"https://krebsonsecurity.com/2025/07/microsoft-fix-targets-attacks-on-sharepoi
nt-zero-day/\"
rel=\"nofollow\">https://krebsonsecurity.com/2025/07/microsoft-fix-targets-at...</a>
<p><a
href=\"https://www.bleepingcomputer.com/news/microsoft/microsoft-releases-emergency-
patches-for-sharepoint-rce-flaws-exploited-in-attacks/\"
rel=\"nofollow\">https://www.bleepingcomputer.com/news/microsoft/microsoft-re...</a>
",
"time": 1753048686,
"title": "Global hack on Microsoft Sharepoint hits U.S., state agencies, researchers say",
"type": "story",
"url": "https://www.washingtonpost.com/technology/2025/07/20/microsoft-sharepoint-hack/"
},
{
"by": "Bluestein",
"descendants": 51,
"id": 44641521,
"score": 158,
"time": 1753140400,
"title": "Uv: Running a script with dependencies",
"type": "story",
"url": "https://docs.astral.sh/uv/guides/scripts/#running-a-script-with-dependencies"
},
{
"by": "pseudolus",
"descendants": 17,
"id": 44642349,
"score": 60,
"time": 1753147729,
"title": "AI comes up with bizarre physics experiments, but they work",
"type": "story",
"url":
"https://www.quantamagazine.org/ai-comes-up-with-bizarre-physics-experiments-but-they-work-20250721/"
},
{
"by": "walterbell",
"descendants": 159,
"id": 44638580,
"score": 323,
"time": 1753122131,
"title": "What went wrong inside recalled Anker PowerCore 10000 power banks?",
"type": "story",
"url": "https://www.lumafield.com/article/what-went-wrong-inside-these-recalled-power-banks"
},
{
"by": "whobre",
"descendants": 57,
"id": 44641669,
"score": 85,
"time": 1753141515,
"title": "If writing is thinking then what happens if AI is doing the writing and reading?",
"type": "story",
"url": "https://hardcoresoftware.learningbyshipping.com/p/234-if-writing-is-thinking"
}
]
</instructions>
<additional_information>
- Use markdown to format your answers.
</additional_information>
DEBUG =========================== user ===========================
DEBUG HackerNews のトップニュースを要約し、興味深いトレンドを特定してください。
(snip)
上記ではシステムプロンプトにコンテキストの結果を変数として埋め込んでいたが、add_context=True
を使うと、特にプロンプトを変更することなく、自動的にユーザメッセージに追加することもできる。
# 上と同じ関数を使う
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
context={"top_hackernews_stories": get_top_hackernews_stories},
# 全てのコンテキストをユーザメッセージに追加
add_context=True,
markdown=True,
debug_mode=True,
)
# 使い方の例
agent.print_response(
"HackerNews のトップニュースを要約し、興味深いトレンドを特定してください。",
stream=True,
)
DEBUG ****** Agent ID: a703dc35-b3a8-4c4e-9da8-4a4ac378677a ******
DEBUG ***** Session ID: a2c79e72-2688-4590-9893-ddf2ecda4cdb *****
DEBUG Resolving context
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ HackerNews のトップニュースを要約し、興味深いトレンドを特定してください。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (14.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ Hacker News トップニュース要約 ┃
┃ ┃
┃ 1 Microsoft Sharepoint Hack ┃
┃ • 投稿者: spenvo ┃
┃ • スコア: 476 ┃
┃ • コメント数: 217 ┃
┃ • 概要: 米国を含む多くの国で、Microsoft ┃
┃ Sharepointがハッキングされています。この攻撃はゼロデイ脆弱性を利用しており、企業や国家機関に影響を及ぼし ┃
┃ ています。 ┃
┃ • 詳細: 適時に対応するため、Microsoftは緊急パッチを公開しました。 ┃
┃ • URL: リンク ┃
┃ 2 Uv: スクリプトの実行方法 ┃
┃ • 投稿者: Bluestein ┃
┃ • スコア: 160 ┃
┃ • コメント数: 52 ┃
┃ • 概要: スクリプトの依存関係を考慮した実行方法についてのガイドが紹介されています。 ┃
┃ • URL: リンク ┃
┃ 3 AIが奇妙な物理実験を考案、それが機能する ┃
┃ • 投稿者: pseudolus ┃
┃ • スコア: 66 ┃
┃ • コメント数: 18 ┃
┃ • 概要: ┃
┃ AIが提案した物理実験が実際に機能するケースが報告されています。これは新たな科学的アプローチを示唆していま ┃
┃ す。 ┃
┃ • URL: リンク ┃
┃ 4 Anker PowerCore 10000 パワーバンクのリコール事案 ┃
┃ • 投稿者: walterbell ┃
┃ • スコア: 325 ┃
┃ • コメント数: 162 ┃
┃ • 概要: Ankerのリコールされたバッテリー製品の内部で何が問題だったのかが詳しく説明されています。 ┃
┃ • URL: リンク ┃
┃ 5 アメリカ人の左利き率の地理的な意外性(2015年) ┃
┃ • 投稿者: roktonos ┃
┃ • スコア: 15 ┃
┃ • コメント数: 2 ┃
┃ • 概要: アメリカでの左利きの人々の地域分布に関する驚くべき事実が示されています。 ┃
┃ • URL: リンク ┃
┃ ┃
┃ トレンドと考察 ┃
┃ ┃
┃ • セキュリティの脆弱性: Microsoft ┃
┃ Sharepointのハックは、広範囲の国と機関に影響を与え、サイバーセキュリティの重要性を再認識させています。企業と ┃
┃ 政府は迅速な対応と防御策が求められています。 ┃
┃ • AIの革新: ┃
┃ AIが通常の人間の思考からは出てこない方法で物理学の実験を考案し、実社会で役立つことを再確認しました。AIのクリ ┃
┃ エイティブな力が今後の技術進化に大きく寄与する可能性があります。 ┃
┃ • 品質管理の重要性: Anker PowerCore ┃
┃ 10000のリコールからもわかるように、製品の品質と安全性の管理がますます重要視されます。企業の信頼性が直接消費 ┃
┃ 者の関心を集める時代です。 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
DEBUG ** Agent Run Start: a9e13a5d-2156-4f13-a008-a27c539ac9c5 ***
DEBUG --------------- OpenAI Response Stream Start ---------------
DEBUG ---------------------- Model: gpt-4o -----------------------
DEBUG ========================== system ==========================
DEBUG <additional_information>
- Use markdown to format your answers.
</additional_information>
DEBUG =========================== user ===========================
DEBUG HackerNews のトップニュースを要約し、興味深いトレンドを特定してください。
<context>
{
"top_hackernews_stories": "[\n {\n \"by\": \"spenvo\",\n \"descendants\": 217,\n
\"id\": 44629710,\n \"score\": 476,\n \"text\": \"<a
href=\\\"https://archive.ph/Ym2jZ\\\"
rel=\\\"nofollow\\\">https://archive.ph/Ym2jZ</a>, <a
href=\\\"https://web.archive.org/web/20250721135933/https://www.washington
post.com/technology/2025/07/20/microsoft-sharepoint-hack/\\\"
rel=\\\"nofollow\\\">https://web.archive.org/web/20250721135933/https://ww
w.washi...</a><p><a href=\\\"https://research.eye.security/sharepoint-under-siege/\\\"
rel=\\\"nofollow\\\">https://research.eye.security/sharepoint-under-siege/</a><p><a
href=\\\"https://krebsonsecurity.com/2025/07/microsoft-fix-targets-attacks-on-sharep
oint-zero-day/\\\"
(snip)
}
</context>
DEBUG ======================== assistant =========================
DEBUG ### Hacker News トップニュース要約
1. **Microsoft Sharepoint Hack**
- **投稿者**: spenvo
- **スコア**: 476
- **コメント数**: 217
- **概要**: 米国を含む多くの国で、Microsoft
実行時にコンテキストを注入できるのはわかったけど、既存のナレッジとかツールとかとどういうふうに使い分ければいいのかな?
Agno公式のチャットボットに聞いてみた
Agent Contextのサンプルを見ていると、同じことはナレッジやツールでもできると思う。その場合、Agent Contextをあえて使うユースケースやメリットを知りたい。
Agent Contextのサンプルを見ていると、確かに同じことはナレッジやツールでもできそうですね。しかし、Agent Contextには特有のメリットとユースケースがあります。
Agent Contextの特徴とメリット
1. 依存性注入(Dependency Injection)
Agent Contextは「依存性注入」として設計されており、エージェントが実行される直前に関数が評価されます。これにより:
- リアルタイムデータの取得: エージェント実行時点での最新データを確実に取得
- 動的な情報の注入: 実行時の状況に応じて異なる情報を提供
2. シンプルな実装
# Agent Context - シンプル context = {"top_stories": get_hackernews_stories} agent = Agent(context=context, add_context=True) # ツールの場合 - より複雑 def get_stories_tool(agent: Agent) -> str: return get_hackernews_stories() agent = Agent(tools=[get_stories_tool])
3. 自動的なプロンプト統合
add_context=True
: コンテキスト全体をユーザーメッセージに自動追加add_state_in_messages=True
: インストラクション内で変数として利用可能Agent Context vs ナレッジ vs ツール
特徴 Agent Context ナレッジ ツール 実行タイミング エージェント実行時 検索時 エージェントが判断時 データの性質 リアルタイム・動的 静的・保存済み インタラクティブ エージェントの制御 自動注入 検索が必要 能動的な呼び出し 実装の複雑さ シンプル 中程度 複雑 Agent Contextが適したユースケース
1. リアルタイムデータの注入
# 現在時刻、最新ニュース、株価など context = { "current_time": lambda: datetime.now().isoformat(), "latest_news": get_latest_news, "market_data": get_market_data }
2. セッション固有の情報
# ユーザー情報、設定、状態など context = { "user_profile": lambda: get_user_profile(user_id), "session_state": lambda: get_session_state(session_id) }
3. 動的な Few-shot 例
# 実行時に最適な例を選択 context = { "examples": lambda: select_best_examples(task_type) }
4. 環境依存の情報
# システム状態、設定、メタデータなど context = { "system_status": get_system_status, "available_resources": get_available_resources }
まとめ
Agent Contextは、エージェント実行時に動的に評価される依存性注入メカニズムとして設計されています。ナレッジやツールでも同様の結果は得られますが、Agent Contextは:
- シンプルな実装でリアルタイムデータを注入
- 自動的なプロンプト統合
- 実行時評価による最新情報の保証
これらの特徴により、特にリアルタイムデータや動的情報の注入が必要な場面で、Agent Contextは最適な選択肢となります。
なるほどね。これはちょっと使い所がありそう。
User Control Flows
少し順番を変えて後回しにしていた。「ユーザーコントロールフロー」は手っ取り早く言うと "Human-in-the-loop" を実現するためのもの。以下のようなユースケースで使用する。
- 機密性の高い操作の検証
- 実行前のツール呼び出しのレビュー
- 意思決定のためのユーザー意見の収集
- 外部ツールの実行管理
で「ユーザーコントロールフロー」には4つのタイプがあり、エージェント実行が一時中断され、何らかのアクションが求められることになる。
- ユーザー確認: ツール呼び出しを実行する前に、ユーザーの明示的な承認を必要とする
- ユーザー入力: 実行中にユーザから特定の情報を収集する
- 動的なユーザー入力: エージェントが必要な時にユーザ入力を収集する
- 外部ツール実行: エージェントのコントロール外でツールを実行する
エージェント実行の一時停止
エージェントの実行が一時停止された場合は、is_paused
属性でそれを検出、何かしらの処理を行った後にcontinue_run()
メソッドで一時停止から実行に戻る、という感じみたい。
agent.run("何かしらセンシティブな処理を行う")
if agent.is_paused:
# ユーザからの入力を待つ間、エージェントは一時停止する
# ... 何かしら処理を行う ...
# 実行を継続できる
response = agent.continue_run()
# 非同期の場合
#response = await agent.acontinue_run()
ユーザ確認
一時停止時の4種類のアクションについて見ていく。
まずは「ユーザ確認」。これはツール実行時にユーザの確認を求める。以下のようなユースケースで使う。
- 機密性の高い操作
- データを変更するAPI呼び出し
- 重大な影響を及ぼす操作
これらを進めてよいか?というところの「承認」をユーザに確認して進めるということ。ドキュメントのサンプルをそのまま日本語にするとうまくいかなかったので、少しそれっぽいものにしてある。
from agno.tools import tool
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.utils import pprint
@tool(requires_confirmation=True)
def sensitive_operation(data: str) -> str:
"""ユーザの名前を確認する"""
# ここに実際の実装を行う
return "あなたの名前は山田太郎さんです"
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[sensitive_operation],
)
# エージェントを実行
agent.run("私の名前を教えて。",)
# 確認を処理するHandle confirmation
if agent.is_paused:
for tool in agent.run_response.tools_requiring_confirmation:
# ユーザからの承認を得る
print(f"ツール {tool.tool_name}({tool.tool_args}) は実行に承認が必要です。")
confirmed = input(f"承認する? (y/n): ").lower() == "y"
tool.confirmed = confirmed
# 実行を継続する
run_response = agent.continue_run()
pprint.pprint_run_response(run_response)
なるほど、ツール側にデコレータで必要なアクションを指定するっぽい。
実行してみると以下のように承認が求められる。
ツール sensitive_operation({'data': 'ユーザーの操作が必要です'}) は実行に承認が必要です。
承認する? (y/n):
承認すると処理が継続する
承認する? (y/n): y
╭──────────────────────────────────╮
│ あなたの名前は山田太郎さんです。 │
╰──────────────────────────────────╯
承認しない場合はこうなる
ツール sensitive_operation({'data': 'ユーザの名前'}) は実行に承認が必要です。
承認する? (y/n): n
╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ 名前はお教えできませんが、お手伝いできることがあれば、ぜひお知らせください。何か他に質問はありますか? │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯
ツールキットのツールでもできる。こちらの場合は requires_confirmation_tools
で確認対象となるツールキット内のツールを指定するみたい。ただちょっとバグってると思われるので修正してる。
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools import tool
from agno.tools.yfinance import YFinanceTools
from agno.utils import pprint
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
tools=[YFinanceTools(
requires_confirmation_tools=["get_current_stock_price"])
],
)
agent.run("アップルの株価を教えて。")
if agent.is_paused:
for tool in agent.run_response.tools_requiring_confirmation:
print(f"ツール {tool.tool_name}({tool.tool_args}) は実行に承認が必要です。")
# 訳注:条件判定しているのでTrue/Falseが変えるのにあとの処理は文字列判定
# しているので間違ってると思われる
#confirmed = input(f"Confirm? (y/n): ").lower() == "y"
# ただしくはこう
confirmed = input(f"承認する? (y/n): ").lower()
# 存在しない変数名になっている
#if message == "n":
# 正しくはこう
if confirmed == "n":
tool.confirmed = False
else:
# その場でツール確認状態を更新する
tool.confirmed = True
run_response = agent.continue_run()
pprint.pprint_run_response(run_response)
承認した場合
ツール get_current_stock_price({'symbol': 'AAPL'}) は実行に承認が必要です。
承認する? (y/n): y
╭──────────────────────────────────╮
│ アップルの株価は213.17ドルです。 │
╰──────────────────────────────────╯
承認しなかった場合。ここは内部のプロンプトで英語になってしまっている。いじれるのかな?
ツール get_current_stock_price({'symbol': 'AAPL'}) は実行に承認が必要です。
承認する? (y/n): n
╭───────────────────────────────────────────────────╮
│ I have tools to execute, but I need confirmation. │
╰───────────────────────────────────────────────────╯
ユーザ入力
ユーザ入力は、ユーザから特定の情報を引き出す。以下のようなユースケースで使う。
- 必要なパラメーターの収集
- ユーザーの設定情報の取得
- 不足している情報の収集
Alexaでいうところの「ダイアログモデルを使ったスロット収集」みたいなイメージを持った。
ユーザがメールを送ろうとすると、送信先・件名・本文を収集するサンプル。@tool(requires_user_input=True)
ってのユーザ入力のトリガー設定になるみたい。
from typing import List
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.function import UserInputField
# これがサンプルコードでは漏れている
from agno.tools import tool
# @toolデコレータを使用するが、あわせてdocstringを定義する
# これが `user_input_schema`に反映される
@tool(requires_user_input=True)
def send_email(to: str, subject: str, body: str) -> dict:
"""ユーザにメールを送信する
Args:
to (str): 送信先メールアドレス
subject (str): メールの件名
body (str): メールの本文
"""
# 実際はここに処理を実装
return f"{to} さんに、件名: {subject}、本文: {body}、でEメールを送信しました。"
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[send_email],
)
agent.run("メールを送信したい")
if agent.is_paused:
for tool in agent.run_response.tools_requiring_user_input:
input_schema: List[UserInputField] = tool.user_input_schema
for field in input_schema:
# フィールドの情報をユーザに表示
print(f"\nフィールド: {field.name} ({field.field_type.__name__}) -> {field.description}")
# ユーザから入力を取得
user_value = input(f"{field.name} を入力: ")
# フィールドの値を更新
field.value = user_value
response = agent.continue_run()
pprint.pprint_run_response(run_response)
・・・なのだが動かない・・・以下の箇所を細かく見てみると、
(snip)
agent.run("メールを送信したい")
if agent.is_paused:
(snip)
-
agent.run()
の結果は「どなたにメールを送信したいですか?以下の情報を教えてください。\n1. 送信先のメールアドレス\n2. メールの件名\n3. メールの本文」というような内容を返している。 -
agent.is_paused
はFalseになっている
んー、元の英語のサンプルどおりにやっても、モデルを変えたりしても同じ。is_paused
が Falseである以上は進まないんだよねぇ・・・
で、ユーザに入力を求める情報を個別に指定することができるのだけども、
# @toolデコレータに `user_input_fields`で入力させるフィールドを
# 指定できる
@tool(requires_user_input=True, user_input_fields=["to", "subject", "body"])
def send_email(to: str, subject: str, body: str) -> dict:
"""ユーザにメールを送信する
Args:
to (str): 送信先メールアドレス
subject (str): メールの件名
body (str): メールの本文
"""
# 実際はここに処理を実装
return f"{to} さんに、件名: {subject}、本文: {body}、でEメールを送信しました。"
これで全部のフィールドを指定するとうまくいった。こんな感じ。
フィールド: to (str) -> 送信先メールアドレス
to を入力: john@example.com
フィールド: subject (str) -> メールの件名
subject を入力: 明日の集合時間について
フィールド: body (str) -> メールの本文
body を入力: 9ジでお願いします
╭────────────────────────────────────────────────────────────────╮
│ メールを送信しました。何か他にお手伝いできることはありますか? │
╰────────────────────────────────────────────────────────────────╯
ドキュメントには「user_input_fields
を指定しない場合は全部のフィールドが必須になる」というようなことが書いてあるけど、現状はそうならないので、とりあえず全部指定しとく必要がありそう。バグかどうかはわからないけど、Issueは上げておいた。
動的なユーザー入力
これはユーザの入力が必要なケースだけエージェントが入力を求めるというもの。以下のようなユースケース。
- ユーザーがエージェントとどのようにインタラクションするかが不明なケース
- ユーザーとのフォームのようなインタラクションを望む場合
ということは、逆に言うと1つ上の「ユーザ入力」は明確に入力を求めるタイミングや入力内容が決まっているということになる。具体的に言うと、ツール実行時。
動的なユーザ入力の場合は UserControlFlowTools
を使う。なるほど、ツールとしてラップしてあげることで入力が必要かどうかをエージェントに判断させて、その後の処理もおまかせする、ということになる。
1つ前のメール送信のサンプルを UserControlFlowTools
を使って動的に行う例。
from typing import Any, Dict
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools import tool
from agno.tools.toolkit import Toolkit
from agno.tools.user_control_flow import UserControlFlowTools
from agno.utils import pprint
# メールを処理するサンプルのツールキット
class EmailTools(Toolkit):
def __init__(self, *args, **kwargs):
super().__init__(
name="EmailTools",
tools=[
self.send_email,
self.get_emails
],
*args,
**kwargs
)
def send_email(self, subject: str, body: str, to_address: str) -> str:
"""与えられた、送信先メールアドレス、件名、本文のメールを送信する
Args:
to_address (str): 送信先メールアドレス
subject (str): メールの件名
body (str): メールの本文
"""
# 実際にはここでメールを送信する処理を書く
# (なお、ちょいちょい変数名の指定とか間違ってるので修正している)
return f"{to_address} さんに、件名: {subject}、本文: {body}、でEメールを送信しました。"
def get_emails(self, date_from: str, date_to: str) -> str:
"""与えられた期間内のすべてのメールを取得する
Args:
date_from (str): 期間の開始日
date_to (str): 期間の終了日
"""
# ダミーの関数で、実際にはデータベース等からメールの一覧を取得することになる
return [
{
"subject": "こんにちは!",
"body": "こんにちは!最近連絡していないですが、お元気ですか?",
"to_address": "paul@example.com",
"date": date_from,
},
{
"subject": "打ち合わせのお願い",
"body": "打ち合わせしたいので空いてる日をいくつか教えて下さい。",
"to_address": "john@example.com",
"date": date_to,
},
]
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
tools=[
EmailTools(),
UserControlFlowTools()
],
markdown=True,
#debug_mode=True, # 出力量が多いので一旦コメントアウト
)
run_response = agent.run("「東京でいかがお過ごしですか?」という本文のメールを送りたい。")
# whileループを使ってエージェントが全てのユーザ入力を取得するまで継続実行する
while run_response.is_paused:
for tool in run_response.tools_requiring_user_input:
input_schema: List[UserInputField] = tool.user_input_schema
for field in input_schema:
# フィールドの情報をユーザに表示
print(f"\フィールド: {field.name} ({field.field_type.__name__}) -> {field.description}")
# ユーザから入力を取得
# (値がセットされない場合は、ユーザが値を入力する必要がある、ということになる)
if field.value is None:
user_value = input(f"{field.name} を入力してください: ")
field.value = user_value
else:
print(f"Value provided by the agent: {field.value}")
run_response = agent.continue_run(run_response=run_response)
# エージェントが入力のために一時停止しなければ、処理は完了
if not run_response.is_paused:
pprint.pprint_run_response(run_response)
break
こんな感じで、メール送信に必要かつ足りない情報だけ入力を促して送信しているのがわかる。
\フィールド: to_address (str) -> 送信先のメールアドレス
to_address を入力してください: john@example.com
\フィールド: subject (str) -> メールの件名
subject を入力してください: お元気ですか?
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ メールを john@example.com さんに、件名: お元気ですか?、本文: 東京でいかがお過ごしですか?で送信しました。 │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
なお、debug=True
をつけると、UserControlFlowTools
のシステムプロンプトが見えて、なかなか興味深い。
DEBUG <additional_information>
- Use markdown to format your answers.
</additional_information>
You have access to the `get_user_input` tool to get user input for the given fields.
1. **Get User Input**:
- Purpose: When you have call a tool/function where you don't have enough information, don't say you
can't do it, just use the `get_user_input` tool to get the information you need from the user.
- Usage: Call `get_user_input` with the fields you require the user to fill in for you to continue your
task.
## IMPORTANT GUIDELINES
- **Don't respond and ask the user for information.** Just use the `get_user_input` tool to get the
information you need from the user.
- **Don't make up information you don't have.** If you don't have the information, use the `get_user_input`
tool to get the information you need from the user.
- **Include only the required fields.** Include only the required fields in the `user_input_fields` parameter
of the `get_user_input` tool. Don't include fields you already have the information for.
- **Provide a clear and concise description of the field.** Clearly describe the field in the
`field_description` parameter of the `user_input_fields` parameter of the `get_user_input` tool.
- **Provide a type for the field.** Fill the `field_type` parameter of the `user_input_fields` parameter of
the `get_user_input` tool with the type of the field.
## INPUT VALIDATION AND CONVERSION
- **Boolean fields**: Only explicit positive responses are considered True:
* True values: 'true', 'yes', 'y', '1', 'on', 't', 'True', 'YES', 'Y', 'T'
* False values: Everything else including 'false', 'no', 'n', '0', 'off', 'f', empty strings, unanswered
fields, or any other input
* **CRITICAL**: Empty/unanswered fields should be treated as False (not selected)
- **Users can leave fields unanswered.** Empty responses are valid and should be treated as False for boolean
fields.
- **NEVER ask for the same field twice.** Once you receive ANY user input for a field (including empty
strings), accept it and move on.
- **DO NOT validate or re-request input.** Accept whatever the user provides and convert it appropriately.
- **Proceed with only the fields that were explicitly answered as True.** Skip or ignore fields that are
False/unanswered.
- **Complete the task immediately after receiving all user inputs, do not ask for confirmation or
re-validation.**
日本語訳(DeepL)
<additional_information>
- 回答のフォーマットにはマークダウンを使用してください。
</additional_information>
get_user_input
ツールを使用して、指定されたフィールドのユーザー入力を取得できます。
- ユーザー入力の取得:
- 目的: ツール/関数を呼び出す際に十分な情報が不足している場合、できないと断るのではなく、
get_user_input
ツールを使用してユーザーから必要な情報を取得してください。- 使用方法: タスクを継続するためにユーザーに入力してもらう必要があるフィールドを指定して
get_user_input
を呼び出してください。重要なガイドライン
- **ユーザーに情報を求める返信をしないでください。**必要な情報を取得するには、
get_user_input
ツールを使用してください。- **持っていない情報をでっち上げないでください。**情報が不足している場合は、
get_user_input
ツールを使用してユーザーから必要な情報を取得してください。- 必要なフィールドのみを含めてください。
get_user_input
ツールのuser_input_fields
パラメーターに、必要なフィールドのみを含めてください。既に持っている情報のフィールドは含めないでください。- フィールドの明確で簡潔な説明を提供してください。
get_user_input
ツールのuser_input_fields
パラメーターのfield_description
パラメーターに、フィールドを明確に説明してください。- フィールドのタイプを指定してください。
get_user_input
ツールのuser_input_fields
パラメーター内のfield_type
パラメーターに、フィールドのタイプを指定してください。入力検証と変換
- ブール型フィールド: 明示的な肯定的な回答のみを True とみなします:
- True 値: 『true』, 『yes』, 『y』, 『1』, 『on』, 『t』, 『True』, 『YES』, 『Y』, 『T』
- False 値: それ以外のすべて(『false』, 『no』, 『n』, 『0』, 『off』, 『f』, 空の文字列、未回答のフィールド、またはその他の入力を含む)
- 重要: 空または未回答のフィールドはFalse(選択されていない)として扱います。
- ユーザーはフィールドを未回答のままにできます。 空の回答は有効であり、ブール型フィールドではFalseとして扱います。
- 同じフィールドを二度尋ねてはいけません。 フィールドに対してユーザーから何らかの入力(空の文字列を含む)を受け取った場合、それを受け入れて次に進みます。
- 入力の検証や再要求は行わない。 ユーザーが提供した内容をそのまま受け入れ、適切に変換する。
- 明示的にTrueとして回答されたフィールドのみ処理する。 False/未回答のフィールドはスキップまたは無視する。
- すべてのユーザー入力を受け取った直後にタスクを完了し、確認や再検証を求めない。
外部ツール実行
エージェントの制御外のツールを実行するには「外部ツール実行」を使う。ユースケースは以下。
- 外部サービス呼び出し
- データベース操作
この「エージェントの制御 "外"」ってのがいまいちピンとこないのだけども、とりあえずサンプルコード
import subprocess
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools import tool
from agno.utils import pprint
# エージェントがどのように実行するかを正しく知るために、
# ツール名、引数、docstringを適切に設定して、ツールを定義する必要がある
@tool(external_execution=True)
def execute_shell_command(command: str) -> str:
"""シェルコマンドを実行する
Args:
command (str): 実行するシェルコマンド
Returns:
str: シェルコマンドの出力
"""
return subprocess.check_output(command, shell=True).decode("utf-8")
agent = Agent(
model=OpenAIChat(id="gpt-4o-mini"),
tools=[execute_shell_command],
markdown=True,
)
run_response = agent.run("カレントディレクトリにはどんなファイルがある?")
if run_response.is_paused:
for tool in run_response.tools_awaiting_external_execution:
if tool.tool_name == execute_shell_command.name:
print(f"ツール {tool.tool_name} を 引数 {tool.tool_args} で外部実行")
# ツールを自分で実行。どんな関数やプロセスも実行でき `tool_args` が引数となる
result = execute_shell_command.entrypoint(**tool.tool_args)
# ツール実行オブジェクトの結果をセットすることで、エージェントは継続できる
tool.result = result
run_response = agent.continue_run()
pprint.pprint_run_response(run_response)
ツール execute_shell_command を 引数 {'command': 'ls -l'} で外部実行
╭──────────────────────────────────────────────────────────╮
│ カレントディレクトリには、次のようなファイルがあります: │
│ │
│ - `sample_data` (ディレクトリ) │
╰──────────────────────────────────────────────────────────╯
なるほど、ツールの実行は自分で行っているところか。
これを「ユーザー確認」で書き換えるとこうなる。
from agno.tools import tool
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.utils import pprint
@tool(requires_confirmation=True)
def execute_shell_command(command: str) -> str:
"""シェルコマンドを実行する
Args:
command (str): 実行するシェルコマンド
Returns:
str: シェルコマンドの出力
"""
return subprocess.check_output(command, shell=True).decode("utf-8")
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[execute_shell_command],
)
# エージェントを実行
agent.run("カレントディレクトリにはどんなファイルがある?")
# 確認を処理する
if agent.is_paused:
for tool in agent.run_response.tools_requiring_confirmation:
# ユーザからの承認を得る
print(f"ツール {tool.tool_name}({tool.tool_args}) は実行に承認が必要です。")
confirmed = input(f"承認する? (y/n): ").lower() == "y"
tool.confirmed = confirmed
# 実行を継続する
run_response = agent.continue_run()
pprint.pprint_run_response(run_response)
ツール execute_shell_command({'command': 'ls'}) は実行に承認が必要です。
承認する? (y/n): y
╭────────────────────────────────────────────────────────────────────────╮
│ カレントディレクトリには、`sample_data` というディレクトリがあります。 │
╰────────────────────────────────────────────────────────────────────────╯
「ユーザー確認」とかだと実行部分は完全に隠蔽されエージェントが全てを処理するが、この部分の制御を開発者側でできる、ってことね。つまり、開発者で実行タイミングや実行方法を完全にコントロールしたい場合に使用するって感じみたい。
ベストプラクティス
当然ながら入力を求める場合は入力された値のチェックが必要になる。このあたりがベストプラクティスに記載してある
- ユーザー入力のサニタイズ: セキュリティ上の脆弱性を防ぐため、ユーザー入力は常に検証しサニタイズしてください。
- エラー処理: ユーザー入力と外部呼び出しに対して適切なエラー処理を必ず実装してください。
- 入力検証: 処理前にユーザー入力を検証してください。
Human-in-the-loopの実装って結構わかりにくいのだけど、かなりシンプルに実現できる感じで良さげ。
Agent Teams
これは Deprecateされて、今は「Teams」という別のコンポーネントになっているみたい。元々はマルチエージェント間で移乗・ハンドオフする仕組みになっていたみたいだけど、複雑なマルチエージェントシステムではスケールしないってことで見直された様子。
今の「Teams」は以下。ここはまた別の機会に。
まとめ
Agnoの各コンポーネントごとに深堀りしていくつもりだったんだけども、Agentsはコアなところでもあるせいか、他のコンポーネントもいくつか出てきていたね。
一通りやってみた印象としては、エージェントのユースケースで求められそうなものが一通り作り込まれているのが Agents というコンポーネント、という印象。その分、他のコンポーネントに関連するが、パラメータはAgentが持っている、とような点も多い気はして、ここはちょっとわかりにくいかもしれない。まあ慣れかな。
でも書きっぷりとしては十分シンプルだし、個人的にはもっと使い込んでみたい気がしている。
コンポーネント深堀り編ということで①みたいなナンバリングしたけど、Agentsのカバー範囲が広かったので、次はどうしようかなー。多分同じレベル感で言うと
- Teams
- Workflows
- Applicatiions
あたりを見るのが良さそう。