ウェブサイト検索

Genaiプラットフォームでカスタムリアルタイムチャットエクスペリエンスを構築する


導入

ニーズに合わせて完全にカスタマイズできるChatGptのような体験を構築することを想像してください。今年のDeploy Conferenceで公開プレビューを開始したばかりのDigitalOceanの新しいGenaiプラットフォームを使用すると、まさにそれを行うことができます。

Genaiプラットフォームで特にエキサイティングなのは、その柔軟性です。多くのユースケースでは、コードをまったく記述する必要はありません。DigaloceanコントロールパネルからJavaScriptスニペットをつかみ、アプリケーションに直接チャットボットを埋め込むだけです。しかし、開発者として、私を本当に興奮させるのは、APIを使用して私が望むあらゆる種類の経験を構築する能力です。

この投稿では、ChatGptエクスペリエンスを模倣するカスタムチャットアプリケーションの構築を進めます。この例では、堅牢なチャットアプリケーションの構築の3つの重要なコンポーネントを教えてください。

  1. API認証 - Genaiプラットフォームに安全に接続します
  2. WebSocketsとのリアルタイム通信 - 双方向通信のセットアップ
  3. チャットサービスとメッセージ処理 - 会話の管理とストリーミング応答

最良の部分は、すべてのピースを理解したら、それをカスタマイズして必要な方法で動作できることです。

genaiプラットフォームの理解

DigitalOcean Genaiプラットフォームは、AIエージェントを迅速に構築およびスケーリングするためのオールインワンソリューションです。 AI開発をすべての人がアクセスできるようにすることに焦点を当てた、人類、メタ、ミストラルAIなどの組織からの最先端の生成AIモデルへのアクセスを提供します。

これが特別なものです:

  • インスタントデプロイメント:わずか数回クリックしてAIエージェントを構築および展開する
  • ファンデーションモデル:人類、メタ、ミストラルAIからの主要なモデルにアクセス
  • 柔軟な統合:ノーコードチャットボットの埋め込みまたは完全なAPIアクセスを選択します
  • ビルトインセキュリティ:有害なコンテンツをフィルタリングするカスタマイズ可能なガードレール
  • 費用対効果:自信のあるスケーリングのための透明な価格設定
  • 高度な機能

    • 知識ベースの統合のための検索総生成(RAG)
    • カスタム機能の関数ルーティング
    • エージェントのカスタマイズとガードレール

このプラットフォームは、インフラストラクチャの作業を処理し、AIエクスペリエンスレベルに関係なくアプリケーションの構築に集中できます。

OpenAI API互換性

Genaiプラットフォームの最も強力な側面の1つは、OpenaiとのAPI互換性です。これは、OpenAI互換のSDKまたはライブラリを使用して、プラットフォームと対話できることを意味します。以前にOpenaiで作業したことがある場合は、Genaiプラットフォームを使用する必要があるすべてのエクスペリエンスを既に持っています。始めたばかりの場合は、OpenAI用に構築されたツールとライブラリの広範なエコシステムを活用できます。

この互換性が開発者にとって意味するものは次のとおりです。

  • お気に入りのOpenai SDK(Python、node.jsなど)を使用してください
  • 既存のコードと例を活用します
  • ツールとライブラリの豊富なエコシステムにアクセスします
  • Openaiに精通している場合は、最小限の学習曲線
  • 既存のアプリケーションの簡単な移行パス

たとえば、チャットアプリケーションでは、openai node.js sdkを使用します。

const { OpenAI } = require('openai');

const client = new OpenAI({
    baseURL: agent_endpoint,
    apiKey: access_token,
});

const response = await client.chat.completions.create({
    model: "n/a", // Model is handled by the GenAI Platform
    messages: conversationHistory,
    stream: true,
});

この互換性は、開発と採用を劇的に簡素化します。新しいAPIを学習したり、既存のコードを書き直す代わりに、ユーザーにとって重要な機能の構築に焦点を当てることができます。

チャットアプリケーションの構築

技術的な詳細に飛び込む前に、私たちが探求しようとしているすべてのものがGitHubのサンプルアプリケーションで利用できることに注意する価値があります。リポジトリをクローンし、ローカルで実行し、これらのコンポーネントが動作していることを確認できます。これにより、自分でコードを追跡して実験しやすくなります。

