Building a Multi-Agent Orchestrator: A Step-by-Step Guide
Zahiruddin Tavargere
Senior Principal Software Engineer@Dell | Opinions are my own
Today, we’re diving into an exciting project: creating a Multi-Agent Orchestrator.
Thanks for reading The Adaptive Engineer! Subscribe for free to receive new posts and support my work.
If you’re new here, I recommend revisiting that post to get up to speed, as we’ll build upon its concepts and code.
In this project, we’ll tackle orchestrating actions between multiple agents, enabling seamless execution of tasks such as fetching weather information and the current time. Let’s jump in!
If you prefer video
What Is a Multi-Agent Orchestrator?
A Multi-Agent Orchestrator is a system that:
Think of it as a manager assigning tasks to specialized team members. This orchestration ensures complex queries involving multiple tasks are handled efficiently.
What We’ll Build
We’ll create:
Key Components of an Agent
An agent has three main components:
Our agents will dynamically decide which tool to use, making them highly adaptable.
The Agent Class
Here’s a high-level breakdown of the Agent class:
Agents also handle parsing JSON responses from the LLM to ensure smooth execution.
from abc import ABC, abstractmethod
import ast
import os
import requests
from llm.llm_ops import query_llm
from tools.base_tool import Tool
import json
class Agent:
def __init__(self, Name: str, Description: str, Tools: list, Model: str):
self.memory = []
self.name = Name
self.description = Description
self.tools = Tools
self.model = Model
self.max_memory = 10
def json_parser(self, input_string):
print(type(input_string))
python_dict = ast.literal_eval(input_string)
json_string = json.dumps(python_dict)
json_dict = json.loads(json_string)
if isinstance(json_dict, dict) or isinstance(json_dict,list):
return json_dict
raise "Invalid JSON response"
def process_input(self, user_input):
self.memory.append(f"User: {user_input}")
12
context = "\n".join(self.memory)
tool_descriptions = "\n".join([f"- {tool.name()}: {tool.description()}" for tool in self.tools])
response_format = {"action":"", "args":""}
prompt = f"""Context:
{context}
Available tools:
{tool_descriptions}
Based on the user's input and context, decide if you should use a tool or respond directly.
If you identify a action, respond with the tool name and the arguments for the tool.
If you decide to respond directly to the user then make the action "respond_to_user" with args as your response in the following format.
Response Format:
{response_format}
"""
response = query_llm(prompt)
self.memory.append(f"Agent: {response}")
response_dict = self.json_parser(response)
# Check if any tool can handle the input
for tool in self.tools:
if tool.name().lower() == response_dict["action"].lower():
return tool.use(response_dict["args"])
return response_dict
The Orchestrator
The orchestrator coordinates multiple agents:
领英推荐
Core Features of the Orchestrator:
import ast
import json
from llm.llm_ops import query_llm
from agents.base_agent import Agent
from logger import log_message
class AgentOrchestrator:
def __init__(self, agents: list[Agent]):
self.agents = agents
self.memory = [] # Stores the reasoning and action steps taken
self.max_memory = 10
def json_parser(self, input_string):
print(type(input_string))
python_dict = ast.literal_eval(input_string)
json_string = json.dumps(python_dict)
json_dict = json.loads(json_string)
if isinstance(json_dict, dict) or isinstance(json_dict,list):
return json_dict
raise "Invalid JSON response"
def orchestrate_task(self, user_input: str):
self.memory = self.memory[-self.max_memory:]
context = "\n".join(self.memory)
print(f"Context: {context}")
response_format = {"action":"", "input":"", "next_action":""}
def get_prompt(user_input):
return f"""
Use the context from memory to plan next steps.
Context:
{context}
You are an expert intent classifier.
You need will use the context provided and the user's input to classify the intent select the appropriate agent.
You will rewrite the input for the agent so that the agent can efficiently execute the task.
Here are the available agents and their descriptions:
{", ".join([f"- {agent.name}: {agent.description}" for agent in self.agents])}
User Input:
{user_input}
###Guidelines###
- Sometimes you might have to use multiple agent's to solve user's input. You have to do that in a loop.
- The original userinput could have multiple tasks, you will use the context to understand the previous actions taken and the next steps you should take.
- Read the context, take your time to understand, see if there were many tasks and if you executed them all
- If there are no actions to be taken, then make the action "respond_to_user" with your final thoughts combining all previous responses as input.
- Respond with "respond_to_user" only when there are no agents to select from or there is no next_action
- You will return the agent name in the form of {response_format}
- Always return valid JSON like {response_format} and nothing else.
"""
response = ""
loop_count = 0
self.memory = self.memory[-10:]
prompt = get_prompt(user_input)
llm_response = query_llm(prompt)
llm_response = self.json_parser(llm_response)
print(f"LLM Response: {llm_response}")
self.memory.append(f"Orchestrator: {llm_response}")
action= llm_response["action"]
user_input = llm_response["input"]
print(f"Action identified by LLM: {action}")
if action == "respond_to_user":
return llm_response
for agent in self.agents:
if agent.name == action:
print("*******************Found Agent Name*******************************")
agent_response = agent.process_input(user_input)
print(f"{action} response: {agent_response}")
self.memory.append(f"Agent Response for Task: {agent_response}")
print(self.memory)
return agent_response
def run(self):
print("LLM Agent: Hello! How can I assist you today?")
user_input = input("You: ")
self.memory.append(f"User: {user_input}")
while True:
if user_input.lower() in ["exit", "bye", "close"]:
print("See you later!")
break
response = self.orchestrate_task(user_input)
print(f"Final response of orchestrator {response}")
if isinstance(response, dict) and response["action"] == "respond_to_user":
log_message(f"Reponse from Agent: {response["input"]}", "RESPONSE")
user_input = input("You: ")
self.memory.append(f"User: {user_input}")
elif response == "No action or agent needed":
print("Reponse from Agent: ", response)
user_input = input("You: ")
else:
user_input = response
Tools in Action
Agents use tools to perform tasks. For example:
Each tool includes:
import os
import requests
from tools.base_tool import Tool
class WeatherTool(Tool):
def name(self):
return "Weather Tool"
def description(self):
return "Provides weather information for a given location. The payload is just the location. Example: New York"
def use(self, location:str):
api_key = os.getenv("OPENWEATHERMAP_API_KEY")
url = f"https://api.openweathermap.org/data/2.5/weather?q={location}&appid={api_key}&units=metric"
response = requests.get(url)
data = response.json()
if data["cod"] == 200:
temp = data["main"]["temp"]
description = data["weather"][0]["description"]
response = f"The weather in {location} is currently {description} with a temperature of {temp}°C."
print(response)
return response
else:
return f"Sorry, I couldn't find weather information for {location}."
Demo: Running the Orchestrator
Here’s a quick demonstration:
Example Output:
from agents.base_agent import Agent
from tools.weather_tool import WeatherTool
from tools.time_tool import TimeTool
from orchestrator import AgentOrchestrator
from dotenv import load_dotenv
import os
# Load environment variables from .env file
load_dotenv()
# Create Weather Agent
weather_agent = Agent(
Name="Weather Agent",
Description="Provides weather information for a given location",
Tools=[WeatherTool()],
Model="gpt-4o-mini"
)
# Create Time Agent
time_agent = Agent(
Name="Time Agent",
Description="Provides the current time for a given city",
Tools=[TimeTool()],
Model="gpt-4o-mini"
)
# Create AgentOrchestrator
agent_orchestrator = AgentOrchestrator([weather_agent, time_agent])
# Run the orchestrator
agent_orchestrator.run()
What’s Next?
This orchestrator is just the beginning. You can:
Full Code
Final Thoughts
Building a Multi-Agent Orchestrator showcases the power of combining LLMs with task-specific agents. By modularizing tasks and leveraging context effectively, you can create systems that are both scalable and intelligent.
Stay tuned for more updates, and feel free to share your thoughts or ask questions in the comments below. Don’t forget to check out the accompanying video for a detailed walkthrough of the code.
Happy coding! ??
Data Science Consultant/Architect @ Dell Technologies || Data Scientist Leader (ML | DL | NLP | Gen AI) || Data Engineering(Spark MLlib) || Leadership (Agile Project Management || Product Management)
3 个月Very helpful!