Multi-Agent Collaboration#

This page covers how to design systems where multiple specialized agents work together, including common collaboration patterns, orchestration strategies, context injection, and how agents decide when to escalate or hand off to another agent.

Learning Objectives#

  • Understand multi-agent system architecture

  • Implement agent collaboration patterns

  • Design agent teams

  • Coordinate agent workflows

  • Master context injection and state management

  • Understand how agents decide to end or escalate

Multi-Agent Systems#

Why Multiple Agents?#

Specialization#

Each agent specializes in a specific domain, improving accuracy and efficiency.

Parallel processing#

Multiple agents can process independent tasks in parallel, reducing processing time.

Modularity#

System is easier to maintain and extend when each agent is an independent module.

Scalability#

Easy to add new agents without affecting existing agents.

Use Cases#

Software development team#

  • Product Manager: Define requirements

  • Architect: Design system

  • Developer: Write code

  • Tester: Test and report bugs

Customer service#

  • FAQ Agent: Answer frequently asked questions

  • Ticket Agent: Manage support tickets

  • IT Support Agent: Technical support

  • Booking Agent: Schedule/book rooms

Content creation pipeline#

  • Research Agent: Search for information

  • Writer Agent: Write content

  • Editor Agent: Edit content

Collaboration Patterns#

1. Sequential (Pipeline)#

Agent 1 β†’ Agent 2 β†’ Agent 3#

Output of previous agent is input of next agent, linear workflow.

Example:

Research Agent β†’ Analyzer Agent β†’ Writer Agent

2. Hierarchical (Supervisor)#

Primary Assistant β†’ Specialized Agents#

Primary assistant coordinates and routes to specialized agents.

FPT Chatbot Example:

Primary Assistant

    β”œβ”€β”€ FAQ Agent (RAG)

    β”œβ”€β”€ Ticket Support Agent

    β”œβ”€β”€ IT Support Agent

    └── Booking Agent

3. Network (Peer-to-Peer)#

Agents communicate directly#

Agents communicate directly with each other, no supervisor needed.

4. Competitive#

Multiple agents solve same problem#

Multiple agents solve the same problem, choose the best solution.

Hierarchical Multi-Agent System#

Architecture#

Primary Assistant (Supervisor)

    β”œβ”€β”€ FAQ Agent

    β”œβ”€β”€ Ticket Support Agent

    β”œβ”€β”€ IT Support Agent

    └── Booking Agent

State Management in Hierarchical System#

Each Agent has its own State#

In hierarchical systems, each agent can have its own state, but all share a common shared state.

State Definition:


from typing import Annotated, Optional, List

from typing_extensions import TypedDict, Literal

from langchain_core.messages import AnyMessage

from langgraph.graph import add_messages

from pydantic import EmailStr



class AgenticState(TypedDict):

    """Shared state for all agents"""



    # Messages - automatically merged

    messages: Annotated[list[AnyMessage], add_messages]



    # Dialog stack - track agent hierarchy

    dialog_state: Annotated[

        list[Literal["primary_assistant", "ticket_agent", "it_agent", "booking_agent"]],

        update_dialog_stack,

    ]



    # Context information

    conversation_id: Annotated[str, "Conversation ID"]

    user_id: Annotated[str, "User ID"]

    email: Annotated[Optional[EmailStr], "Email from context (optional)"]

Dialog Stack Management#

Push State (Switch to child agent):


def update_dialog_stack(left: list[str], right: Optional[str]) -> list[str]:

    """Push or pop dialog stack"""

    if right is None:

        return left

    if right == "pop":

        return left[:-1]  # Pop: return to previous agent

    return left + [right]  # Push: add new agent to stack

Dialog Stack Example:


# Start

dialog_state = ["primary_assistant"]



# Switch to Ticket Agent

dialog_state = ["primary_assistant", "ticket_agent"]



# Pop (return to Primary)

dialog_state = ["primary_assistant"]

Pop State (Return to Primary Assistant)#


def pop_dialog_state(state: AgenticState) -> dict:

    """Pop dialog stack and return to primary assistant"""

    messages = []

    if state["messages"][-1].tool_calls:

        messages.append(

            ToolMessage(

                content="Resuming dialog with the host assistant. "

                       "Please reflect on the past conversation and assist the user as needed.",

                tool_call_id=state["messages"][-1].tool_calls[0]["id"],

            )

        )

    return {

        "dialog_state": "pop",  # Trigger pop operation

        "messages": messages,

    }

