Zenn
Open9

「Dapr Agents」を試す

kun432kun432

CNCFってのが期待できそう。

https://www.cncf.io/blog/2025/03/12/announcing-dapr-ai-agents/

本日、Daprの上に構築され、ステートフルなワークフローの調整と高度なAgentic AI機能を組み合わせたフレームワークであるDaprエージェントを発表できることを嬉しく思います。Daprエージェントは、エンタープライズユースケースに適したエージェントシステムを構築する最善の方法です。

  • 単一のコア上で数千ものエージェントを確実に実行
  • 複雑なエージェントワークフローを自動的に再試行し、各エージェントタスクが確実に完了することを保証
  • Kubernetes上でネイティブにデプロイおよび運用
  • ドキュメント、データベース、非構造化データからエージェントへのデータロードを直接実行可能
  • デフォルトでセキュアかつ監視可能なマルチエージェントシステムを簡単に作成できるビルトインサポート
  • ベンダーニュートラル:ライセンス変更や知的財産権侵害のリスクを軽減
  • Daprを基盤として構築:Daprは、世界中の政府機関や数千社で使用されている、スケーラブルな監視、セキュリティ、回復力をカバーする信頼性の高いエンタープライズフレームワークです。

で、ベースとなっている「Dapr」はここがわかりやすかった。

https://qiita.com/takashiuesaka/items/ecb8d232d00f0a6591a5

この記事に書いてあることしか見てないけど、これエージェント間のルーティングとかステートの受け渡しとかで、めっちゃ使えるんじゃなかろうか?アプリケーションとは別レイヤーで抽象化されてて、設計としてかなり納得感あるし、この辺も含めてDapr Agents期待できそうと感じる。

kun432kun432

Dapr AgentsのGitHubレポジトリ

https://github.com/dapr/dapr-agents

Dapr Agents: エージェント的AIシステムのためのフレームワーク

Dapr Agentsは、大規模に運用可能なプロダクショングレードの堅牢なAIエージェントシステムを構築するために設計された開発者向けフレームワークです。実績あるDaprプロジェクトの上に構築されており、ソフトウェア開発者は、内蔵の可観測性と状態管理付きワークフロー実行を活用しながら、複雑なエージェントワークフローが確実に成功することを保証し、LLM(大規模言語モデル)を用いて推論、行動、協働を行うAIエージェントを作成できます。


referred from https://github.com/dapr/dapr-agents

主要な特徴

  • スケールと効率: 単一コア上で数千のエージェントを効率的に実行。Daprは単一および複数エージェントのアプリケーションをマシンのフリート全体に透過的に分散し、そのライフサイクルを管理します。
  • ワークフローの堅牢性: エージェントワークフローを自動的に再試行し、タスクの完了を保証します。
  • Kubernetesネイティブ: Kubernetes環境でのエージェントの容易なデプロイと管理を実現します。
  • データ駆動型エージェント: 複数の異なるデータソースに接続することにより、データベース、文書、非構造化データと直接統合できます。
  • マルチエージェントシステム: 初期状態で安全かつ可観測なため、エージェント間の協働が可能です。
  • ベンダーニュートラル&オープンソース: ベンダーロックインを回避し、クラウドおよびオンプレミスの展開において柔軟性を提供します。
  • プラットフォーム対応: 組み込みのRBAC、アクセススコープ、宣言的リソースにより、プラットフォームチームがDaprエージェントをシステムに統合することが可能です。

なぜDapr Agentsを選ぶのか?

第一級市民としてのスケーラブルワークフロー

Dapr Agentsは、ネットワークの中断、ノードのクラッシュ、その他の破壊的な障害に直面しても各エージェントタスクが確実に完了することを保証する耐久性実行ワークフローエンジンを使用しています。開発者はワークフローエンジンの基礎概念を理解する必要はなく、任意のタスクを実行するエージェントを書くだけで、これらは自動的にクラスタ全体に分散されます。もしタスクが失敗した場合でも、自動的に再試行され、途中から状態を復元します。

コスト効果の高いAI導入

Dapr Agentsは、DaprのWorkflow APIの上に構築されており、内部的には各エージェントをアクター(計算と状態の単位)として表現しています。このアクターはスレッドセーフでネイティブに分散されるため、エージェント的Scale-To-Zeroアーキテクチャに非常に適しています。これにより、インフラコストが最小限に抑えられ、誰もがAI導入にアクセスできるようになります。基盤となる仮想アクターモデルにより、ゼロからスケールする際に、単一コアマシン上で数千のエージェントがダブルディジットミリ秒のレイテンシでオンデマンド実行されます。使用されていない場合、エージェントはシステムにより回収されますが、次に必要となるまでその状態は保持されます。この設計により、パフォーマンスとリソース効率の間に妥協はありません。

データ中心のAIエージェント

50以上のエンタープライズデータソースへの組み込み接続により、Dapr Agentsは構造化データと非構造化データの両方を効率的に扱います。基本的なPDF抽出から大規模なデータベースとの連携まで、最小限のコード変更でシームレスなデータ駆動型AIワークフローを実現します。Daprのバインディングおよびステートストアは、エージェントにデータを取り込むために使用できる多数のデータソースへのアクセスを提供します。MCP統合は近日公開予定です。

開発の加速

Dapr Agentsは、一般的な問題に取り組むための完全なAPIサーフェスを開発者に提供する一連のAI機能を提供します。これには以下が含まれます:

  • マルチエージェント間通信
  • 構造化出力
  • 複数のLLMプロバイダー
  • コンテクストメモリ
  • 柔軟なプロンプティング
  • インテリジェントなツール選択

統合されたセキュリティと信頼性

Dapr上に構築されているため、プラットフォームおよびインフラストラクチャチームは、Dapr Agentsが使用するデータベースやメッセージブローカーに対してDaprの回復性ポリシーを適用することができます。これらのポリシーには、タイムアウト、再試行/バックオフ、サーキットブレーカーが含まれます。セキュリティに関しては、Daprは特定のデータベースまたはメッセージブローカーへのアクセスを1つ以上のエージェント的アプリケーションデプロイメントに制限するオプションを提供します。さらに、Dapr AgentsはmTLSを使用して、基盤となるコンポーネント間の通信レイヤーを暗号化しています。

組み込みのメッセージングと状態インフラストラクチャ

  • 🎯 サービス間呼び出し: 組み込みのサービスディスカバリー、エラーハンドリング、分散トレーシングを備えた、エージェント間の直接通信を促進します。エージェントはこれを利用して、マルチエージェントワークフローにおける同期メッセージングを行います。
  • ⚡️ パブリッシュとサブスクライブ: 共有メッセージバスを通じてエージェント間の緩やかな連携をサポートします。これにより、タスクの分配や調整に不可欠なリアルタイムのイベント駆動型相互作用が可能になります。
  • 🔄 耐久性ワークフロー: 決定論的プロセスとLLMベースの意思決定を組み合わせた長時間持続する永続的なワークフローを定義します。Dapr Agentsはこれを使用して、複雑なマルチステップのエージェントワークフローをシームレスにオーケストレーションします。
  • 🧠 状態管理: エージェントがやり取りの間にコンテキストを保持し、ワークフロー中の継続性と適応性を確保するための柔軟なキー・バリューストアを提供します。
  • 🤖 アクター: 仮想アクターパターンを実装し、エージェントが自己完結型の状態を持つ単位として順次メッセージを処理できるようにします。これにより、並行性の懸念が解消され、エージェントシステムのスケーラビリティが向上します。

ベンダーニュートラルかつオープンソース

CNCFの一部として、Dapr Agentsはベンダーロックイン、知的財産リスク、または独自制限に関する懸念を排除します。組織は、監査および貢献が可能なオープンソースソフトウェアを使用することで、AIアプリケーションに対する完全な柔軟性と制御を得ることができます。

始めるにあたって

前提条件:

kun432kun432

Dapr Agentsも気になるけども、Dapr先にやったほうがいいかなぁ。ただかなりボリュームありそう・・・・でも気になる・・・

kun432kun432

Quicstart

とりあえずDapr AgentsのQuickstartを進めてみる

https://github.com/dapr/dapr-agents/tree/main/quickstarts/README.md

前提として必要なのは以下。

  • Python-3.10以上
  • Docker
  • Dapr CLI
  • OpenAI APIキー(実際には他のLLMも利用可能)

ということで、ローカルのMacでやることとする。

Dapr CLIインストール

まず、Dapr CLIをインストール。Dapr CLIのインストールは以下を参照。

https://docs.dapr.io/getting-started/install-dapr-cli/

いくつかのインストール方法が用意されているが、今回はHomebrewでインストールすることとする。なお、Intel MacとARM64 Mac(Apple Silocon)でオプションが異なる様子。自分はM2 Macなので以下。

arch -arm64 brew install dapr/tap/dapr-cli
dapr --version
出力
CLI version: 1.15.0
Runtime version: n/a

Usageも。

dapr -h
出力
	  __
     ____/ /___ _____  _____
    / __  / __ '/ __ \/ ___/
   / /_/ / /_/ / /_/ / /
   \__,_/\__,_/ .___/_/
	     /_/

===============================
Distributed Application Runtime

Usage:
  dapr [flags]
  dapr [command]

Available Commands:
  annotate       Add dapr annotations to a Kubernetes configuration. Supported platforms: Kubernetes
  build-info     Print build info of Dapr CLI and runtime
  completion     Generates shell completion scripts
  components     List all Dapr components. Supported platforms: Kubernetes
  configurations List all Dapr configurations. Supported platforms: Kubernetes
  dashboard      Start Dapr dashboard. Supported platforms: Kubernetes and self-hosted
  help           Help about any command
  init           Install Dapr on supported hosting platforms. Supported platforms: Kubernetes and self-hosted
  invoke         Invoke a method on a given Dapr application. Supported platforms: Self-hosted
  list           List all Dapr instances. Supported platforms: Kubernetes and self-hosted
  logs           Get Dapr sidecar logs for an application. Supported platforms: Kubernetes
  mtls           Check if mTLS is enabled. Supported platforms: Kubernetes
  publish        Publish a pub-sub event. Supported platforms: Self-hosted
  run            Run Dapr and (optionally) your application side by side. Supported platforms: Self-hosted
  status         Show the health status of Dapr services. Supported platforms: Kubernetes
  stop           Stop Dapr instances and their associated apps. Supported platforms: Self-hosted
  uninstall      Uninstall Dapr runtime. Supported platforms: Kubernetes and self-hosted
  upgrade        Upgrades or downgrades a Dapr control plane installation in a cluster. Supported platforms: Kubernetes
  version        Print the Dapr runtime and CLI version

