Spaces:
Running
Running
DeL-TaiseiOzaki
commited on
Commit
·
6a71f13
1
Parent(s):
d8c5049
LLMと会話する機能を追加
Browse files- app.py +67 -34
- config/__pycache__/__init__.cpython-310.pyc +0 -0
- config/llm_settings.py +24 -0
- core/__pycache__/__init__.cpython-310.pyc +0 -0
- core/__pycache__/file_scanner.cpython-310.pyc +0 -0
- main.py +0 -69
- scan.sh +0 -49
- services/__init__.py +0 -0
- services/llm_service.py +83 -51
app.py
CHANGED
@@ -1,10 +1,9 @@
|
|
1 |
import streamlit as st
|
2 |
import tempfile
|
3 |
import git
|
|
|
4 |
from pathlib import Path
|
5 |
from datetime import datetime
|
6 |
-
import time
|
7 |
-
from core.file_scanner import FileScanner
|
8 |
from services.llm_service import LLMService
|
9 |
|
10 |
# ページ設定
|
@@ -14,20 +13,34 @@ st.set_page_config(
|
|
14 |
layout="wide"
|
15 |
)
|
16 |
|
17 |
-
#
|
18 |
st.markdown("""
|
19 |
<style>
|
20 |
-
.
|
|
|
|
|
|
|
|
|
21 |
padding: 1rem;
|
22 |
margin: 1rem 0;
|
|
|
23 |
}
|
24 |
-
.
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
}
|
28 |
</style>
|
29 |
""", unsafe_allow_html=True)
|
30 |
|
|
|
31 |
def clone_repository(repo_url: str) -> Path:
|
32 |
"""リポジトリをクローンして一時ディレクトリに保存"""
|
33 |
temp_dir = Path(tempfile.mkdtemp())
|
@@ -40,15 +53,24 @@ if 'repo_content' not in st.session_state:
|
|
40 |
if 'temp_dir' not in st.session_state:
|
41 |
st.session_state.temp_dir = None
|
42 |
if 'llm_service' not in st.session_state:
|
43 |
-
|
|
|
|
|
|
|
|
|
44 |
|
45 |
# メインのUIレイアウト
|
46 |
st.title("🔍 リポジトリ解析・質問システム")
|
47 |
|
48 |
-
#
|
49 |
-
|
50 |
-
if
|
51 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
# URLの入力
|
54 |
repo_url = st.text_input(
|
@@ -65,38 +87,50 @@ if st.button("スキャン開始", disabled=not repo_url):
|
|
65 |
|
66 |
with st.spinner('ファイルをスキャン中...'):
|
67 |
scanner = FileScanner(temp_dir)
|
68 |
-
|
69 |
-
|
70 |
-
if st.session_state.llm_service:
|
71 |
-
st.session_state.repo_content = LLMService.format_code_content(files_content)
|
72 |
|
73 |
-
st.success(f"スキャン完了: {len(
|
|
|
|
|
74 |
|
75 |
except Exception as e:
|
76 |
st.error(f"エラーが発生しました: {str(e)}")
|
77 |
|
78 |
# スキャン完了後の質問セクション
|
79 |
-
if st.session_state.repo_content
|
80 |
st.divider()
|
81 |
st.subheader("💭 コードについて質問する")
|
82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
query = st.text_area(
|
84 |
"質問を入力してください",
|
85 |
placeholder="例: このコードの主な機能は何ですか?"
|
86 |
)
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
|
101 |
# セッション終了時のクリーンアップ
|
102 |
if st.session_state.temp_dir and Path(st.session_state.temp_dir).exists():
|
@@ -110,10 +144,9 @@ if st.session_state.temp_dir and Path(st.session_state.temp_dir).exists():
|
|
110 |
with st.sidebar:
|
111 |
st.subheader("📌 使い方")
|
112 |
st.markdown("""
|
113 |
-
1.
|
114 |
-
2.
|
115 |
-
3.
|
116 |
-
4. コードについて質問
|
117 |
""")
|
118 |
|
119 |
st.subheader("🔍 スキャン対象")
|
|
|
1 |
import streamlit as st
|
2 |
import tempfile
|
3 |
import git
|
4 |
+
from core.file_scanner import FileScanner
|
5 |
from pathlib import Path
|
6 |
from datetime import datetime
|
|
|
|
|
7 |
from services.llm_service import LLMService
|
8 |
|
9 |
# ページ設定
|
|
|
13 |
layout="wide"
|
14 |
)
|
15 |
|
16 |
+
# ダークテーマの設定
|
17 |
st.markdown("""
|
18 |
<style>
|
19 |
+
.stApp {
|
20 |
+
background-color: #0e1117;
|
21 |
+
color: #ffffff;
|
22 |
+
}
|
23 |
+
.chat-message {
|
24 |
padding: 1rem;
|
25 |
margin: 1rem 0;
|
26 |
+
border-radius: 0.5rem;
|
27 |
}
|
28 |
+
.assistant-message {
|
29 |
+
background-color: #1e2329;
|
30 |
+
color: #ffffff;
|
31 |
+
}
|
32 |
+
.stButton button {
|
33 |
+
background-color: #2ea44f;
|
34 |
+
color: #ffffff;
|
35 |
+
}
|
36 |
+
.stTextArea textarea {
|
37 |
+
background-color: #1e2329;
|
38 |
+
color: #ffffff;
|
39 |
}
|
40 |
</style>
|
41 |
""", unsafe_allow_html=True)
|
42 |
|
43 |
+
|
44 |
def clone_repository(repo_url: str) -> Path:
|
45 |
"""リポジトリをクローンして一時ディレクトリに保存"""
|
46 |
temp_dir = Path(tempfile.mkdtemp())
|
|
|
53 |
if 'temp_dir' not in st.session_state:
|
54 |
st.session_state.temp_dir = None
|
55 |
if 'llm_service' not in st.session_state:
|
56 |
+
try:
|
57 |
+
st.session_state.llm_service = LLMService()
|
58 |
+
except ValueError as e:
|
59 |
+
st.error(str(e))
|
60 |
+
st.stop()
|
61 |
|
62 |
# メインのUIレイアウト
|
63 |
st.title("🔍 リポジトリ解析・質問システム")
|
64 |
|
65 |
+
# サイドバーでモデル選択
|
66 |
+
available_models = st.session_state.llm_service.settings.get_available_models()
|
67 |
+
if len(available_models) > 1:
|
68 |
+
selected_model = st.sidebar.selectbox(
|
69 |
+
"使用するモデル",
|
70 |
+
available_models,
|
71 |
+
index=available_models.index(st.session_state.llm_service.current_model)
|
72 |
+
)
|
73 |
+
st.session_state.llm_service.switch_model(selected_model)
|
74 |
|
75 |
# URLの入力
|
76 |
repo_url = st.text_input(
|
|
|
87 |
|
88 |
with st.spinner('ファイルをスキャン中...'):
|
89 |
scanner = FileScanner(temp_dir)
|
90 |
+
files = scanner.scan_files() # List[FileInfo] を取得
|
91 |
+
st.session_state.repo_content = LLMService.format_code_content(files)
|
|
|
|
|
92 |
|
93 |
+
st.success(f"スキャン完了: {len(files)}個のファイルを検出")
|
94 |
+
# 新しいスキャン時に会話履歴をクリア
|
95 |
+
st.session_state.llm_service.clear_history()
|
96 |
|
97 |
except Exception as e:
|
98 |
st.error(f"エラーが発生しました: {str(e)}")
|
99 |
|
100 |
# スキャン完了後の質問セクション
|
101 |
+
if st.session_state.repo_content:
|
102 |
st.divider()
|
103 |
st.subheader("💭 コードについて質問する")
|
104 |
|
105 |
+
# 会話履歴の表示(アシスタントの回答のみ)
|
106 |
+
for message in st.session_state.llm_service.conversation_history:
|
107 |
+
if message.role == "assistant": # アシスタントの回答のみを表示
|
108 |
+
st.markdown(f'<div class="chat-message assistant-message">{message.content}</div>',
|
109 |
+
unsafe_allow_html=True)
|
110 |
+
|
111 |
query = st.text_area(
|
112 |
"質問を入力してください",
|
113 |
placeholder="例: このコードの主な機能は何ですか?"
|
114 |
)
|
115 |
|
116 |
+
col1, col2 = st.columns([1, 5])
|
117 |
+
with col1:
|
118 |
+
if st.button("履歴クリア"):
|
119 |
+
st.session_state.llm_service.clear_history()
|
120 |
+
st.rerun()
|
121 |
+
|
122 |
+
with col2:
|
123 |
+
if st.button("質問する", disabled=not query):
|
124 |
+
with st.spinner('回答を生成中...'):
|
125 |
+
response, error = st.session_state.llm_service.get_response(
|
126 |
+
st.session_state.repo_content,
|
127 |
+
query
|
128 |
+
)
|
129 |
+
|
130 |
+
if error:
|
131 |
+
st.error(error)
|
132 |
+
else:
|
133 |
+
st.rerun() # 会話履歴を更新するために再表示
|
134 |
|
135 |
# セッション終了時のクリーンアップ
|
136 |
if st.session_state.temp_dir and Path(st.session_state.temp_dir).exists():
|
|
|
144 |
with st.sidebar:
|
145 |
st.subheader("📌 使い方")
|
146 |
st.markdown("""
|
147 |
+
1. GitHubリポジトリのURLを入力
|
148 |
+
2. スキャンを実行
|
149 |
+
3. コードについて質問(最大5ターンの会話が可能)
|
|
|
150 |
""")
|
151 |
|
152 |
st.subheader("🔍 スキャン対象")
|
config/__pycache__/__init__.cpython-310.pyc
CHANGED
Binary files a/config/__pycache__/__init__.cpython-310.pyc and b/config/__pycache__/__init__.cpython-310.pyc differ
|
|
config/llm_settings.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from dotenv import load_dotenv
|
3 |
+
from typing import Literal
|
4 |
+
|
5 |
+
class LLMSettings:
|
6 |
+
def __init__(self):
|
7 |
+
load_dotenv()
|
8 |
+
|
9 |
+
self.openai_api_key = os.getenv('OPENAI_API_KEY')
|
10 |
+
self.anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')
|
11 |
+
self.default_llm = os.getenv('DEFAULT_LLM', 'claude')
|
12 |
+
|
13 |
+
# API キーの存在確認
|
14 |
+
if not self.openai_api_key and not self.anthropic_api_key:
|
15 |
+
raise ValueError("少なくとも1つのAPIキーが必要です。")
|
16 |
+
|
17 |
+
def get_available_models(self) -> list[Literal['claude', 'gpt']]:
|
18 |
+
"""利用可能なモデルのリストを返す"""
|
19 |
+
models = []
|
20 |
+
if self.anthropic_api_key:
|
21 |
+
models.append('claude')
|
22 |
+
if self.openai_api_key:
|
23 |
+
models.append('gpt')
|
24 |
+
return models
|
core/__pycache__/__init__.cpython-310.pyc
CHANGED
Binary files a/core/__pycache__/__init__.cpython-310.pyc and b/core/__pycache__/__init__.cpython-310.pyc differ
|
|
core/__pycache__/file_scanner.cpython-310.pyc
CHANGED
Binary files a/core/__pycache__/file_scanner.cpython-310.pyc and b/core/__pycache__/file_scanner.cpython-310.pyc differ
|
|
main.py
DELETED
@@ -1,69 +0,0 @@
|
|
1 |
-
import sys
|
2 |
-
from pathlib import Path
|
3 |
-
from config.settings import Settings
|
4 |
-
from core.git_manager import GitManager
|
5 |
-
from core.file_scanner import FileScanner
|
6 |
-
from utils.file_writer import FileWriter
|
7 |
-
|
8 |
-
def main():
|
9 |
-
# コマンドライン引数からパスを取得
|
10 |
-
if len(sys.argv) != 2:
|
11 |
-
print("Usage: python main.py <github_url or directory_path>")
|
12 |
-
return 1
|
13 |
-
|
14 |
-
target_path = sys.argv[1]
|
15 |
-
timestamp = Settings.get_timestamp()
|
16 |
-
output_file = Settings.get_output_file(timestamp)
|
17 |
-
|
18 |
-
# GitHubのURLかローカルパスかを判定
|
19 |
-
is_github = target_path.startswith(('http://', 'https://')) and 'github.com' in target_path
|
20 |
-
|
21 |
-
try:
|
22 |
-
if is_github:
|
23 |
-
# GitHubリポジトリの場合
|
24 |
-
clone_dir = Settings.get_clone_dir(timestamp)
|
25 |
-
print(f"Cloning repository: {target_path}")
|
26 |
-
|
27 |
-
git_manager = GitManager(target_path, clone_dir)
|
28 |
-
git_manager.clone_repository()
|
29 |
-
|
30 |
-
scanner = FileScanner(clone_dir)
|
31 |
-
cleanup_needed = True
|
32 |
-
else:
|
33 |
-
# ローカルディレクトリの場合
|
34 |
-
target_dir = Path(target_path)
|
35 |
-
if not target_dir.exists():
|
36 |
-
print(f"Error: Directory not found: {target_dir}")
|
37 |
-
return 1
|
38 |
-
|
39 |
-
scanner = FileScanner(target_dir)
|
40 |
-
cleanup_needed = False
|
41 |
-
|
42 |
-
# ファイルスキャンと保存
|
43 |
-
print("Scanning files...")
|
44 |
-
files = scanner.scan_files()
|
45 |
-
|
46 |
-
print(f"Writing contents to {output_file}")
|
47 |
-
writer = FileWriter(output_file)
|
48 |
-
writer.write_contents(files)
|
49 |
-
|
50 |
-
print(f"Found {len(files)} files")
|
51 |
-
print(f"Results saved to {output_file}")
|
52 |
-
|
53 |
-
except Exception as e:
|
54 |
-
print(f"Error: {e}")
|
55 |
-
return 1
|
56 |
-
|
57 |
-
finally:
|
58 |
-
# GitHubリポジトリの場合はクリーンアップ
|
59 |
-
if is_github and cleanup_needed and 'git_manager' in locals():
|
60 |
-
try:
|
61 |
-
git_manager.cleanup()
|
62 |
-
print("Cleanup completed")
|
63 |
-
except Exception as e:
|
64 |
-
print(f"Cleanup error: {e}")
|
65 |
-
|
66 |
-
return 0
|
67 |
-
|
68 |
-
if __name__ == "__main__":
|
69 |
-
exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scan.sh
DELETED
@@ -1,49 +0,0 @@
|
|
1 |
-
#!/bin/bash
|
2 |
-
|
3 |
-
# エラーが発生した場合に停止
|
4 |
-
set -e
|
5 |
-
|
6 |
-
# デフォルトのターゲットパスを設定
|
7 |
-
# ここを変更することで対象を変更できます
|
8 |
-
TARGET_PATH="https://github.com/DeL-TaiseiOzaki/idebate_scraping.git" # 例: Linuxカーネル
|
9 |
-
# TARGET_PATH="/path/to/your/directory" # ローカルディレクトリの例
|
10 |
-
|
11 |
-
# 必要なディレクトリの存在確認
|
12 |
-
if [ ! -d "output" ]; then
|
13 |
-
mkdir output
|
14 |
-
fi
|
15 |
-
|
16 |
-
# Pythonの存在確認
|
17 |
-
if ! command -v python3 &> /dev/null; then
|
18 |
-
echo "Error: Python3 is not installed"
|
19 |
-
exit 1
|
20 |
-
fi
|
21 |
-
|
22 |
-
# GitHubリポジトリの場合、Gitの存在確認
|
23 |
-
if [[ $TARGET_PATH == http* ]] && [[ $TARGET_PATH == *github.com* ]]; then
|
24 |
-
if ! command -v git &> /dev/null; then
|
25 |
-
echo "Error: Git is not installed"
|
26 |
-
exit 1
|
27 |
-
fi
|
28 |
-
echo "Scanning GitHub repository: $TARGET_PATH"
|
29 |
-
else
|
30 |
-
if [ ! -d "$TARGET_PATH" ]; then
|
31 |
-
echo "Error: Directory not found: $TARGET_PATH"
|
32 |
-
exit 1
|
33 |
-
fi
|
34 |
-
echo "Scanning local directory: $TARGET_PATH"
|
35 |
-
fi
|
36 |
-
|
37 |
-
# スキャンの実行
|
38 |
-
echo "Starting directory scan..."
|
39 |
-
python3 main.py "$TARGET_PATH"
|
40 |
-
|
41 |
-
exit_code=$?
|
42 |
-
|
43 |
-
if [ $exit_code -eq 0 ]; then
|
44 |
-
echo "Scan completed successfully!"
|
45 |
-
echo "Results are saved in the 'output' directory"
|
46 |
-
else
|
47 |
-
echo "Scan failed with exit code: $exit_code"
|
48 |
-
exit $exit_code
|
49 |
-
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
services/__init__.py
ADDED
File without changes
|
services/llm_service.py
CHANGED
@@ -1,26 +1,39 @@
|
|
1 |
-
from typing import Optional
|
2 |
import openai
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
class LLMService:
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
def create_prompt(self, content: str, query: str) -> str:
|
16 |
-
"""
|
17 |
-
プロンプトを生成
|
18 |
-
Args:
|
19 |
-
content: コードの内容
|
20 |
-
query: ユーザーからの質問
|
21 |
-
Returns:
|
22 |
-
生成されたプロンプト
|
23 |
-
"""
|
24 |
return f"""以下はGitHubリポジトリのコード解析結果です。このコードについて質問に答えてください。
|
25 |
|
26 |
コード解析結果:
|
@@ -30,49 +43,68 @@ class LLMService:
|
|
30 |
|
31 |
できるだけ具体的に、コードの内容を参照しながら回答してください。"""
|
32 |
|
33 |
-
def
|
34 |
-
"""
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
try:
|
43 |
prompt = self.create_prompt(content, query)
|
|
|
44 |
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
58 |
|
59 |
-
|
|
|
60 |
|
61 |
except Exception as e:
|
62 |
return None, f"エラーが発生しました: {str(e)}"
|
63 |
|
64 |
@staticmethod
|
65 |
-
def format_code_content(
|
66 |
-
"""
|
67 |
-
ファイル内容をプロンプト用にフォーマット
|
68 |
-
Args:
|
69 |
-
files_content: ファイルパスと内容の辞書
|
70 |
-
Returns:
|
71 |
-
フォーマットされたテキスト
|
72 |
-
"""
|
73 |
formatted_content = []
|
74 |
-
for
|
75 |
formatted_content.append(
|
76 |
-
f"#ファイルパス\n{
|
77 |
)
|
78 |
return "\n".join(formatted_content)
|
|
|
1 |
+
from typing import Optional, List, Dict, Any
|
2 |
import openai
|
3 |
+
import anthropic
|
4 |
+
from dataclasses import dataclass
|
5 |
+
from config.llm_settings import LLMSettings
|
6 |
+
from core.file_scanner import FileInfo
|
7 |
+
|
8 |
+
@dataclass
|
9 |
+
class Message:
|
10 |
+
role: str
|
11 |
+
content: str
|
12 |
|
13 |
class LLMService:
|
14 |
+
MAX_TURNS = 5
|
15 |
+
|
16 |
+
def __init__(self):
|
17 |
+
"""LLMサービスの初期化"""
|
18 |
+
self.settings = LLMSettings()
|
19 |
+
self.current_model = self.settings.default_llm
|
20 |
+
|
21 |
+
# API クライアントの初期化
|
22 |
+
if self.settings.anthropic_api_key:
|
23 |
+
self.claude_client = anthropic.Anthropic(api_key=self.settings.anthropic_api_key)
|
24 |
+
if self.settings.openai_api_key:
|
25 |
+
openai.api_key = self.settings.openai_api_key
|
26 |
|
27 |
+
self.conversation_history: List[Message] = []
|
28 |
+
|
29 |
+
def switch_model(self, model: str):
|
30 |
+
"""使用するモデルを切り替え"""
|
31 |
+
if model not in self.settings.get_available_models():
|
32 |
+
raise ValueError(f"モデル {model} は利用できません")
|
33 |
+
self.current_model = model
|
34 |
+
|
35 |
def create_prompt(self, content: str, query: str) -> str:
|
36 |
+
"""プロンプトを生成"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
return f"""以下はGitHubリポジトリのコード解析結果です。このコードについて質問に答えてください。
|
38 |
|
39 |
コード解析結果:
|
|
|
43 |
|
44 |
できるだけ具体的に、コードの内容を参照しながら回答してください。"""
|
45 |
|
46 |
+
def _add_to_history(self, role: str, content: str):
|
47 |
+
"""会話履歴に追加(最大5ターン)"""
|
48 |
+
self.conversation_history.append(Message(role=role, content=content))
|
49 |
+
# 最大ターン数を超えた場合、古い会話を削除
|
50 |
+
if len(self.conversation_history) > self.MAX_TURNS * 2: # 各ターンは質問と回答で2メッセージ
|
51 |
+
self.conversation_history = self.conversation_history[-self.MAX_TURNS * 2:]
|
52 |
+
|
53 |
+
def _format_messages_for_claude(self) -> List[Dict[str, str]]:
|
54 |
+
"""Claude用にメッセージをフォーマット"""
|
55 |
+
return [{"role": msg.role, "content": msg.content}
|
56 |
+
for msg in self.conversation_history]
|
57 |
+
|
58 |
+
def _format_messages_for_gpt(self) -> List[Dict[str, str]]:
|
59 |
+
"""GPT用にメッセージをフォーマット"""
|
60 |
+
return [
|
61 |
+
{"role": "system", "content": "あなたはコードアナリストとして、リポジトリの解析と質問への回答を行います。"},
|
62 |
+
*[{"role": msg.role, "content": msg.content}
|
63 |
+
for msg in self.conversation_history]
|
64 |
+
]
|
65 |
+
|
66 |
+
def get_conversation_history(self) -> List[Dict[str, str]]:
|
67 |
+
"""会話履歴を取得"""
|
68 |
+
return [{"role": msg.role, "content": msg.content}
|
69 |
+
for msg in self.conversation_history]
|
70 |
+
|
71 |
+
def clear_history(self):
|
72 |
+
"""会話履歴をクリア"""
|
73 |
+
self.conversation_history = []
|
74 |
+
|
75 |
+
def get_response(self, content: str, query: str) -> tuple[Optional[str], Optional[str]]:
|
76 |
+
"""LLMを使用して回答を生成"""
|
77 |
try:
|
78 |
prompt = self.create_prompt(content, query)
|
79 |
+
self._add_to_history("user", prompt)
|
80 |
|
81 |
+
if self.current_model == 'claude':
|
82 |
+
response = self.claude_client.messages.create(
|
83 |
+
model="claude-3-sonnet-20240229",
|
84 |
+
max_tokens=4000,
|
85 |
+
messages=self._format_messages_for_claude()
|
86 |
+
)
|
87 |
+
answer = response.content[0].text
|
88 |
+
|
89 |
+
else: # gpt
|
90 |
+
response = openai.ChatCompletion.create(
|
91 |
+
model="gpt-3.5-turbo-16k",
|
92 |
+
messages=self._format_messages_for_gpt()
|
93 |
+
)
|
94 |
+
answer = response.choices[0].message.content
|
95 |
|
96 |
+
self._add_to_history("assistant", answer)
|
97 |
+
return answer, None
|
98 |
|
99 |
except Exception as e:
|
100 |
return None, f"エラーが発生しました: {str(e)}"
|
101 |
|
102 |
@staticmethod
|
103 |
+
def format_code_content(files: List[FileInfo]) -> str:
|
104 |
+
"""ファイル内容をプロンプト用にフォーマット"""
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
formatted_content = []
|
106 |
+
for file_info in files:
|
107 |
formatted_content.append(
|
108 |
+
f"#ファイルパス\n{file_info.path}\n------------\n{file_info.content}\n"
|
109 |
)
|
110 |
return "\n".join(formatted_content)
|