Context Injection#

Why Context Injection?#

Context injection allows automatic injection of user information (email, user_id) into all tool calls without agents needing to remember and pass it each time.

Context Injection Implementation#

1. Store Context in State:


class AgenticState(TypedDict):

    # ... other fields

    user_id: Annotated[str, "User ID"]

    email: Annotated[Optional[EmailStr], "Email from context (optional)"]

2. Inject Context into Tool Calls:


def inject_user_info(state: AgenticState, result):

    """Automatically inject user_id and email into tool calls"""

    if hasattr(result, "tool_calls") and result.tool_calls:

        for tool_call in result.tool_calls:

            # Inject into tool calls that need user context

            if tool_call["name"] in [

                "ToTicketAssistant",

                "ToITAssistant",

                "ToBookingAssistant"

            ]:

                # Automatically add user_id and email from state

                tool_call["args"]["user_id"] = state["user_id"]

                if state.get("email"):

                    tool_call["args"]["email"] = state["email"]

    return result

3. Use in Primary Assistant:


def assistant_runnable_with_user_info(state):

    """Primary assistant with context injection"""

    # Bind tools for primary assistant

    result = (

        primary_assistant_prompt

        | llm.bind_tools([

            ToTicketAssistant,

            ToITAssistant,

            ToBookingAssistant,

            RAG_Agent

        ])

    ).invoke(state)



    # Inject user info into tool calls

    return inject_user_info(state, result)

4. Tool Schema with Optional Email:


from pydantic import BaseModel, EmailStr

from typing import Optional



class CreateTicket(BaseModel):

    """Schema for create_ticket tool"""

    content: str

    description: Optional[str] = None

    customer_name: Optional[str] = None

    customer_phone: Optional[str] = None

    # Email is OPTIONAL - can be injected from context

    email: Optional[EmailStr] = None

    # user_id will be automatically injected

    user_id: Optional[str] = None

5. Tool Implementation using Context:


@tool("create_ticket", args_schema=CreateTicket)

def create_ticket(

    content: str,

    description: Optional[str] = None,

    customer_name: Optional[str] = None,

    customer_phone: Optional[str] = None,

    email: Optional[EmailStr] = None,  # Optional - can be from context

    user_id: Optional[str] = None,  # Will be automatically injected

) -> dict:

    """Create new ticket"""

    # Email may have been injected from context

    # If not available, tool still works normally

    ticket_id = f"TICKET_{generate_short_id()}"



    new_ticket = Ticket(

        ticket_id=ticket_id,

        content=content,

        description=description,

        customer_name=customer_name,

        customer_phone=customer_phone,

        email=email,  # Can be None

        user_id=user_id,

        status="Pending"

    )



    # Save to database

    db.add(new_ticket)

    db.commit()



    # Only send email if email exists

    if email:

        send_email_notification(email, ticket_id)



    return {

        "ticket_id": ticket_id,

        "message": f"Ticket created successfully. Email sent to {email}" if email else "Ticket created successfully."

    }

State Transitions#

Entry Node (Enter Child Agent)#


def create_entry_node(assistant_name: str, new_dialog_state: str):

    """Create entry node to switch to child agent"""



    def entry_node(state: AgenticState) -> dict:

        tool_call_id = state["messages"][-1].tool_calls[0]["id"]



        return {

            "messages": [

                ToolMessage(

                    content=f"The assistant is now the {assistant_name}. "

                           f"Reflect on the above conversation. "

                           f"Use the provided tools to assist the user. "

                           f"If task is complete, call CompleteOrEscalate to return control.",

                    tool_call_id=tool_call_id,

                )

            ],

            "dialog_state": new_dialog_state,  # Push to stack

        }



    return entry_node

Routing Logic#


def route_primary_assistant(state: AgenticState):

    """Route from primary assistant to appropriate agent"""

    last_message = state["messages"][-1]



    if not hasattr(last_message, "tool_calls") or not last_message.tool_calls:

        return END



    tool_name = last_message.tool_calls[0]["name"]



    # Route based on called tool

    routing_map = {

        "ToTicketAssistant": "enter_ticket_node",

        "ToITAssistant": "enter_it_node",

        "ToBookingAssistant": "enter_booking_node",

        "rag_agent": "rag_agent_node",

    }



    return routing_map.get(tool_name, END)