このチャットアプリケーションを機能させる3つの重要なコンポーネントに飛び込みましょう。

API認証

APIを使用する場合、安全な認証が不可欠です。 Genaiプラットフォームは、アクセスキーを使用してリクエストを認証し、すべての呼び出しで機密性の高い資格情報を送信せずにAPIと対話するためのシンプルで安全な方法を提供します。

必要です:

  1. DigitalOceanコントロールパネルにアクセスキーを作成します
  2. アプリケーションにしっかりと保管してください
  3. それを使用して、APIリクエストを認証します

チャットアプリケーションにこれを実装する方法は次のとおりです。

const { OpenAI } = require('openai');

class TokenService {
    constructor() {
        this.AGENT_ENDPOINT = process.env.AGENT_ENDPOINT + "/api/v1/";
        this.AGENT_KEY = process.env.AGENT_KEY;
        
        if (!this.AGENT_ENDPOINT || !this.AGENT_KEY) {
            throw new Error('Missing required configuration');
        }
    }

    getClient() {
        return new OpenAI({
            baseURL: this.AGENT_ENDPOINT,
            apiKey: this.AGENT_KEY,
        });
    }
}

この合理化されたアプローチ:

  • 認証に単一のアクセスキーを使用します
  • 最小限のセットアップとメンテナンスが必要です
  • セキュリティのベストプラクティスに従います
  • Openai SDKとシームレスに動作します

GenaiプラットフォームにAPI呼び出しを行うとき、クライアントを使用してエージェントと対話できます。

const client = tokenService.getClient();
const response = await client.chat.completions.create({
    model: "n/a", // Model is handled by the GenAI Platform
    messages: conversationHistory,
    stream: true,
});

WebSocketsとのリアルタイム通信

WebSocketは、単一のTCP接続にわたって全二重通信チャネルを提供する通信プロトコルです。従来のHTTP要求とは異なり、WebSocketsはクライアントとサーバーの間の永続的な接続を維持し、以下を許可します。

  • リアルタイム、双方向コミュニケーション
  • レイテンシが低い(接続後に新しい握手は必要ありません)
  • データの効率的なストリーミング
  • より良いリソース利用

このチャットアプリの場合、WebSocketsは私たちに次のことを許可するため、理想的です。

  1. AIの応答は、生成されるときにリアルタイムでストリーミングします
  2. チャットセッションごとに接続状態を維持します
  3. 再接続シナリオを優雅に処理します
  4. 接続ステータスに関する即時のフィードバックを提供します

サーバー側の実装

ここでは、 ws パッケージを使用しました。

WebSocketサーバーをセットアップする方法は次のとおりです。

const { WebSocketServer } = require('ws');
const WS_PING_INTERVAL = 30000; // 30 seconds

// Create WebSocket server with no direct HTTP server attachment
const wss = new WebSocketServer({ noServer: true });

// Handle WebSocket connections
wss.on('connection', async (ws, req) => {
    // Extract chat session ID from URL parameters
    const chatId = new URL(req.url, 'ws://localhost').searchParams.get('chatId');
    if (!chatId) {
        ws.close(1008, 'Chat ID is required');
        return;
    }

    try {
        // Store connection in chat service
        chatService.addConnection(chatId, ws);
        
        // Implement connection health checks
        const pingInterval = setInterval(() => {
            if (ws.readyState === ws.OPEN) ws.ping();
        }, WS_PING_INTERVAL);
        
        // Clean up on connection close
        ws.on('close', () => {
            clearInterval(pingInterval);
            chatService.removeConnection(chatId);
        });
    } catch (error) {
        ws.close(1011, 'Failed to initialize connection');
    }
});

// Set up WebSocket upgrade handling
server.on('upgrade', (request, socket, head) => {
    wss.handleUpgrade(request, socket, head, (ws) => {
        wss.emit('connection', ws, request);
    });
});

サーバーの実装の重要な側面:

  • noserver:true で手動でWebSocketのアップグレードを処理し、接続プロセスをより制御し、接続を受け入れる前にセッションデータを検証できるようにします
  • 接続の健康監視のためにping/pongを実装します
  • チャットセッションごとに接続を管理します
  • 接続のクリーンアップを扱います

接続管理

