* 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> |
||
---|---|---|
.. | ||
components | ||
services | ||
README.md | ||
dapr-llm.yaml | ||
dapr-random.yaml | ||
dapr-roundrobin.yaml | ||
requirements.txt |
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
- Create a
.env
file for your API keys:
OPENAI_API_KEY=your_api_key_here
- Make sure Dapr is initialized on your system:
dapr init
- The quickstart includes the necessary Dapr components in the
components
directory:
statestore.yaml
: Agent state configurationpubsub.yaml
: Pub/Sub message bus configurationworkflowstate.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:
- RoundRobin: Cycles through agents sequentially, ensuring equal task distribution
- Random: Selects agents randomly for tasks, useful for load balancing and testing
- 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
- Console Logs: Monitor real-time workflow execution and agent interactions
- Dapr Dashboard: View components, configurations and service details at http://localhost:8080/
- Zipkin Tracing: Access distributed tracing at http://localhost:9411/zipkin/
- Dapr Metrics: Access agent performance metrics via (ex: HobbitApp) http://localhost:6001/metrics when configured
Troubleshooting
- Service Startup: If services fail to start, verify Dapr components configuration
- Communication Issues: Check Redis connection and pub/sub setup
- Workflow Errors: Check Zipkin traces for detailed request flows
- Port Conflicts: If ports are already in use, check which port is already in use
- 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