Skip to main content

Architecture Decision: Agent Framework Options (Updated with AG-UI)

Date: October 3, 2025 Status: 🚨 DECISION REQUIRED Impact: HIGH - Affects entire agent implementation strategy

Executive Summary

Current State: All agent tools are non-functional due to OpenAI schema validation errors in the Vercel AI SDK’s Zod-to-JSON-Schema converter. NEW DISCOVERY: AG-UI protocol supports staying in TypeScript while getting advanced agent features. Decision Required: Choose between:
  1. Quick Fix - Rewrite tools with explicit JSON Schema (3-5 days)
  2. AG-UI + TypeScript - Add protocol layer, stay in TypeScript (1 week) ⭐ NEW
  3. AG-UI + LangGraph + Python - Full Python backend (2-3 weeks)

Current Issues with Vercel AI SDK

Root Cause: Broken Zod-to-JSON-Schema Conversion

The Vercel AI SDK’s internal schema converter produces invalid schemas that OpenAI rejects:
Invalid schema for function 'X': schema must be a JSON Schema of 'type: "object"', got 'type: "None"'

Problematic Patterns (ALL FAIL):

  1. .optional() on nested objects
  2. .partial() modifier
  3. .passthrough() modifier
  4. z.union([complexObject, z.null()]) for optional complex types
  5. ❌ Objects with all optional fields

Tools Affected (10 total):

Complex Tools:
  • update_workspace_context - Workspace focus management
  • record_agent_draft - Draft change proposals
  • emit_agent_insight - Insight notifications
  • apply_agent_draft - Apply approved drafts
Spreadsheet Tools:
  • spreadsheet_set_cell, spreadsheet_get_cell, spreadsheet_set_formula
  • spreadsheet_select_range, spreadsheet_insert_row, spreadsheet_analyze

Option 1: Fix Vercel AI SDK (Quick Workaround)

Approach

Rewrite all tool schemas using explicit JSON Schema with jsonSchema() from the ‘ai’ package:
import { jsonSchema } from 'ai';
import { tool } from 'ai';

const tools = {
  spreadsheet_set_cell: tool({
    description: 'Set a cell value in the active spreadsheet',
    parameters: jsonSchema<SpreadsheetSetCellInput>({
      type: 'object',
      properties: {
        row: { type: 'number', minimum: 0, description: 'Row index (0-based)' },
        col: { type: 'number', minimum: 0, description: 'Column index (0-based)' },
        value: { description: 'Cell value - can be string, number, boolean, or formula' },
        sheetId: { type: 'string', description: 'Sheet ID (optional)' },
      },
      required: ['row', 'col', 'value'],
      additionalProperties: false,
    }),
    execute: async (params) => {
      return await univerAgentBridge.executeCommand({
        action: 'setCellValue',
        params,
      });
    },
  }),
};

Pros ✅

  • Fastest implementation - 3-5 days
  • Minimal architectural change - Keep existing Next.js/Vercel infrastructure
  • Known framework - Team already familiar with Vercel AI SDK
  • Type safety - Can maintain TypeScript types alongside JSON Schema

Cons ❌

  • Manual schema maintenance - Must maintain Zod AND JSON Schema separately
  • Verbose syntax - JSON Schema is more verbose than Zod
  • Future vulnerability - Vercel AI SDK may have other conversion issues
  • No advanced features - No state graphs, validation nodes, retry logic
  • Technical debt - Workaround for a fundamental SDK issue

Effort Estimate

  • Schema rewrite: 2 days (10 tools × ~2 hours each with testing)
  • Testing & validation: 1 day
  • Documentation: 1 day
  • Total: 3-5 days

What is AG-UI?

AG-UI (Agent-User Interaction Protocol) is a framework-agnostic protocol for building collaborative agent experiences with bidirectional state synchronization. Key Discovery: You can use AG-UI in TypeScript without switching to Python!

Architecture

┌─────────────────────────────────────────────────────┐
│                   Next.js Frontend                  │
│  - React components with AG-UI client (@ag-ui/client) │
│  - Bidirectional state sync                         │
└─────────────────┬───────────────────────────────────┘

                  │ SSE/WebSocket (AG-UI Data Stream Protocol)

┌─────────────────▼───────────────────────────────────┐
│           Next.js API Route (TypeScript)            │
│  - AG-UI core (@ag-ui/core)                         │
│  - Tool definitions with JSON Schema (no Zod!)      │
│  - Vercel AI SDK for LLM streaming                  │
│  - Emit AG-UI protocol events via Data Stream       │
└─────────────────┬───────────────────────────────────┘

                  │ Supabase client

┌─────────────────▼───────────────────────────────────┐
│              Supabase Database                      │
│  (Existing schema - no changes needed)              │
└─────────────────────────────────────────────────────┘

Implementation Example

1. Install AG-UI TypeScript SDK

npm install @ag-ui/core @ag-ui/client

2. Define Tools with AG-UI (Backend)

import { Tool, EventType } from "@ag-ui/core";
import { createDataStream } from "ai";

