Skip to content

Repository files navigation

Concierge - Multi-Agent AI System with Tool Calling

A demonstration application showcasing how to build multi-agent systems using Angular and Genkit, implementing the tool calling agentic pattern. This project serves as a hands-on codelab for understanding how specialized AI agents can work together to solve complex user requests.

🎯 What This App Demonstrates

This application demonstrates a multi-agent system architecture where a central orchestrator (concierge agent) coordinates multiple specialized agents to handle different types of user requests. It implements the tool calling agentic pattern, one of the key patterns described in the Genkit Agentic Patterns documentation.

Understanding Tool Calling as an Agentic Pattern

Tool calling is an agentic pattern that extends the capabilities of Large Language Models (LLMs) by allowing them to invoke external functions or APIs. Instead of relying solely on the model's training data, the LLM can:

  1. Analyze the user's request
  2. Decide which tool(s) to use
  3. Execute the appropriate tool(s) with the right parameters
  4. Synthesize the results into a coherent response

This pattern transforms a static LLM into a dynamic agent that can access real-time information, perform calculations, query databases, or delegate to specialized sub-agents.

Multi-Agent Architecture

This app implements a hierarchical multi-agent system with:

  • 1 Orchestrator Agent (Concierge): Routes requests and coordinates responses
  • 4 Specialized Agents: Each expert in a specific domain
    • πŸš— Day Trip Agent: Plans full-day itineraries with real-time information
    • 🍽️ Foodie Agent: Recommends restaurants and culinary experiences
    • πŸŽ‰ Weekend Guide Agent: Finds events, concerts, and festivals
    • πŸ—ΊοΈ Transport Agent: Provides navigation and route guidance

πŸ—οΈ Architecture Overview

User Query β†’ Angular Frontend β†’ Firebase Function β†’ Concierge Agent
                                                           ↓
                                        [Tool Calling Decision]
                                                           ↓
                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    ↓                      ↓                      ↓
                            Day Trip Agent        Foodie Agent        Weekend Guide Agent
                                    ↓                      ↓                      ↓
                            [Google Search]        [Google Search]        [Google Search]
                                    ↓                      ↓                      ↓
                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                           ↓
                                            Synthesized Response β†’ User

πŸ“š Code Walkthrough

1. Defining Specialized Agent Tools

Each specialized agent is defined as a tool that the concierge can call. Here's how the Day Trip Agent is implemented:

// functions/src/index.ts

export const _dayTripAgentToolLogic = ai.defineTool(
  {
    name: 'dayTripAgentTool',
    description: 'Assists with planning day trips',
    inputSchema: z.object({
      input: z.string(),
      history: z.array(conversationMessageSchema).optional(),
    }),
    outputSchema: z.string(),
  },
  async ({ input, history }) => {
    const response = await ai.generate({
      system: DAY_TRIP_AGENT_PROMPT,
      messages: [
        ...toGenkitMessages(history ?? []),
        { role: 'user', content: [{ text: input }] },
      ],
      config: {
        googleSearchRetrieval: {}, // Enables real-time web search
      },
    });

    if (!response.text) {
      throw new Error('No output from AI');
    }

    return response.text;
  }
);

What's happening here:

  • ai.defineTool() creates a callable function that the LLM can invoke
  • The description helps the LLM understand when to use this tool
  • inputSchema and outputSchema define the data contract using Zod
  • googleSearchRetrieval enables the agent to fetch real-time information from the web
  • The agent combines its specialized prompt with the user's query

2. The Concierge Orchestrator

The concierge agent acts as the orchestrator, deciding which specialized agent(s) to invoke:

// functions/src/index.ts

export const _conciergeAgentLogic = ai.defineFlow(
  {
    name: 'conciergeAgentFlow',
    inputSchema: z.object({
      input: z.string(),
      history: z.array(conversationMessageSchema).optional(),
    }),
    outputSchema: z.string(),
  },
  async ({ input, history }) => {
    const response = await ai.generate({
      system: CONCIERGE_AGENT_PROMPT,
      messages: [
        ...toGenkitMessages(history ?? []),
        { role: 'user', content: [{ text: input }] },
      ],
      tools: [
        _dayTripAgentToolLogic,
        _foodieAgentToolLogic,
        _weekendGuideAgentToolLogic,
        _findAndNavigateAgentToolLogic,
      ],
    });

    const result = response.text || response.output;

    if (!result) {
      throw new Error('No output from AI');
    }

    return result;
  }
);

What's happening here:

  • ai.defineFlow() creates a workflow that can use multiple tools
  • The tools array provides all available specialized agents
  • The LLM automatically decides which tool(s) to call based on the user's query
  • Genkit handles the tool invocation, parameter passing, and response aggregation
  • The concierge synthesizes the final response for the user

3. Agent System Prompts

Each agent has a specialized system prompt that defines its expertise and behavior:

// functions/src/system-prompt.ts