Flags:
  -h, --help                  help for dapr
      --log-as-json           Log output in JSON format
      --runtime-path string   The path to the dapr runtime installation directory
  -v, --version               version for dapr

Use "dapr [command] --help" for more information about a command.

Daprの初期化

Dapr CLIでDaprを初期化する。

https://docs.dapr.io/getting-started/install-dapr-selfhost/

Daprはサイドカーパターンを採用している。サイドカーパターンについては冒頭のQiitaの記事にわかりやすいのでそちらを参照するのが良いと思うが、Kubereneteのpodなんかでもよく見られる一般的なマイクロサービスのパターンだと思う。例えばログとかだとこんな感じになる。

ローカルでDaprを実行する場合、これは単にホスト上で複数コンテナ(というかプロセス)が立つことになる。ドキュメントでは以下とある。

  1. 状態管理やメッセージングに使用されるローカルの状態ストアとメッセージブローカーとして動作するRedisコンテナを起動。
  2. 分散トレーシングによる可観測性向上のために使用Zipkinコンテナを起動。
  3. これらのサービスに対応するデフォルトのコンポーネント定義を含むフォルダを作成
  4. ローカルアクターサポートを提供するDaprプレイスメントサービスコンテナを起動。
  5. ジョブのスケジューリング(定期実行など)を行うDaprスケジューラーサービスコンテナを起動。

では実行。

dapr init

なのだが、

出力
⌛  Making the jump to hyperspace...
❌  could not connect to docker. docker may not be installed or running

うーん、Dockerは動いているのだけど・・・と思ったらドキュメントに書いてあった。

https://docs.dapr.io/operations/troubleshooting/common_issues/#dapr-cant-connect-to-docker-when-installing-the-dapr-cli

自分の場合、マルチプラットフォーム向けビルド環境作るにいろいろいじってたせいで、多分おかしくなってしまったのかも知れない。設定上問題なさそうだったので、Dockerを再起動+アップデートしたら実行できた。

dapr init
出力
⌛  Making the jump to hyperspace...
ℹ️  Container images will be pulled from Docker Hub
ℹ️  Installing runtime version 1.15.3
↗  Downloading binaries and setting up components...
Dapr runtime installed to /Users/kun432/.dapr/bin, you may run the following to add it to your path if you want to run daprd directly:
    export PATH=$PATH:/Users/kun432/.dapr/bin
✅  Downloading binaries and setting up components...
✅  Downloaded binaries and completed components set up.
ℹ️  daprd binary has been installed to /Users/kun432/.dapr/bin.
ℹ️  dapr_placement container is running.
ℹ️  dapr_redis container is running.
ℹ️  dapr_zipkin container is running.
ℹ️  dapr_scheduler container is running.
ℹ️  Use `docker ps` to check running containers.
✅  Success! Dapr is up and running. To get started, go here: https://docs.dapr.io/getting-started

コンテナを見てみると上記で記載された通りのコンテナが立ち上がっている。

docker ps
出力
CONTAINER ID   IMAGE                COMMAND                  CREATED         STATUS                   PORTS                                                                                                 NAMES
0d4e20d5ced0   openzipkin/zipkin    "start-zipkin"           3 minutes ago   Up 3 minutes (healthy)   9410/tcp, 0.0.0.0:9411->9411/tcp                                                                      dapr_zipkin
06959423798b   daprio/dapr:1.15.3   "./scheduler --etcd-…"   3 minutes ago   Up 3 minutes             0.0.0.0:50006->50006/tcp, 0.0.0.0:52379->2379/tcp, 0.0.0.0:58081->8080/tcp, 0.0.0.0:59091->9090/tcp   dapr_sched
773eb5c491d6   daprio/dapr:1.15.3   "./placement"            3 minutes ago   Up 3 minutes             0.0.0.0:50005->50005/tcp, 0.0.0.0:58080->8080/tcp, 0.0.0.0:59090->9090/tcp                            dapr_placement
c6b9f46aeea0   redis:6              "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes             0.0.0.0:6379->6379/tcp                                                                                dapr_redis
kun432kun432

Quickstartスクリプト

以下に6つのQuickstart(ただしナンバリングは1〜5になっているが。)が用意されている様子。

https://github.com/dapr/dapr-agents/tree/main/quickstarts

利用可能なクイックスタート

Hello World

シンプルなデモを通じてDapr Agentsの基本概念を迅速に紹介します:

  • 基本的なLLMの使用: OpenAIモデルを用いたシンプルなテキスト生成
  • エージェントの作成: 20行未満のコードでカスタムツールを備えたエージェントの構築
  • ReActパターン: エージェントにおける推論と行動のサイクルの実装
  • シンプルなワークフロー: 複数ステップのLLMプロセスの設定

Daprチャットクライアントを使ったLLM呼び出し

Dapr Agents の DaprChatClient を使用して言語モデルと対話する方法を学びます:

  • テキスト補完: プロンプトに対する応答の生成
  • LLMプロバイダーの切り替え: アプリケーションコードを変更せずにLLMバックエンドを切り替え
  • 回復性: タイムアウト、再試行、サーキットブレーカーの設定
  • PIIのマスキング – ユーザーの個人情報を自動で検出しマスク

このクイックスタートは、プレーンテキストのプロンプトとテンプレートを使用した基本的なテキスト生成を示しています。DaprChatClient を使用することで、エージェントのコードを変更することなく、異なるLLMプロバイダーをターゲットにできます。

