feat: Implement unified error handling system

- Added `errors.py` to define a centralized error management system with error codes and metadata.
- Created `VRCTError` class for generating standardized error responses.
- Introduced `error_handling_migration_guide.md` to document migration patterns for existing error handling to the new system.
- Updated error handling patterns in the codebase to utilize the new error management system.
This commit is contained in:
misyaguziya
2026-01-02 19:38:06 +09:00
parent cab7d48de1
commit b0cf8bf335
3 changed files with 1227 additions and 461 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,212 @@
# エラーハンドリング統一システム移行ガイド
## 概要
`errors.py`で定義された統一エラーシステムを使用して、すべてのエラーハンドリングを標準化しました。
## 変更パターン
### 1. 基本的なエラーレスポンス
#### 修正前:
```python
response = {
"status": 400,
"result": {
"message": "Error message",
"data": some_value
}
}
```
#### 修正後:
```python
from errors import ErrorCode, VRCTError
response = VRCTError.create_error_response(
ErrorCode.APPROPRIATE_ERROR_CODE,
data=some_value
)
```
### 2. run_mapping経由のエラー通知
#### 修正前:
```python
self.run(
400,
self.run_mapping["error_device"],
{
"message": "No mic device detected",
"data": None
},
)
```
#### 修正後:
```python
error_response = VRCTError.create_error_response(
ErrorCode.DEVICE_NO_MIC,
data=None
)
self.run(
error_response["status"],
self.run_mapping["error_device"],
error_response["result"],
)
```
### 3. 例外からのエラー生成
#### 修正前:
```python
except Exception as e:
errorLogging()
response = {
"status": 400,
"result": {
"message": f"Error {e}",
"data": original_value
}
}
```
#### 修正後:
```python
except Exception as e:
errorLogging()
response = VRCTError.create_exception_error_response(
e,
data=original_value
)
```
## 既に移行済みの箇所
### デバイスエラー
-`progressBarMicEnergy` - `ErrorCode.DEVICE_NO_MIC`
-`progressBarSpeakerEnergy` - `ErrorCode.DEVICE_NO_SPEAKER`
### ウェイトダウンロードエラー
-`DownloadCTranslate2.downloaded` - `ErrorCode.WEIGHT_CTRANSLATE2_DOWNLOAD`
-`DownloadWhisper.downloaded` - `ErrorCode.WEIGHT_WHISPER_DOWNLOAD`
### 翻訳エラー
-`micMessage` - `ErrorCode.TRANSLATION_ENGINE_LIMIT`, `ErrorCode.TRANSLATION_VRAM_MIC`, `ErrorCode.TRANSLATION_DISABLED_VRAM`
-`speakerMessage` - `ErrorCode.TRANSLATION_ENGINE_LIMIT`, `ErrorCode.TRANSLATION_VRAM_SPEAKER`, `ErrorCode.TRANSLATION_DISABLED_VRAM`
-`chatMessage` - `ErrorCode.TRANSLATION_ENGINE_LIMIT`, `ErrorCode.TRANSLATION_VRAM_CHAT`, `ErrorCode.TRANSLATION_DISABLED_VRAM`
-`setEnableTranslation` - `ErrorCode.TRANSLATION_VRAM_ENABLE`, `ErrorCode.TRANSLATION_DISABLED_VRAM`
### バリデーションエラー
-`setMicThreshold` - `ErrorCode.VALIDATION_MIC_THRESHOLD`
-`setSpeakerThreshold` - `ErrorCode.VALIDATION_SPEAKER_THRESHOLD`
-`setMicRecordTimeout` - `ErrorCode.VALIDATION_MIC_RECORD_TIMEOUT`
-`setMicPhraseTimeout` - `ErrorCode.VALIDATION_MIC_PHRASE_TIMEOUT`
-`setMicMaxPhrases` - `ErrorCode.VALIDATION_MIC_MAX_PHRASES`
-`setSpeakerRecordTimeout` - `ErrorCode.VALIDATION_SPEAKER_RECORD_TIMEOUT`
-`setSpeakerPhraseTimeout` - `ErrorCode.VALIDATION_SPEAKER_PHRASE_TIMEOUT`
-`setSpeakerMaxPhrases` - `ErrorCode.VALIDATION_SPEAKER_MAX_PHRASES`
-`setOscIpAddress` - `ErrorCode.VALIDATION_INVALID_IP`, `ErrorCode.VALIDATION_CANNOT_SET_IP`
### VRC連携エラー
-`setEnableVrcMicMuteSync` - `ErrorCode.VRC_MIC_MUTE_SYNC_OSC_DISABLED`
### 認証エラー
-`setDeeplAuthKey` - `ErrorCode.AUTH_DEEPL_LENGTH`, `ErrorCode.AUTH_DEEPL_FAILED`
## 未移行の箇所(要対応)
以下の箇所は同様のパターンで移行が必要です:
### 認証関連
-`setPlamoAuthKey` - `ErrorCode.AUTH_PLAMO_LENGTH`, `ErrorCode.AUTH_PLAMO_FAILED`
-`setPlamoModel` - `ErrorCode.MODEL_PLAMO_INVALID`
-`setGeminiAuthKey` - `ErrorCode.AUTH_GEMINI_LENGTH`, `ErrorCode.AUTH_GEMINI_FAILED`
-`setGeminiModel` - `ErrorCode.MODEL_GEMINI_INVALID`
-`setOpenAIAuthKey` - `ErrorCode.AUTH_OPENAI_INVALID`, `ErrorCode.AUTH_OPENAI_FAILED`
-`setOpenAIModel` - `ErrorCode.MODEL_OPENAI_INVALID`
-`setGroqAuthKey` - `ErrorCode.AUTH_GROQ_INVALID`, `ErrorCode.AUTH_GROQ_FAILED`
-`setGroqModel` - `ErrorCode.MODEL_GROQ_INVALID`
-`setOpenRouterAuthKey` - `ErrorCode.AUTH_OPENROUTER_INVALID`, `ErrorCode.AUTH_OPENROUTER_FAILED`
-`setOpenRouterModel` - `ErrorCode.MODEL_OPENROUTER_INVALID`
### 接続関連
-`checkTranslatorLMStudioConnection` - `ErrorCode.CONNECTION_LMSTUDIO_FAILED`
-`setTranslatorLMStudioURL` - `ErrorCode.CONNECTION_LMSTUDIO_URL_INVALID`
-`setTranslatorLMStudioModel` - `ErrorCode.MODEL_LMSTUDIO_INVALID`
-`checkTranslatorOllamaConnection` - `ErrorCode.CONNECTION_OLLAMA_FAILED`
-`setTranslatorOllamaModel` - `ErrorCode.MODEL_OLLAMA_INVALID`
### WebSocket関連
-`setWebSocketHost` - `ErrorCode.VALIDATION_INVALID_IP`, `ErrorCode.WEBSOCKET_HOST_INVALID`
-`setWebSocketPort` - `ErrorCode.WEBSOCKET_PORT_UNAVAILABLE`
-`setEnableWebSocketServer` - `ErrorCode.WEBSOCKET_SERVER_UNAVAILABLE`
### 音声認識VRAM関連
-`startTranscriptionSendMessage` - `ErrorCode.TRANSCRIPTION_VRAM_MIC`, `ErrorCode.TRANSCRIPTION_SEND_DISABLED_VRAM`
-`startTranscriptionReceiveMessage` - `ErrorCode.TRANSCRIPTION_VRAM_SPEAKER`, `ErrorCode.TRANSCRIPTION_RECEIVE_DISABLED_VRAM`
## エラーコードとエンドポイントの対応
`errors.py``ENDPOINT_ERROR_MAPPING`に、すべてのエンドポイントとエラーコードの対応が定義されています。
UI開発者はこのマッピングを参照して、各エンドポイントがどのようなエラーを返すか確認できます。
## エラーレスポンスの構造
統一されたエラーレスポンスは以下の構造を持ちます:
```python
{
"status": 400, # HTTPステータスコード
"result": {
"error_code": "ERROR_CODE_CONSTANT", # エラーコード定数
"message": "Human readable message", # 人間が読めるメッセージ
"data": None or original_value, # エラー時に戻す値(通常は元の値)
"details": {}, # 追加情報(オプション)
"category": "category_name", # エラーカテゴリ
"severity": "warning|error|critical", # 重要度
}
}
```
## UI側での活用
UI側では`error_code`を使用して、エラーの種類を判定し、適切な処理を行うことができます:
```javascript
if (response.status === 400) {
const { error_code, message, data, severity } = response.result;
switch (error_code) {
case "DEVICE_NO_MIC":
// マイクデバイスエラーの処理
break;
case "VALIDATION_MIC_THRESHOLD":
// バリデーションエラーの処理(元の値に戻す)
setValue(data);
break;
// ...
}
// 重要度に応じた表示
if (severity === "critical") {
showCriticalError(message);
}
}
```
## 移行作業の進め方
1. **パターンの確認**: 上記の変更パターンを参照
2. **エラーコードの特定**: `errors.py`から適切な`ErrorCode`を選択
3. **コードの置き換え**: 古いエラーハンドリングを新しいシステムに置き換え
4. **テスト**: エラーが正しく返されることを確認
5. **チェックリストの更新**: このドキュメントの✅を更新
## 注意事項
- すべてのエラーは`errors.py`に定義されたエラーコードを使用すること
- 新しいエラーが必要な場合は、まず`errors.py`に追加すること
- エラーメッセージは`ERROR_METADATA`で定義されたデフォルトメッセージを使用すること
- カスタムメッセージが必要な場合は`custom_message`パラメータを使用
- `data`パラメータには、エラー時にUIが元の値に戻せるように、元の値を渡すこと

