|
@@ -46,81 +46,82 @@ Editor Assistant API は、OpenAI AssistantAPI を使用して、マークダウ
|
|
|
|
|
|
|
|
## 実装のポイント
|
|
## 実装のポイント
|
|
|
|
|
|
|
|
-### 1. jsonrepair による JSON 修復
|
|
|
|
|
-- ストリーミングされる不完全な JSON データを処理するために jsonrepair を使用
|
|
|
|
|
-- 部分的に届く JSON フラグメントを修復して有効な JSON に変換
|
|
|
|
|
-
|
|
|
|
|
-### 2. EditorStreamProcessor の実装
|
|
|
|
|
-- 生のテキストストリームを処理し、完全な JSON オブジェクトに変換する専用クラス
|
|
|
|
|
-- データ処理の最適化とメモリ効率:
|
|
|
|
|
- - 差分データの重複送信を防ぐためのキーベースの追跡システム(`sentDiffKeys` Set)
|
|
|
|
|
- - 型安全性を確保するための Zod スキーマによるバリデーション
|
|
|
|
|
- - 無駄な通信を減らすための差分更新検出と通知タイミングの最適化
|
|
|
|
|
-- インテリジェントな差分処理:
|
|
|
|
|
- - メッセージ(message)と差分(diff)の処理ロジックを分離:
|
|
|
|
|
- - メッセージ処理: 最新のメッセージのみを保持し、メッセージが変更された場合は即座に通知。冗長なメッセージ更新を防ぎつつ、ユーザーに迅速にフィードバックを提供。
|
|
|
|
|
- - 差分処理: 各差分データに一意のキーを割り当て(`getDiffKey` メソッド)、重複処理を防止。差分のコンテキストを保持するため配列形式で蓄積。
|
|
|
|
|
- - ストリーム中の差分の「確定状態」を判定:
|
|
|
|
|
- - `lastContentIndex` を活用して最新コンテンツの境界を追跡
|
|
|
|
|
- - 最終要素より前の差分データは「確定」と判断し、即座に送信
|
|
|
|
|
- - 最終要素(現在編集中の可能性がある要素)は「未確定」と判断し、変更が安定するまで送信を保留
|
|
|
|
|
- - 型ガード関数による適切な処理の振り分け:
|
|
|
|
|
- - `isMessageItem`: オブジェクトが説明メッセージかを判定
|
|
|
|
|
- - `isDiffItem`: オブジェクトが差分データ(insert/delete/retain)かを判定
|
|
|
|
|
- - 差分の完成度判定ロジック:
|
|
|
|
|
- - コンテンツ配列内の位置に基づく判定(前方の要素ほど安定している可能性が高い)
|
|
|
|
|
- - 最終インデックスの変化を検出して確定状態を判断(インデックスが進んだ = 前の要素は確定)
|
|
|
|
|
-- リソース管理:
|
|
|
|
|
- - クライアント通信終了時のクリーンアップ処理(`destroy` メソッド)
|
|
|
|
|
- - 一時データの格納と最終結果での統合処理
|
|
|
|
|
-
|
|
|
|
|
-### 3. ストリーミング応答処理
|
|
|
|
|
-- SSE フォーマット(Server-Sent Events)を使用したリアルタイム通信
|
|
|
|
|
-- 非同期ストリーミング処理と適切なエラーハンドリング
|
|
|
|
|
-- クライアント切断時のリソース解放機構
|
|
|
|
|
-
|
|
|
|
|
-### 4. Assistant API 統合
|
|
|
|
|
-- OpenAI Assistant API を使用したエディタアシスタントの生成
|
|
|
|
|
-- スレッド管理とメッセージ処理
|
|
|
|
|
-- カスタムレスポンス形式の指定(Zod スキーマによる型安全な応答)
|
|
|
|
|
-
|
|
|
|
|
-### 5. 差分情報の処理
|
|
|
|
|
-- クォーターエディタの Delta 形式(insert, delete, retain)に対応
|
|
|
|
|
-- 差分情報とメッセージの混合コンテンツ処理
|
|
|
|
|
-- メッセージデルタのハンドリングとアノテーション処理
|
|
|
|
|
-- 差分の重複処理防止のためのユニーク識別子生成(`getDiffKey` メソッド)
|
|
|
|
|
-
|
|
|
|
|
-### 6. エラー処理とリソース管理
|
|
|
|
|
-- ストリーム処理中のエラーを適切に捕捉し、クライアントに通知
|
|
|
|
|
-- クリーンアップ処理によるメモリリークの防止
|
|
|
|
|
-- クライアント切断やサーバーエラー時の適切なリソース解放
|
|
|
|
|
-- 不完全なJSON解析エラーのサイレント処理(対象ログレベルを debug に設定)
|
|
|
|
|
-
|
|
|
|
|
-### 7. 応答形式の標準化
|
|
|
|
|
-- エディタアシスタントの一貫した応答形式の保証:
|
|
|
|
|
- - メッセージと差分データを分離した構造
|
|
|
|
|
- - クライアントがリアルタイムに処理できるフォーマットでの送信
|
|
|
|
|
- - 処理完了を示す `isDone` フラグによる明示的な終了通知
|
|
|
|
|
-- コンテキストに基づいた適切な指示による高品質な応答生成
|
|
|
|
|
-- 複数言語対応(ユーザーの言語に合わせた応答)
|
|
|
|
|
-
|
|
|
|
|
-### 8. 差分データの段階的送信
|
|
|
|
|
-- ストリームから受信した差分データを確定状態と未確定状態に分類:
|
|
|
|
|
- - 最終インデックスより前の要素、または最終コンテンツが変更された場合は「確定」と判断
|
|
|
|
|
- - 変化しやすい現在の最終要素は「未確定」と判断し送信を保留
|
|
|
|
|
- - この方法により、継続的に変化している最新の差分による不必要な更新通知を抑制
|
|
|
|
|
-- 差分の安定性の判断基準:
|
|
|
|
|
- - OpenAI からのストリームでは、通常前方から順に差分が確定していくため、配列内の位置を基準に判定
|
|
|
|
|
- - 新たな要素が追加された場合(`currentContentIndex > this.lastContentIndex`)、以前の要素は確定と見なす
|
|
|
|
|
- - 最終要素は OpenAI が生成中の可能性が高いため、処理完了まで確定を遅延させる
|
|
|
|
|
-- 効率的な更新通知の実装:
|
|
|
|
|
- - メッセージはリアルタイムで更新通知(ユーザーへの説明は即時性が重要)
|
|
|
|
|
- - 差分データは新たに確定したものがある場合のみ通知(エディタの頻繁な更新を避ける)
|
|
|
|
|
- - 処理終了時に最終確認として未送信の差分も含めた完全な結果を送信(`sendFinalResult` メソッド)
|
|
|
|
|
- - `processedDiffIndex` と `lastSentDiffIndex` を比較して、新しい確定差分がある場合のみ更新通知
|
|
|
|
|
-
|
|
|
|
|
-### 9. 型安全性の確保
|
|
|
|
|
-- Zod スキーマを用いた厳格な型チェックと変換
|
|
|
|
|
-- TypeScript の型ガード関数による実行時型安全性の確保
|
|
|
|
|
-- エッジケース処理(不完全データ、無効な差分など)の堅牢な実装
|
|
|
|
|
|
|
+### 1. ストリーミング処理と不完全JSONの修復
|
|
|
|
|
+
|
|
|
|
|
+ストリーミング処理において、最大の課題は不完全なJSON文字列の処理です。OpenAI APIから部分的に届くJSONデータを即座に解析するために、以下の対策を実装しています:
|
|
|
|
|
+
|
|
|
|
|
+- **jsonrepair ライブラリの採用理由**:
|
|
|
|
|
+ - 通常、JSON文字列は完全な形でなければパースできません。これはストリーム処理において大きな制約となります。
|
|
|
|
|
+ - 全ての文字列を受け取るまで待たずに、途中経過をリアルタイムにユーザーに提示するため、jsonrepairを使用して部分的なJSON文字列を修復しています。
|
|
|
|
|
+ - これにより、メッセージと差分情報を受信次第、速やかにクライアントに届けることが可能になり、ユーザー体験が大幅に向上します。
|
|
|
|
|
+
|
|
|
|
|
+ **具体例**:
|
|
|
|
|
+ ```javascript
|
|
|
|
|
+ // ストリームから受け取った不完全なJSONの例
|
|
|
|
|
+ const partialJson = '{"contents": [{"message": "テキストを修正し';
|
|
|
|
|
+
|
|
|
|
|
+ // 通常のJSON.parseではエラー
|
|
|
|
|
+ // JSON.parse(partialJson); // SyntaxError: Unexpected end of JSON input
|
|
|
|
|
+
|
|
|
|
|
+ // jsonrepairを使用した修復
|
|
|
|
|
+ const repairedJson = jsonrepair(partialJson);
|
|
|
|
|
+ // 結果: '{"contents": [{"message": "テキストを修正しています"}]}'
|
|
|
|
|
+
|
|
|
|
|
+ // 修復されたJSONはパース可能
|
|
|
|
|
+ const parsedJson = JSON.parse(repairedJson);
|
|
|
|
|
+ // 結果: { contents: [{ message: 'テキストを修正しています' }] }
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+ - このように、正常なJSONとして完結していない途中のデータでも、jsonrepairは欠けている部分を補完して有効なJSONに変換します。OpenAI APIからの応答では、完全なJSONが揃うまで待つことなく、部分的に受信したデータを即座に処理できるようになります。
|
|
|
|
|
+
|
|
|
|
|
+- **rawBufferの累積と継続的な解析**:
|
|
|
|
|
+ - 受信したテキストチャンクを`rawBuffer`に累積し、その都度jsonrepairでパース可能な形に修復しています。
|
|
|
|
|
+ - これは特にOpenAI APIの応答がJSON形式で指定されているにもかかわらず、ストリームではその一部だけが届く特性に対応するための実装です。
|
|
|
|
|
+
|
|
|
|
|
+### 2. 差分検出と適応的送信制御
|
|
|
|
|
+
|
|
|
|
|
+エディタアシスタントの核心部分は、OpenAI APIからのレスポンスから差分情報を適切に抽出し、効率的にクライアントに送信する機能です。以下のような工夫を行っています:
|
|
|
|
|
+
|
|
|
|
|
+- **メッセージと差分の処理の分離**:
|
|
|
|
|
+ - これはUI/UX要件に基づいた設計判断です。メッセージと差分では、ユーザーに対する重要度と提示タイミングの最適値が異なります。
|
|
|
|
|
+ - **メッセージ処理**:説明メッセージは変更があればすぐにユーザーに見せるべきです。そのため、最新のメッセージを検出したら即座に通知するロジックを実装しています。
|
|
|
|
|
+ - **差分処理**:エディタの頻繁な更新はユーザー体験を損なうため、確定した差分のみを送信する制御機構を設けています。
|
|
|
|
|
+
|
|
|
|
|
+- **OpenAIストリームの特性に対応した差分確定判定**:
|
|
|
|
|
+ - OpenAI APIからのJSONストリームは「前方から順に確定していく」特性があります。このAPIの特性を活用し、以下の判定ロジックを実装しています:
|
|
|
|
|
+ ```javascript
|
|
|
|
|
+ // 最終要素が変化した、またはこれが最終要素ではない場合 → 差分を確定とみなす
|
|
|
|
|
+ if (i < currentContentIndex || currentContentIndex > this.lastContentIndex) {
|
|
|
|
|
+ // 差分を確定して送信リストに追加
|
|
|
|
|
+ }
|
|
|
|
|
+ ```
|
|
|
|
|
+ - この条件判定は単なる技術的工夫ではなく、UXの向上を目的としています。確定していない(変更中の可能性がある)差分を頻繁に送信すると、エディタが頻繁に更新されてユーザー体験が悪化するためです。
|
|
|
|
|
+
|
|
|
|
|
+- **重複防止メカニズム**:
|
|
|
|
|
+ - 差分の重複送信を避けるため、一意のキーを生成する`getDiffKey`メソッドを実装しています。
|
|
|
|
|
+ - Setデータ構造(`sentDiffKeys`)を使うことで、O(1)の時間複雑度で効率的に重複チェックを行います。
|
|
|
|
|
+ - この実装は、ストリームデータの累積的な性質(同じデータが何度も現れる可能性がある)に対応するために不可欠です。
|
|
|
|
|
+
|
|
|
|
|
+- **差分の通知タイミングの最適化**:
|
|
|
|
|
+ - 全ての差分更新を即座に送信すると、クライアント側での処理負荷が高まります。
|
|
|
|
|
+ - `processedDiffIndex`と`lastSentDiffIndex`を比較し、新たに確定した差分がある場合のみ通知することで、通信量と処理負荷を削減しています。
|
|
|
|
|
+ - 最終処理時には`sendFinalResult`で未送信の差分を含めた完全な結果を送信し、データの一貫性を保証します。
|
|
|
|
|
+
|
|
|
|
|
+### 3. エラー耐性とリソース管理
|
|
|
|
|
+
|
|
|
|
|
+ストリーミング処理においてエラー耐性とリソース管理は特に重要です。以下の対策を講じています:
|
|
|
|
|
+
|
|
|
|
|
+- **エラーハンドリングの階層化**:
|
|
|
|
|
+ - JSONパースエラーはデバッグ用にログ出力するのみとし、処理を継続します。これはストリーミングの性質上、部分的なデータでパースエラーが発生するのは正常な動作だからです。
|
|
|
|
|
+ - 重大なエラーはクライアントに適切に通知し、リソースを解放します。
|
|
|
|
|
+
|
|
|
|
|
+- **リソース解放の徹底**:
|
|
|
|
|
+ - クライアント切断時やエラー発生時、処理完了時など、あらゆるシナリオでリソースを確実に解放するクリーンアップ処理を実装しています。
|
|
|
|
|
+ - `destroy`メソッドでメモリキャッシュをクリアし、イベントリスナーを解除することで、メモリリークを防止しています。
|
|
|
|
|
+
|
|
|
|
|
+- **非同期ストリーム処理の安全な終了**:
|
|
|
|
|
+ - ストリームの終了を適切に検出し、完全な結果を送信してから接続を終了する機構を設けています。
|
|
|
|
|
+ - エラー時でも可能な限り正常な形でレスポンスを返し、クライアント側での復旧を容易にします。
|
|
|
|
|
+
|
|
|
|
|
+このような設計と実装により、リアルタイム性と正確性を両立したエディタアシスタント機能を実現しています。ストリーミング処理の特性を活かしつつ、効率的なデータ処理と適応的な通知制御によって優れたユーザー体験を提供しています。
|
|
|
|
|
|