File size: 5,071 Bytes
6a71f13
fac50d3
6a71f13
 
 
 
 
 
 
 
 
227e75d
 
6a71f13
 
 
 
 
 
 
 
 
 
 
fac50d3
227e75d
6a71f13
 
 
 
0520d98
 
 
 
 
 
 
 
 
 
 
 
6a71f13
227e75d
6a71f13
227e75d
 
 
 
 
 
 
 
 
6a71f13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227e75d
 
6a71f13
227e75d
8969ec5
 
 
 
6a71f13
e32a461
8969ec5
6a71f13
 
 
 
8969ec5
 
 
 
6a71f13
 
227e75d
6a71f13
 
8969ec5
227e75d
8baa940
 
 
 
 
 
 
 
 
 
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from typing import Optional, List, Dict, Any
from openai import OpenAI
import anthropic
from dataclasses import dataclass
from config.llm_settings import LLMSettings
from core.file_scanner import FileInfo

@dataclass
class Message:
    role: str
    content: str

class LLMService:
    MAX_TURNS = 5
    
    def __init__(self):
        """LLMサービスの初期化"""
        self.settings = LLMSettings()
        self.current_model = self.settings.default_llm
        
        # API クライアントの初期化
        if self.settings.anthropic_api_key:
            self.claude_client = anthropic.Anthropic(api_key=self.settings.anthropic_api_key)
        if self.settings.openai_api_key:
            self.openai_client = OpenAI(api_key=self.settings.openai_api_key)
        
        self.conversation_history: List[Message] = []
    
    def switch_model(self, model: str):
        """使用するモデルを切り替え"""
        model_name = model.lower()
        if model_name == "claude" and self.settings.anthropic_api_key:
            self.current_model = "claude"
        elif model_name == "openai" and self.settings.openai_api_key:
            self.current_model = "openai"
        else:
            available = []
            if self.settings.anthropic_api_key:
                available.append("claude")
            if self.settings.openai_api_key:
                available.append("openai")
            raise ValueError(f"利用可能なモデル: {', '.join(available)}")
    
    def create_prompt(self, content: str, query: str) -> str:
        """プロンプトを生成"""
        return f"""以下はGitHubリポジトリのコード解析結果です。このコードについて質問に答えてください。

コード解析結果:
{content}

質問: {query}

できるだけ具体的に、コードの内容を参照しながら回答してください。"""

    def _add_to_history(self, role: str, content: str):
        """会話履歴に追加(最大5ターン)"""
        self.conversation_history.append(Message(role=role, content=content))
        # 最大ターン数を超えた場合、古い会話を削除
        if len(self.conversation_history) > self.MAX_TURNS * 2:  # 各ターンは質問と回答で2メッセージ
            self.conversation_history = self.conversation_history[-self.MAX_TURNS * 2:]
    
    def _format_messages_for_claude(self) -> List[Dict[str, str]]:
        """Claude用にメッセージをフォーマット"""
        return [{"role": msg.role, "content": msg.content} 
                for msg in self.conversation_history]

    def _format_messages_for_gpt(self) -> List[Dict[str, str]]:
        """GPT用にメッセージをフォーマット"""
        return [
            {"role": "system", "content": "あなたはコードアナリストとして、リポジトリの解析と質問への回答を行います。"},
            *[{"role": msg.role, "content": msg.content} 
              for msg in self.conversation_history]
        ]

    def get_conversation_history(self) -> List[Dict[str, str]]:
        """会話履歴を取得"""
        return [{"role": msg.role, "content": msg.content} 
                for msg in self.conversation_history]
    
    def clear_history(self):
        """会話履歴をクリア"""
        self.conversation_history = []

    def get_response(self, content: str, query: str) -> tuple[Optional[str], Optional[str]]:
        """LLMを使用して回答を生成"""
        try:
            prompt = self.create_prompt(content, query)
            self._add_to_history("user", prompt)
            
            print(f"Current model: {self.current_model}")  # デバッグ用出力
            
            if self.current_model.lower() == 'claude':
                print("Using Claude API")  # デバッグ用出力
                response = self.claude_client.messages.create(
                    model="claude-3-5-sonnet-latest",
                    messages=self._format_messages_for_claude()
                )
                answer = response.content[0].text
                
            else:  # gpt
                print("Using GPT API")  # デバッグ用出力
                response = openai.chat.completions.create(
                    model="gpt-4",
                    messages=self._format_messages_for_gpt()
                )
                answer = response.choices[0].message.content
            
            self._add_to_history("assistant", answer)
            return answer, None
                
        except Exception as e:
            return None, f"エラーが発生しました: {str(e)}"
        
    @staticmethod
    def format_code_content(files: List[FileInfo]) -> str:
        """ファイル内容をプロンプト用にフォーマット"""
        formatted_content = []
        for file_info in files:
            formatted_content.append(
                f"#ファイルパス\n{file_info.path}\n------------\n{file_info.content}\n"
            )
        return "\n".join(formatted_content)