OpenAIクライアントを使ったLLM呼び出し

Dapr AgentsとネイティブLLMクライアントライブラリを使用して、言語モデルと対話する方法を学びます。

  • テキスト補完: プロンプトに対する応答の生成
  • 構造化出力: LLMの応答をPydanticオブジェクトに変換

このクイックスタートは、基本的なテキスト生成とLLMからの構造化データ抽出の両方を示しています。このクイックスタートでは、OpenAIChatClientを使用しており、チャット補完に加え、音声利用や埋め込みも可能です。

注意: 他の特定クライアント向けのクイックスタートも、ElevenlabsHugging FaceNvidia向けに用意されています。

エージェントツール呼び出し

カスタムツールを用いた最初のAIエージェントの作成:

  • ツール定義: @toolデコレーターを使用した再利用可能なツールの作成
  • エージェント設定: 役割、目標、ツールを備えたエージェントの設定
  • 関数呼び出し: LLMにPython関数を実行させる機能の有効化

このクイックスタートは、情報取得やアクション実行が可能な天気アシスタントの構築方法を示しています。

エージェント的ワークフロー

Dapr Agentsを使用した状態管理付きワークフローの使用方法の入門:

  • LLMを活用したタスク: ワークフロー内で言語モデルを使用
  • タスクチェイニング: 順次実行される回復性の高い複数ステッププロセスの作成
  • ファンアウト/ファンイン: 並行して実行されるアクティビティの後、全ての前のアクティビティが完了するまで同期

このクイックスタートは、Dapr Agentsのワークフロー機能を使用して、順次および並行タスクをオーケストレーションする方法を示しています。

マルチエージェントワークフロー

複数の自律エージェントを用いたイベント駆動型ワークフローの高度な例:

  • マルチエージェントシステム: 専門化されたエージェントのネットワークの作成
  • イベント駆動型アーキテクチャ: エージェント間のパブリッシュ/サブスクライブメッセージングの実装
  • アクターモデル: 状態管理付きエージェントのためのDaprアクターの使用
  • ワークフローオーケストレーション: 異なる選択戦略を通じてエージェントを調整

このクイックスタートは、エージェントが協働して問題を解決する、ロード・オブ・ザ・リングをテーマにしたマルチエージェントシステムを示しています。

上から順にやってみようと思う。

kun432kun432

01: Hello World

https://github.com/dapr/dapr-agents/tree/main/quickstarts/01-hello-world

DaprエージェントによるHello World

このクイックスタートでは、簡単な例を通してDaprエージェントのハンズオン入門を提供します。LLMの操作、基本的なエージェントの作成、ReActパターンの実装、簡単なワークフローの設定など、すべての例で20行以下のコードで作業の基本を学べます。

ということで、まずこちらから。作業ディレクトリ&Python仮想環境を作成。自分はuvを使う

mkdir -p dapr-agent-work/01-hello-world && cd dapr-agent-work/01-hello-world
uv venv -p 3.12.9

パッケージインストール。

uv pip install dapr-agents python-dotenv
出力
 + dapr==1.15.0
 + dapr-agents==0.3.1
 + dapr-ext-fastapi==1.15.0
 + dapr-ext-workflow==1.15.0

OpenAI APIキーを.envにセット。

.env
OPENAI_API_KEY=XXXXXXXXXX

ではまず基本的なLLMへのリクエスト。

01_ask_llm.py
from dapr_agents import OpenAIChatClient
from dotenv import load_dotenv

load_dotenv()

llm = OpenAIChatClient()

response = llm.generate("なにかジョークを言ってみて。")

if len(response.get_content())>0:
    print("Got response:", response.get_content())

OpenAIChatClient()でOpenAIクライアントのインスタンスを作成して、.generate()メソッドでリクエスト送信してレスポンスを受信、レスポンスのget_content()で回答を受け取る、という感じ。

実行

uv run 01_ask_llm.py

Got response: もちろん!では、こちらをどうぞ。

「数学の先生がパイを焼いたら何ができる? 答えは…パイの半径を二乗してもとに戻らない円周率!」

モデルはgpt-4o-2024-08-06が使用されていた。モデルを指定する場合は以下のようにgenerate()メソッドで指定すればOK。

(snip)

response = llm.generate("なにかジョークを言ってみて。", model="gpt-4o-mini")

(snip)

次にツールをもたせたシンプルなエージェント。ツールは@toolデコレータで関数をラップする。この書き方だと、おそらくdocstringや型ヒントからツールのスキーマを取得するのではないだろうか?でツールをAgentに渡す。

02_build_agent.py
from dapr_agents import tool, Agent
from dotenv import load_dotenv

load_dotenv()

@tool
def my_weather_func() -> str:
    """現在の天気を取得する。"""
    return "現在の天気は晴れ、気温は22℃です。"

weather_agent = Agent(
    name="WeatherAgent",
    role="Weather Assistant",
    instructions=["ユーザに天気をの情報を伝える。"],
    tools=[my_weather_func]
)

response = weather_agent.run("今の天気はどう?")
print(response)

実行

uv run 02_build_agent.py
出力
No type hints provided for function 'my_weather_func'. Defaulting to 'str'.
user:
今の天気はどう?

--------------------------------------------------------------------------------

assistant:
Function name: MyWeatherFunc (Call Id: call_YhE02Aui1MFn4OZby9V36wYq)
Arguments: {}

--------------------------------------------------------------------------------

