Skip to main content

Overview

Meridian’s AI agents provide intelligent assistance for data analysis through natural language conversations. Powered by Google Gemini 2.5 Flash, agents can understand your questions, generate SQL queries, create visualizations, and provide insights.

Agent Types

Meridian offers two specialized agent modes:

Query Agent

Generates and executes SQL queries based on your questions. Best for:
  • Data exploration
  • Filtering and aggregations
  • Complex joins
  • Ad-hoc analysis

Analysis Agent

Performs deep data analysis and generates insights. Best for:
  • Statistical analysis
  • Trend identification
  • Data quality checks
  • Pattern discovery

Using the Agent

Starting a Conversation

The agent editor is located in the right sidebar:
<AgentEditor
  input={input}
  onInputChange={setInput}
  onExecute={handleExecute}
  isExecuting={isExecuting}
  messages={messages}
  mode="query" // or "analysis"
  onModeChange={setMode}
/>
Switch between agent modes using the segmented control:
<SegmentedControl
  value={mode}
  onChange={(value) => onModeChange(value as 'query' | 'analysis')}
  data={[
    { value: 'query', label: 'Query Agent' },
    { value: 'analysis', label: 'Analysis Agent' },
  ]}
/>

Message Flow

Conversations follow a structured message pair format:
interface Message {
  role: 'user' | 'assistant'
  content: string
  description?: string
  commands?: string[]          // SQL commands generated
  toolSteps?: ToolStep[]       // Tool usage trace
  mode?: 'query' | 'analysis'
  agentName?: string
  createdAt?: number
}

interface ToolStep {
  tool: string           // Tool name (e.g., "queryDuckDB")
  args: any             // Tool arguments
  result?: any          // Tool result
  finished: boolean     // Completion status
}

Tool Usage Visualization

The agent provides real-time visibility into its reasoning process:

Running Tools

When a tool is executing, it’s displayed with a glowing gradient:
<Box
  style={{
    background: 'linear-gradient(135deg, rgba(55, 114, 255, 0.1) 0%, rgba(138, 43, 226, 0.1) 100%)',
    border: '2px solid rgba(55, 114, 255, 0.3)',
    animation: 'toolPulse 2s ease-in-out infinite',
  }}
>
  <Group>
    <Box style={{ animation: 'toolPulseDot 1.5s ease-in-out infinite' }} />
    <Text>Running: {toolName}</Text>
  </Group>
</Box>

Completed Tools

Finished tools are collapsed and can be expanded to view details:
<Collapse in={!toolStepsCollapsed}>
  {toolSteps.map((step, idx) => (
    <Box key={idx}>
      <Text>{step.tool}</Text>
      <pre>{JSON.stringify(step.args, null, 2)}</pre>
      <pre>{JSON.stringify(step.result, null, 2)}</pre>
    </Box>
  ))}
</Collapse>

Agent Tools

The agent has access to several tools for data operations:
Execute SQL queries against DuckDB tables:
{
  tool: 'queryDuckDB',
  args: {
    query: 'SELECT * FROM sales WHERE amount > 1000'
  },
  result: {
    rows: [...],
    rowCount: 42
  }
}
Generate visualizations from query results:
{
  tool: 'createChart',
  args: {
    type: 'bar',
    data: [...],
    xAxisKey: 'month',
    yAxisKey: 'revenue'
  }
}
Produce statistical insights from data:
{
  tool: 'generateInsights',
  args: {
    tableName: 'sales',
    data: [...]
  },
  result: {
    insights: [/* AI-generated insights */]
  }
}
Retrieve table structure information:
{
  tool: 'getTableSchema',
  args: { tableName: 'customers' },
  result: {
    columns: [
      { name: 'id', type: 'INTEGER' },
      { name: 'name', type: 'VARCHAR' }
    ]
  }
}
See the full list in Agent Tools API Reference.

Conversation Threading

Manage multiple conversation threads:
<Select
  placeholder="Select a conversation"
  data={threads.map(thread => ({
    value: thread.agentThreadId,
    label: thread.title?.slice(0, 20) + '...',
  }))}
  value={selectedThreadId}
  onChange={onThreadSelect}