// Define tools using AG-UI Tool type (avoids Zod entirely!)
const spreadsheetTools: Tool[] = [
  {
    name: "spreadsheet_set_cell",
    description: "Set a cell value in the active spreadsheet",
    parameters: {
      type: "object",
      properties: {
        row: { type: "number", minimum: 0 },
        col: { type: "number", minimum: 0 },
        value: {}, // Any type
        sheetId: { type: "string" }
      },
      required: ["row", "col", "value"]
    }
  },
  // ... other tools
];

// API Route: POST /api/agent
export async function POST(req: Request) {
  const { messages } = await req.json();

  const stream = createDataStream({
    execute: async (dataStream) => {

      // Emit AG-UI state update events
      dataStream.writeData({
        type: EventType.STATE_UPDATE,
        delta: { activeSheet: "sheet-1", focusMode: "sheet" }
      });

      // Use Vercel streamText with JSON Schema tools
      const result = streamText({
        model: openai('gpt-4o'),
        messages,
        tools: convertAGUIToolsToVercel(spreadsheetTools), // Convert once
        onToolCall: async ({ toolCall }) => {
          // Execute tool
          const result = await executeSpreadsheetTool(toolCall);

          // Emit AG-UI tool result event
          dataStream.writeData({
            type: EventType.TOOL_CALL_RESULT,
            toolCallId: toolCall.id,
            result
          });

          return result;
        }
      });

      // Merge LLM stream into data stream
      result.mergeIntoDataStream(dataStream);
    }
  });

  return stream.toDataStreamResponse();
}

// Helper: Convert AG-UI tools to Vercel format
function convertAGUIToolsToVercel(aguiTools: Tool[]) {
  return aguiTools.reduce((acc, t) => ({
    ...acc,
    [t.name]: tool({
      description: t.description,
      parameters: jsonSchema(t.parameters), // Use jsonSchema() to avoid Zod
      execute: async (params) => executeSpreadsheetTool({ name: t.name, params })
    })
  }), {});
}

3. Frontend Integration with AG-UI

import { useAGUIClient } from "@ag-ui/client";
import { EventType } from "@ag-ui/core";

function AgentConsole() {
  const { state, messages, sendMessage, subscribe } = useAGUIClient({
    url: "/api/agent"
  });

  // Subscribe to state updates
  useEffect(() => {
    const unsubscribe = subscribe(EventType.STATE_UPDATE, (event) => {
      // Update local UI state when agent changes it
      setWorkspaceContext(event.delta);
    });
    return unsubscribe;
  }, []);

  // Send message (agent can update state in response)
  const handleSubmit = async (message: string) => {
    await sendMessage(message);
  };

  return (
    <div>
      <div>Active Sheet: {state.activeSheet}</div>
      <ChatMessages messages={messages} />
      <ChatInput onSubmit={handleSubmit} />
    </div>
  );
}

Pros ✅

  • Stay in TypeScript - No Python needed ✅
  • Avoid Zod issues - Use AG-UI Tool type with JSON Schema directly ✅
  • Bidirectional state sync - Agent and UI share state via AG-UI protocol ✅
  • Keep Next.js infrastructure - No new backend service ✅
  • Framework agnostic - Can migrate to other backends later ✅
  • Event-driven architecture - Clean separation of concerns ✅
  • Vercel AI SDK compatibility - Use Data Stream Protocol ✅

Cons ❌

  • New protocol to learn - AG-UI is relatively new ⚠️
  • Manual tool conversion - Need helper to convert AG-UI → Vercel format ⚠️
  • Still manual schemas - JSON Schema is verbose (but only defined once) ⚠️
  • Limited advanced features - No state graphs/validation nodes (Python has this) ⚠️

Effort Estimate

  • Install AG-UI SDK: 1 hour
  • Setup Data Stream with AG-UI events: 1 day
  • Rewrite 10 tools with AG-UI Tool type: 2 days
  • Frontend integration with AG-UI client: 1 day
  • Testing & debugging: 1 day
  • Total: ~1 week

Option 3: AG-UI + LangGraph + Python

Architecture

┌─────────────────────────────────────────────────────┐
│                   Next.js Frontend                  │
│  - React components with AG-UI client               │
│  - Bidirectional state sync                         │
└─────────────────┬───────────────────────────────────┘

                  │ WebSocket (AG-UI protocol)

┌─────────────────▼───────────────────────────────────┐
│              FastAPI Backend (Python)               │
│  - LangGraph agent workflows                        │
│  - Pydantic tool schemas (no conversion issues!)    │
│  - AG-UI protocol adapter (@ag-ui/langgraph)        │
│  - State management & persistence                   │
└─────────────────┬───────────────────────────────────┘

                  │ Supabase client

┌─────────────────▼───────────────────────────────────┐
│              Supabase Database                      │
│  (Existing schema - no changes needed)              │
└─────────────────────────────────────────────────────┘

Implementation Example

from langgraph.graph import StateGraph
from ag_ui.langgraph import LangGraphAdapter
from pydantic import BaseModel

