AG-UI: When an agent is interacting with your Frontend

Sylvain DEDIEU

Frontend engineer – #ui-foundations 🛸

Sylvain DEDIEU

Frontend engineer – #ui-foundation 🛸

         s.dedieu@criteo.com

         @sdedieu.bsky.social

         s.dedieu@criteo.com

Sylvain Dedieu

Agenda

1

2

3

4

What is and why AG-UI ?

How does it work under the hood ?

Live coding demo

Real use case example in Criteo

Get the presentation slides

🤳

1

2

3

4

What is and why AG-UI ?

How does it work under the hood ?

Live coding demo

Real use case example in Criteo

What is and why AG-UI ?

The AG-UI protocol

AG-UI is an open, lightweight, event-based protocol that standardizes how AI agents connect to user-facing applications.

Concepts

  • Application: User-facing apps (i.e. chat or any AI-enabled application).
  • AG-UI Client: Generic communication clients like HttpAgent or specialized clients for connecting to existing protocols.
  • Agents: Backend AI agents that process requests and generate streaming responses.
  • Secure Proxy: Backend services that provide additional capabilities and act as a secure proxy.

Demo time !

1

2

3

4

What is and why AG-UI ?

How does it work under the hood ?

Live coding demo

Real use case example in Criteo

How does it work under the hood ?

Core principles

  1. Event-Driven Communication: Agents need to emit standardized event types during execution, creating a stream of updates that clients can process.
  2. Bidirectional Interaction: Agents accept input from users, enabling collaborative workflows where humans and AI work together seamlessly.
  • Flexible Event Structure: Events don’t need to match AG-UI’s format exactly—they just need to be AG-UI-compatible.
  • Transport Agnostic: AG-UI supports various transport mechanisms including Server-Sent Events (SSE), webhooks, WebSockets, and more.

Protocol Flexibility

Built-in Middlewares

Events

CategoryDescription
Lifecycle EventsMonitor the progression of agent runs
Text Message EventsHandle streaming textual content
Tool Call EventsManage tool executions by agents
State management EventsSynchronize state between agents and UI
Activity EventsRepresent ongoing activity progress
Subagent EventsTrack subagents and attribute their output
Special EventsSupport custom functionality
Draft EventsProposed events under development

Lifecycle Events

Lifecycle events provide crucial structure to agent runs, enabling frontends to track progress, manage UI states appropriately, and handle errors gracefully.

This streaming approach enables real-time display of message content as it’s generated, creating a more responsive user experience compared to waiting for the entire message to be complete before showing anything.

Text Message Events

Middleware implementation

io.on('connection', (socket) => {
  console.log('✅ Client connected');
  socket.on('chat-message', async (message: Message) => {
  
  // 1. Formalize input message, create run ids

  // 2. Emit RUN_STARTED event

  // 3. Call openai chat stream mode
  
  // 4. For every chunk reponses send the right event
  
  // 4-a. If chunk has content text, emit TEXT_MESSAGE_CHUNK event
  
  // 4-b. If chunk has toolCalls, emit TOOL_CALL_CHUNK event
  
  // 5. Emit RUN_FINISHED event
  
  // 6. On error emit RUN_ERROR event
  });

  socket.on('disconnect', () => {
    console.log('❌ Client disconnected');
  });
});

Middleware implementation

io.on('connection', (socket) => {
  console.log('✅ Client connected');
  socket.on('chat-message', async (message: Message) => {
    console.log('📩 User:', message);
    
    // 1. Formalize input message, create run ids
    const input: RunAgentInput = {
      threadId: generateUUID(),
      runId: generateUUID(),
      messages: [
        {
          content: message.content,
          role: message.role,
          id: message.messageId,
        },
      ],
      tools: [...],
      context: [],
    };

    // 2. Emit RUN_STARTED event
    socket.emit('event', {
      type: EventType.RUN_STARTED,
      threadId: input.threadId,
      runId: input.runId,
    });

    try {
      // 3. Call openai chat stream mode
      const completions = await openai.chat.completions.create({
        model: 'gpt-4o',
        tools: input.tools.map((tool) => ({
          type: 'function',
          function: {
            name: tool.name,
            description: tool.description,
            parameters: tool.parameters,
          },
        })),
        messages: input.messages.map((msg) => ({
          role: msg.role as 'user' | 'system' | 'assistant' | 'developer',
          content: (msg.content as string) ?? '',
        })),
        stream: true,
      });

      const messageId = generateUUID();
      let toolCallId: string | undefined = undefined;

      for await (const chunk of completions) {
        // 4. For every chunk reponses send the right event
        const content = chunk.choices[0]?.delta?.content;
        const toolCalls = chunk.choices[0]?.delta?.tool_calls;

        if (content) {
          // 4-a. If chunk has content text, emit TEXT_MESSAGE_CHUNK event
          socket.emit('event', {
            type: EventType.TEXT_MESSAGE_CHUNK,
            messageId,
            delta: chunk.choices[0]?.delta?.content || '',
          });
        } else if (toolCalls) {
          // 4-b. If chunk has toolCalls, emit TOOL_CALL_CHUNK event
          const toolCall = toolCalls[0];
          if (toolCall?.id) toolCallId = toolCall.id;
          socket.emit('event', {
            type: EventType.TOOL_CALL_CHUNK,
            parentMessageId: messageId,
            toolCallId,
            toolCallName: toolCall?.function?.name,
            delta: toolCall.function?.arguments,
          } as ToolCallChunkEvent);
        }
      }

      // 5. ✅ Emit RUN_FINISHED event
      socket.emit('event', {
        type: EventType.RUN_FINISHED,
        threadId: input.threadId,
        runId: input.runId,
      });
    } catch (error: any) {
      // 6. ❌ On error emit RUN_ERROR event
      console.error('❌ OpenAI Error:', error);
      socket.emit('event', {
        type: EventType.RUN_ERROR,
        message: error?.message,
      });
    }
  });

  socket.on('disconnect', () => {
    console.log('❌ Client disconnected');
  });
});

1

2

3

4

What is and why AG-UI ?

How does it work under the hood ?

Live coding demo

Real use case example in Criteo

Live coding demo

With SSR

Easier as there is no need to duplicate the state

STATE 

FORM 

CHAT SERVER 

Demo time !

With CSR

Need to duplicate and sync the state both in the Frontend and the Backend

STATE 

FORM 

CHAT SERVER 

BACKEND STATE 

Demo time !

1

2

3

4

What is and why AG-UI ?

How does it work under the hood ?

Live coding demo

Real use case example in Criteo

Real use case example in Criteo

Thank You !

Sylvain DEDIEU

Frontend engineer – #ui-foundation 🛸

         s.dedieu@criteo.com

         @sdedieu.bsky.social

         s.dedieu@criteo.com

Sylvain Dedieu