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