WebSocket接続の管理は、信頼できるチャットアプリケーションにとって重要です。アクティブな接続を追跡し、切断を優雅に処理し、リソースが不要になったらクリーンアップする必要があります。チャットサービスでアクティブな接続を維持する方法は次のとおりです。

class ChatService {
    constructor() {
        this.activeConnections = new Map();
        this.connectionTimeouts = new Map();
        this.CLEANUP_TIMEOUT = 5 * 60 * 1000; // 5 minutes
    }

    addConnection(id, ws) {
        // Remove any existing connection
        if (this.activeConnections.has(id)) {
            this.removeConnection(id);
        }

        // Store new connection
        this.activeConnections.set(id, ws);

        // Set up cleanup timeout
        ws.on('close', () => {
            this.connectionTimeouts.set(id, setTimeout(() => {
                this.conversations.delete(id);
                this.connectionTimeouts.delete(id);
            }, this.CLEANUP_TIMEOUT));
        });
    }

    removeConnection(id) {
        const connection = this.activeConnections.get(id);
        if (connection?.readyState === 1) {
            connection.send(JSON.stringify({ content: 'Connection closed' }));
        }
        this.activeConnections.delete(id);
    }
}

ここで何が起こっているのか分解しましょう:

  1. 接続ストレージ

    • map を使用して、セッションIDでキー付きアクティブなWebSocket接続を保存します
    • 別のマップは、切断されたセッションのクリーンアップタイムアウトを追跡します
    • 5分間の cleanup_timeout は、ユーザーがコンテキストを失うことなく再接続する時間を与えます
  2. 接続の追加

    • 新しい接続を追加する前に、そのセッションの既存の接続をクリーンアップします
    • これにより、リソースの漏れが防止され、セッションごとに1つのアクティブな接続が保証されます
    • 各接続は一意のチャットセッションIDに関連付けられています
  3. クリーンアップ処理

    • 接続が閉じると、クリーンアップタイマーを開始します
    • ユーザーが5分以内に再接続しない場合、会話履歴を削除します
    • これは、ユーザーをリソースの解放と再接続するためのコンテキストを維持するバランスをとる
  4. 接続削除

    • 接続がまだアクティブである場合、クライアントにクライアントに通知する前に通知します
    • すべての接続リソースは、メモリリークを防ぐために適切にクリーンアップされています

この接続の慎重な管理は、多くの同時ユーザーであっても、チャットアプリケーションが安定して効率的なままであることを保証するのに役立ちます。

クライアント側の実装

クライアント側のWebSocketの実装は、リアルタイム通信のいくつかの重要な側面を処理する必要があります。

  • 接続の確立と維持
  • 切断を優雅に処理します
  • 着信メッセージの処理
  • ユーザーに視覚的なフィードバックを提供します

これらの機能を実装する方法は次のとおりです。

const MAX_RECONNECT_ATTEMPTS = 5;
const RECONNECT_DELAY = 1000; // Start with 1 second
let reconnectAttempts = 0;
let ws = null;

function initializeWebSocket() {
    if (ws) {
        ws.close();
        ws = null;
    }

    // Determine WebSocket protocol based on page protocol
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    ws = new WebSocket(`${protocol}//${window.location.host}?chatId=${window.chatId}`);
    
    // Update UI connection status
    updateConnectionStatus('connecting', 'Connecting...');
    
    // Handle incoming messages
    ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        if (data.content === 'Connected to chat stream') {
            // Initial connection successful
            updateConnectionStatus('connected', 'Connected');
            reconnectAttempts = 0;
        } else if (data.content) {
            // Process chat message
            addMessage(data.content, false);
        } else if (data.error) {
            // Handle error messages
            console.error('WebSocket Error:', data.error);
            addMessage(`Error: ${data.error}`, false);
        }
    };

    // Implement reconnection with exponential backoff
    ws.onclose = (event) => {
        updateConnectionStatus('disconnected', 'Connection lost');
        
        if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
            // Calculate delay with exponential backoff
            const delay = Math.min(RECONNECT_DELAY * Math.pow(2, reconnectAttempts), 30000);
            updateConnectionStatus('reconnecting', 
                `Reconnecting in ${delay/1000} seconds...`);
            setTimeout(initializeWebSocket, delay);
            reconnectAttempts++;
        } else {
            // Max retries reached
            updateConnectionStatus('disconnected', 
                'Connection failed. Please refresh the page.');
        }
    };

    // Handle connection errors
    ws.onerror = (error) => {
        console.error('WebSocket error:', error);
        updateConnectionStatus('error', 'Connection error');
    };
}

主な機能を分解しましょう。

  1. 接続管理

    • セキュア( wss:)と非セキュア( ws:)接続の両方を処理します
    • 新しい接続を作成する前に既存の接続を閉じます
    • 接続URLにチャットセッションIDが含まれています
    • UIの接続状態を維持します
  2. メッセージ処理

    • 入ってくるJSONメッセージを解析します
    • さまざまなメッセージタイプ(接続ステータス、チャットコンテンツ、エラー)を処理する
    • 新しいメッセージでUIを更新します
    • デバッグのためのログエラー
  3. スマートな再接続

    • サーバーの洪水を防ぐために、指数関数的なバックオフを実装します
    • 1秒の遅延から始まり、各試行で2倍になります
    • キャップ30秒で最大遅延
    • 合計再接続試行の制限5
    • 再接続試行中に明確なフィードバックを提供します
  4. エラー処理

    • WebSocketエラーをキャッチしてログします
    • 接続状態を反映するようにUIを更新します
    • ユーザーフレンドリーなエラーメッセージを提供します
    • 接続が永続的に失敗した場合のアクションを提案します

クライアントの実装機能:

  • 安全/非セキュア接続のプロトコル検出
  • 視覚的な接続ステータスの更新
  • 指数バックオフによる自動再接続
  • 無限のループを防止しようとする最大再試行
  • エラー処理とユーザーフィードバック

接続ステータス管理

リアルタイムアプリケーションに対するユーザーの信頼性の構築は非常に重要であり、これを達成する簡単な方法の1つは、接続状態を表示することです。 WebSocketの実装で接続ステータスを既に追跡するため、これをユーザーに簡単に表現できます。

function updateConnectionStatus(status, message) {
    const statusDot = document.querySelector('.status-dot');
    const statusText = document.querySelector('.status-text');
    
    // Update visual indicators
    statusDot.className = `status-dot ${status}`;
    statusText.textContent = message;
    
    // Disable/enable chat input based on connection status
    const chatInput = document.getElementById('message-input');
    const sendButton = document.querySelector('button[type="submit"]');
    const isConnected = status === 'connected';
    
    chatInput.disabled = !isConnected;
    sendButton.disabled = !isConnected;
}

この簡単な追加により、ユーザーは接続状態に関する即時フィードバックを提供し、接続が失われたときに入力を自動的に無効にします。アプリケーションがより洗練され、信頼性が高くなるようにする小さなタッチです。

チャットサービスとメッセージ処理

Genaiプラットフォームでチャットインターフェースを構築するとき、興味深い課題の1つは、ストリーミング応答を処理することです。 AIモデルは、トークンによるテキストトークンを生成し、興味深い質問をいくつか提起します。

  1. これらの小さなチャンクをスムーズな読書体験にどのように変えることができますか?
  2. 意味のある相互作用のために会話のコンテキストを維持するにはどうすればよいですか?
  3. ストリーミング中に発生する可能性のあるエラーを管理するにはどうすればよいですか?
  4. レスポンシブユーザーインターフェイスを維持するための最良の方法は何ですか?

これらの課題に対処するために取ることができるアプローチは次のとおりです。ここでは、クライアントに送信する前に、トークンを意味のあるチャンクに収集するバッファリング戦略を実装し、コンテキスト対応の応答の会話履歴も維持します。

メッセージ処理アプローチ

各トークンが到着したときに送信するのではなく、コンテンツをバッファーして自然なブレークポイントで送信できます。実装は次のとおりです。

async processStream(stream, ws, sessionId) {
    const conversationHistory = this.conversations.get(sessionId);
    let currentChunk = [];
    let fullResponse = [];
    
    for await (const chunk of stream) {
        if (chunk.choices[0]?.delta?.content) {
            const content = chunk.choices[0].delta.content;
            currentChunk.push(content);
            
            // Send chunk at natural breaks (punctuation or word count)
            if (this.shouldSendChunk(content, currentChunk)) {
                const message = currentChunk.join('');
                fullResponse.push(message);
                ws.send(JSON.stringify({ content: message }));
                currentChunk = [];
            }
        }
    }
    
    // Store the complete response in conversation history
    if (fullResponse.length > 0) {
        conversationHistory.push({
            role: 'assistant',
            content: fullResponse.join('')
        });
    }
}

