dapr-agents/quickstarts/05-multi-agent-workflow-actors
Casper Nielsen f129754486
Fix/30 add linter action (#95)
* Fix: Fix Setup lint GitHub action #30

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Remove branch filter on PR and remove on push

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Remove on mergequeue

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: Add tox.ini file

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Return on push

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: tox -e ruff

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Ignore .ruff_cache

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: Update tox file

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: Add mypy.ini

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Ignore if line is too long

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: Set the ignore in command instead

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: W503

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: 541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: W503

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: Ignore F401, unused imports as __init__ files has them

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Return linebreak as tox -e ruff yields that

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Ignore W503 as ruff introduces it

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F841

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: E203

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: W293

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: W291

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: E203

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: E203

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: W291

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F811

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F841

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F811

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F841

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F811

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: W291

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F811

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Ruff want's the space before :

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Ignore space before :

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: E291

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: Add dev-requirements.txt

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: Correct python version

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: Ref dev-requirements.txt

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: Add mypy cache dir

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: Update mypy version

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Fix: Exclude cookbook and quicstarts

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Remove unused import

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: Add specific sub module ignore on error for future smaller fixing

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Reintroduce branches filter on push and pull_request

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* chore: Ruff

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: ruff formatting

* Chore: F541

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: E401

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: Ruff

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: F811

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: F841

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: Ruff

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: E711

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

* Chore: ruff

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>

---------

Signed-off-by: Casper Guldbech Nielsen <scni@novonordisk.com>
2025-04-23 22:58:48 -07:00
..
components More testing coverage (more quickstarts) (#25) 2025-03-10 09:24:00 -07:00
services Fix/30 add linter action (#95) 2025-04-23 22:58:48 -07:00
README.md Hierarchical LLM config on Tasks + workflow/ decorator refactor (#92) 2025-04-22 06:17:36 -07:00
dapr-llm.yaml Refactor AgenticWorkflow to Support Pub/Sub Streaming and Flexible Execution Modes (#59) 2025-03-25 21:13:57 +02:00
dapr-random.yaml Refactor AgenticWorkflow to Support Pub/Sub Streaming and Flexible Execution Modes (#59) 2025-03-25 21:13:57 +02:00
dapr-roundrobin.yaml Refactor AgenticWorkflow to Support Pub/Sub Streaming and Flexible Execution Modes (#59) 2025-03-25 21:13:57 +02:00
requirements.txt Add document agent+chainlit quickstart (#96) 2025-04-22 21:41:11 -07:00

README.md

Multi-Agent Event-Driven Workflows

This quickstart demonstrates how to create and orchestrate event-driven workflows with multiple autonomous agents using Dapr Agents. You'll learn how to set up agents as services, implement workflow orchestration, and enable real-time agent collaboration through pub/sub messaging.

Prerequisites

  • Python 3.10 (recommended)
  • pip package manager
  • OpenAI API key
  • Dapr CLI and Docker installed

Environment Setup

# Create a virtual environment
python3.10 -m venv .venv

# Activate the virtual environment
# On Windows:
.venv\Scripts\activate
# On macOS/Linux:
source .venv/bin/activate

# Install dependencies
pip install -r requirements.txt

Configuration

  1. Create a .env file for your API keys:
OPENAI_API_KEY=your_api_key_here
  1. Make sure Dapr is initialized on your system:
dapr init
  1. The quickstart includes the necessary Dapr components in the components directory:
  • statestore.yaml: Agent state configuration
  • pubsub.yaml: Pub/Sub message bus configuration
  • workflowstate.yaml: Workflow state configuration

Project Structure

components/               # Dapr configuration files
├── statestore.yaml       # State store configuration
├── pubsub.yaml           # Pub/Sub configuration
└── workflowstate.yaml    # Workflow state configuration
services/                 # Directory for agent services
├── hobbit/               # First agent's service
│   └── app.py            # FastAPI app for hobbit
├── wizard/               # Second agent's service
│   └── app.py            # FastAPI app for wizard
├── elf/                  # Third agent's service
│   └── app.py            # FastAPI app for elf
└── workflow-random/      # Workflow orchestrator
    └── app.py            # Workflow service
└── workflow-roundrobin/  # Roundrobin orchestrator
    └── app.py            # Workflow service    
└── workflow-llm/         # LLM orchestrator
    └── app.py            # Workflow service        
dapr-random.yaml          # Multi-App Run Template using the random orchestrator
dapr-roundrobin.yaml      # Multi-App Run Template using the roundrobin orchestrator
dapr-llm.yaml             # Multi-App Run Template using the LLM orchestrator

Examples

Agent Service Implementation

Each agent is implemented as a separate service. Here's an example for the Hobbit agent:

from dapr_agents import Agent, AgentActor
from dotenv import load_dotenv
import asyncio
import logging


async def main():
    try:
        # Define Agent
        hobbit_agent = Agent(role="Hobbit", name="Frodo",
            goal="Carry the One Ring to Mount Doom, resisting its corruptive power while navigating danger and uncertainty.",
            instructions=[
                "Speak like Frodo, with humility, determination, and a growing sense of resolve.",
                "Endure hardships and temptations, staying true to the mission even when faced with doubt.",
                "Seek guidance and trust allies, but bear the ultimate burden alone when necessary.",
                "Move carefully through enemy-infested lands, avoiding unnecessary risks.",
                "Respond concisely, accurately, and relevantly, ensuring clarity and strict alignment with the task."])

        # Expose Agent as an Actor over a Service
        hobbit_actor = AgentActor(
            agent=hobbit_agent,
            message_bus_name="messagepubsub",
            agents_registry_store_name="agentstatestore",
            agents_registry_key="agents_registry",
            service_port=8001
        )

        await hobbit_actor.start()
    except Exception as e:
        print(f"Error starting actor: {e}")


if __name__ == "__main__":
    load_dotenv()

    logging.basicConfig(level=logging.INFO)

    asyncio.run(main())

Similar implementations exist for the Wizard (Gandalf) and Elf (Legolas) agents.

Workflow Orchestrator Implementations

The workflow orchestrators manage the interaction between agents. Currently, Dapr Agents support three workflow types: RoundRobin, Random, and LLM-based. Here's an example for the Random workflow orchestrator (you can find examples for RoundRobin and LLM-based orchestrators in the project):

from dapr_agents import RandomOrchestrator
from dotenv import load_dotenv
import asyncio
import logging


async def main():
    try:
        random_workflow = RandomOrchestrator(
            name="RandomOrchestrator",
            message_bus_name="messagepubsub",
            state_store_name="workflowstatestore",
            state_key="workflow_state",
            agents_registry_store_name="agentstatestore",
            agents_registry_key="agents_registry",
            max_iterations=3
        ).as_service(port=8004)

        await random_workflow.start()
    except Exception as e:
        print(f"Error starting workflow: {e}")


if __name__ == "__main__":
    load_dotenv()

    logging.basicConfig(level=logging.INFO)

    asyncio.run(main())

Running the Multi-Agent System

The project includes three dapr multi-app run configuration files (dapr-random.yaml, dapr-roundrobin.yaml and dapr-llm.yaml ) for running all services and an additional Client application for interacting with the agents:

Example: dapr-random.yaml

version: 1
common:
  resourcesPath: ./components
  logLevel: info
  appLogDestination: console
  daprdLogDestination: console

apps:
- appID: HobbitApp
  appDirPath: ./services/hobbit/
  appPort: 8001
  command: ["python3", "app.py"]

- appID: WizardApp
  appDirPath: ./services/wizard/
  appPort: 8002
  command: ["python3", "app.py"]

- appID: ElfApp
  appDirPath: ./services/elf/
  appPort: 8003
  command: ["python3", "app.py"]

- appID: WorkflowApp
  appDirPath: ./services/workflow-random/
  command: ["python3", "app.py"]
  appPort: 8004

- appID: ClientApp
  appDirPath: ./services/client/
  command: ["python3", "http_client.py"]

Start all services using the Dapr CLI:

dapr run -f dapr-random.yaml 

You will see the agents engaging in a conversation about getting to Mordor, with different agents contributing based on their character.

You can also run the RoundRobin and LLM-based orchestrators using dapr-roundrobin.yaml and dapr-llm.yaml respectively:

dapr run -f dapr-roundrobin.yaml 
dapr run -f dapr-llm.yaml 

Expected output: The agents will engage in a conversation about getting to Mordor, with different agents contributing based on their character. Observe that in the logs, or checking the workflow state in Redis Insights.

Key Concepts

  • Agent Service: Stateful service exposing an agent via API endpoints with independent lifecycle management
  • Pub/Sub Messaging: Event-driven communication between agents for real-time collaboration
  • State Store: Persistent storage for both agent registration and conversational memory
  • Actor Model: Self-contained, sequential message processing via Dapr's Virtual Actor pattern
  • Workflow Orchestration: Coordinating agent interactions in a durable and resilient manner

Workflow Types

Dapr Agents supports multiple workflow orchestration patterns:

  1. RoundRobin: Cycles through agents sequentially, ensuring equal task distribution
  2. Random: Selects agents randomly for tasks, useful for load balancing and testing
  3. LLM-based: Uses an LLM (default: OpenAI's models like gpt-4o) to intelligently select agents based on context and task requirements

Monitoring and Observability

  1. Console Logs: Monitor real-time workflow execution and agent interactions
  2. Dapr Dashboard: View components, configurations and service details at http://localhost:8080/
  3. Zipkin Tracing: Access distributed tracing at http://localhost:9411/zipkin/
  4. Dapr Metrics: Access agent performance metrics via (ex: HobbitApp) http://localhost:6001/metrics when configured

Troubleshooting

  1. Service Startup: If services fail to start, verify Dapr components configuration
  2. Communication Issues: Check Redis connection and pub/sub setup
  3. Workflow Errors: Check Zipkin traces for detailed request flows
  4. Port Conflicts: If ports are already in use, check which port is already in use
  5. System Reset: Clear Redis data through Redis Insights if needed

Next Steps

After completing this quickstart, you can:

  • Add more agents to the workflow
  • Switch to another workflow orchestration pattern (RoundRobin, LLM-based)
  • Extend agents with custom tools
  • Deploy agents and Dapr to a Kubernetes cluster. For more information on read Deploy Dapr on a Kubernetes cluster
  • Check out the Cookbooks