Graph Construction#


def setup_agentic_graph():

    """Create graph with nodes and edges"""

    builder = StateGraph(AgenticState)



    # Primary Assistant

    builder.add_node("primary_assistant", Assistant(assistant_runnable, "Primary Assistant"))



    # Ticket Agent nodes

    builder.add_node("enter_ticket_node", create_entry_node("Ticket Agent", "ticket_agent"))

    builder.add_node("call_ticket_agent", Assistant(ticket_runnable, "Ticket Agent"))

    builder.add_node("ticket_tools", create_tool_node_with_fallback(ticket_tools))



    # IT Agent nodes

    builder.add_node("enter_it_node", create_entry_node("IT Agent", "it_agent"))

    builder.add_node("call_it_agent", Assistant(it_runnable, "IT Agent"))

    builder.add_node("it_tools", create_tool_node_with_fallback(it_tools))



    # Booking Agent nodes

    builder.add_node("enter_booking_node", create_entry_node("Booking Agent", "booking_agent"))

    builder.add_node("call_booking_agent", Assistant(booking_runnable, "Booking Agent"))

    builder.add_node("booking_tools", create_tool_node_with_fallback(booking_tools))



    # Pop state node

    builder.add_node("leave_skill", pop_dialog_state)



    # Edges

    builder.add_edge(START, "primary_assistant")

    builder.add_edge("enter_ticket_node", "call_ticket_agent")

    builder.add_edge("ticket_tools", "call_ticket_agent")

    builder.add_edge("enter_it_node", "call_it_agent")

    builder.add_edge("it_tools", "call_it_agent")

    builder.add_edge("enter_booking_node", "call_booking_agent")

    builder.add_edge("booking_tools", "call_booking_agent")

    builder.add_edge("leave_skill", "primary_assistant")



    # Conditional routing

    builder.add_conditional_edges(

        "primary_assistant",

        route_primary_assistant,

        ["enter_ticket_node", "enter_it_node", "enter_booking_node", "rag_agent_node", END]

    )



    builder.add_conditional_edges(

        "call_ticket_agent",

        route_ticket_agent,

        ["ticket_tools", "leave_skill", END]

    )



    return builder.compile(checkpointer=saver)

Tool Call Fallback#

Why Fallback?#

When tool call fails (network error, database error, validation error), system needs fallback mechanism to handle gracefully.

Fallback Implementation#


def create_tool_node_with_fallback(tools: list):

    """Create tool node with fallback handling"""



    def log_tool_result(state):

        """Wrapper to log and handle errors"""

        try:

            # Execute tool

            result = ToolNode(tools).invoke(state)

            return result

        except Exception as e:

            # Fallback: return error message

            last_message = state.get("messages", [])[-1]

            tool_call_id = "unknown"

            if last_message and hasattr(last_message, "tool_calls"):

                tool_call_id = last_message.tool_calls[0]["id"]



            return {

                "messages": [

                    ToolMessage(

                        content=f"Error executing tool: {str(e)}. Please try again or contact support.",

                        tool_call_id=tool_call_id

                    )

                ]

            }



    # Use with_fallbacks from LangChain

    tool_node = RunnableLambda(log_tool_result)



    return tool_node.with_fallbacks(

        [RunnableLambda(lambda state: {

            "messages": [

                ToolMessage(

                    content="Tool execution failed. Please try again.",

                    tool_call_id=state["messages"][-1].tool_calls[0]["id"]

                    if state.get("messages") and state["messages"][-1].tool_calls else "unknown"

                )

            ]

        })],

        exception_key="error"

    )

Complete or Escalate Pattern#

When should Agent End?#

Agent should end (END) when:

  1. Task completed successfully

  2. Fully answered user’s question

  3. No additional information needed from user

When should Agent Escalate?#

Agent should escalate (return to Primary Assistant) when:

  1. Task outside agent’s scope

  2. Need capabilities only available in Primary Assistant

  3. User changes mind or cancels task

  4. Need clarification from user but cannot handle independently

Implementation CompleteOrEscalate#