694
src-python/errors.py Normal file
View File

@@ -0,0 +1,694 @@
# src-python/errors.py
"""
統一エラー管理システム
すべてのエラーを一元管理し、エンドポイントとエラーコードの対応を明確にする。
"""
from typing import Any, Optional, Dict
from enum import Enum
class ErrorCode(str, Enum):
"""エラーコード定数
命名規則: カテゴリ_具体的な内容
"""
# ============================================================================
# デバイス関連エラー (DEVICE_*)
# ============================================================================
DEVICE_NO_MIC = "DEVICE_NO_MIC"
DEVICE_NO_SPEAKER = "DEVICE_NO_SPEAKER"
# ============================================================================
# 翻訳関連エラー (TRANSLATION_*)
# ============================================================================
TRANSLATION_ENGINE_LIMIT = "TRANSLATION_ENGINE_LIMIT"
TRANSLATION_VRAM_CHAT = "TRANSLATION_VRAM_CHAT"
TRANSLATION_VRAM_MIC = "TRANSLATION_VRAM_MIC"
TRANSLATION_VRAM_SPEAKER = "TRANSLATION_VRAM_SPEAKER"
TRANSLATION_VRAM_ENABLE = "TRANSLATION_VRAM_ENABLE"
TRANSLATION_DISABLED_VRAM = "TRANSLATION_DISABLED_VRAM"
# ============================================================================
# 音声認識関連エラー (TRANSCRIPTION_*)
# ============================================================================
TRANSCRIPTION_VRAM_MIC = "TRANSCRIPTION_VRAM_MIC"
TRANSCRIPTION_VRAM_SPEAKER = "TRANSCRIPTION_VRAM_SPEAKER"
TRANSCRIPTION_SEND_DISABLED_VRAM = "TRANSCRIPTION_SEND_DISABLED_VRAM"
TRANSCRIPTION_RECEIVE_DISABLED_VRAM = "TRANSCRIPTION_RECEIVE_DISABLED_VRAM"
# ============================================================================
# ウェイトダウンロード関連エラー (WEIGHT_*)
# ============================================================================
WEIGHT_CTRANSLATE2_DOWNLOAD = "WEIGHT_CTRANSLATE2_DOWNLOAD"
WEIGHT_WHISPER_DOWNLOAD = "WEIGHT_WHISPER_DOWNLOAD"
# ============================================================================
# バリデーションエラー (VALIDATION_*)
# ============================================================================
VALIDATION_MIC_THRESHOLD = "VALIDATION_MIC_THRESHOLD"
VALIDATION_SPEAKER_THRESHOLD = "VALIDATION_SPEAKER_THRESHOLD"
VALIDATION_MIC_RECORD_TIMEOUT = "VALIDATION_MIC_RECORD_TIMEOUT"
VALIDATION_MIC_PHRASE_TIMEOUT = "VALIDATION_MIC_PHRASE_TIMEOUT"
VALIDATION_MIC_MAX_PHRASES = "VALIDATION_MIC_MAX_PHRASES"
VALIDATION_SPEAKER_RECORD_TIMEOUT = "VALIDATION_SPEAKER_RECORD_TIMEOUT"
VALIDATION_SPEAKER_PHRASE_TIMEOUT = "VALIDATION_SPEAKER_PHRASE_TIMEOUT"
VALIDATION_SPEAKER_MAX_PHRASES = "VALIDATION_SPEAKER_MAX_PHRASES"
VALIDATION_INVALID_IP = "VALIDATION_INVALID_IP"
VALIDATION_CANNOT_SET_IP = "VALIDATION_CANNOT_SET_IP"
# ============================================================================
# 認証エラー (AUTH_*)
# ============================================================================
AUTH_DEEPL_LENGTH = "AUTH_DEEPL_LENGTH"
AUTH_DEEPL_FAILED = "AUTH_DEEPL_FAILED"
AUTH_PLAMO_LENGTH = "AUTH_PLAMO_LENGTH"
AUTH_PLAMO_FAILED = "AUTH_PLAMO_FAILED"
AUTH_GEMINI_LENGTH = "AUTH_GEMINI_LENGTH"
AUTH_GEMINI_FAILED = "AUTH_GEMINI_FAILED"
AUTH_OPENAI_INVALID = "AUTH_OPENAI_INVALID"
AUTH_OPENAI_FAILED = "AUTH_OPENAI_FAILED"
AUTH_GROQ_INVALID = "AUTH_GROQ_INVALID"
AUTH_GROQ_FAILED = "AUTH_GROQ_FAILED"
AUTH_OPENROUTER_INVALID = "AUTH_OPENROUTER_INVALID"
AUTH_OPENROUTER_FAILED = "AUTH_OPENROUTER_FAILED"
# ============================================================================
# モデル選択エラー (MODEL_*)
# ============================================================================
MODEL_PLAMO_INVALID = "MODEL_PLAMO_INVALID"
MODEL_GEMINI_INVALID = "MODEL_GEMINI_INVALID"
MODEL_OPENAI_INVALID = "MODEL_OPENAI_INVALID"
MODEL_GROQ_INVALID = "MODEL_GROQ_INVALID"
MODEL_OPENROUTER_INVALID = "MODEL_OPENROUTER_INVALID"
MODEL_LMSTUDIO_INVALID = "MODEL_LMSTUDIO_INVALID"
MODEL_OLLAMA_INVALID = "MODEL_OLLAMA_INVALID"
# ============================================================================
# 接続エラー (CONNECTION_*)
# ============================================================================
CONNECTION_LMSTUDIO_FAILED = "CONNECTION_LMSTUDIO_FAILED"
CONNECTION_OLLAMA_FAILED = "CONNECTION_OLLAMA_FAILED"
CONNECTION_LMSTUDIO_URL_INVALID = "CONNECTION_LMSTUDIO_URL_INVALID"
# ============================================================================
# WebSocketエラー (WEBSOCKET_*)
# ============================================================================
WEBSOCKET_HOST_INVALID = "WEBSOCKET_HOST_INVALID"
WEBSOCKET_PORT_UNAVAILABLE = "WEBSOCKET_PORT_UNAVAILABLE"
WEBSOCKET_SERVER_UNAVAILABLE = "WEBSOCKET_SERVER_UNAVAILABLE"
# ============================================================================
# VRC連携エラー (VRC_*)
# ============================================================================
VRC_MIC_MUTE_SYNC_OSC_DISABLED = "VRC_MIC_MUTE_SYNC_OSC_DISABLED"
# ============================================================================
# 汎用エラー (GENERAL_*)
# ============================================================================
GENERAL_EXCEPTION = "GENERAL_EXCEPTION"
GENERAL_UNKNOWN = "GENERAL_UNKNOWN"
class ErrorCategory(str, Enum):
"""エラーカテゴリ"""
DEVICE = "device"
TRANSLATION = "translation"
TRANSCRIPTION = "transcription"
WEIGHT = "weight"
VALIDATION = "validation"
AUTH = "auth"
MODEL = "model"
CONNECTION = "connection"
WEBSOCKET = "websocket"
VRC = "vrc"
GENERAL = "general"
# エラーコードのメタデータ定義
ERROR_METADATA: Dict[ErrorCode, Dict[str, Any]] = {
# デバイスエラー
ErrorCode.DEVICE_NO_MIC: {
"category": ErrorCategory.DEVICE,
"message": "No mic device detected",
"severity": "error",
"user_action_required": True,
},
ErrorCode.DEVICE_NO_SPEAKER: {
"category": ErrorCategory.DEVICE,
"message": "No speaker device detected",
"severity": "error",
"user_action_required": True,
},
# 翻訳エラー
ErrorCode.TRANSLATION_ENGINE_LIMIT: {
"category": ErrorCategory.TRANSLATION,
"message": "Translation engine limit error",
"severity": "warning",
"user_action_required": False,
"auto_fallback": True,
},
ErrorCode.TRANSLATION_VRAM_CHAT: {
"category": ErrorCategory.TRANSLATION,
"message": "VRAM out of memory during translation of chat",
"severity": "critical",
"user_action_required": True,
},
ErrorCode.TRANSLATION_VRAM_MIC: {
"category": ErrorCategory.TRANSLATION,
"message": "VRAM out of memory during translation of mic",
"severity": "critical",
"user_action_required": True,
},
ErrorCode.TRANSLATION_VRAM_SPEAKER: {
"category": ErrorCategory.TRANSLATION,
"message": "VRAM out of memory during translation of speaker",
"severity": "critical",
"user_action_required": True,
},
ErrorCode.TRANSLATION_VRAM_ENABLE: {
"category": ErrorCategory.TRANSLATION,
"message": "VRAM out of memory enabling translation",
"severity": "critical",
"user_action_required": True,
},
ErrorCode.TRANSLATION_DISABLED_VRAM: {
"category": ErrorCategory.TRANSLATION,
"message": "Translation disabled due to VRAM overflow",
"severity": "critical",
"user_action_required": True,
},
# 音声認識エラー
ErrorCode.TRANSCRIPTION_VRAM_MIC: {
"category": ErrorCategory.TRANSCRIPTION,
"message": "VRAM out of memory during mic transcription",
"severity": "critical",
"user_action_required": True,
},
ErrorCode.TRANSCRIPTION_VRAM_SPEAKER: {
"category": ErrorCategory.TRANSCRIPTION,
"message": "VRAM out of memory during speaker transcription",
"severity": "critical",
"user_action_required": True,
},
ErrorCode.TRANSCRIPTION_SEND_DISABLED_VRAM: {
"category": ErrorCategory.TRANSCRIPTION,
"message": "Transcription send disabled due to VRAM overflow",
"severity": "critical",
"user_action_required": True,
},
ErrorCode.TRANSCRIPTION_RECEIVE_DISABLED_VRAM: {
"category": ErrorCategory.TRANSCRIPTION,
"message": "Transcription receive disabled due to VRAM overflow",
"severity": "critical",
"user_action_required": True,
},
# ウェイトダウンロードエラー
ErrorCode.WEIGHT_CTRANSLATE2_DOWNLOAD: {
"category": ErrorCategory.WEIGHT,
"message": "CTranslate2 weight download error",
"severity": "error",
"user_action_required": True,
},
ErrorCode.WEIGHT_WHISPER_DOWNLOAD: {
"category": ErrorCategory.WEIGHT,
"message": "Whisper weight download error",
"severity": "error",
"user_action_required": True,
},
# バリデーションエラー
ErrorCode.VALIDATION_MIC_THRESHOLD: {
"category": ErrorCategory.VALIDATION,
"message": "Mic energy threshold value is out of range",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_SPEAKER_THRESHOLD: {
"category": ErrorCategory.VALIDATION,
"message": "Speaker energy threshold value is out of range",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_MIC_RECORD_TIMEOUT: {
"category": ErrorCategory.VALIDATION,
"message": "Mic record timeout value is out of range",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_MIC_PHRASE_TIMEOUT: {
"category": ErrorCategory.VALIDATION,
"message": "Mic phrase timeout value is out of range",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_MIC_MAX_PHRASES: {
"category": ErrorCategory.VALIDATION,
"message": "Mic max phrases value is out of range",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_SPEAKER_RECORD_TIMEOUT: {
"category": ErrorCategory.VALIDATION,
"message": "Speaker record timeout value is out of range",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_SPEAKER_PHRASE_TIMEOUT: {
"category": ErrorCategory.VALIDATION,
"message": "Speaker phrase timeout value is out of range",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_SPEAKER_MAX_PHRASES: {
"category": ErrorCategory.VALIDATION,
"message": "Speaker max phrases value is out of range",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_INVALID_IP: {
"category": ErrorCategory.VALIDATION,
"message": "Invalid IP address",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.VALIDATION_CANNOT_SET_IP: {
"category": ErrorCategory.VALIDATION,
"message": "Cannot set IP address",
"severity": "error",
"user_action_required": True,
},
# 認証エラー
ErrorCode.AUTH_DEEPL_LENGTH: {
"category": ErrorCategory.AUTH,
"message": "DeepL auth key length is not correct",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.AUTH_DEEPL_FAILED: {
"category": ErrorCategory.AUTH,
"message": "Authentication failure of deepL auth key",
"severity": "error",
"user_action_required": True,
},
ErrorCode.AUTH_PLAMO_LENGTH: {
"category": ErrorCategory.AUTH,
"message": "Plamo auth key length is not correct",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.AUTH_PLAMO_FAILED: {
"category": ErrorCategory.AUTH,
"message": "Authentication failure of plamo auth key",
"severity": "error",
"user_action_required": True,
},
ErrorCode.AUTH_GEMINI_LENGTH: {
"category": ErrorCategory.AUTH,
"message": "Gemini auth key length is not correct",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.AUTH_GEMINI_FAILED: {
"category": ErrorCategory.AUTH,
"message": "Authentication failure of gemini auth key",
"severity": "error",
"user_action_required": True,
},
ErrorCode.AUTH_OPENAI_INVALID: {
"category": ErrorCategory.AUTH,
"message": "OpenAI auth key is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.AUTH_OPENAI_FAILED: {
"category": ErrorCategory.AUTH,
"message": "Authentication failure of OpenAI auth key",
"severity": "error",
"user_action_required": True,
},
ErrorCode.AUTH_GROQ_INVALID: {
"category": ErrorCategory.AUTH,
"message": "Groq auth key is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.AUTH_GROQ_FAILED: {
"category": ErrorCategory.AUTH,
"message": "Authentication failure of Groq auth key",
"severity": "error",
"user_action_required": True,
},
ErrorCode.AUTH_OPENROUTER_INVALID: {
"category": ErrorCategory.AUTH,
"message": "OpenRouter auth key is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.AUTH_OPENROUTER_FAILED: {
"category": ErrorCategory.AUTH,
"message": "Authentication failure of OpenRouter auth key",
"severity": "error",
"user_action_required": True,
},
# モデル選択エラー
ErrorCode.MODEL_PLAMO_INVALID: {
"category": ErrorCategory.MODEL,
"message": "Plamo model is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.MODEL_GEMINI_INVALID: {
"category": ErrorCategory.MODEL,
"message": "Gemini model is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.MODEL_OPENAI_INVALID: {
"category": ErrorCategory.MODEL,
"message": "OpenAI model is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.MODEL_GROQ_INVALID: {
"category": ErrorCategory.MODEL,
"message": "Groq model is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.MODEL_OPENROUTER_INVALID: {
"category": ErrorCategory.MODEL,
"message": "OpenRouter model is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.MODEL_LMSTUDIO_INVALID: {
"category": ErrorCategory.MODEL,
"message": "LMStudio model is not valid",
"severity": "warning",
"user_action_required": True,
},
ErrorCode.MODEL_OLLAMA_INVALID: {
"category": ErrorCategory.MODEL,
"message": "ollama model is not valid",
"severity": "warning",
"user_action_required": True,
},
# 接続エラー
ErrorCode.CONNECTION_LMSTUDIO_FAILED: {
"category": ErrorCategory.CONNECTION,
"message": "Cannot connect to LMStudio server",
"severity": "error",
"user_action_required": True,
},
ErrorCode.CONNECTION_OLLAMA_FAILED: {
"category": ErrorCategory.CONNECTION,
"message": "Cannot connect to ollama server",
"severity": "error",
"user_action_required": True,
},
ErrorCode.CONNECTION_LMSTUDIO_URL_INVALID: {
"category": ErrorCategory.CONNECTION,
"message": "LMStudio URL is not valid",
"severity": "warning",
"user_action_required": True,
},
# WebSocketエラー
ErrorCode.WEBSOCKET_HOST_INVALID: {
"category": ErrorCategory.WEBSOCKET,
"message": "WebSocket server host is not available",
"severity": "error",
"user_action_required": True,
},
ErrorCode.WEBSOCKET_PORT_UNAVAILABLE: {
"category": ErrorCategory.WEBSOCKET,
"message": "WebSocket server port is not available",
"severity": "error",
"user_action_required": True,
},
ErrorCode.WEBSOCKET_SERVER_UNAVAILABLE: {
"category": ErrorCategory.WEBSOCKET,
"message": "WebSocket server host or port is not available",
"severity": "error",
"user_action_required": True,
},
# VRC連携エラー
ErrorCode.VRC_MIC_MUTE_SYNC_OSC_DISABLED: {
"category": ErrorCategory.VRC,
"message": "Cannot enable VRC mic mute sync while OSC query is disabled",
"severity": "warning",
"user_action_required": True,
},
# 汎用エラー
ErrorCode.GENERAL_EXCEPTION: {
"category": ErrorCategory.GENERAL,
"message": "An error occurred",
"severity": "error",
"user_action_required": False,
},
ErrorCode.GENERAL_UNKNOWN: {
"category": ErrorCategory.GENERAL,
"message": "Unknown error",
"severity": "error",
"user_action_required": False,
},
}
class VRCTError:
"""VRCTエラーハンドリングクラス"""
@staticmethod
def create_error_response(
error_code: ErrorCode,
data: Any = None,
details: Optional[Dict[str, Any]] = None,
custom_message: Optional[str] = None
) -> Dict[str, Any]:
"""統一されたエラーレスポンスを生成
Args:
error_code: エラーコード
data: エラー時に戻す値(通常は元の値)
details: 追加の詳細情報
custom_message: カスタムメッセージ(指定しない場合はデフォルトメッセージ)
Returns:
エラーレスポンス辞書
"""
metadata = ERROR_METADATA.get(error_code, ERROR_METADATA[ErrorCode.GENERAL_UNKNOWN])
return {
"status": 400,
"result": {
"error_code": error_code.value,
"message": custom_message or metadata["message"],
"data": data,
"details": details or {},
"category": metadata["category"].value,
"severity": metadata["severity"],
}
}
@staticmethod
def create_exception_error_response(
exception: Exception,
data: Any = None,
error_code: ErrorCode = ErrorCode.GENERAL_EXCEPTION
) -> Dict[str, Any]:
"""例外からエラーレスポンスを生成
Args:
exception: 発生した例外
data: エラー時に戻す値
error_code: エラーコード
Returns:
エラーレスポンス辞書
"""
return VRCTError.create_error_response(
error_code=error_code,
data=data,
custom_message=f"Error: {str(exception)}",
details={"exception_type": type(exception).__name__}
)
# エンドポイントとエラーコードのマッピング
# UIがエラーハンドリングする際の参照として使用
ENDPOINT_ERROR_MAPPING: Dict[str, Dict[str, ErrorCode]] = {
# run_mapping経由のエラー通知
"/run/error_device": {
"NO_MIC": ErrorCode.DEVICE_NO_MIC,
"NO_SPEAKER": ErrorCode.DEVICE_NO_SPEAKER,
},
"/run/error_translation_engine": {
"LIMIT": ErrorCode.TRANSLATION_ENGINE_LIMIT,
},
"/run/error_translation_chat_vram_overflow": {
"VRAM": ErrorCode.TRANSLATION_VRAM_CHAT,
},
"/run/error_translation_mic_vram_overflow": {
"VRAM": ErrorCode.TRANSLATION_VRAM_MIC,
},
"/run/error_translation_speaker_vram_overflow": {
"VRAM": ErrorCode.TRANSLATION_VRAM_SPEAKER,
},
"/run/error_transcription_mic_vram_overflow": {
"VRAM": ErrorCode.TRANSCRIPTION_VRAM_MIC,
},
"/run/error_transcription_speaker_vram_overflow": {
"VRAM": ErrorCode.TRANSCRIPTION_VRAM_SPEAKER,
},
"/run/error_ctranslate2_weight": {
"DOWNLOAD": ErrorCode.WEIGHT_CTRANSLATE2_DOWNLOAD,
},
"/run/error_whisper_weight": {
"DOWNLOAD": ErrorCode.WEIGHT_WHISPER_DOWNLOAD,
},
# エンドポイント直接のエラーレスポンス
"/set/data/mic_threshold": {
"OUT_OF_RANGE": ErrorCode.VALIDATION_MIC_THRESHOLD,
},
"/set/data/speaker_threshold": {
"OUT_OF_RANGE": ErrorCode.VALIDATION_SPEAKER_THRESHOLD,
},
"/set/data/mic_record_timeout": {
"OUT_OF_RANGE": ErrorCode.VALIDATION_MIC_RECORD_TIMEOUT,
},
"/set/data/mic_phrase_timeout": {
"OUT_OF_RANGE": ErrorCode.VALIDATION_MIC_PHRASE_TIMEOUT,
},
"/set/data/mic_max_phrases": {
"OUT_OF_RANGE": ErrorCode.VALIDATION_MIC_MAX_PHRASES,
},
"/set/data/speaker_record_timeout": {
"OUT_OF_RANGE": ErrorCode.VALIDATION_SPEAKER_RECORD_TIMEOUT,
},
"/set/data/speaker_phrase_timeout": {
"OUT_OF_RANGE": ErrorCode.VALIDATION_SPEAKER_PHRASE_TIMEOUT,
},
"/set/data/speaker_max_phrases": {
"OUT_OF_RANGE": ErrorCode.VALIDATION_SPEAKER_MAX_PHRASES,
},
"/set/data/osc_ip_address": {
"INVALID": ErrorCode.VALIDATION_INVALID_IP,
"CANNOT_SET": ErrorCode.VALIDATION_CANNOT_SET_IP,
},
"/set/data/deepl_auth_key": {
"LENGTH": ErrorCode.AUTH_DEEPL_LENGTH,
"FAILED": ErrorCode.AUTH_DEEPL_FAILED,
},
"/set/data/plamo_auth_key": {
"LENGTH": ErrorCode.AUTH_PLAMO_LENGTH,
"FAILED": ErrorCode.AUTH_PLAMO_FAILED,
},
"/set/data/selected_plamo_model": {
"INVALID": ErrorCode.MODEL_PLAMO_INVALID,
},
"/set/data/gemini_auth_key": {
"LENGTH": ErrorCode.AUTH_GEMINI_LENGTH,
"FAILED": ErrorCode.AUTH_GEMINI_FAILED,
},
"/set/data/selected_gemini_model": {
"INVALID": ErrorCode.MODEL_GEMINI_INVALID,
},
"/set/data/openai_auth_key": {
"INVALID": ErrorCode.AUTH_OPENAI_INVALID,
"FAILED": ErrorCode.AUTH_OPENAI_FAILED,
},
"/set/data/selected_openai_model": {
"INVALID": ErrorCode.MODEL_OPENAI_INVALID,
},
"/set/data/groq_auth_key": {
"INVALID": ErrorCode.AUTH_GROQ_INVALID,
"FAILED": ErrorCode.AUTH_GROQ_FAILED,
},
"/set/data/selected_groq_model": {
"INVALID": ErrorCode.MODEL_GROQ_INVALID,
},
"/set/data/openrouter_auth_key": {
"INVALID": ErrorCode.AUTH_OPENROUTER_INVALID,
"FAILED": ErrorCode.AUTH_OPENROUTER_FAILED,
},
"/set/data/selected_openrouter_model": {
"INVALID": ErrorCode.MODEL_OPENROUTER_INVALID,
},
"/run/lmstudio_connection": {
"FAILED": ErrorCode.CONNECTION_LMSTUDIO_FAILED,
},
"/set/data/lmstudio_url": {
"INVALID": ErrorCode.CONNECTION_LMSTUDIO_URL_INVALID,
},
"/set/data/selected_lmstudio_model": {
"INVALID": ErrorCode.MODEL_LMSTUDIO_INVALID,
},
"/run/ollama_connection": {
"FAILED": ErrorCode.CONNECTION_OLLAMA_FAILED,
},
"/set/data/selected_ollama_model": {
"INVALID": ErrorCode.MODEL_OLLAMA_INVALID,
},
"/set/data/websocket_host": {
"INVALID_IP": ErrorCode.VALIDATION_INVALID_IP,
"UNAVAILABLE": ErrorCode.WEBSOCKET_HOST_INVALID,
},
"/set/data/websocket_port": {
"UNAVAILABLE": ErrorCode.WEBSOCKET_PORT_UNAVAILABLE,
},
"/set/enable/websocket_server": {
"UNAVAILABLE": ErrorCode.WEBSOCKET_SERVER_UNAVAILABLE,
},
"/set/enable/vrc_mic_mute_sync": {
"OSC_DISABLED": ErrorCode.VRC_MIC_MUTE_SYNC_OSC_DISABLED,
},
}
def get_error_metadata(error_code: ErrorCode) -> Dict[str, Any]:
"""エラーコードのメタデータを取得
Args:
error_code: エラーコード
Returns:
メタデータ辞書
"""
return ERROR_METADATA.get(error_code, ERROR_METADATA[ErrorCode.GENERAL_UNKNOWN])
def is_critical_error(error_code: ErrorCode) -> bool:
"""クリティカルエラーかどうかを判定
Args:
error_code: エラーコード
Returns:
クリティカルエラーの場合True
"""
metadata = get_error_metadata(error_code)
return metadata.get("severity") == "critical"
def requires_user_action(error_code: ErrorCode) -> bool:
"""ユーザーアクションが必要なエラーかどうかを判定
Args:
error_code: エラーコード
Returns:
ユーザーアクションが必要な場合True
"""
metadata = get_error_metadata(error_code)
return metadata.get("user_action_required", False)