/>

<Button onClick={onCreateThread}>
  <IconPlus size={16} />
</Button>
Threads are stored with metadata:
interface AgentThreadSummary {
  agentThreadId: string
  title?: string | null
  lastMessageSummary?: string | null
  lastMessageAt?: number
  lastMode?: 'query' | 'analysis'
  agentName?: string | null
}

Command Queue

When the agent generates multiple SQL commands, they’re queued and executed sequentially:
{remainingCommands.length > 0 && (
  <Collapse in={queueExpanded}>
    <Stack>
      {remainingCommands.map((cmd, idx) => (
        <Box key={idx}>
          <Badge>{currentCommandIndex + 1 + idx}</Badge>
          <Text>{cmd.slice(0, 60)}...</Text>
        </Box>
      ))}
    </Stack>
  </Collapse>
)}
Progress indicator shows current position:
<Badge>
  {currentCommandIndex + 1} / {commandQueue.length}
</Badge>

Implementation Example

Here’s how the agent is integrated in a table view:
const [messages, setMessages] = useState<Message[]>([])
const [isExecuting, setIsExecuting] = useState(false)
const [input, setInput] = useState('')

const handleExecute = async () => {
  setIsExecuting(true)
  
  // Add user message
  const userMessage: Message = {
    role: 'user',
    content: input,
  }
  setMessages(prev => [...prev, userMessage])
  
  try {
    // Call agent API
    const response = await runAgent({
      prompt: input,
      tableName: currentTable,
      mode: agentMode,
    })
    
    // Add assistant message with tool steps
    const assistantMessage: Message = {
      role: 'assistant',
      content: response.content,
      commands: response.commands,
      toolSteps: response.toolSteps,
    }
    setMessages(prev => [...prev, assistantMessage])
  } finally {
    setIsExecuting(false)
    setInput('')
  }
}

Keyboard Shortcuts

The agent editor supports keyboard shortcuts for efficiency:
useEffect(() => {
  const handler = (e: KeyboardEvent) => {
    if (
      document.activeElement === textareaRef.current &&
      (e.ctrlKey || e.metaKey) &&
      e.key === 'Enter'
    ) {
      e.preventDefault()
      onExecute()
    }
  }
  window.addEventListener('keydown', handler)
  return () => window.removeEventListener('keydown', handler)
}, [onExecute])
Press Ctrl+Enter (or Cmd+Enter on Mac) to send your message

Advanced Tips

Effective Prompting

Instead of: “Show me sales data”Try: “Show me the top 10 products by revenue in Q4 2024, grouped by category”
The agent has access to your table schema and sample data. You can reference:
  • Column names: “Filter by the status column”
  • Table relationships: “Join with the customers table”
  • Data patterns: “Find outliers in the price column”
Build on previous responses:
  • “Now filter those results by region”
  • “Create a chart from that query”
  • “What insights can you find in this data?”

Performance Optimization

The agent uses context trimming to stay within token limits:
const GEMINI_PROMPT_LIMIT = 8000

function trimToLimit(str: string, limit = GEMINI_PROMPT_LIMIT) {
  if (str.length <= limit) return str
  const keep = Math.floor((limit - 200) / 2)
  return (
    str.slice(0, keep) +
    '\n\n... [TRUNCATED FOR LENGTH] ...\n\n' +
    str.slice(str.length - keep - 200)
  )
}
This ensures fast response times while preserving important context (from /home/daytona/workspace/source/convex/table_agent.ts:35).

Error Handling

The agent provides clear error messages:
{error && (
  <Alert
    color="red"
    title="Error"
    onClose={onErrorClose}
    withCloseButton
  >
    <Text size="xs">{error}</Text>
  </Alert>
)}
Common error scenarios:
  • Invalid SQL syntax
  • Table or column not found
  • API rate limits
  • Network connectivity issues

Next Steps

Query Editor

Write SQL queries directly for more control

Chart Visualization

Create and customize data visualizations

Build docs developers (and LLMs) love