1. Define Schema:


class CompleteOrEscalate(BaseModel):

    """Tool for agent to decide end or escalate"""



    cancel: bool = True  # True = escalate, False = continue

    reason: str  # Reason



    class Config:

        json_schema_extra = {

            "example": {

                "cancel": True,

                "reason": "Task completed successfully. Returning to primary assistant."

            },

            "example 2": {

                "cancel": True,

                "reason": "User changed their mind. Need to return to primary assistant."

            },

            "example 3": {

                "cancel": False,

                "reason": "Need more information from user to complete the task."

            }

        }

2. Agent uses CompleteOrEscalate:


# Ticket Agent has tools

ticket_safe_tools = [track_ticket]  # Read-only

ticket_sensitive_tools = [create_ticket, update_ticket, cancel_ticket]  # Write operations

ticket_tools = ticket_safe_tools + ticket_sensitive_tools + [CompleteOrEscalate]



def create_ticket_tool(model):

    """Create ticket agent with tools"""

    ticket_tools_runnable = (

        ticket_assistant_prompt

        | model.bind_tools(ticket_tools + [CompleteOrEscalate])

    )

    return ticket_tools_runnable

3. Routing Logic:


def route_ticket_agent(state: AgenticState):

    """Route for ticket agent"""

    route = tools_condition(state)

    if route == END:

        return END



    tool_calls = state["messages"][-1].tool_calls

    tool_names = [tc.get("name") for tc in tool_calls]



    # If calling CompleteOrEscalate with cancel=True -> return to primary

    if "CompleteOrEscalate" in tool_names:

        for tc in tool_calls:

            if tc["name"] == "CompleteOrEscalate":

                args = tc.get("args", {})

                if args.get("cancel", True):

                    return "leave_skill"  # Pop state



    # Distinguish safe tools and sensitive tools

    safe_toolnames = [t.name for t in ticket_safe_tools]

    if all(tc["name"] in safe_toolnames for tc in tool_calls):

        return "ticket_safe_tools"



    return "ticket_sensitive_tools"

4. Agent Prompt usage guide:


TICKET_SYSTEM_PROMPT = """

You are a Ticket Support Agent for FPT Customer Service.



Your responsibilities:

- Create tickets for IT or customer support issues

- Track ticket status

- Update ticket information

- Cancel tickets when requested



When to use CompleteOrEscalate:

1. Task completed successfully -> cancel=True, reason="Task completed"

2. User changed their mind -> cancel=True, reason="User changed mind"

3. Question outside your scope -> cancel=True, reason="Out of scope"

4. Need more info but can't proceed -> cancel=False, reason="Need clarification"



Always try to complete the task before escalating.

"""

Tool Arg Schema in Multi-Tool System#

Why Tool Arg Schema?#

Tool arg schema helps:

  • Validate input before executing tool

  • Clearly document parameters

  • Type safety

  • Auto-completion in IDE

Pydantic Schema for Tools#

1. Basic Schema:


from pydantic import BaseModel, EmailStr, Field

from typing import Optional, Annotated



class CreateTicket(BaseModel):

    """Schema for create_ticket tool"""



    content: str = Field(..., description="Ticket content")

    description: Optional[str] = Field(None, description="Detailed description")

    customer_name: Optional[str] = Field(None, description="Customer name")

    customer_phone: Optional[str] = Field(None, description="Phone number")

    email: Optional[EmailStr] = Field(None, description="Email (optional, can be injected from context)")

    user_id: Optional[str] = Field(None, description="User ID (will be automatically injected)")



    class Config:

        json_schema_extra = {

            "example": {

                "content": "Laptop screen is broken",

                "description": "Screen repair needed",

                "customer_name": "John Doe",

                "customer_phone": "0123456789",

                "email": "john@example.com"

            }

        }

2. Schema with Validation:


from pydantic import field_validator



class BookRoom(BaseModel):

    """Schema for book_room tool"""



    reason: str = Field(..., description="Reason for booking")

    time: datetime = Field(..., description="Booking time")

    customer_name: Optional[str] = None

    customer_phone: Optional[str] = None

    email: Optional[EmailStr] = None

    note: Optional[str] = Field(None, max_length=500)



    @field_validator("time")

    @classmethod

    def validate_time(cls, v):

        if v < datetime.now():

            raise ValueError("Booking time must be in the future")

        return v



    @field_validator("customer_phone")

    @classmethod

    def validate_phone(cls, v):

        if v and not v.isdigit():

            raise ValueError("Phone number must contain only digits")

        return v

3. Schema with Literal Types:


from typing import Literal



class UpdateTicket(BaseModel):

    """Schema for update_ticket tool"""



    ticket_id: str = Field(..., description="Ticket ID")

    content: Optional[str] = None

    description: Optional[str] = None

    status: Optional[Literal["Pending", "Resolving", "Finished", "Canceled"]] = None

    email: Optional[EmailStr] = None

4. Use Schema in Tool:


from langchain_core.tools import tool



@tool("create_ticket", args_schema=CreateTicket)

def create_ticket(

    content: str,

    description: Optional[str] = None,

    customer_name: Optional[str] = None,

    customer_phone: Optional[str] = None,

    email: Optional[EmailStr] = None,

    user_id: Optional[str] = None,

) -> dict:

    """Create new ticket"""

    # Implementation...

    pass

5. Schema for Multi-Tool System:


# Each agent has its own tool schemas

ticket_tool_schemas = [CreateTicket, UpdateTicket, TrackTicket, CancelTicket]

it_tool_schemas = [TavilySearch]  # IT agent only has 1 tool

booking_tool_schemas = [BookRoom, UpdateBooking, TrackBooking, CancelBooking]



# Primary assistant has routing tools

primary_tool_schemas = [

    ToTicketAssistant,

    ToITAssistant,

    ToBookingAssistant,

    RAG_Agent

]

Communication Patterns#

Message Passing#

Structured Messages#


{

    "from": "primary_assistant",

    "to": "ticket_agent",

    "type": "tool_call",

    "tool": "ToTicketAssistant",

    "args": {

        "user_id": "user123",

        "email": "user@example.com"

    }

}

State Updates#

Each agent updates state through messages:


# Agent returns message

return {

    "messages": [

        AIMessage(

            content="Ticket created successfully",

            tool_calls=[...]

        )

    ]

}

Shared Memory#

Common Knowledge Base#

All agents share:

  • Conversation context (email, user_id)

  • Dialog stack

  • Messages history

State Accessibility#


# Agent can access entire state

def ticket_agent(state: AgenticState):

    # Access context

    user_id = state["user_id"]

    email = state.get("email")

    conversation_id = state["conversation_id"]



    # Access messages

    messages = state["messages"]



    # Access dialog stack

    dialog_state = state["dialog_state"]

Error Handling#

Retry logic#


@retry(max_attempts=3, backoff=exponential_backoff)

def create_ticket_with_retry(...):

    return create_ticket(...)

Graceful degradation#


def create_ticket_graceful(...):

    try:

        # Try full flow

        return create_ticket(...)

    except DatabaseError:

        # Fallback: only save to memory

        return {"ticket_id": "temp_123", "status": "pending_save"}

Communication Failure#

When agent cannot communicate, system will:

  1. Log error

  2. Return error message to user

  3. Optionally retry or escalate

Caching#

Cache tool call results to avoid repeated calls:


from functools import lru_cache



@lru_cache(maxsize=100)

def get_ticket_status(ticket_id: str):

    return db.query(Ticket).filter(Ticket.ticket_id == ticket_id).first()

Multi-Agent Pattern Comparison#

Detailed Comparison Table#

Criteria

ReAct Agent

Hierarchical Multi-Agent

Supervisor Pattern

Architecture

Single agent with reasoning loop

Multiple specialized agents with hierarchy

Central supervisor routing to specialists

Number of Agents

1 agent

3-10+ agents

1 supervisor + N workers

Complexity

Low - Simple

High - Complex

Medium

State Management

Simple state

Shared state + Dialog stack

Centralized state

Reasoning

Step-by-step with tools

Distributed reasoning

Supervisor decides routing

Tool Access

All tools available

Tools distributed by agent

Tools grouped by specialist

Scalability

Limited (1 agent)

Good (easy to add agents)

Good (add specialists)

Latency

Low

Medium-High

Medium

Cost

Low

High

Medium-High

Use Cases

Simple tasks, Q&A

Complex workflows, enterprises

Multi-domain support