MyWeatherFunc(tool) (Id: call_YhE02Aui1MFn4OZby9V36wYq):
現在の天気は晴れ、気温は22°Cです。

--------------------------------------------------------------------------------

assistant:
現在の天気は晴れで、気温は22°Cです。

--------------------------------------------------------------------------------

現在の天気は晴れで、気温は22°Cです。

実際にはカラーリングされて出力されているのだが、予想通り、型ヒントが使用される様子。

次にReActなエージェント。ReActAgentを使う。

03_reason_act.py
from dapr_agents import tool, ReActAgent
from dotenv import load_dotenv

load_dotenv()

@tool
def search_weather(city: str) -> str:
    """与えられた都市の天気情報を返す。"""
    weather_data = {"ロンドン": "雨", "パリ": "晴れ", "東京": "曇り"}
    return weather_data.get(city, "Unknown")

@tool
def get_activities(weather: str) -> str:
    """天気に応じておすすめのアクティビティを取得する"""
    activities = {"雨": "美術館を巡る", "晴れ": "ハイキングに行く"}
    return activities.get(weather, "家でゆっくりする")

react_agent = ReActAgent(
    name="TravelAgent",
    role="Travel Assistant",
    instructions=["天気を確認して、おすすめのアクティビティを提案する"],
    tools=[search_weather, get_activities]
)

react_agent.run("ロンドンで今日のおすすめを教えて。")
uv run 03_reason_act.py
出力
user:
ロンドンで今日のおすすめを教えて。

--------------------------------------------------------------------------------

Thought: ロンドンの天気情報を調べて、それに基づいておすすめのアクティビティを提案するために、まず天気を確認する必要があります。
Action: {"name": "SearchWeather", "arguments": {"city": "\u30ed\u30f3\u30c9\u30f3"}}
Observation: 雨
Thought: ロンドンは雨の天気であるため、この天気に適したアクティビティを提案するために、次におすすめのアクティビティを確認する必要があります。
Thought:
Action: {"name": "GetActivities", "arguments": {"weather": "\u96e8"}}
Observation: 美術館を巡る
Thought: ロンドンの天気は雨ということで、美術館巡りが推奨されています。 Answer: ロンドンでは雨の日には美術館を巡るアクティビティがおすすめです。例えば、テート・ブリテンや大英博物館などを訪れるのはいかがでしょうか。

--------------------------------------------------------------------------------

assistant:
ロンドンでは雨の日には美術館を巡るアクティビティがおすすめです。例えば、テート・ブリテンや大英博物館などを訪れるのはいかがでしょうか。

引数が日本語になったり英語になったりするとうまくいかない。docstringに指定すればいけた。

(snip)

@tool
def search_weather(city: str) -> str:
    """与えられた都市の天気情報を返す。

    Args:
        city (str): 都市名。日本語で。

    Returns:
        str: 天気情報
    """
    weather_data = {"ロンドン": "雨", "パリ": "晴れ", "東京": "曇り"}
    return weather_data.get(city, "Unknown")

@tool
def get_activities(weather: str) -> str:
    """天気に応じておすすめのアクティビティを取得する

    Args:
        weather (str): 天気。日本語で。

    Returns:
        str: アクティビティ情報
    """
    activities = {"雨": "美術館を巡る", "晴れ": "ハイキングに行く"}
    return activities.get(weather, "家でゆっくりする")

(snip)

次にシンプルなワークフロー。

04_chain_tasks.py
from dapr_agents.workflow import WorkflowApp, workflow, task
from dapr_agents.types import DaprWorkflowContext

from dotenv import load_dotenv

load_dotenv()

@workflow(name='analyze_topic')
def analyze_topic(ctx: DaprWorkflowContext, topic: str):
    # 各ステップは耐久性があり、再試行が可能
    outline = yield ctx.call_activity(create_outline, input=topic)
    blog_post = yield ctx.call_activity(write_blog, input=outline)
    return blog_post

@task(description="「{topic}」に関する詳細なアウトラインを作成して。")
def create_outline(topic: str) -> str:
    pass

@task(description="このアウトラインに従い、包括的なブログ記事を書いてください: {outline}")
def write_blog(outline: str) -> str:
    pass

if __name__ == '__main__':
    wfapp = WorkflowApp()

    results = wfapp.run_and_monitor_workflow(
        analyze_topic,
        input="AI Agents"
    )
    print(f"結果: {results}")

@taskでタスクを定義、タスクをチェーンで実行するワークフローを@workflowで定義、WorkflowApp()でワークフローを実行、という感じか。

で、ここまではシンプルなPythonスクリプトとして実行してきたが、これはDaprで実行する。

dapr run --app-id dapr-agent-wf -- uv run python 04_chain_tasks.py

色々ログが出力される。

