Configurable Multi-Agent Workflows in Python: A YAML-Driven Orchestration
Managing merchant operations demands flexibility, security, and transparent routing of requests to the right agents or services. But what if you could describe that logic—who gets called for what operation—not in code, but in a simple YAML file, so anyone could update it?
In this post, we’ll walk through:
- Setting up a Python-based multi-agent orchestration demo with the LangGraph framework
- Making workflow definitions YAML-driven for easy reconfiguration
Why Use Multi-Agent Orchestration?
- Security: Route destructive operations (like delete) to privileged agents only
- Auditability: Track who handles what
- Extensibility: Plug in new agent roles (finance, compliance, etc.)
- Flexibility: Easily update workflow logic without touching code
Workflow Design
- Supervisor: Routes requests
- Specialized agents: SME Agent (for “safe” operations), Admin Agent (for destructive ops)
- Declarative YAML file: Defines how requests map to agents
Example YAML: Your Workflow Specification
Here’s a sample multiagent_workflow.yaml declaring agents, their types, and routing:
agents:
sme_agent:
type: SME
responsible_for: [list, retrieve, update, submit, stats]
admin_agent:
type: Admin
Now, if you want to add a new operation or redirect one, just edit the YAML. No Python code changes required!
Key Implementation Pieces
1. Loading the YAML Config
You’ll need PyYAML (pip install pyyaml):
import yaml
def load_workflow_config(config_path="multiagent_workflow.yaml"):
with open(config_path, "r") as f:
config = yaml.safe_load(f)
return config
2. Dynamically Building Agents & Workflow
Interpret the YAML configuration to initialize agents and set up routing rules:
from typing import Dict
async def build_agents_from_config(config: dict):
agent_objs = {}
for agent_key, agent_info in config["agents"].items():
if agent_info["type"].lower() == "sme":
# ...initialize SME agent...
elif agent_info["type"].lower() == "admin":
# ...initialize Admin agent...
return agent_objs
3. Supervisor: YAML-Driven Routing
The Supervisor class reads the operation from the input and dispatches it to the agent declared in the config:
class Supervisor:
def __init__(self, agent_map, op_agent_map):
self.agent_map = agent_map
self.op_agent_map = op_agent_map
async def handle_request(self, request):
op = extract_operation(request)
agent = self.op_agent_map.get(op)
# ...route to agent...
A basic extract_operation implementation:
def extract_operation(request: str) -> str:
tokens = request.lower().split()
for op in ["delete", "submit", "update", "retrieve", "list", "stats"]:
if op in tokens:
return op
return "retrieve"
4. LangGraph Integration
Update your workflow runner to glue the system together:
async def run_langgraph(prompt: str, agent_map: dict, op_agent_map: dict):
graph = LangGraph()
graph.add_node("supervisor", Supervisor(agent_map, op_agent_map))
# ... connect agents, define edges as needed
result = await graph.run(start_node="supervisor", input=prompt)
return result
5. Bringing It All Together
Your entry function can now be:
async def execute_demo(prompt: str = None, config_path: str = "multiagent_workflow.yaml"):
logger.info(f"Loading workflow config from {config_path}...")
config = load_workflow_config(config_path)
agent_map = await build_agents_from_config(config)
op_agent_map = build_operation_agent_map(config)
# ...run workflow...
When You Run the Demo
- Agents get loaded according to YAML config
- Workflow logic (“who handles what?”) is read from YAML
- Supervisor dynamically dispatches each operation to the declared agent
- To change the workflow: just edit
multiagent_workflow.yaml!
Summary
- Decoupled orchestration logic—no code updates for workflow/routing tweaks
- Declarative and transparent workflow—easy for domain experts to audit or update
- Clear path to extensibility—add new agents, operations, or conditional routing via YAML
Ready to try?
Clone namburik/mcp_board_agent, tweak multiagent_workflow.yaml, and run your workflows!