Best For

- FAQ answering- Simple automation- Single-domain tasks

- Enterprise systems- Multi-step workflows- Department coordination

- Customer service- Multi-category support- Triage systems

PRACTICE:#

FPT Customer Chatbot System - Multi-Agent System#

1. System overview#

FPT customer chatbot system is designed with hierarchical multi-agent architecture, allowing users to interact with multiple specialized agents through a primary assistant. The system supports: FAQ about FPT policies, support ticket management, IT support, and meeting room booking.

REFERENCE: https://langchain-ai.github.io/langgraph/tutorials/customer-support/customer-support/

REQUIREMENT: REFERENCE THIS LINK

2. Context Injection#

2.1. User Information#

  • System must support automatic context injection from user information

  • Users can provide email when starting conversation (OPTIONAL)

  • This information is automatically injected into all related tool calls if provided

  • Email is stored in conversation context and can be used by all agents

2.2. Context Management#

  • Context is maintained throughout the entire conversation

  • Child agents can access and use context information from primary assistant

  • Context can be updated during conversation

3. FAQ Agent - RAG Tool for FPT Policy#

3.1. Functionality#

  • Answer questions about FPT policies and regulations

  • Search information from knowledge base about FPT-related issues

  • Support multiple languages (Vietnamese, English)

3.2. Requirements#

  • Agent must be able to understand questions and search for related information

  • Results must be accurate and have reference sources

  • Support semantic search to understand user intent

4. Ticket Support Agent#

4.1. Functionality#

Ticket Support Agent handles requests to create, track and update support tickets for IT or customer support issues.

4.2. Tools#

4.2.1. Create Ticket#

  • Purpose: Create new ticket for IT or customer support issues

  • Required information:

    • Ticket content (content)

    • Detailed description (description)

  • Optional information:

    • Email (automatically injected from context if available)

    • Customer name (customer_name)

    • Phone number (customer_phone)

  • Result: Create ticket with β€œPending” status and return ticket_id

4.2.2. Track Ticket#

  • Purpose: Track status and information of ticket

  • Input: ticket_id

  • Output: Complete ticket information including:

    • ticket_id

    • content

    • description

    • status (Pending, Resolving, Canceled, Finished)

    • customer_name

    • customer_phone

    • email (if available)

    • creation time

    • user_id

4.2.3. Update Ticket#

  • Purpose: Update information of existing ticket

  • Required input: ticket_id

  • Optional input:

    • content

    • description

    • customer_name

    • customer_phone

    • email (automatically injected from context if available)

  • Rule: Only update provided fields, keep other fields unchanged

4.3. Ticket Status Management#

  • Status: Pending β†’ Resolving β†’ Finished

  • Special status: Canceled (can be set from any status before Finished)

5. IT Support Agent#

5.1. Functionality#

IT Support Agent helps users resolve technical issues with computers and electronic devices through Tavily search tool.

6. Booking Agent#

6.1. Functionality#

Booking Agent handles meeting room booking for users, similar to Appointment Agent but specifically for room booking.

6.2. Tools#

6.2.1. Book Room#

  • Purpose: Book meeting room

  • Required information:

    • Reason for booking (reason)

    • Time (time)

  • Optional information:

    • Email (automatically injected from context if available)

    • Booker name (customer_name)

    • Phone number (customer_phone)

    • Note (note)

  • Result: Create booking with β€œScheduled” status and return booking_id

6.2.2. Track Booking#

  • Purpose: Track booking information and status

  • Input: booking_id

  • Output: Complete booking information

6.2.3. Update Booking#

  • Purpose: Update booking information

  • Required input: booking_id

  • Optional input: reason, time, customer_name, customer_phone, note, email

  • Rule: Only update provided fields

6.2.4. Cancel Booking#

  • Purpose: Cancel booking

  • Input: booking_id

  • Rule: Can only cancel unfinished bookings

6.3. Booking Status Management#

  • Status: Scheduled β†’ Finished

  • Special status: Canceled

7. Primary Assistant#

7.1. Functionality#

Primary Assistant is the main agent that coordinates and routes requests to appropriate specialized agents.

