Merge pull request #214 from rohitg00/main

Added motia stream example usage agent that allows Real-time AI streaming Chat in Chatbot
This commit is contained in:
Shubham Saboo 2025-06-12 17:59:25 -05:00 committed by GitHub
commit d37218bbcc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 458 additions and 0 deletions

View file

@ -0,0 +1,6 @@
# OpenAI Configuration - Required for AI responses
OPENAI_API_KEY=your-openai-api-key-here
# Azure OpenAI Configuration (commented out for demo)
# AZURE_OPENAI_API_KEY=your-azure-openai-api-key-here
# AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/

View file

@ -0,0 +1,34 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Build outputs
dist/
build/
.motia/
.mermaid/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
logs/
*.log
package-lock.json

View file

@ -0,0 +1,149 @@
# Streaming AI Chatbot
A minimal example demonstrating **real-time AI streaming** and **conversation state management** using the Motia framework.
![streaming-ai-chatbot](docs/images/streaming-ai-chatbot.gif)
## 🚀 Features
- **Real-time AI Streaming**: Token-by-token response generation using OpenAI's streaming API
- **Live State Management**: Conversation state updates in real-time with message history
- **Event-driven Architecture**: Clean API → Event → Streaming Response flow
- **Minimal Complexity**: Maximum impact with just 3 core files
## 📁 Architecture
```
streaming-ai-chatbot/
├── steps/
│ ├── conversation.stream.ts # Real-time conversation state
│ ├── chat-api.step.ts # Simple chat API endpoint
│ └── ai-response.step.ts # Streaming AI response handler
├── package.json # Dependencies
├── .env.example # Configuration template
└── README.md # This file
```
## 🛠️ Setup
### Installation & Setup
```bash
# Clone the repository
git clone https://github.com/Shubhamsaboo/awesome-llm-apps.git
cd advanced_llm_apps/chat_with_X_tutorials/chat_with_llms
# Install dependencies
npm install
# Start the development server
npm run dev
```
### Configure OpenAI API
```bash
cp .env.example .env
# Edit .env and add your OpenAI API key
```
**Open Motia Workbench**:
Navigate to `http://localhost:3000` to interact with the chatbot
## 🔧 Usage
### Send a Chat Message
**POST** `/chat`
```json
{
"message": "Hello, how are you?",
"conversationId": "optional-conversation-id" // Optional: If not provided, a new conversation will be created
}
```
**Response:**
```json
{
"conversationId": "uuid-v4",
"message": "Message received, AI is responding...",
"status": "streaming"
}
```
The response will update as the AI processes the message, with possible status values:
- `created`: Initial message state
- `streaming`: AI is generating the response
- `completed`: Response is complete with full message
When completed, the response will contain the actual AI message instead of the processing message.
### Real-time State Updates
The conversation state stream provides live updates as the AI generates responses:
- **User messages**: Immediately stored with `status: 'completed'`
- **AI responses**: Start with `status: 'streaming'`, update in real-time, end with `status: 'completed'`
## 🎯 Key Concepts Demonstrated
### 1. **Streaming API Integration**
```typescript
const stream = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [...],
stream: true, // Enable streaming
})
for await (const chunk of stream) {
// Update state with each token
await streams.conversation.set(conversationId, messageId, {
message: fullResponse,
status: 'streaming',
// ...
})
}
```
### 2. **Real-time State Management**
```typescript
export const config: StateStreamConfig = {
name: 'conversation',
schema: z.object({
message: z.string(),
from: z.enum(['user', 'assistant']),
status: z.enum(['created', 'streaming', 'completed']),
timestamp: z.string(),
}),
baseConfig: { storageType: 'state' },
}
```
### 3. **Event-driven Flow**
```typescript
// API emits event
await emit({
topic: 'chat-message',
data: { message, conversationId, assistantMessageId },
})
// Event handler subscribes and processes
export const config: EventConfig = {
subscribes: ['chat-message'],
// ...
}
```
## 🌟 Why This Example Matters
This example showcases Motia's power in just **3 files**:
- **Effortless streaming**: Real-time AI responses with automatic state updates
- **Type-safe events**: End-to-end type safety from API to event handlers
- **Built-in state management**: No external state libraries needed
- **Scalable architecture**: Event-driven design that grows with your needs
Perfect for demonstrating how Motia makes complex real-time applications simple and maintainable.
## 🔑 Environment Variables
- `OPENAI_API_KEY`: Your OpenAI API key (required)

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 MiB

View file

@ -0,0 +1,12 @@
{
"chat": {
"steps/chat-api.step.ts": {
"x": 0,
"y": 0
},
"steps/ai-response.step.ts": {
"x": 5.5,
"y": 238
}
}
}

View file