重要な機能を見てみましょう。

  1. スマートチャンキング

    • トークンを意味のあるチャンクに収集します
    • 自然なブレークポイントで更新を送信します(句読点または単語数)
    • ユーザー向けのスムーズな読書体験を作成します
  2. 会話の歴史

    • メモリ内の各チャットセッションのコンテキストを維持します
    • セッション中にユーザーメッセージとAI応答の両方を保存します
    • 接続中に、よりコヒーレントなコンテキスト相互作用を可能にします
    • 各セッションには独自の独立した歴史があり、5分間の不活動の後にクリアされます
  3. エラー処理

    • ストリーミング中に接続状態を監視します
    • 切断を優雅に処理します
    • ユーザーに明確なエラーメッセージを提供します
    • リソースリークを防ぎます
  4. リソース管理

    • ストリーミング応答を効率的に処理します
    • 接続が閉じるとリソースをクリーンアップします
    • 各セッションの個別のコンテキストを維持します
    • メモリの使用量とユーザーエクスペリエンスのバランス

この実装は、次のような堅牢なチャットエクスペリエンスを作成します。

  • ユーザーに反応がよく、自然に感じられます
  • 会話のコンテキストを効果的に維持します
  • エラーを優雅に処理します
  • 複数のユーザーとよくスケールします

もちろん、これは問題にアプローチする1つの方法にすぎません。さまざまなチャンク戦略、代替エラー処理アプローチ、または会話状態を管理する他の方法など、特定のニーズに合った他の戦略を見つけることができます。

アプリケーションの実行

チャットアプリケーションの構築について読むことは一つのことですが、手を汚して自分で試してみることに勝るものはありません。いくつかの簡単な手順から簡単に始めることができました。

  1. リポジトリをクローンし、依存関係をインストールします。

    git clone https://github.com/wadewegner/do-genai-customchatbot.git
    cd do-genai-customchatbot
    npm install
    
  2. エージェントを作成して構成します。

    • Genaiプラットフォームクイックスタートガイドに従ってエージェントを作成する
    • これらの指示に従ってエージェントのエンドポイントを公開します
    • 次のステップのために、エージェントのID、キー、およびエンドポイントURLをコピーします
  3. 環境変数を .env に設定します。

    API_BASE=https://cluster-api.do-ai.run/v1
    AGENT_ID=your-agent-id
    AGENT_KEY=your-agent-key
    AGENT_ENDPOINT=your-agent-endpoint
    SESSION_SECRET=your-session-secret
    
  4. サーバーを開始します:

    npm start
    

これで、ブラウザを http:// localhost:3000 に開き、チャットを開始できます。これは、アプリケーションを試してニーズに合わせてカスタマイズするチャンスです。チャットインターフェイスを変更したり、メッセージ処理を調整したり、新機能を追加したりしてみてください。Genaiプラットフォームの柔軟性とは無限です。

作品を世界と共有する準備ができたら、DigitalOceanのアプリプラットフォームは、アプリケーションを公に展開して実行するのに最適な場所です。

チャットボットを超えて

この例はチャットインターフェイスを示していますが、Genaiプラットフォームの機能はこのユースケースをはるかに超えています。あなたは出来る:

  • RAGパイプラインを使用してカスタムナレッジベースアシスタントを構築します
  • カスタムモデルを使用してコンテンツ生成ツールを作成します
  • コード分析と生成システムを実装します
  • 言語翻訳サービスを開発します
  • 既存のアプリケーションにAI機能を追加します

Genaiプラットフォームで構築することで私が最も興奮しているのは、AI開発の創造的な側面に集中できる方法です。ここで構築したチャットアプリケーションは、表面をひっかくだけです。これは、あなたが上に建て、実験し、完全に自分で作ることができる基盤です。このウォークスルーがあなた自身のプロジェクトのためのいくつかのアイデアを引き起こしたことを願っています。今、これらのパターンを取り、素晴らしいものを作成する番です!