出力
ℹ️  Starting Dapr with id dapr-agent-wf. HTTP Port: 64159. gRPC Port: 64160
ℹ️  Checking if Dapr sidecar is listening on HTTP port 64159
Flag --components-path has been deprecated, use --resources-path
INFO[0000] Starting Dapr Runtime -- version 1.15.3 -- commit c3687714f9592def48b14b8300bf5170fe8a1bff  app_id=dapr-agent-wf instance=my_mac scope=dapr.runtime type=log ver=1.15.3
INFO[0000] Log level set to: info                        app_id=dapr-agent-wf instance=my_mac scope=dapr.runtime type=log ver=1.15.3
WARN[0000] mTLS is disabled. Skipping certificate request and tls validation  app_id=dapr-agent-wf instance=my_mac scope=dapr.runtime.security type=log ver=1.15.3
(snip)
heduler.cluster type=log ver=1.15.3
== APP == 2025-03-31 00:49:39.202 durabletask-worker INFO: No longer listening for work items
== APP == 2025-03-31 00:49:39.202 durabletask-worker INFO: Worker shutdown completed
== APP == 結果: "### I. \u306f\u3058\u3081\u306b\n\n#### A. AI\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u306e\u5b9a\u7fa9\nAI\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u3068\u306f\u3001\u7279\u5b9a\u306e\u76ee\u7684\u306e\u305f\u3081\u306b\u81ea\u5f8b\u7684\u306b\u30bf\u30b9\u30af\u3092\u9042\u884c\u3059\u308b\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2\u30d7\u30ed\u30b0\u30e9
(snip)
8b\u3053\u3068\u3067\u3001\u3088\u308a\u5b89\u5168\u3067\u4fe1\u983c\u6027\u306e\u9ad8\u3044\u30b7\u30b9\u30c6\u30e0\u306e\u5b9f\u73fe\u3092\u76ee\u6307\u3059\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002"
✅  Exited App successfully
ℹ️
terminated signal received: shutting down
✅  Exited Dapr successfully

何やらいろいろ動いているようには見えるが、最終結果がUnicodeエスケープされてて読めない・・・

とりあえず以下のようにデコードすれば出力はされる。

(snip)
    print("結果:", results.encode("utf-8").decode("unicode_escape"))
出力
== APP == 2025-03-31 00:51:04.101 durabletask-worker INFO: No longer listening for work items
== APP == 2025-03-31 00:51:04.101 durabletask-worker INFO: Worker shutdown completed
== APP == 結果: "## AIエージェント: 概念、応用、未来
== APP ==
== APP == ### I. はじめに
== APP ==
== APP == #### A. AIエージェントの定義
== APP == AIエージェントとは、自律的に動作し、環境との相互作用を通じて目標を達成するために設計されたソフトウェアプログラムです。AIシステム全体の中で、明確な目標を持ち、自ら学びながら環境に適応できる特徴を持つ点で他のAIシステムと区別されます。
== APP ==
== APP == #### B. AIエージェントの重要性
== APP == AIエージェントは現代社会において大きな役割を果たしています。例えば、顧客サービスチャットボットから製造業におけるロボットまで、様々な場面で効率を向上させています。また、AI技術の進化により、これらのエージェントはますます「知的」になり、新たな可能性を広げています。
== APP ==
== APP == ### II. AIエージェントの構造と機能
== APP ==
== APP == #### A. 基本構造
== APP == AIエージェントの基本構造は以下のような機能に分けられます。
== APP == 1. **センシング(感知)**: センサーやデータ収集手段を用いて環境やユーザーから情報を集めます。
== APP == 2. **プランニング(計画)**: 集めた情報に基づいて行動計画を立てます。
== APP == 3. **アクティング(実行)**: 計画通りに行動を実行する機能です。
== APP == 4. **ラーニング(学習)**: 自らの行動結果から学習し、未来の行動に役立てます。
== APP ==
== APP == #### B. 主なタイプ
== APP == 1. **自律型エージェント**: 環境から独立して自律的に行動できるエージェント。
== APP == 2. **協調型エージェント**: 他のエージェントと協力し合いながらタスクを達成するエージェント。
== APP == 3. **ヒューマンインタラクティブエージェント**: 人間と直接的にやり取りするエージェント。
== APP ==
== APP == #### C. 実装技術
== APP == 1. **機械学習**: データから学習してパターンを抽出する手法。
== APP == 2. **ディープラーニング**: ニューラルネットワークを用いた高度な学習方法。
== APP == 3. **強化学習**: 試行錯誤を通じて最適な行動を学ぶ手法。
== APP ==
== APP == ### III. AIエージェントの応用分野
== APP ==
== APP == #### A. 産業界での応用
== APP == 1. **製造業**: 生産ラインの最適化や故障予測などに利用。
== APP == 2. **小売業**: 需要予測や在庫管理、顧客体験の向上に貢献。
== APP == 3. **金融業**: リスク管理や市場分析をサポート。
== APP ==
== APP == #### B. サービス業での応用
== APP == 1. **ヘルスケア**: 診断支援や患者管理に利用。
== APP == 2. **カスタマーサービス**: チャットボットによる即時対応が可能。
== APP == 3. **教育**: 個別学習プログラムの提供と支援。
== APP ==
== APP == #### C. 日常生活での応用
== APP == 1. **スマートホーム**: 家電制御やセキュリティ管理。
== APP == 2. **パーソナルアシスタント**: 日常のタスクをサポート。
== APP == 3. **エンターテインメント**: コンテンツ推薦やインタラクティブ体験を提供。
== APP ==
== APP == ### IV. AIエージェントがもたらす課題
== APP ==
== APP == #### A. 倫理的問題
== APP == 1. **プライバシーの侵害**: 個人データの収集と管理に関する問題。
== APP == 2. **差別とバイアスの問題**: 学習データ中のバイアスが結果に影響を与える可能性。
== APP ==
== APP == #### B. 技術的課題
== APP == 1. **信頼性と安全性**: 正確な動作とミスの最小化。
== APP == 2. **相互運用性**: 異なるシステム間でのデータ交換。
== APP ==
== APP == #### C. 経済的・社会的影響
== APP == 1. **雇用への影響**: 自動化による職の消失。
== APP == 2. **経済格差の拡大**: 技術格差により生じる社会的課題。
== APP ==
== APP == ### V. AIエージェントの未来
== APP ==
== APP == #### A. 今後の技術的進化
== APP == 1. **AIエージェントの自己進化**: 独自に能力を向上するシステム。
== APP == 2. **高度な協調システムの構築**: より複雑なタスクの協調実行。
== APP ==
== APP == #### B. 社会との共生
== APP == 1. **法律と規制の進化**: 新しい技術に対応するための法律整備。
== APP == 2. **社会的受容と教育の重要性**: 広く理解され、受け入れられるための教育。
== APP ==
== APP == #### C. 新しい応用の探索
== APP == 1. **宇宙探査**: 人が行けない場所での探査活動。
== APP == 2. **環境問題の解決**: 資源管理や環境保護のための施策。
== APP ==
== APP == ### VI. 結論
== APP ==
== APP == #### A. AIエージェントのインパクト
== APP == AIエージェントはあらゆる産業に影響を与え、効率化を進める一方で、倫理的・技術的課題も提示しています。
== APP ==
== APP == #### B. 続く研究と開発の必要性
== APP == これらの課題に対応しつつ、新しい機会を最大限に活かすためには、継続的な研究と開発が不可欠です。
== APP ==
== APP == AIエージェントは私たちの生活と社会を変える可能性を持っていますが、その発展を見守り、共に成長することが重要です。"