@ -0,0 +1,29 @@
{
"name": "streaming-ai-chatbot",
"description": "Minimal streaming AI chatbot demonstrating real-time responses and state management",
"scripts": {
"dev": "motia dev --verbose",
"dev:debug": "motia dev --debug",
"generate-types": "motia generate-types",
"build": "motia build",
"clean": "rm -rf dist node_modules python_modules .motia .mermaid"
},
"keywords": [
"motia",
"streaming",
"ai",
"chatbot",
"openai"
],
"dependencies": {
"motia": "0.2.2",
"openai": "^4.102.0",
"zod": "^3.25.20"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^19.0.12",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}

View file

@ -0,0 +1,100 @@
import { EventConfig, Handlers } from 'motia'
import { OpenAI } from 'openai'
import { z } from 'zod'
// import { AzureOpenAI } from 'openai'
export const config: EventConfig = {
type: 'event',
name: 'AiResponse',
description: 'Generate streaming AI response',
subscribes: ['chat-message'],
emits: [],
input: z.object({
message: z.string(),
conversationId: z.string(),
assistantMessageId: z.string(),
}),
flows: ['chat'],
}
export const handler: Handlers['AiResponse'] = async (input, context) => {
const { logger, streams } = context
const { message, conversationId, assistantMessageId } = input
logger.info('Generating AI response', { conversationId })
// For Azure OpenAI
// const openai = new AzureOpenAI({
// endpoint: process.env.AZURE_OPENAI_ENDPOINT || 'demo-key',
// apiKey: process.env.AZURE_OPENAI_API_KEY || 'demo-key',
// deployment: 'gpt-4o-mini',
// apiVersion: '2024-12-01-preview'
// })
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
})
try {
await streams.conversation.set(conversationId, assistantMessageId, {
message: '',
from: 'assistant',
status: 'streaming',
timestamp: new Date().toISOString(),
})
const stream = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: 'You are a helpful AI assistant. Keep responses concise and friendly.'
},
{
role: 'user',
content: message
}
],
stream: true,
})
let fullResponse = ''
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || ''
if (content) {
fullResponse += content
await streams.conversation.set(conversationId, assistantMessageId, {
message: fullResponse,
from: 'assistant',
status: 'streaming',
timestamp: new Date().toISOString(),
})
}
}
await streams.conversation.set(conversationId, assistantMessageId, {
message: fullResponse,
from: 'assistant',
status: 'completed',
timestamp: new Date().toISOString(),
})
logger.info('AI response completed', {
conversationId,
responseLength: fullResponse.length
})
} catch (error) {
logger.error('Error generating AI response', { error, conversationId })
await streams.conversation.set(conversationId, assistantMessageId, {
message: 'Sorry, I encountered an error. Please try again.',
from: 'assistant',
status: 'completed',
timestamp: new Date().toISOString(),
})
}
}

View file

@ -0,0 +1,66 @@
import { ApiRouteConfig, Handlers } from 'motia'
import { z } from 'zod'
import { conversationSchema } from './conversation.stream'
const inputSchema = z.object({
message: z.string().min(1, 'Message is required'),
conversationId: z.string().optional(),
})
export const config: ApiRouteConfig = {
type: 'api',
name: 'ChatApi',
description: 'Send a message to the AI chatbot',
path: '/chat',
method: 'POST',
emits: ['chat-message'],
bodySchema: inputSchema,
responseSchema: {
200: conversationSchema
},
flows: ['chat'],
}
export const handler: Handlers['ChatApi'] = async (req, { logger, emit, streams }) => {
const conversationId = req.body.conversationId || crypto.randomUUID()
const userMessageId = crypto.randomUUID()
const assistantMessageId = crypto.randomUUID()
logger.info('New chat message received', {
conversationId,
message: req.body.message
})
await streams.conversation.set(conversationId, userMessageId, {
message: req.body.message,
from: 'user',
status: 'completed',
timestamp: new Date().toISOString(),
})
const aiResponse = await streams.conversation.set(conversationId, assistantMessageId, {
message: '',
from: 'assistant',
status: 'created',
timestamp: new Date().toISOString(),
})
await emit({
topic: 'chat-message',
data: {
message: req.body.message,
conversationId,
assistantMessageId,
},
})
logger.info('Returning chat response', {
conversationId,
messageId: assistantMessageId,
})
return {
status: 200,
body: aiResponse,
}
}

View file

@ -0,0 +1,15 @@
import { StreamConfig } from 'motia'
import { z } from 'zod'
export const conversationSchema = z.object({
message: z.string(),
from: z.enum(['user', 'assistant']),
status: z.enum(['created', 'streaming', 'completed']),
timestamp: z.string(),
})
export const config: StreamConfig = {
name: 'conversation',
schema: conversationSchema,
baseConfig: { storageType: 'default' },
}

View file

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"allowJs": true,
"outDir": "dist",
"rootDir": ".",
"baseUrl": ".",
"jsx": "react-jsx"
},
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx",
"types.d.ts"
],
"exclude": [
"node_modules",
"dist",
"tests"
]
}

View file

@ -0,0 +1,18 @@
/**
* Automatically generated types for motia
* Do NOT edit this file manually.
*
* Consider adding this file to .prettierignore and eslint ignore.
*/
import { EventHandler, ApiRouteHandler, ApiResponse, IStateStream } from 'motia'
declare module 'motia' {
interface FlowContextStateStreams {
'conversation': IStateStream<{ message: string; from: string; status: string; timestamp: string }>
}
type Handlers = {
'ChatApi': ApiRouteHandler<{ message: string; conversationId?: string }, ApiResponse<200, { conversationId: string; message: string; status?: string }>, { topic: 'chat-message'; data: { message: string; conversationId: string; assistantMessageId: string } }>
'AiResponse': EventHandler<{ message: string; conversationId: string; assistantMessageId: string }, never>
}
}