diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/.env.example b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/.env.example new file mode 100644 index 0000000..cb0863d --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/.env.example @@ -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/ diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/.gitignore b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/.gitignore new file mode 100644 index 0000000..fde3b21 --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/.gitignore @@ -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 \ No newline at end of file diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/README.md b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/README.md new file mode 100644 index 0000000..a3cd794 --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/README.md @@ -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) \ No newline at end of file diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/docs/images/streaming-ai-chatbot.gif b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/docs/images/streaming-ai-chatbot.gif new file mode 100644 index 0000000..3b01147 Binary files /dev/null and b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/docs/images/streaming-ai-chatbot.gif differ diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/motia-workbench.json b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/motia-workbench.json new file mode 100644 index 0000000..0e909b1 --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/motia-workbench.json @@ -0,0 +1,12 @@ +{ + "chat": { + "steps/chat-api.step.ts": { + "x": 0, + "y": 0 + }, + "steps/ai-response.step.ts": { + "x": 5.5, + "y": 238 + } + } +} \ No newline at end of file diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/package.json b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/package.json new file mode 100644 index 0000000..a7d47d8 --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/package.json @@ -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" + } +} diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/ai-response.step.ts b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/ai-response.step.ts new file mode 100644 index 0000000..d8b7686 --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/ai-response.step.ts @@ -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(), + }) + } +} diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/chat-api.step.ts b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/chat-api.step.ts new file mode 100644 index 0000000..00257fd --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/chat-api.step.ts @@ -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, + } +} diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/conversation.stream.ts b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/conversation.stream.ts new file mode 100644 index 0000000..632d263 --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/steps/conversation.stream.ts @@ -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' }, +} diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/tsconfig.json b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/tsconfig.json new file mode 100644 index 0000000..9c30de1 --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/tsconfig.json @@ -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" + ] +} diff --git a/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/types.d.ts b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/types.d.ts new file mode 100644 index 0000000..7efd7d3 --- /dev/null +++ b/advanced_llm_apps/chat_with_X_tutorials/chat_with_llms/types.d.ts @@ -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> + } +} \ No newline at end of file