Introduction
Core Concepts
Full Stack Agents
Frontend & UI
Platform & Tools
Framework Examples
Deployment
Tutorials
Resources
Vercel AI SDK Chatbot
Build AI-powered chatbots that integrate with Kubiya workflow orchestration
Vercel AI SDK Integration
Build AI-powered chatbots that can discover and execute workflows through natural language using the Vercel AI SDK and Kubiya’s ADK orchestration servers.
What You’ll Build
A working chatbot that can:
- 🔍 Automatically discover running ADK orchestration servers
- 💬 Chat with AI models for general questions
- 🚀 Execute workflows through natural language
- 📊 Stream real-time responses from workflow execution
Quick Setup (5 minutes)
1. Create Next.js Project
npx create-next-app@latest kubiya-chatbot --typescript --tailwind --app
cd kubiya-chatbot
npm install ai @ai-sdk/openai lucide-react
2. Environment Variables
# .env.local
OPENAI_API_KEY=your_openai_api_key
KUBIYA_API_KEY=your_kubiya_api_key
ORCHESTRATION_SERVER_URL=http://localhost:8001
3. Server Discovery Client
// lib/server-discovery.ts
export interface OrchestrationServer {
id: string;
name: string;
endpoint: string;
provider: string;
isHealthy: boolean;
capabilities: {
streaming: boolean;
modes: string[];
orchestration: boolean;
mcp_support: boolean;
};
models: Array<{
id: string;
name: string;
provider: string;
}>;
}
export class ServerDiscovery {
private servers: OrchestrationServer[] = [];
async discoverServers(serverUrls: string[]): Promise<OrchestrationServer[]> {
const discovered: OrchestrationServer[] = [];
for (const url of serverUrls) {
try {
console.log(`🔍 Discovering server at ${url}`);
const response = await fetch(`${url}/discover`);
if (!response.ok) {
console.warn(`❌ Server at ${url} returned ${response.status}`);
continue;
}
const data = await response.json();
const server: OrchestrationServer = {
id: data.server.id,
name: data.server.name,
endpoint: url,
provider: data.server.provider,
isHealthy: data.health.status === 'healthy',
capabilities: data.server.capabilities,
models: data.models || []
};
discovered.push(server);
console.log(`✅ Discovered ${server.name} (${server.provider})`);
} catch (error) {
console.warn(`❌ Failed to discover server at ${url}:`, error);
}
}
this.servers = discovered;
return discovered;
}
getHealthyServers(): OrchestrationServer[] {
return this.servers.filter(s => s.isHealthy);
}
getServer(id: string): OrchestrationServer | undefined {
return this.servers.find(s => s.id === id);
}
}
4. Chat API Route
// app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
interface ChatRequest {
messages: Array<{
role: 'user' | 'assistant' | 'system';
content: string;
}>;
selectedServer?: string;
workflowMode?: 'plan' | 'act';
}
// Initialize server discovery
const serverUrls = [
process.env.ORCHESTRATION_SERVER_URL || 'http://localhost:8001',
'http://localhost:8002' // Add more servers as needed
];
async function executeWorkflow(
serverUrl: string,
message: string,
mode: 'plan' | 'act' = 'plan'
) {
const response = await fetch(`${serverUrl}/compose`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.KUBIYA_API_KEY}`
},
body: JSON.stringify({
messages: [{ role: 'user', content: message }],
prompt: message,
mode,
conversationId: `chat-${Date.now()}`,
model: 'deepseek-ai/DeepSeek-V3'
})
});
if (!response.ok) {
throw new Error(`Workflow execution failed: ${response.statusText}`);
}
return response;
}
export async function POST(request: Request) {
try {
const { messages, selectedServer, workflowMode }: ChatRequest = await request.json();
const latestMessage = messages[messages.length - 1];
// Check if this is a workflow request
const isWorkflowRequest = selectedServer && (
latestMessage.content.toLowerCase().includes('workflow') ||
latestMessage.content.toLowerCase().includes('create') ||
latestMessage.content.toLowerCase().includes('execute') ||
latestMessage.content.toLowerCase().includes('deploy') ||
workflowMode
);
if (isWorkflowRequest) {
console.log(`🚀 Executing workflow on server: ${selectedServer}`);
try {
// Find the server URL
const serverUrl = serverUrls.find(url => url.includes('8001')) || serverUrls[0];
// Execute workflow and return streaming response
const workflowResponse = await executeWorkflow(
serverUrl,
latestMessage.content,
workflowMode || 'plan'
);
// Return the streaming response directly
return new Response(workflowResponse.body, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
} catch (error: any) {
console.error('❌ Workflow execution failed:', error);
// Return error as streaming response
const errorStream = new ReadableStream({
start(controller) {
const encoder = new TextEncoder();
const errorMessage = `❌ **Workflow failed:** ${error.message}`;
controller.enqueue(encoder.encode(`0:"${errorMessage}"\n`));
controller.close();
}
});
return new Response(errorStream, {
headers: {
'Content-Type': 'text/event-stream'
}
});
}
}
// Regular AI chat
const result = await streamText({
model: openai('gpt-4'),
messages,
system: `You are an AI assistant that can help with various tasks including workflow orchestration.
When users ask about workflows, automation, deployments, or task execution, suggest they select an orchestration server and set the workflow mode to execute their requests.
Available workflow modes:
- **plan**: Generate a workflow without executing it
- **act**: Generate and execute the workflow
Be helpful and suggest specific workflow requests like:
- "Create a deployment workflow"
- "Set up a backup process"
- "Deploy my application to staging"`
});
return result.toAIStreamResponse();
} catch (error) {
console.error('💥 Chat API error:', error);
return new Response(
JSON.stringify({ error: 'Internal server error' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
}
5. Chat Interface Component
// components/chat-interface.tsx
'use client';
import { useState, useEffect } from 'react';
import { useChat } from 'ai/react';
import { Send, Server, Zap, RefreshCw } from 'lucide-react';
interface ServerOption {
id: string;
name: string;
provider: string;
isHealthy: boolean;
}
export function ChatInterface() {
const [servers, setServers] = useState<ServerOption[]>([]);
const [selectedServer, setSelectedServer] = useState<string>('');
const [workflowMode, setWorkflowMode] = useState<'plan' | 'act'>('plan');
const [isDiscovering, setIsDiscovering] = useState(false);
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
body: {
selectedServer,
workflowMode
}
});
// Discover servers on component mount
const discoverServers = async () => {
setIsDiscovering(true);
try {
// In a real app, you'd call your server discovery API
// For this example, we'll simulate discovering the local ADK server
const serverUrls = ['http://localhost:8001', 'http://localhost:8002'];
const discovered: ServerOption[] = [];
for (const url of serverUrls) {
try {
const response = await fetch(`${url}/discover`);
if (response.ok) {
const data = await response.json();
discovered.push({
id: data.server.id,
name: data.server.name,
provider: data.server.provider,
isHealthy: data.health.status === 'healthy'
});
}
} catch {
// Server not available
}
}
setServers(discovered);
// Auto-select first healthy server
const healthy = discovered.find(s => s.isHealthy);
if (healthy && !selectedServer) {
setSelectedServer(healthy.id);
}
} catch (error) {
console.error('Discovery failed:', error);
} finally {
setIsDiscovering(false);
}
};
useEffect(() => {
discoverServers();
}, []);
return (
<div className="flex flex-col h-screen bg-gray-50">
{/* Header with Server Selection */}
<div className="bg-white border-b border-gray-200 p-4">
<div className="max-w-4xl mx-auto">
<div className="flex items-center justify-between mb-4">
<div>
<h1 className="text-xl font-semibold text-gray-900">
Kubiya Workflow Assistant
</h1>
<p className="text-sm text-gray-600">
Chat with AI or execute workflows through orchestration servers
</p>
</div>
<button
onClick={discoverServers}
disabled={isDiscovering}
className="flex items-center gap-2 px-3 py-2 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
>
<RefreshCw className={`w-4 h-4 ${isDiscovering ? 'animate-spin' : ''}`} />
{isDiscovering ? 'Discovering...' : 'Refresh Servers'}
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Server Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Orchestration Server
</label>
<select
value={selectedServer}
onChange={(e) => setSelectedServer(e.target.value)}
className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
>
<option value="">None (AI Chat Only)</option>
{servers.map(server => (
<option key={server.id} value={server.id}>
{server.name} ({server.provider})
{server.isHealthy ? ' ✅' : ' ❌'}
</option>
))}
</select>
</div>
{/* Workflow Mode */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Workflow Mode
</label>
<select
value={workflowMode}
onChange={(e) => setWorkflowMode(e.target.value as 'plan' | 'act')}
disabled={!selectedServer}
className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100"
>
<option value="plan">Plan (Generate Only)</option>
<option value="act">Act (Generate & Execute)</option>
</select>
</div>
{/* Status */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Status
</label>
<div className="flex items-center gap-2 p-2 bg-gray-50 rounded-md">
{selectedServer ? (
<>
<Zap className="w-4 h-4 text-green-500" />
<span className="text-sm">Workflow Mode: {workflowMode}</span>
</>
) : (
<>
<Server className="w-4 h-4 text-gray-400" />
<span className="text-sm text-gray-500">AI Chat Mode</span>
</>
)}
</div>
</div>
</div>
{servers.length === 0 && !isDiscovering && (
<div className="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded-md">
<p className="text-yellow-800 text-sm">
⚠️ No orchestration servers found. Make sure your ADK server is running on port 8001.
</p>
</div>
)}
</div>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4">
<div className="max-w-4xl mx-auto space-y-4">
{messages.length === 0 && (
<div className="text-center text-gray-500 mt-8">
<div className="mb-4">
<Zap className="w-12 h-12 mx-auto text-blue-500" />
</div>
<h3 className="text-lg font-medium mb-2">
Welcome to Kubiya Workflow Assistant
</h3>
<p className="text-sm mb-4">
Ask questions or describe workflows you'd like to create
</p>
{servers.length > 0 && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 max-w-md mx-auto">
<p className="text-blue-800 text-sm font-medium mb-2">
🚀 {servers.filter(s => s.isHealthy).length} server(s) available!
</p>
<p className="text-blue-700 text-sm">
Try: "Create a hello world workflow" or "Deploy my application"
</p>
</div>
)}
</div>
)}
{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-3xl px-4 py-2 rounded-lg ${
message.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-white border border-gray-200 shadow-sm'
}`}
>
<div className="whitespace-pre-wrap">{message.content}</div>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-white border border-gray-200 rounded-lg px-4 py-2 shadow-sm">
<div className="flex items-center space-x-2">
<div className="animate-spin w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full" />
<span className="text-gray-600">
{selectedServer ? 'Executing workflow...' : 'Thinking...'}
</span>
</div>
</div>
</div>
)}
</div>
</div>
{/* Input */}
<div className="bg-white border-t border-gray-200 p-4">
<div className="max-w-4xl mx-auto">
<form onSubmit={handleSubmit} className="flex space-x-2">
<input
value={input}
onChange={handleInputChange}
placeholder={
selectedServer
? `Describe a workflow to ${workflowMode}...`
: "Ask me anything..."
}
className="flex-1 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center"
>
<Send className="w-5 h-5" />
</button>
</form>
{selectedServer && (
<p className="text-xs text-gray-500 mt-2 text-center">
Connected to {servers.find(s => s.id === selectedServer)?.name} -
workflows will be {workflowMode === 'plan' ? 'generated' : 'executed'} automatically
</p>
)}
</div>
</div>
</div>
);
}
6. Main Page
// app/page.tsx
import { ChatInterface } from '@/components/chat-interface';
export default function HomePage() {
return (
<main className="h-screen">
<ChatInterface />
</main>
);
}
How It Works
1. Server Discovery
- Frontend automatically discovers running ADK servers on startup
- Uses the
/discover
endpoint to get server capabilities and models - Shows healthy/unhealthy status for each server
2. Chat Integration
- Regular messages go to OpenAI’s GPT-4 for general conversation
- When a server is selected and workflow keywords are detected, requests go to the ADK server
- ADK server returns streaming responses compatible with Vercel AI SDK
3. Workflow Execution
- Plan mode: Generates workflow code without executing
- Act mode: Generates and executes the workflow
- Real-time streaming shows the generation and execution progress
Example Conversations
AI Chat Mode (No Server Selected)
User: "How do I deploy an application?"
AI: "To deploy an application, you typically need to:
1. Select an orchestration server above
2. Set mode to 'act' for execution
3. Ask me to 'Create a deployment workflow for my app'
This will generate and execute a real workflow!"
Workflow Mode (Server Selected)
User: "Create a simple hello world workflow"
ADK Server: (Streaming response)
```python
from kubiya_workflow_sdk import workflow
@workflow
def hello_world_workflow():
"""Create a simple hello world workflow"""
print("Hello, World! This is a Kubiya workflow.")
return {"status": "completed", "message": "Hello World workflow executed successfully"}
Executing workflow…
- Initializing workflow environment…
- Loading required dependencies…
- Executing workflow logic…
- Workflow completed successfully!
Execution Result:
{
"status": "completed",
"message": "Hello World workflow executed successfully"
}
### Deployment Workflow Example
User: “Deploy my Node.js application to staging”
ADK Server: (Streaming response)
from kubiya_workflow_sdk import workflow
@workflow
def deploy_nodejs_app():
"""Deploy Node.js application to staging environment"""
# Build step
print("Building Node.js application...")
# Test step
print("Running tests...")
# Deploy step
print("Deploying to staging environment...")
return {
"status": "deployed",
"environment": "staging",
"url": "https://my-app-staging.example.com"
}
Executing workflow…
- Building Node.js application…
- Running tests…
- Deploying to staging environment…
- Deployment completed successfully!
## Testing Your Setup
### 1. Start ADK Server
```bash
# In terminal 1 - Start ADK server
cd /path/to/your/kubiya-project
source venv/bin/activate
export TOGETHER_API_KEY="your-together-api-key"
export KUBIYA_API_KEY="your-kubiya-api-key"
export PORT=8001
python3 workflow_sdk/adk_orchestration_server.py
2. Start Frontend
# In terminal 2 - Start Next.js app
cd kubiya-chatbot
npm run dev
3. Test Discovery
# Test server discovery
curl http://localhost:8001/discover
# Should return server info with capabilities
4. Test Integration
- Open http://localhost:3000
- Click “Refresh Servers” - should discover your ADK server
- Select the “Local ADK Orchestrator”
- Set mode to “plan” and try: “Create a hello world workflow”
- Set mode to “act” and try: “Deploy a simple web app”
Troubleshooting
Server Not Discovered
Problem: “No orchestration servers found” warning
Solutions:
# 1. Check if ADK server is running
curl http://localhost:8001/health
# 2. Check server logs for errors
# Look for "Uvicorn running on http://0.0.0.0:8001"
# 3. Verify environment variables
echo $KUBIYA_API_KEY
echo $TOGETHER_API_KEY
# 4. Check for port conflicts
lsof -i :8001
CORS Errors
Problem: Cross-origin request blocked
Solution: ADK server already includes CORS middleware, but if you see issues:
# In adk_orchestration_server.py
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # Add your frontend URL
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Streaming Not Working
Problem: Messages appear all at once instead of streaming
Solutions:
- Check browser dev tools Network tab for SSE connection
- Verify ADK server is returning
text/event-stream
content type - Check if response is properly formatted for Vercel AI SDK
Authentication Errors
Problem: “No idToken provided” errors
Solution: Ensure your Kubiya API key is set correctly:
# Check API key is set
echo $KUBIYA_API_KEY
# Restart servers after setting environment variables
Production Deployment
Environment Variables
# .env.production
OPENAI_API_KEY=your_production_openai_key
KUBIYA_API_KEY=your_production_kubiya_key
ORCHESTRATION_SERVER_URL=https://your-adk-server.com
Deployment Options
Vercel (Recommended):
npm install -g vercel
vercel
# Follow prompts and set environment variables
Docker:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
Self-hosted:
npm run build
npm start
Advanced Features
Multiple Server Support
// Add multiple servers in the discovery
const serverUrls = [
'http://localhost:8001', // Local ADK
'http://localhost:8002', // Local MCP
'https://prod-adk.company.com', // Production ADK
];
Custom Models
// In your chat API route, support model selection
export async function POST(request: Request) {
const { messages, selectedServer, workflowMode, selectedModel } = await request.json();
// Pass model to ADK server
body: JSON.stringify({
messages: [{ role: 'user', content: message }],
prompt: message,
mode,
model: selectedModel || 'deepseek-ai/DeepSeek-V3',
conversationId: `chat-${Date.now()}`
})
}
Workflow History
// Add to your component state
const [workflowHistory, setWorkflowHistory] = useState([]);
// Save executed workflows
const saveWorkflow = (workflow) => {
setWorkflowHistory(prev => [workflow, ...prev.slice(0, 9)]); // Keep last 10
};
Security Considerations
API Key Management
// Never expose API keys in frontend
// Always use environment variables
// Use different keys for development/production
CORS Configuration
# Restrict CORS in production
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourdomain.com"], # Specific domains only
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["*"],
)
Rate Limiting
// Add rate limiting to your API routes
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
Performance Tips
1. Server Discovery Caching
// Cache discovery results
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
let discoveryCache = null;
let cacheTime = 0;
const getCachedServers = () => {
if (Date.now() - cacheTime < CACHE_TTL && discoveryCache) {
return discoveryCache;
}
return null;
};
2. Connection Pooling
// Reuse connections for the same server
const connections = new Map();
const getConnection = (serverUrl) => {
if (!connections.has(serverUrl)) {
connections.set(serverUrl, fetch); // Use appropriate connection pooling
}
return connections.get(serverUrl);
};
3. Error Recovery
// Implement exponential backoff for failed requests
const retryWithBackoff = async (fn, maxRetries = 3) => {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
};
Next Steps
🔧 Multi-Server Setup
Set up multiple orchestration servers with load balancing
📊 Workflow Dashboard
Build a comprehensive workflow management dashboard
🔌 MCP Integration
Add Model Context Protocol support for Claude Desktop
📚 Full Tutorial
Complete end-to-end development tutorial
Resources
Community
Was this page helpful?
- Vercel AI SDK Integration
- What You’ll Build
- Quick Setup (5 minutes)
- 1. Create Next.js Project
- 2. Environment Variables
- 3. Server Discovery Client
- 4. Chat API Route
- 5. Chat Interface Component
- 6. Main Page
- How It Works
- 1. Server Discovery
- 2. Chat Integration
- 3. Workflow Execution
- Example Conversations
- AI Chat Mode (No Server Selected)
- Workflow Mode (Server Selected)
- 2. Start Frontend
- 3. Test Discovery
- 4. Test Integration
- Troubleshooting
- Server Not Discovered
- CORS Errors
- Streaming Not Working
- Authentication Errors
- Production Deployment
- Environment Variables
- Deployment Options
- Advanced Features
- Multiple Server Support
- Custom Models
- Workflow History
- Security Considerations
- API Key Management
- CORS Configuration
- Rate Limiting
- Performance Tips
- 1. Server Discovery Caching
- 2. Connection Pooling
- 3. Error Recovery
- Next Steps
- Resources
- Community