export const CONCIERGE_AGENT_PROMPT = `
  You are a helpful concierge AI assistant. Your role is to answer user questions 
  by intelligently using the tools provided to you.

  Guidelines:
    1. **Use Available Tools**: When a user asks a question, analyze which tools 
       are available and use the most appropriate one(s) to gather information.
    2. **Be Comprehensive**: Combine information from multiple tools if needed 
       to provide a complete answer.
    3. **Be Conversational**: Present the information in a friendly, helpful 
       manner as a concierge would.
    4. **Clarify When Needed**: If a user's request is unclear, ask clarifying 
       questions before using tools.
    5. **Provide Context**: When presenting results from tools, add helpful 
       context and recommendations.
`;

export const DAY_TRIP_AGENT_PROMPT = `
  You are the "Spontaneous Day Trip" Generator πŸš— - a specialized AI assistant 
  that creates engaging full-day itineraries.

  Your Mission:
  Transform a simple mood or interest into a complete day-trip adventure with 
  real-time details, while respecting a budget.

  Guidelines:
    1. **Budget-Aware**: Pay close attention to budget hints like 'cheap', 
       'affordable', or 'splurge'.
    2. **Full-Day Structure**: Create morning, afternoon, and evening activities.
    3. **Real-Time Focus**: Search for current operating hours and special events.
    4. **Mood Matching**: Align suggestions with the requested mood.
`;

What's happening here:

  • System prompts define each agent's personality, expertise, and behavior
  • The concierge prompt emphasizes tool usage and coordination
  • Specialized agent prompts focus on domain-specific expertise
  • Clear guidelines ensure consistent, high-quality responses

4. Frontend Integration

The Angular frontend communicates with the multi-agent system through Firebase Functions:

// src/app/services/core/ai/ai.service.ts

@Injectable({
  providedIn: 'root',
})
export class AiService {
  private readonly functions = inject(Functions);

  sendMessage(query: string, history: ConversationMessage[] = []): Observable<{ data: string }> {
    const conciergeAgentFlow = httpsCallable<{ input: string; history: ConversationMessage[] }, string>(
      this.functions,
      'conciergeAgentFlow'
    );
    return from(conciergeAgentFlow({ input: query, history }));
  }
}

What's happening here:

  • The service uses Angular's inject() function for dependency injection
  • httpsCallable creates a typed function that calls the Firebase backend
  • The entire multi-agent orchestration happens server-side
  • The history array carries prior conversation turns so agents have full context
  • RxJS observables provide reactive, asynchronous communication

πŸ’¬ Conversation Context

Agents can ask follow-up questions and remember the user's answers across turns. The Angular client maintains the full conversation history in memory and sends it with every request, keeping the backend stateless and scalable.

// ChatComponent β€” snapshot history before each send
const historySnapshot = this.conversationHistory();
this.conversationHistory.update((h) => [...h, { role: 'user', content: query }]);

this.aiService.sendMessage(query, historySnapshot).subscribe({
  next: (response) => {
    // Append AI reply so the next turn has full context
    this.conversationHistory.update((h) => [...h, { role: 'model', content: response.data }]);
  },
});

πŸ“„ For a deep-dive into the design decisions, data flow, and full implementation details, see documentation/conversation-context.md.


πŸš€ Getting Started

Prerequisites

  • Node.js (v18 or higher)
  • Angular CLI (npm install -g @angular/cli)
  • Firebase CLI (npm install -g firebase-tools)
  • A Google Cloud project with Gemini API access

Installation

  1. Clone the repository:
git clone <repository-url>
cd concierge
  1. Install dependencies:
npm install
cd functions
npm install
cd ..
  1. Set up Firebase:
firebase login
firebase init
  1. Configure your Gemini API key:
firebase functions:secrets:set GEMINI_API_KEY

Development

Run the Angular development server:

ng serve

Navigate to http://localhost:4200/

Run Firebase Functions locally:

cd functions
npm run serve

Run both concurrently:

npm run dev

πŸ§ͺ Testing the Multi-Agent System

Try these example queries to see different agents in action:

  • Day Trip Agent: "Plan a budget-friendly day trip in San Francisco with an artsy vibe"
  • Foodie Agent: "Where can I find the best sushi in Tokyo?"
  • Weekend Guide Agent: "What events are happening in New York this weekend?"
  • Transport Agent: "How do I get from Times Square to Central Park?"
  • Multi-Agent: "Find me a great Italian restaurant in Rome and tell me how to get there from the Colosseum"

πŸ“š Additional Resources

πŸ› οΈ Built With

  • Angular 21 - Frontend framework
  • Genkit - Google's open-source AI orchestration framework
  • Firebase Functions - Serverless backend
  • Google Gemini - Large Language Model
  • TypeScript - Type-safe development

πŸ“ License

This project is intended for educational purposes as part of a codelab session on building multi-agent systems.

About

Concierge is a sophisticated web-based command center designed to eliminate the friction of modern living. By unifying four specialized AI agents into a single, seamless interface, Concierge transforms the way you eat, travel, and explore.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages