File size: 3,941 Bytes
0447f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""
BaseAI - すべてのAIモデルアダプターの基底クラス
"""
from abc import ABC, abstractmethod
from typing import List, Tuple, Optional


class BaseAI(ABC):
    """
    すべてのAIモデルアダプターが実装すべき基底クラス
    
    各モデルは以下のメソッドを実装する必要があります:
    - get_token_probabilities: トークン確率の取得
    - build_chat_prompt: モデル固有のプロンプト形式への変換
    """
    
    @abstractmethod
    def get_token_probabilities(self, text: str, k: int = 5) -> List[Tuple[str, float]]:
        """
        テキストから次のトークン候補と確率を取得
        
        Args:
            text: 入力テキスト(プロンプト)
            k: 取得するトークン候補数
            
        Returns:
            List[Tuple[str, float]]: (トークン, 確率)のリスト(確率順)
        """
        raise NotImplementedError
    
    @abstractmethod
    def build_chat_prompt(
        self, 
        user_content: str, 
        system_content: str = "",
        assistant_content: Optional[str] = None
    ) -> str:
        """
        モデル固有のチャットプロンプト形式に変換
        
        注意: モデルによってuser/assistantの分離方法が異なります
        - OpenAI, Claude: user/assistantを明確に分離することを推奨
        - Gemini: user/assistantを分離しない方が良い場合もある
        - Transformers: モデルによって異なる(Llamaは分離推奨)
        
        Args:
            user_content: ユーザーのメッセージ
            system_content: システムプロンプト(オプション)
            assistant_content: アシスタントの既存応答(会話履歴用、オプション)
            
        Returns:
            str: モデル固有のプロンプト形式
        """
        raise NotImplementedError
    
    def _clean_text(self, text: str) -> str:
        """
        制御文字・不可視文字・置換文字を厳密に取り除く(共通処理)
        
        Args:
            text: クリーンアップするテキスト
            
        Returns:
            str: クリーンアップされたテキスト
        """
        if not text:
            return ""
        
        # 制御文字(0x00-0x1F、0x7F-0x9F)を除去
        # ただし、改行・タブ・復帰は許可
        cleaned = []
        for ch in text:
            code = ord(ch)
            # 許可する制御文字: 改行(0x0A), タブ(0x09), 復帰(0x0D)
            if code in [0x09, 0x0A, 0x0D]:
                cleaned.append(ch)
            # 通常の印刷可能文字
            elif ch.isprintable():
                # 置換文字(U+FFFD)を除去
                if ch != "\uFFFD":
                    cleaned.append(ch)
            # その他の制御文字や不可視文字は除去
        
        result = "".join(cleaned)
        # ゼロ幅文字を除去
        result = result.replace("\u200B", "")  # Zero-width space
        result = result.replace("\u200C", "")  # Zero-width non-joiner
        result = result.replace("\u200D", "")  # Zero-width joiner
        result = result.replace("\uFEFF", "")  # Zero-width no-break space
        # その他の不可視文字(結合文字など)を除去
        result = result.replace("\u200E", "")  # Left-to-right mark
        result = result.replace("\u200F", "")  # Right-to-left mark
        result = result.replace("\u202A", "")  # Left-to-right embedding
        result = result.replace("\u202B", "")  # Right-to-left embedding
        result = result.replace("\u202C", "")  # Pop directional formatting
        result = result.replace("\u202D", "")  # Left-to-right override
        result = result.replace("\u202E", "")  # Right-to-left override
        return result.strip()