7.2. Routing Requirements#

  • Analyze user intent

  • Route to FAQ Agent when questions about FPT policies

  • Route to Ticket Support Agent when user wants to create/track/update ticket

  • Route to IT Support Agent when user has troubleshooting issues

  • Route to Booking Agent when user wants to book room

  • Support returning to primary assistant after completing task in child agent

8. Database Schema#

8.2. Ticket Table#

Store support ticket information:

  • ticket_id (Primary Key): Unique ticket ID

  • content: Ticket content

  • description: Detailed description

  • customer_name: Customer name

  • customer_phone: Phone number

  • email: Email (optional, can be null)

  • time: Ticket creation time

  • status: Status (Pending, Resolving, Canceled, Finished)

8.3. Booking Table#

Store booking information:

  • booking_id (Primary Key): Unique booking ID

  • customer_name: Booker name

  • customer_phone: Phone number

  • email: Email (optional, can be null)

  • reason: Reason for booking

  • time: Booking time

  • note: Note (optional)

  • status: Status (Scheduled, Canceled, Finished)

8.4. Conversation Context Table#

Store conversation context:

  • conversation_id (Primary Key): Unique conversation ID

  • user_id (Foreign Key): Reference to Customer Info

  • email: Email from context (optional)

  • created_at: Conversation creation time

  • updated_at: Last update time

9. Business Logic Requirements#

9.1. Ticket Management#

  • New tickets always have β€œPending” status

  • Can only update tickets not in β€œFinished” or β€œCanceled” status

  • Email is OPTIONAL, can be injected from context or set to null

  • System still creates ticket successfully even without email

9.2. Booking Management#

  • New bookings always have β€œScheduled” status

  • Can only cancel bookings not in β€œFinished” status

  • Email is OPTIONAL, can be injected from context or set to null

  • System still creates booking successfully even without email

9.3. Context Management#

  • Email is stored in conversation context when user provides it

  • Tools automatically use email from context if not provided directly (if available in context)

  • Users can override email in tool call if desired

9.4. Agent Communication#

  • Primary assistant can transfer control to child agent

  • Child agent can return to primary assistant after completing task

  • Context is shared between all agents in the same conversation

10. ADVANCED - Advanced Features (OPTIONAL)#

10.1. Email Notification (OPTIONAL)#

  • Feature: Send email notification to users when there are changes to ticket or booking

  • Requirements:

    • Email is completely OPTIONAL in all tools related to ticket and booking

    • System only sends email if user provides email in context or in tool call

    • If no email, system still works normally and does not send notification

    • Email can be automatically injected from context if previously provided

    • Users can override email in each tool call if they want to use different email

10.2. Human-in-the-Loop Confirmation (OPTIONAL)#

  • Feature: User confirmation before performing important operations

  • Requirements:

    • Human-in-the-loop confirmation is completely OPTIONAL

    • System can operate with or without confirmation

    • If enabled, system will request confirmation before:

      • Creating new ticket

      • Creating new booking

      • Canceling ticket or booking

      • Updating important information

    • If not enabled, system will perform operations directly

    • Confirmation can be configured at conversation or tool level

10.3. Notes on Advanced Features#

  • All features in ADVANCED section are OPTIONAL

  • System must function fully even without email or human-in-the-loop confirmation

  • Implementation of these features does not affect core system functionality

  • Users can use system without providing email or without confirmation

12. Workflow#

12.1. Ticket Creation Flow#

  1. User can provide email (OPTIONAL - first time)

  2. Primary assistant receives ticket creation request

  3. Route to Ticket Support Agent

  4. Ticket Support Agent uses create_ticket tool

  5. Tool automatically injects email from context (if available in context)

  6. Create ticket in database (with or without email)

  7. Return ticket_id to user

12.2. Booking Flow#

  1. User requests room booking

  2. Primary assistant routes to Booking Agent

  3. Booking Agent collects information (reason, time)

  4. Use book_room tool with email from context (if available in context)

  5. Create booking in database (with or without email)

  6. Return booking_id to user

12.3. FAQ Flow#

  1. User asks about FPT policies

  2. Primary assistant routes to FAQ Agent

  3. FAQ Agent uses RAG tool to search

  4. Return information from knowledge base

12.4 IT Support Flow#

  1. User asks about technical issues

  2. Primary assistant routes to IT Support Agent

  3. IT Support Agent uses Tavily tool to search

  4. Return troubleshooting guide