とりあえずエージェント的に動いているのはなんとなくわかったが、裏でDaprをどういう風に使っているか?というところはまだ見えてこない。ただ、Daprを使うことで以下のようなメリットがある。

これらの例はDaprの基本要素を直接公開しているわけではありませんが、裏ではDapr Agents上に構築されており、Daprランタイムの全機能を活用しています:

  • 回復性: 組み込みの再試行ポリシー、サーキットブレーカー、タイムアウト処理による外部システムとの連携
  • オーケストレーション: プロセスの再起動を乗り越え、途中から実行を継続できる状態管理された耐久性ワークフロー
  • 相互運用性: アプリケーションコードを変更することなく、様々なバックエンドやクラウドサービスと連携可能なプラグイン式コンポーネントアーキテクチャ
  • スケーラビリティ: ローカル開発環境からマルチノードのKubernetesクラスターまで、インフラ全体にエージェントを分散配置
  • イベント駆動型: イベント駆動型のエージェント協働と調整のためのPub/Subメッセージング
  • 可観測性: エージェントの動作を可視化するための、統合された分散トレーシング、メトリクス収集、ログ記録
  • セキュリティ: スコーピング、暗号化、シークレット管理、認証/認可制御による保護

後のクイックスタートでは、ステートストア、Pub/Sub、ワークフローサービスを通じた明示的なDapr統合が見られます。

ということでもうしばらく進めてみたほうが良さそう。

kun432kun432

02: LLM Call with Dapr Chat Client

https://github.com/dapr/dapr-agents/tree/main/quickstarts/02_llm_call_dapr

このクイックスタートでは、DaprエージェントのDaprChatClientを使用してLLMを呼び出す方法を説明します。Daprコンポーネントを使用してさまざまなLLMバックエンドを構成し、アプリケーションコードを変更せずにそれらを切り替える方法を学びます。

作業ディレクトリ&Python仮想環境を作成。自分はuvを使う。

mkdir -p dapr-agent-work/02_llm_call_dapr && cd dapr-agent-work/02_llm_call_dapr
uv venv -p 3.12.9

パッケージインストール。

uv pip install dapr-agents python-dotenv
出力
 + dapr==1.15.0
 + dapr-agents==0.3.1
 + dapr-ext-fastapi==1.15.0
 + dapr-ext-workflow==1.15.0

最初にEchoコンポーネントを使う。これはどうやらDaprのコンポーネントの1つで、入力されたものをそのまま出力するコンポーネントらしい。LLMで使う場合はプロンプトを入力すればプロンプトがそのまま返ってくるということかな?

.envでEchoコンポーネントを有効にする。

.env
DAPR_LLM_COMPONENT_DEFAULT=echo

次にコンポーネントの定義を書く。componentsディレクトリを作成し、そこに以下のecho.yamlを配置する。

mkdir components
components/echo.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: echo
spec:
  type: conversation.echo
  version: v1

シンプルなスクリプト。Hello WorldではOpenAIChatClientを使用していたが、今回はDaprChatClientを使用している。

text_completion.py
from dapr_agents.llm import DaprChatClient
from dapr_agents.types import UserMessage
from dotenv import load_dotenv

load_dotenv()

llm = DaprChatClient()
response = llm.generate("こんにちは!今日はいい天気だね!")

if len(response.get_content()) > 0:
    print("応答: ", response.get_content())

Daprで実行する。

dapr run \
    --app-id dapr-llm \
    --resources-path ./components \
    -- uv run python text_completion.py
出力
(snip)
ℹ️  Checking if Dapr sidecar is listening on GRPC port 64234
ℹ️  Dapr sidecar is up and running.
ℹ️  Updating metadata for appPID: 20677
ℹ️  Updating metadata for app command: uv run python text_completion.py
✅  You're up and running! Both Dapr and your app logs will appear here.

== APP == 応答:  こんにちは!今日はいい天気だね!
✅  Exited App successfully
ℹ️
terminated signal received: shutting down
✅  Exited Dapr successfully

入力がそのまま出力として返ってきている。この時の流れは以下となっている。

  1. Daprが起動し、componentsフォルダからすべてのリソースを読み込みます。
  2. クライアントアプリケーションはDAPR_LLM_COMPONENT_DEFAULT環境変数を取得し、Daprのechoコンポーネントとの通信に使用します。
  3. Daprのechoコンポーネントは、受信した入力をそのまま返します。
  4. アプリケーションは、入力と一致する出力を表示します。

これをOpenAIに切り替える。componentsディレクトリに、以下のoepnai.yamlを配置する。

components/openai.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: openai
spec:
  type: conversation.openai
  metadata:
    - name: key
      value: "XXXXX...(OPENAIのAPIキーをセット)...XXXXX"
    - name: model
      value: gpt-4o-mini
    - name: cacheTTL
      value: 10m

そして、.envでこれを使うようにする。

env
DAPR_LLM_COMPONENT_DEFAULT=openai

再度Daprで実行。

dapr run \
    --app-id dapr-llm \
    --resources-path ./components \
    -- uv run python text_completion.py
出力
(snip)
ℹ️  Checking if Dapr sidecar is listening on GRPC port 64572
ℹ️  Dapr sidecar is up and running.
ℹ️  Updating metadata for appPID: 25275
ℹ️  Updating metadata for app command: uv run python text_completion.py
✅  You're up and running! Both Dapr and your app logs will appear here.

== APP == 応答:  こんにちは!そうですね、いい天気だと気分も良くなりますよね。今日は何か特別なことをする予定ですか?
✅  Exited App successfully
ℹ️
terminated signal received: shutting down
✅  Exited Dapr successfully

今度はOpenAIからのレスポンスが出力されている。なるほど、DaprChatClientを使うと設定だけでコンポーネントを切り替えてくれるのか。これは便利。

DaprChatClientは耐障害性も備えている。それを確認するためにここでは敢えて間違ったコンポーネントを設定する。Bedrock向けに設定されているが、エンドポイントはローカルになっているので、当然繋がらない。

awsbedrock.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: awsbedrock
spec:
  type: conversation.aws.bedrock
  metadata:
    - name: endpoint
      value: "http://localhost:4566"
    - name: model
      value: amazon.titan-text-express-v1
    - name: cacheTTL
      value: 10m

DAPR_LLM_COMPONENT_DEFAULTでこれを使うようにする。

env
DAPR_LLM_COMPONENT_DEFAULT=awsbedrock

で、耐障害性に関する定義を別のファイルに設定する。

components/resiliency.yaml
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: awsbedrock-resiliency
spec:
  policies:
    timeouts:
      short-timeout: 1s
    retries:
      fixed-retry:
        policy: constant
        duration: 1s
        maxRetries: 3
  targets:
    components:
      awsbedrock:
        outbound:
          timeout: short-timeout
          retry: fixed-retry

この設定により、1 秒でタイムアウト、3回リトライ、リトライ間隔は1秒、というような設定ができる。なお、この設定だとawsbedrockコンポーネントにしか適用されないらしいが、nameを見てるってことなのかな?

再度Daprで実行。

dapr run \
    --app-id dapr-llm \
    --resources-path ./components \
    -- uv run python text_completion.py
出力
(snip)
WARN[0003] Error processing operation component[awsbedrock] output. Retrying in 1s…  app_id=dapr-llm instance=my_mac scope=dapr.runtime type=log ver=1.15.3
(snip)
❌  The App process exited with error code: exit status 1
ℹ️
terminated signal received: shutting down
✅  Exited Dapr successfully
❌  Error exiting App: exit status 1

Quickstartのように「最大リトライ回数を超えた」みたいなログは出力されなかったのだけど、少なくともリトライしていることがわかる。

DaprDaprChatClientのメリットは以下のように記載されている

DaprChatClient を使用する利点

  1. プロバイダーに依存しない: 一度コードを書けば、異なる LLM プロバイダー間で切り替えることが可能です。
  2. プロンプトキャッシング: 繰り返しのプロンプトをローカルキャッシュに保存・再利用することで、レイテンシとコストを削減します。
  3. 個人情報(PII)のマスキング: 入出力から自動的に機微なユーザー情報を検出してマスクします。
  4. シークレット管理: Dapr の シークレットストア を介して API キーを安全に管理します。
  5. レジリエンスパターン: Dapr に組み込まれたタイムアウト、リトライ、サーキットブレーカー機能の恩恵を受けます。
  6. テストの簡素化: 開発およびテスト中に echo コンポーネントを使用可能です。

Dapr コンポーネントを LLM とのやり取りに利用することで、柔軟性、モジュール性、関心の分離が実現され、アプリケーションがより保守しやすく、要件や LLM プロバイダーの変更に対応しやすくなります。

2・3・4あたりについては具体的な説明がないのでわからないけど、Daprのコンポーネントを使うのだろうと思われる。

あと、上記ではスキップしたのだけども、llm.generate()でのプロンプトの渡し方は他にも以下がある。

  • メッセージのリストとして渡す
  • Promptyでプロンプトを定義、その定義に合わせて入力を渡す

Promptyとか初めて知ったけど、どこにもリンクしてなくて、流石に初見ではわからない・・・

ログインするとコメントできます