# Define tool schemas with Pydantic (no conversion issues!)
class SpreadsheetSetCellInput(BaseModel):
    row: int
    col: int
    value: Any
    sheetId: Optional[str] = None

@tool
def spreadsheet_set_cell(input: SpreadsheetSetCellInput):
    """Set a cell value in the active spreadsheet"""
    return execute_spreadsheet_tool(input)

# LangGraph state machine
graph = StateGraph(AgentState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
graph.add_edge("agent", "tools")
graph.add_edge("tools", "agent")

# AG-UI protocol wrapper
adapter = LangGraphAdapter(graph)
app = adapter.create_fastapi_app()

Pros ✅

  • No schema conversion issues - Pydantic is battle-tested ✅
  • Advanced state management - State graphs, checkpointing, time-travel ✅
  • Built-in validation - Validation nodes with retry logic ✅
  • Production-ready - LangChain/LangGraph used by thousands ✅
  • Better debugging - LangSmith observability ✅
  • AG-UI protocol - Bidirectional state sync ✅
  • Python ecosystem - Access to ML/data libraries ✅

Cons ❌

  • Longest timeline - 2-3 weeks ❌
  • New stack - Team needs to learn Python/FastAPI/LangGraph ❌
  • Deployment complexity - Need to deploy Python backend ❌
  • Dual codebase - Next.js + FastAPI ❌

Effort Estimate

  • FastAPI setup + LangGraph config: 3 days
  • Tool migration (10 tools): 5 days
  • AG-UI protocol integration: 2 days
  • Frontend integration: 3 days
  • Testing & debugging: 2 days
  • Total: 2-3 weeks

Decision Matrix

CriteriaOption 1: Vercel FixOption 2: AG-UI + TSOption 3: AG-UI + Python
Implementation Time3-5 days ✅~1 week ✅2-3 weeks ❌
Stay in TypeScriptYes ✅Yes ✅No ❌
Schema ReliabilityWorkaround ⚠️Solid ✅Battle-tested ✅
Bidirectional StateManual ❌AG-UI ✅AG-UI ✅
State GraphsNo ❌No ❌Yes ✅
Validation NodesNo ❌No ❌Yes ✅
Team Learning CurveNone ✅Small ✅Significant ❌
DeploymentExisting ✅Existing ✅New service ❌
Future ScalabilityLimited ⚠️Good ✅Excellent ✅
Technical DebtHigh ❌Low ✅None ✅

Recommendation: Option 2 (AG-UI + TypeScript) ⭐

Why This is the Sweet Spot

  1. Solves the schema problem - Use AG-UI Tool type with JSON Schema, avoid Zod entirely
  2. Stays in TypeScript - No Python backend needed
  3. Gets modern features - Bidirectional state sync via AG-UI protocol
  4. Quick to implement - ~1 week vs 2-3 weeks for Python
  5. Future-proof - Can migrate to LangGraph later if needed (AG-UI supports both)
  6. Low risk - Keep existing Next.js infrastructure

Migration Path

Week 1: AG-UI + TypeScript Implementation

Day 1: Setup
  • Install @ag-ui/core and @ag-ui/client
  • Setup Data Stream Protocol in /api/agent
  • Emit basic AG-UI events (STATE_UPDATE)
Day 2-3: Tool Migration
  • Rewrite 10 tools using AG-UI Tool type (JSON Schema)
  • Create helper to convert AG-UI tools → Vercel format
  • Test each tool individually
Day 4: Frontend Integration
  • Replace useChat with AG-UI client hooks
  • Subscribe to STATE_UPDATE events
  • Update UI components to use shared state
Day 5: Testing & Polish
  • End-to-end testing
  • Fix any edge cases
  • Documentation

Next Steps (If Choosing Option 2)

  1. I’ll install AG-UI TypeScript SDK packages
  2. Create a proof of concept with 1 tool (spreadsheet_set_cell)
  3. Show bidirectional state sync working
  4. Once proven, migrate remaining 9 tools
  5. Update frontend to use AG-UI client

Alternative Paths

If Python is Acceptable: Option 3

  • Best long-term architecture
  • Advanced state management
  • Production-ready from day 1
  • Worth the 2-3 week investment

If Need Quick Win: Option 1

  • Get 3 spreadsheet tools working in 2 days with jsonSchema()
  • Accept technical debt
  • Plan proper migration for next sprint

User Context Alignment

Based on your interest in AG-UI protocol and the question “doesn’t have to be langgraph”: Option 2 (AG-UI + TypeScript) perfectly aligns with your needs:
  • Get AG-UI protocol benefits ✅
  • Stay in TypeScript (no Python) ✅
  • Bidirectional state sync ✅
  • Reasonable timeline (~1 week) ✅
  • Future flexibility (can migrate to LangGraph later) ✅

Resources

AG-UI Protocol (TypeScript)

Vercel AI SDK Integration

AG-UI + LangGraph (Python)


Decision Owner: Product/Engineering Lead Recommended Decision: Option 2 (AG-UI + TypeScript) Timeline: Decide within 24 hours to maintain momentum