LogoA2A Docs

Multi-Agent Web App

Building a web application with multiple A2A agents

Building a Multi-Agent Web Application

This guide demonstrates how to build a web application that coordinates multiple A2A agents for complex tasks.

Overview

A multi-agent web application leverages the specialized capabilities of different A2A agents to solve complex problems. In this example, we'll build a travel planning application that coordinates multiple agents:

  1. Research Agent: Researches destinations based on user preferences
  2. Flight Agent: Finds suitable flights to destinations
  3. Itinerary Agent: Creates detailed itineraries based on research
  4. Visualization Agent: Generates visual content for the itinerary

Each agent specializes in a particular task, and the web application coordinates their work to produce a complete travel plan for the user.

Architecture

The application consists of the following components:

Multi-Agent Web App Architecture

  1. Frontend UI: React-based user interface
  2. Backend Gateway: Node.js API server that coordinates agent interactions
  3. A2A Agents: Multiple specialized agents accessible via the A2A protocol
  4. Shared State: Database to maintain session and agent state

Setup and Configuration

Agent Configuration

First, define the configuration for each A2A agent:

// config/agents.js
export const agents = {
  research: {
    url: 'https://research-agent.example.com/a2a',
    authToken: process.env.RESEARCH_AGENT_TOKEN,
    description: 'Researches travel destinations based on preferences'
  },
  flight: {
    url: 'https://flight-agent.example.com/a2a',
    authToken: process.env.FLIGHT_AGENT_TOKEN,
    description: 'Finds suitable flights to destinations'
  },
  itinerary: {
    url: 'https://itinerary-agent.example.com/a2a',
    authToken: process.env.ITINERARY_AGENT_TOKEN,
    description: 'Creates detailed travel itineraries'
  },
  visualization: {
    url: 'https://visualization-agent.example.com/a2a',
    authToken: process.env.VISUALIZATION_AGENT_TOKEN,
    description: 'Generates visual content for itineraries'
  }
};

Backend Gateway Setup

Create a Node.js/Express server to coordinate the agents:

// server.js
import express from 'express';
import cors from 'cors';
import { A2AClient } from 'a2a-client';
import { agents } from './config/agents.js';
import { db } from './db.js';
 
const app = express();
app.use(cors());
app.use(express.json());
 
// Initialize A2A clients
const a2aClients = {};
for (const [name, config] of Object.entries(agents)) {
  a2aClients[name] = new A2AClient({
    url: config.url,
    authToken: config.authToken
  });
}
 
// Initialize a new planning session
app.post('/api/sessions', async (req, res) => {
  const { userId } = req.body;
  
  const sessionId = await db.sessions.create({
    userId,
    status: 'planning',
    createdAt: new Date(),
    state: {
      stage: 'initial',
      preferences: {},
      research: null,
      flights: null,
      itinerary: null,
      visualizations: []
    }
  });
  
  res.json({ sessionId });
});
 
// Progress a session to the next stage
app.post('/api/sessions/:sessionId/advance', async (req, res) => {
  const { sessionId } = req.params;
  const { userInput } = req.body;
  
  const session = await db.sessions.findById(sessionId);
  if (!session) {
    return res.status(404).json({ error: 'Session not found' });
  }
  
  try {
    // Process based on current stage
    switch (session.state.stage) {
      case 'initial':
        // Store user preferences and start research
        session.state.preferences = userInput;
        session.state.stage = 'researching';
        
        // Create task for research agent
        const researchTask = await a2aClients.research.createTask({
          message: {
            parts: [
              {
                mimeType: 'application/json',
                data: JSON.stringify(userInput)
              }
            ]
          }
        });
        
        session.state.tasks = {
          ...session.state.tasks,
          research: researchTask.taskId
        };
        break;
        
      case 'researching':
        // Get research results and start flight search
        const researchResult = await a2aClients.research.getTask(session.state.tasks.research);
        if (researchResult.status !== 'Completed') {
          return res.status(400).json({ error: 'Research not completed yet' });
        }
        
        session.state.research = JSON.parse(researchResult.artifacts[0].parts[0].data);
        session.state.stage = 'booking_flights';
        
        // Create task for flight agent
        const flightTask = await a2aClients.flight.createTask({
          message: {
            parts: [
              {
                mimeType: 'application/json',
                data: JSON.stringify({
                  preferences: session.state.preferences,
                  destinations: session.state.research.destinations
                })
              }
            ]
          }
        });
        
        session.state.tasks = {
          ...session.state.tasks,
          flight: flightTask.taskId
        };
        break;
        
      // Additional stages omitted for brevity...
    }
    
    await db.sessions.update(sessionId, session);
    res.json({ session });
    
  } catch (error) {
    console.error('Error advancing session:', error);
    res.status(500).json({ error: 'Failed to advance session' });
  }
});
 
// Additional endpoints for retrieving session state, etc.
 
app.listen(3001, () => {
  console.log('Backend gateway listening on port 3001');
});

Frontend Implementation

Create a React component to manage the multi-agent conversation:

// TripPlanner.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
const API_URL = 'http://localhost:3001/api';
 
const stageTitles = {
  initial: 'Tell us about your trip',
  researching: 'Researching destinations...',
  booking_flights: 'Finding flights...',
  planning_itinerary: 'Creating itinerary...',
  generating_visuals: 'Generating visuals...',
  completed: 'Your trip is ready!'
};
 
const TripPlanner = () => {
  const [sessionId, setSessionId] = useState(null);
  const [session, setSession] = useState(null);
  const [userInput, setUserInput] = useState('');
  
  // Initialize session
  useEffect(() => {
    const initSession = async () => {
      const response = await axios.post(`${API_URL}/sessions`, {
        userId: 'user123' // In a real app, use authenticated user ID
      });
      setSessionId(response.data.sessionId);
      fetchSession(response.data.sessionId);
    };
    
    initSession();
  }, []);
  
  // Fetch session state
  const fetchSession = async (id) => {
    const response = await axios.get(`${API_URL}/sessions/${id}`);
    setSession(response.data.session);
  };
  
  // Advance to next stage with user input
  const advanceSession = async () => {
    if (!sessionId) return;
    
    const response = await axios.post(`${API_URL}/sessions/${sessionId}/advance`, {
      userInput: session.state.stage === 'initial' 
        ? {
            destinations: userInput.split(',').map(d => d.trim()),
            startDate: '2024-07-01', // In a real app, use date picker
            endDate: '2024-07-10',
            budget: 2000,
            travelers: 2,
            preferences: ['beach', 'culture', 'food']
          }
        : userInput
    });
    
    setSession(response.data.session);
    setUserInput('');
  };
  
  if (!session) return <div>Loading...</div>;
  
  return (
    <div className="trip-planner">
      <h1>AI Travel Planner</h1>
      
      <div className="stage-indicator">
        <h2>{stageTitles[session.state.stage]}</h2>
        <progress value={Object.keys(stageTitles).indexOf(session.state.stage)} 
                 max={Object.keys(stageTitles).length - 1} />
      </div>
      
      {session.state.stage === 'initial' && (
        <div className="input-section">
          <p>Where would you like to go? Enter potential destinations separated by commas.</p>
          <textarea 
            value={userInput}
            onChange={(e) => setUserInput(e.target.value)}
            placeholder="e.g. Paris, Rome, Barcelona"
          />
          <button onClick={advanceSession}>Start Planning</button>
        </div>
      )}
      
      {session.state.stage === 'researching' && (
        <div className="loading-section">
          <p>Our research agent is finding the best options based on your preferences...</p>
          <div className="spinner"></div>
        </div>
      )}
      
      {/* Additional stage-specific UI components omitted for brevity... */}
      
      {session.state.stage === 'completed' && (
        <div className="results-section">
          <h3>Your Personalized Trip Plan</h3>
          
          <div className="destination-summary">
            <h4>{session.state.itinerary.destination}</h4>
            <p>{session.state.itinerary.summary}</p>
          </div>
          
          <div className="flight-details">
            <h4>Flight Details</h4>
            <p><strong>Outbound:</strong> {session.state.flights.outbound}</p>
            <p><strong>Return:</strong> {session.state.flights.return}</p>
            <p><strong>Price:</strong> ${session.state.flights.price}</p>
          </div>
          
          <div className="itinerary">
            <h4>Daily Itinerary</h4>
            {session.state.itinerary.days.map((day, i) => (
              <div key={i} className="day">
                <h5>Day {i+1}: {day.title}</h5>
                <p>{day.description}</p>
                
                {day.activities.map((activity, j) => (
                  <div key={j} className="activity">
                    <p><strong>{activity.time}:</strong> {activity.description}</p>
                  </div>
                ))}
              </div>
            ))}
          </div>
          
          <div className="visualizations">
            <h4>Trip Visualizations</h4>
            {session.state.visualizations.map((visual, i) => (
              <img key={i} src={visual.url} alt={visual.description} />
            ))}
          </div>
        </div>
      )}
    </div>
  );
};
 
export default TripPlanner;

Key Implementation Highlights

1. Session Management

The application maintains a session for each trip planning process, storing:

  • Current planning stage
  • User preferences
  • Agent task IDs
  • Results from each agent

2. Agent Coordination

The backend gateway coordinates the agents by:

  • Determining which agent to call based on the current stage
  • Formatting input data appropriately for each agent
  • Retrieving and storing results from each agent
  • Progressing through the planning stages

3. Stage Progression

The planning process follows a clear sequence:

  1. Gather user preferences
  2. Research destinations
  3. Find flights
  4. Create detailed itinerary
  5. Generate visualizations

4. Artifact Processing

Each agent produces artifacts that are:

  • Stored in the session state
  • Used as input for subsequent agents
  • Presented to the user in the UI

5. Asynchronous Processing

Long-running tasks are handled asynchronously:

  • The UI shows loading indicators during processing
  • The backend polls for task completion
  • The session advances when agents complete their tasks

Advanced Features

Consider adding these enhancements to your multi-agent web application:

WebSocket Support

Implement WebSocket connections for real-time updates:

// server.js (additional code)
import { Server } from 'socket.io';
const server = app.listen(3001);
const io = new Server(server, { cors: { origin: '*' } });
 
io.on('connection', (socket) => {
  socket.on('join-session', (sessionId) => {
    socket.join(sessionId);
  });
});
 
// Function to notify clients of session updates
function notifySessionUpdate(sessionId, session) {
  io.to(sessionId).emit('session-updated', session);
}

User Authentication

Add user authentication to secure the application:

// auth.js
import jwt from 'jsonwebtoken';
 
export function verifyToken(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.userId = decoded.userId;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}
 
// Apply middleware to protected routes
app.use('/api/sessions', verifyToken);

Trip Persistence

Allow users to save and load trip plans:

// Additional endpoints in server.js
app.get('/api/users/:userId/trips', async (req, res) => {
  const { userId } = req.params;
  const trips = await db.sessions.findByUserId(userId, { 
    status: 'completed' 
  });
  
  res.json({ trips });
});
 
app.post('/api/sessions/:sessionId/save', async (req, res) => {
  const { sessionId } = req.params;
  const { name } = req.body;
  
  const session = await db.sessions.findById(sessionId);
  if (!session || session.state.stage !== 'completed') {
    return res.status(400).json({ error: 'Only completed trips can be saved' });
  }
  
  session.name = name;
  session.savedAt = new Date();
  
  await db.sessions.update(sessionId, session);
  res.json({ success: true });
});

Agent Discovery

Implement dynamic agent discovery:

// agentDiscovery.js
import { A2AClient } from 'a2a-client';
 
export async function discoverAgents(agentUrls) {
  const discoveredAgents = {};
  
  for (const url of agentUrls) {
    try {
      const client = new A2AClient({ url });
      const agentCard = await client.getAgentCard();
      
      discoveredAgents[agentCard.name] = {
        url,
        name: agentCard.name,
        description: agentCard.description,
        skills: agentCard.skills,
        client
      };
    } catch (error) {
      console.error(`Failed to discover agent at ${url}:`, error);
    }
  }
  
  return discoveredAgents;
}

Fallback Handling

Implement fallback mechanisms for when agents fail:

// Fallback handling in server.js
async function createAgentTask(agentName, data, fallbackAgent = null) {
  try {
    return await a2aClients[agentName].createTask({
      message: {
        parts: [
          {
            mimeType: 'application/json',
            data: JSON.stringify(data)
          }
        ]
      }
    });
  } catch (error) {
    console.error(`Error creating task for ${agentName}:`, error);
    
    if (fallbackAgent) {
      console.log(`Attempting fallback to ${fallbackAgent}`);
      return await a2aClients[fallbackAgent].createTask({
        message: {
          parts: [
            {
              mimeType: 'application/json',
              data: JSON.stringify({
                ...data,
                _fallback: true
              })
            }
          ]
        }
      });
    }
    
    throw error;
  }
}

Deployment Considerations

When deploying your multi-agent web application, consider:

Security

  • Use HTTPS for all communications
  • Implement proper authentication for both users and agents
  • Sanitize all user inputs before passing to agents
  • Use secure, environment-specific API keys

Rate Limiting

  • Implement rate limiting for API endpoints
  • Consider agent usage limits and costs
  • Cache results where appropriate

Monitoring

  • Add logging for agent interactions
  • Monitor task completion times
  • Track success/failure rates of agents
  • Set up alerts for system issues

Cost Management

  • Implement usage quotas for users
  • Track agent usage costs
  • Consider batching requests where possible
  • Cache frequently requested data

Caching

  • Cache agent responses for similar queries
  • Implement TTL (time-to-live) for cached data
  • Use Redis or similar for distributed caching
  • Invalidate cache when dependencies change

Conclusion

A multi-agent web application leverages the A2A protocol to create powerful, collaborative AI systems. By coordinating specialized agents, your application can tackle complex tasks that would be difficult for a single agent to handle effectively.

The architecture presented here can be adapted for various domains beyond travel planning, such as:

  • Content creation workflows
  • Research and analysis pipelines
  • Customer service systems
  • Product recommendation engines
  • Educational tutoring platforms

The A2A protocol makes it possible to build modular, extensible agent systems where each component excels at specific tasks while collaborating seamlessly with others.