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:
commit
d37218bbcc
11 changed files with 458 additions and 0 deletions
|
|
@ -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/
|
||||
34
advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/.gitignore
vendored
Normal file
34
advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/.gitignore
vendored
Normal 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
|
||||
149
advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/README.md
Normal file
149
advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/README.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# Streaming AI Chatbot
|
||||
|
||||
A minimal example demonstrating **real-time AI streaming** and **conversation state management** using the Motia framework.
|
||||

|
||||
|
||||
## 🚀 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 |
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"chat": {
|
||||
"steps/chat-api.step.ts": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"steps/ai-response.step.ts": {
|
||||
"x": 5.5,
|
||||
"y": 238
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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' },
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
18
advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/types.d.ts
vendored
Normal file
18
advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/types.d.ts
vendored
Normal 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>
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue