Multi Agent City Information System Reference Architecture v2
Agents are revolutionizing the landscape of generative AI, serving as the bridge between large language models (LLMs) and real-world applications. These intelligent, autonomous systems are poised to become the cornerstone of AI adoption across industries, heralding a new era of human-AI collaboration and problem-solving. By using the power of LLMs and combining them with specialized tools and APIs, agents can tackle complex, multistep tasks that were previously beyond the reach of traditional AI systems. The Multi-Agent City Information System demonstrated in this post exemplifies the potential of agent-based architectures to create sophisticated, adaptable, and highly capable AI applications.
As we look to the future, agents will have a very important role to play in:
Building and deploying multi-agent systems like the one in this post is a step toward unlocking the full potential of generative AI. As these systems evolve, they will transform industries, expand possibilities, and open new doors for artificial intelligence.
In this post, we explore how to use LangGraph and Mistral models on Amazon Bedrock to create a powerful multi-agent system that can handle sophisticated workflows through collaborative problem-solving. This integration enables the creation of AI agents that can work together to solve complex problems, mimicking humanlike reasoning and collaboration.
The result is a system that delivers comprehensive details about events, weather, activities, and recommendations for a specified city, illustrating how stateful, multi-agent applications can be built and deployed on Amazon Web Services (AWS) to address real-world challenges.
LangGraph is essential to our solution by providing a well-organized method to define and manage the flow of information between agents. It provides built-in support for state management and checkpointing, providing smooth process continuity. This framework also allows for straightforward visualization of the agentic workflows, enhancing clarity and understanding. It integrates easily with LLMs and Amazon Bedrock, providing a versatile and powerful solution. Additionally, its support for conditional routing allows for dynamic workflow adjustments based on intermediate results, providing flexibility in handling different scenarios.
The multi-agent architecture we present offers several key benefits:
In this section, we explore how our Multi-Agent City Information System works, based on the multi-agent LangGraph Mistral Jupyter notebook available in the Mistral on AWS examples for Bedrock & SageMaker repository on GitHub.
This agentic workflow takes a city name as input and provides detailed information, demonstrating adaptability in handling different scenarios:
The system’s ability to work with varying levels of information is showcased through its adaptive approach, which means that users receive the most comprehensive and up-to-date information possible, regardless of the varying availability of data for different cities. For instance:
The following diagram is the solution’s reference architecture:
The Multi-Agent City Information System can take advantage of two sources of data.
This SQLite database is populated with city events data from a JSON file, providing quick access to local event information that ranges from community happenings to cultural events and citywide activities. This database is used by the events_database_tool()
for efficient querying and retrieval of city event details, including location, date, and event type.
For restaurant recommendations, the generate_restaurants_dataset()
function generates synthetic data, creating a custom dataset specifically tailored to our recommendation system. The create_restaurant_vector_store()
function processes this data, generates embeddings using Amazon Titan Text Embeddings, and builds a vector store with Facebook AI Similarity Search (FAISS). Although this approach is suitable for prototyping, for a more scalable and enterprise-grade solution, we recommend using Amazon Bedrock Knowledge Bases.
At the heart of our Multi-Agent City Information System lies a set of specialized functions and tools designed to gather, process, and synthesize information from various sources. They form the backbone of our system, enabling it to provide comprehensive and up-to-date information about cities. In this section, we explore the key components that drive our system: the generate_text()
function, which uses Mistral model, and the specialized data retrieval functions for local database queries, online searches, weather information, and restaurant recommendations. Together, these functions and tools create a robust and versatile system capable of delivering valuable insights to users.
This function serves as the core of our agents, allowing them to generate text using the Mistral model as needed. It uses the Amazon Bedrock Converse API, which supports text generation, streaming, and external function calling (tools).
The function works as follows:
Here’s the implementation:
def generate_text(bedrock_client, model_id, tool_config, input_text):
......
while True:
response = bedrock_client.converse(**kwargs)
output_message = response['output']['message']
messages.append(output_message) # Add assistant's response to messages
stop_reason = response.get('stopReason')
if stop_reason == 'tool_use' and tool_config:
tool_use = output_message['content'][0]['toolUse']
tool_use_id = tool_use['toolUseId']
tool_name = tool_use['name']
tool_input = tool_use['input']
try:
if tool_name == 'get_upcoming_events':
tool_result = local_info_database_tool(tool_input['city'])
json_result = json.dumps({"events": tool_result})
elif tool_name == 'get_city_weather':
tool_result = weather_tool(tool_input['city'])
json_result = json.dumps({"weather": tool_result})
elif tool_name == 'search_and_summarize_events':
tool_result = search_tool(tool_input['city'])
json_result = json.dumps({"events": tool_result})
else:
raise ValueError(f"Unknown tool: {tool_name}")
tool_response = {
"toolUseId": tool_use_id,
"content": [{"json": json.loads(json_result)}]
}
......
messages.append({
"role": "user",
"content": [{"toolResult": tool_response}]
})
# Update kwargs with new messages
kwargs["messages"] = messages
else:
break
return output_message, tool_result
The events_database_tool()
queries the local SQLite database for events information by connecting to the database, executing a query to fetch upcoming events for the specified city, and returning the results as a formatted string. It’s used by the events_database_agent()
function. Here’s the code:
def events_database_tool(city: str) -> str:
conn = sqlite3.connect(db_path)
query = """
SELECT event_name, event_date, description
FROM local_events
WHERE city = ?
ORDER BY event_date
LIMIT 3
"""
df = pd.read_sql_query(query, conn, params=(city,))
conn.close()
print(df)
if not df.empty:
events = df.apply(
lambda row: (
f"{row['event_name']} on {row['event_date']}: {row['description']}"
),
axis=1
).tolist()
return "n".join(events)
else:
return f"No upcoming events found for {city}."
The weather_tool()
fetches current weather data for the specified city by calling the OpenWeatherMap API. It’s used by the weather_agent()
function. Here’s the code:
def weather_tool(city: str) -> str:
weather = OpenWeatherMapAPIWrapper()
tool_result = weather.run("Tampa")
return tool_result
When local event information is unavailable, the search_tool()
performs an online search using the Tavily API to find upcoming events in the specified city and return a summary. It’s used by the search_agent()
function. Here’s the code:
def search_tool(city: str) -> str:
client = TavilyClient(api_key=os.environ['TAVILY_API_KEY'])
query = f"What are the upcoming events in {city}?"
response = client.search(query, search_depth="advanced")
results_content = "nn".join([result['content'] for result in response['results']])
return results_content
The query_restaurants_RAG()
function uses a RAG system to provide restaurant recommendations by performing a similarity search in the vector database for relevant restaurant information, filtering for highly rated restaurants in the specified city and using Amazon Bedrock with the Mistral model to generate a summary of the top restaurants based on the retrieved information. It’s used by the query_restaurants_agent()
function.
For the detailed implementation of these functions and tools, environment setup, and use cases, refer to the Multi-Agent LangGraph Mistral Jupyter notebook.
Our multi-agent system consists of several specialized agents. Each agent in this architecture is represented by a Node in LangGraph, which, in turn, interacts with the tools and functions defined previously. The following diagram shows the workflow:
The workflow follows these steps:
events_database_tool()
to query a local SQLite database and find local event informationsearch_tool()
to find upcoming events by searching online for a given cityweather_tool()
for the specified cityquery_restaurants_RAG()
function to provide restaurant recommendations for a specified cityHere’s an example of how we created the weather agent:
def weather_agent(state: State) -> State:
......
tool_config = {
"tools": [
{
"toolSpec": {
"name": "get_city_weather",
"description": "Get current weather information for a specific city",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city to look up weather for"
}
},
"required": ["city"]
}
}
}
}
]
}
input_text = f"Get current weather for {state.city}"
output_message, tool_result = generate_text(bedrock_client, DEFAULT_MODEL, tool_config, input_text)
if tool_result:
state.weather_info = {"city": state.city, "weather": tool_result}
else:
state.weather_info = {"city": state.city, "weather": "Weather information not available."}
print(f"Weather info set to: {state.weather_info}")
return state
In the Multi-Agent City Information System, several key primitives orchestrate agent collaboration. The build_graph()
function defines the workflow in LangGraph, utilizing nodes, routes, and conditions. The workflow is dynamic, with conditional routing based on event search results, and incorporates memory persistence to store the state across different executions of the agents. Here’s an overview of the function’s behavior:
workflow
, which is initialized with a State. In LangGraph, the State
represents the data or context that is passed through the workflow as the agents perform their tasks. In our example, the state includes things like the results from previous agents (for example, event data, search results, and weather information), input parameters (for example, city name), and other relevant information that the agents might need to process:# Define the graph
def build_graph():
workflow = StateGraph(State)
...
workflow.add_node("Events Database Agent", events_database_agent)
workflow.add_node("Online Search Agent", search_agent)
workflow.add_node("Weather Agent", weather_agent)
workflow.add_node("Restaurants Recommendation Agent", query_restaurants_agent)
workflow.add_node("Analysis Agent", analysis_agent)
Events Database Agent
, meaning the execution of the workflow starts from this agent. Also, the function defines a conditional route using the add_conditional_edges
method. The route_events()
function decides the next step based on the results from the Events Database Agent
: workflow.set_entry_point("Events Database Agent")
def route_events(state):
print(f"Routing events. Current state: {state}")
print(f"Events content: '{state.events_result}'")
if f"No upcoming events found for {state.city}" in state.events_result:
print("No events found in local DB. Routing to Online Search Agent.")
return "Online Search Agent"
else:
print("Events found in local DB. Routing to Weather Agent.")
return "Weather Agent"
workflow.add_conditional_edges(
"Events Database Agent",
route_events,
{
"Online Search Agent": "Online Search Agent",
"Weather Agent": "Weather Agent"
}
)
Online Search Agent
to Weather Agent
, from Weather Agent
to Restaurants Recommendation Agent
, and from there to Analysis Agent
, before finally reaching the END
: workflow.add_edge("Online Search Agent", "Weather Agent")
workflow.add_edge("Weather Agent", "Restaurants Recommendation Agent")
workflow.add_edge("Restaurants Recommendation Agent", "Analysis Agent")
workflow.add_edge("Analysis Agent", END)
MemorySaver
class is used to make sure that the state of the workflow is preserved between runs. This is especially useful in multi-agent systems where the state of the system needs to be maintained as the agents interact: # Initialize memory to persist state between graph runs
checkpointer = MemorySaver()
checkpointer
) is included to make sure that the state is persisted between executions. Then, it outputs a graphical representation of the workflow: # Compile the workflow
app = workflow.compile(checkpointer=checkpointer)
# Visualize the graph
display(
Image(
app.get_graph().draw_mermaid_png(
draw_method=MermaidDrawMethod.API
)
)
)
The following diagram illustrates these steps:
To demonstrate the versatility of our Multi-Agent City Information System, we run it for three different cities: Tampa, Philadelphia, and New York. Each example showcases different aspects of the system’s functionality.
The used function main()
orchestrates the entire process:
build_graph()
function, which implements the agentic workflowTo run the code, do the following:
if __name__ == "__main__":
cities = ["Tampa", "Philadelphia", "New York"]
for city in cities:
print(f"nStarting script execution for city: {city}")
main(city)
For Example 1 (Tampa), the following diagram shows how the agentic workflow produces the output in response to the user’s question, “What’s happening in Tampa and what should I wear?”
The system produced the following results:
For Example 2 (Philadelphia), the agentic workflow identified events in the local database, including cultural events and festivals. It retrieved weather data from the OpenWeatherMap API, then suggested activities based on local events and weather conditions. Outfit recommendations were made in line with the weather forecast, and restaurant recommendations were provided through the RAG system.
For Example 3 (New York), the workflow identified events such as Broadway shows and city attractions in the local database. It retrieved weather data from the OpenWeatherMap API and suggested activities based on the variety of local events and weather conditions. Outfit recommendations were tailored to New York’s weather and urban environment. However, the RAG system was unable to provide restaurant recommendations for New York because the synthetic dataset created earlier hadn’t included any restaurants from this city.
These examples demonstrate the system’s ability to adapt to different scenarios. For detailed output of these examples, refer to the Results and Analysis section of the Multi-Agent LangGraph Mistral Jupyter notebook.
In the Multi-Agent City Information System we developed, agents integrate various data sources and APIs within a flexible, modular framework to provide valuable information about events, weather, activities, outfit recommendations, and dining options across different cities. Using Amazon Bedrock and LangGraph, we’ve created a sophisticated agent-based workflow that adapts seamlessly to varying levels of available information, switching between local and online data sources as needed. These agents autonomously gather, process, and consolidate data into actionable insights, orchestrating and automating business logic to streamline processes and provide real-time insights. As a result, this multi-agent approach enables the creation of robust, scalable, and intelligent agentic systems that push the boundaries of what’s possible with generative AI.
Want to dive deeper? Explore the implementation of Multi-Agent Collaboration and Orchestration using LangGraph for Mistral Models on GitHub to observe the code in action and try out the solution yourself. You’ll find step-by-step instructions for setting up and running the multi-agent system, along with code for interacting with data sources, agents, routing data, and visualizing the workflow.
This tutorial is in two parts; they are: • Using DistilBart for Summarization • Improving…
Those clicks and pops aren't supposed to be there! Give your music a bath with…
Overfitting is one of the most (if not the most!) common problems encountered when building…
Global trade patterns are being redefined. As tariffs reshape international commerce, enterprises face a once-in-a-generation…
This post is co-authored with Sundeep Sardana, Malolan Raman, Joseph Lam, Maitri Shah and Vaibhav…
AI Hypercomputer is a fully integrated supercomputing architecture for AI workloads – and it’s easier…