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:
Improving decision-making with deeper, context-aware information
Automating complex workflows across various domains, from customer service to scientific research
Enabling more natural and intuitive human-AI interactions
Generating new ideas by bringing together diverse data sources and specialized knowledge
Addressing ethical concerns by providing more transparent and explainable AI systems
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.
Solution overview
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:
Modularity – Each agent focuses on a specific task, making the system easier to maintain and extend
Flexibility – Agents can be quickly added, removed, or modified without affecting the entire system
Complex workflow handling – The system can manage advanced and complex workflows by distributing tasks among multiple agents
Specialization – Each agent is optimized for its specific task, improving latency, accuracy, and overall system efficiency
Security – The system enhances security by making sure that each agent only has access to the tools necessary for its task, reducing the potential for unauthorized access to sensitive data or other agents’ tasks
How our multi-agent system works
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:
Events – It searches a local database and online sources for upcoming events in the city. Whenever local database information is unavailable, it triggers an online search using the Tavily API. This makes sure that users receive up-to-date event information, regardless of whether it’s stored locally or needs to be retrieved from the web
Weather – The system fetches current weather data using the OpenWeatherMap API, providing accurate and timely weather information for the queried location. Based on the weather, the system also offers outfit and activity recommendations tailored to the conditions, providing relevant suggestions for each city
Restaurants – Recommendations are provided through a Retrieval Augmented Generation (RAG) system. This method combines prestored information with real-time generation to offer relevant and up-to-date dining suggestions
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:
Some cities might require the use of the search tool for event information when local database data is unavailable
Other cities might have data available in the local database, providing quick access to event information without needing an online search
In cases where restaurant recommendations are unavailable for a particular city, the system can still provide valuable insights based on the available event and weather data
The following diagram is the solution’s reference architecture:
Data sources
The Multi-Agent City Information System can take advantage of two sources of data.
Local events database
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.
Restaurant RAG system
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.
Building the multi-agent architecture
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.
Text generation function
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:
Sends a user message to the Mistral model using the Amazon Bedrock Converse API
Invokes the appropriate tool and incorporates the results into the conversation
Continues the conversation until a final response is generated
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
Local database query tool
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}.”
Weather tool
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
Online search tool
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
Restaurant recommendation function
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.
Implementing AI agents with LangGraph
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 agent (events_database_agent) – Uses the events_database_tool() to query a local SQLite database and find local event information
Online search agent (search_agent) – Whenever local event information is unavailable in the database, this agent uses the search_tool() to find upcoming events by searching online for a given city
Weather agent (weather_agent) – Fetches current weather data using the weather_tool() for the specified city
Restaurant recommendation agent (query_restaurants_agent) – Uses the query_restaurants_RAG() function to provide restaurant recommendations for a specified city
Analysis agent (analysis_agent) – Aggregates information from other agents to provide comprehensive recommendations
Here’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
Orchestrating agent collaboration
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:
Initialize workflow – The function begins by creating a StateGraph object called 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)
…
Add nodes (agents) – Each agent is associated with a specific function, such as retrieving event data, performing an online search, fetching weather information, recommending restaurants, or analyzing the gathered information:
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)
Set entry point and conditional routing – The entry point for the workflow is set to the 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”
}
)
Add Edges between agents – These edges define the order in which agents interact in the workflow. The agents will proceed in a specific sequence: from 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)
Initialize memory for state persistence – The 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()
Compile the workflow and visualize the graph – The workflow is compiled, and the memory-saving object (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:
Results and analysis
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:
Calls the build_graph() function, which implements the agentic workflow
Initializes the state with the specified city
Streams the events through the workflow
Retrieves and displays the final analysis and recommendations
To 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)
Three example use cases
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:
Events – Not found in the local database, triggering the search tool which called the Tavily API to find several upcoming events
Weather – Retrieved from weather tool. Current conditions include moderate rain, 28°C, and 87% humidity
Activities – The system suggested various indoor and outdoor activities based on the events and weather
Outfit recommendations – Considering the warm, humid, and rainy conditions, the system recommended light, breathable clothing and rain protection
Restaurants – Recommendations provided through the RAG system
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.
Conclusion
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.
About the Author
Andre Boaventura is a Principal AI/ML Solutions Architect at AWS, specializing in generative AI and scalable machine learning solutions. With over 25 years in the high-tech software industry, he has deep expertise in designing and deploying AI applications using AWS services such as Amazon Bedrock, Amazon SageMaker, and Amazon Q. Andre works closely with global system integrators (GSIs) and customers across industries to architect and implement cutting-edge AI/ML solutions to drive business value. Outside of work, Andre enjoys practicing Brazilian Jiu-Jitsu with his son (often getting pinned or choked by a teenager), cheering for his daughter at her dance competitions (despite not knowing ballet terms—he claps enthusiastically anyway), and spending ‘quality time’ with his wife—usually in shopping malls, pretending to be interested in clothes and shoes while secretly contemplating a new hobby.