Yasu777 commited on
Commit
0b7cae1
·
verified ·
1 Parent(s): d75bde0

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -562
app.py DELETED
@@ -1,562 +0,0 @@
1
- import streamlit as st
2
- import os
3
- import json
4
- import re
5
- from groq import Groq
6
- from pathlib import Path
7
- import time
8
- from langdetect import detect
9
- from langdetect.lang_detect_exception import LangDetectException
10
-
11
- # 言語検出関数を追加
12
- def detect_language(text):
13
- try:
14
- lang = detect(text)
15
- return lang
16
- except LangDetectException:
17
- return "ja" # デフォルトは日本語
18
-
19
- # システムプロンプトに言語指示を追加する関数
20
- def enhance_system_prompt_with_language(system_prompt, user_input):
21
- detected_lang = detect_language(user_input)
22
-
23
- language_instruction = ""
24
- if detected_lang == "ja":
25
- language_instruction = "\n\nユーザーの入力は日本語です。必ず日本語で回答してください。"
26
- elif detected_lang == "en":
27
- language_instruction = "\n\nThe user's input is in English. Please respond in English."
28
- elif detected_lang == "zh-cn" or detected_lang == "zh-tw":
29
- language_instruction = "\n\n用户的输入是中文的。请用中文回答。"
30
- else:
31
- language_instruction = f"\n\nPlease respond in the same language as the user's input (detected: {detected_lang})."
32
-
33
- enhanced_prompt = system_prompt + language_instruction
34
- return enhanced_prompt, detected_lang
35
-
36
- # ページ設定
37
- st.set_page_config(
38
- page_title="マルチモデルGroqチャットアプリ",
39
- page_icon="🤖",
40
- layout="centered"
41
- )
42
-
43
- # シークレットからAPIキーを取得(Hugging Face Spaces用)
44
- def get_api_key():
45
- # 1. Hugging Face Spaces のシークレットから取得
46
- api_key = os.environ.get("GROQ_API_KEY", "")
47
-
48
- # 2. 環境変数から取得できない場合はUI入力を使用
49
- if not api_key:
50
- api_key = st.session_state.get("api_key_input", "")
51
-
52
- return api_key
53
-
54
- # システムプロンプトの読み込み
55
- def load_system_prompt(prompt_type="default"):
56
- # プロンプトタイプに応じたファイル名を決定
57
- filename_map = {
58
- "default": "system_prompt_qwq.txt",
59
- "general": "system_prompt_general.txt",
60
- "coder": "system_prompt_coder.txt"
61
- }
62
-
63
- filename = filename_map.get(prompt_type, "system_prompt_qwq.txt")
64
-
65
- # 環境変数からパスが指定されている場合
66
- env_var_map = {
67
- "default": "SYSTEM_PROMPT_QWQ_PATH",
68
- "general": "SYSTEM_PROMPT_GENERAL_PATH",
69
- "coder": "SYSTEM_PROMPT_CODER_PATH"
70
- }
71
-
72
- prompt_path = os.environ.get(env_var_map.get(prompt_type, ""), "")
73
-
74
- # デフォルトのパスを確認
75
- default_paths = [
76
- filename, # ルートディレクトリ
77
- f"app/{filename}", # Spaces上のapp/ディレクトリ
78
- f"prompts/{filename}" # prompts/ディレクトリ
79
- ]
80
-
81
- # 環境変数で指定されたパスを試す
82
- if prompt_path and Path(prompt_path).is_file():
83
- try:
84
- with open(prompt_path, "r", encoding="utf-8") as file:
85
- return file.read().strip()
86
- except Exception:
87
- pass
88
-
89
- # デフォルトパスを順に試す
90
- for path in default_paths:
91
- if Path(path).is_file():
92
- try:
93
- with open(path, "r", encoding="utf-8") as file:
94
- return file.read().strip()
95
- except Exception:
96
- continue
97
-
98
- # デフォルトのプロンプト
99
- default_prompts = {
100
- "default": "あなたは親切なAIアシスタントです。質問を分析して、一般的な質問かコード生成かを判断してください。",
101
- "general": "あなたは親切で知識豊富なAIアシスタントです。様々な話題について詳しく解説します。",
102
- "coder": "あなたは経験豊富なプログラマーAIアシスタントです。質の高いコードと詳細な説明を提供します。"
103
- }
104
-
105
- return default_prompts.get(prompt_type, default_prompts["default"])
106
-
107
- def analyze_message_type(response_text):
108
- """
109
- 分析レスポンスから質問タイプ、理由、キーワードを抽出する関数
110
- 中国語プロンプトの応答形式に対応
111
- """
112
- # レスポンスの最初の部分を取得
113
- response_lines = response_text.strip().split('\n')
114
-
115
- # 変数初期化
116
- msg_type = None
117
- reason = None
118
- keywords = None
119
-
120
- # サイドバーにデバッグ情報を表示
121
- with st.sidebar:
122
- with st.expander("応答分析(デバッグ)", expanded=False):
123
- # 最初の10行を表示
124
- st.markdown("### 応答全文")
125
- st.code("\n".join(response_lines), language="markdown")
126
-
127
- # 中国語プロンプトからの応答を解析
128
- for line in response_lines:
129
- if "TYPE:" in line.upper():
130
- msg_type = "general" if "GENERAL" in line.upper() else "code"
131
- st.write(f"検出されたタイプ: {msg_type}")
132
- elif "REASON:" in line.upper():
133
- reason = line
134
- st.write(f"検出された理由: {reason}")
135
- elif "KEYWORDS:" in line.upper():
136
- keywords = line
137
- st.write(f"検出されたキーワード: {keywords}")
138
-
139
- # デフォルト値の設定
140
- if not msg_type or not reason or not keywords:
141
- with st.sidebar:
142
- with st.expander("応答分析(デバッグ)", expanded=False):
143
- st.warning("完全な分析情報が得られませんでした。コンテンツ分析を実行します。")
144
-
145
- # 応答テキスト全体からキーワードを検索
146
- code_indicators = {
147
- 'コード': 5,
148
- 'プログラム': 4,
149
- '実装': 4,
150
- 'function': 5,
151
- 'class': 4,
152
- 'def': 4,
153
- 'import': 4,
154
- 'python': 5,
155
- 'javascript': 4,
156
- 'java': 3,
157
- 'c++': 4,
158
- 'コーディング': 5,
159
- '<think>技術評估': 5, # 中国語プロンプトの技術評価セクション
160
- 'programming': 4,
161
- 'code': 4,
162
- 'implementation': 4
163
- }
164
-
165
- # スコアベースの判定
166
- score = 0
167
- response_lower = response_text.lower()
168
- matched_keywords = []
169
-
170
- for keyword, weight in code_indicators.items():
171
- if keyword.lower() in response_lower:
172
- score += weight
173
- matched_keywords.append(keyword)
174
-
175
- # スコアに基づいて判定
176
- if score >= 5:
177
- msg_type = "code"
178
- reason = f"REASON: 技術的なキーワード({', '.join(matched_keywords)})が検出されたため、コード生成と判断"
179
- else:
180
- msg_type = "general"
181
- reason = "REASON: 一般的な内容と判断(技術的キーワードが少ない)"
182
-
183
- if not keywords:
184
- keywords = f"KEYWORDS: {', '.join(matched_keywords) if matched_keywords else '特徴的なキーワードなし'}"
185
-
186
- return msg_type, reason, keywords
187
-
188
- # 分析レスポンスからコードブロックを除外する関数(テキストは要約しない)
189
- def remove_code_blocks(analysis_text):
190
- """
191
- 分析レスポンスからコードブロックを除外する関数(テキストは要約しない)
192
- """
193
- if not analysis_text:
194
- return ""
195
-
196
- # ```で囲まれたコードブロックを検出
197
- code_blocks = re.findall(r'```.*?```', analysis_text, re.DOTALL)
198
- clean_text = analysis_text
199
-
200
- # コードブロックを除外
201
- for code_block in code_blocks:
202
- clean_text = clean_text.replace(code_block, "```[コードブロックは省略されました]```")
203
-
204
- return clean_text
205
-
206
- # Groq APIを使ってチャット応答を取得する関数
207
- def get_groq_chat_response(messages, model_name, temperature=0.7, max_tokens=2000):
208
- api_key = get_api_key()
209
- if not api_key:
210
- return "APIキーが設定されていません。"
211
-
212
- try:
213
- client = Groq(api_key=api_key)
214
-
215
- response = client.chat.completions.create(
216
- messages=messages,
217
- model=model_name,
218
- temperature=temperature,
219
- max_tokens=max_tokens,
220
- stream=False
221
- )
222
-
223
- return response.choices[0].message.content
224
- except Exception as e:
225
- return f"エラーが発生しました: {str(e)}"
226
-
227
- # ストリーミング応答を処理する関数
228
- def process_streaming_response(messages, model_name, temperature=0.7, max_tokens=2000):
229
- api_key = get_api_key()
230
- if not api_key:
231
- return "APIキーが設定されていません。"
232
-
233
- try:
234
- client = Groq(api_key=api_key)
235
-
236
- message_placeholder = st.empty()
237
- full_response = ""
238
-
239
- chat_completion = client.chat.completions.create(
240
- messages=messages,
241
- model=model_name,
242
- temperature=temperature,
243
- max_tokens=max_tokens,
244
- stream=True,
245
- )
246
-
247
- for chunk in chat_completion:
248
- content = chunk.choices[0].delta.content
249
- if content is not None:
250
- full_response += content
251
- message_placeholder.markdown(full_response + "▌")
252
-
253
- message_placeholder.markdown(full_response)
254
- return full_response
255
- except Exception as e:
256
- st.error(f"エラーが発生しました: {str(e)}")
257
- return f"エラーが発生しました: {str(e)}"
258
-
259
- # セッション状態の初期化
260
- if "messages" not in st.session_state:
261
- st.session_state.messages = []
262
-
263
- if "api_key_configured" not in st.session_state:
264
- st.session_state.api_key_configured = False
265
-
266
- if "conversation_id" not in st.session_state:
267
- st.session_state.conversation_id = 0
268
-
269
- if "initial_response_cache" not in st.session_state:
270
- st.session_state.initial_response_cache = None
271
-
272
- if "identified_type" not in st.session_state:
273
- st.session_state.identified_type = None
274
-
275
- if "active_model" not in st.session_state:
276
- st.session_state.active_model = "qwen-qwq-32b"
277
-
278
- if "reasoning" not in st.session_state:
279
- st.session_state.reasoning = None
280
-
281
- if "keywords" not in st.session_state:
282
- st.session_state.keywords = None
283
-
284
- if "analysis_complete" not in st.session_state:
285
- st.session_state.analysis_complete = False
286
-
287
- if "analysis_response" not in st.session_state:
288
- st.session_state.analysis_response = None
289
-
290
- # 言語情報をセッション状態に追加
291
- if "detected_language" not in st.session_state:
292
- st.session_state.detected_language = "ja" # デフォルト言語
293
-
294
- # サイドバーの設定
295
- with st.sidebar:
296
- st.title("マルチモデルGroqチャット")
297
- st.markdown("---")
298
-
299
- # APIキー入力
300
- api_key = get_api_key()
301
- if not api_key:
302
- st.session_state.api_key_input = st.text_input("Groq API キーを入力してください", type="password")
303
- if st.session_state.api_key_input:
304
- api_key = st.session_state.api_key_input
305
- st.success("API キーが入力されました!このキーはセッション中のみ保存されます。")
306
-
307
- # APIキーの状態を更新
308
- if api_key:
309
- st.session_state.api_key_configured = True
310
- else:
311
- st.warning("Groq APIキーが設定されていません。Hugging Face Spacesの'Secrets'タブでGROQ_API_KEYを設定するか、左側のサイドバーに入力してください。")
312
- st.session_state.api_key_configured = False
313
-
314
- # モデル情報表示
315
- st.subheader("モデル設定")
316
- st.markdown("**初期応答モデル**: qwen-qwq-32b (固定)")
317
- st.markdown("**一般質問モデル**: qwen-2.5-32b")
318
- st.markdown("**コード生成モデル**: qwen-2.5-coder-32b")
319
-
320
- # 検出された言語情報を表示
321
- if st.session_state.detected_language:
322
- st.markdown(f"**検出された言語**: {st.session_state.detected_language}")
323
-
324
- if st.session_state.identified_type:
325
- st.success(f"質問タイプ: {'一般質問' if st.session_state.identified_type == 'general' else 'コード生成'}")
326
- st.info(f"現在のモデル: {st.session_state.active_model}")
327
-
328
- # 思考プロセス表示
329
- with st.expander("qwen-qwq-32bの思考分析", expanded=True):
330
- if st.session_state.identified_type:
331
- st.markdown("### 質問分析結果")
332
- st.success(f"**質問タイプ**: {'一般的な質問' if st.session_state.identified_type == 'general' else 'コード生成の質問'}")
333
- st.info(f"**選択されたモデル**: {st.session_state.active_model}")
334
-
335
- # AIの判断理由を表示(原文のまま表示)
336
- if st.session_state.reasoning:
337
- st.markdown("### 判断理由(原文)")
338
- st.code(st.session_state.reasoning, language="markdown")
339
-
340
- # 抽出されたキーワードを表示(原文のまま表示)
341
- if st.session_state.keywords:
342
- st.markdown("### 抽出キーワード(原文)")
343
- st.code(st.session_state.keywords, language="markdown")
344
-
345
- # 分析レポート
346
- if st.session_state.analysis_response:
347
- st.markdown("### 気づきの連鎖思考プロセス(原文)")
348
- st.code(st.session_state.analysis_response, language="markdown")
349
-
350
- # モデル切り替えボタン
351
- st.markdown("### モデル切り替え")
352
- col1, col2 = st.columns(2)
353
- with col1:
354
- if st.button("一般質問モデルに切り替え"):
355
- st.session_state.identified_type = "general"
356
- st.session_state.active_model = "qwen-2.5-32b"
357
- st.session_state.reasoning += "\n(ユーザーが手動で一般質問モデルに切り替えました)"
358
- st.rerun()
359
- with col2:
360
- if st.button("コード生成モデルに切り替え"):
361
- st.session_state.identified_type = "code"
362
- st.session_state.active_model = "qwen-2.5-coder-32b"
363
- st.session_state.reasoning += "\n(ユーザーが手動でコード生成モデルに切り替えました)"
364
- st.rerun()
365
- else:
366
- st.markdown("会話が開始されると、AIの思考プロセスがここに表示されます。")
367
- st.markdown("最初の質問を入力すると、AIが質問タイプを分析し、適切なモデルを選択します。")
368
-
369
- # 会話管理
370
- st.subheader("会話管理")
371
- if st.button("新しい会話を開始"):
372
- st.session_state.messages = []
373
- st.session_state.conversation_id += 1
374
- st.session_state.initial_response_cache = None
375
- st.session_state.identified_type = None
376
- st.session_state.active_model = "qwen-qwq-32b"
377
- st.session_state.reasoning = None
378
- st.session_state.keywords = None
379
- st.session_state.analysis_complete = False
380
- st.session_state.analysis_response = None
381
- st.session_state.detected_language = "ja" # 言語情報もリセット
382
- st.info("新しい会話を開始しました。")
383
-
384
- # メインコンテンツエリア
385
- st.title("マルチモデルAIチャット 💬")
386
- st.markdown("""
387
- 質問内容に応じて最適なAIモデルが自動選択されます。
388
- - 一般的な質問 → qwen-2.5-32b
389
- - コード生成の質問 → qwen-2.5-coder-32b
390
- """)
391
- st.markdown("*分析プロセスはサイドバーで確認できます。*")
392
- st.markdown("---")
393
-
394
- # メッセージ履歴の表示
395
- for message in st.session_state.messages:
396
- with st.chat_message(message["role"]):
397
- st.markdown(message["content"])
398
-
399
- # ユーザー入力
400
- if prompt := st.chat_input("メッセージを入力..."):
401
- if not st.session_state.api_key_configured:
402
- st.error("Groq API キーを設定してください!")
403
- else:
404
- # ユーザーメッセージの追加
405
- st.session_state.messages.append({"role": "user", "content": prompt})
406
- with st.chat_message("user"):
407
- st.markdown(prompt)
408
-
409
- # AIによる応答の生成
410
- with st.chat_message("assistant"):
411
- # 初回の質問か継続会話かをチェック
412
- if not st.session_state.analysis_complete:
413
- # ステップ1: 質問タイプの分析 (qwen-qwq-32b)
414
- with st.spinner("質問を分析中... (qwen-qwq-32b)"):
415
- # 中国語のシステムプロンプトのみを使用
416
- system_prompt = load_system_prompt("default")
417
-
418
- analysis_messages = [
419
- {"role": "system", "content": system_prompt},
420
- {"role": "user", "content": prompt}
421
- ]
422
-
423
- # qwen-qwq-32bで質問タイプを分析
424
- analysis_response = get_groq_chat_response(
425
- analysis_messages,
426
- "qwen-qwq-32b",
427
- temperature=0.6,
428
- max_tokens=5000
429
- )
430
-
431
- # 完全な分析レスポンスを保存
432
- st.session_state.analysis_response = analysis_response
433
-
434
- # 質問タイプを分析
435
- msg_type, reason, keywords = analyze_message_type(analysis_response)
436
-
437
- # 状態を更新
438
- st.session_state.identified_type = msg_type
439
- st.session_state.reasoning = reason
440
- st.session_state.keywords = keywords
441
- st.session_state.active_model = "qwen-2.5-32b" if msg_type == "general" else "qwen-2.5-coder-32b"
442
- st.session_state.analysis_complete = True
443
-
444
- # ステップ2: 選択されたモデルで回答生成
445
- if st.session_state.identified_type == "general":
446
- model = "qwen-qwq-32b"
447
- system_prompt = load_system_prompt("general")
448
- else:
449
- model = "qwen-2.5-coder-32b"
450
- system_prompt = load_system_prompt("coder")
451
-
452
- # 分析結果を次のモデルに伝えるための構造化
453
- analysis_summary = ""
454
- if st.session_state.identified_type:
455
- question_type = "一般的な質問" if st.session_state.identified_type == "general" else "コード生成の質問"
456
-
457
- # 理由部分を抽出("REASON:" プレフィックスを削除)
458
- reason = st.session_state.reasoning.replace("REASON:", "").strip() if st.session_state.reasoning else "不明"
459
-
460
- # キーワード部分を抽出("KEYWORDS:" プレフィックスを削除)
461
- keywords = st.session_state.keywords.replace("KEYWORDS:", "").strip() if st.session_state.keywords else "特になし"
462
-
463
- # コードブロックを除外した分析テキストを取得(要約しない)
464
- clean_analysis = remove_code_blocks(st.session_state.analysis_response)
465
-
466
- # 分析サマリーを作成(評価ステップを追加)
467
- analysis_summary = f"""
468
- # 分析AIによる質問分析結果
469
- - 質問タイプ: {question_type}
470
- - 判断理由: {reason}
471
- - 抽出されたキーワード: {keywords}
472
-
473
- ## 分析AIによる思考プロセス
474
- {clean_analysis}
475
-
476
- ## 指示
477
- ### ステップ1: 分析結果の評価
478
- まず、上記の���析結果を評価してください。以下の点について考慮してください:
479
- 1. 分析AIが判断した質問タイプは適切か?
480
- 2. 抽出されたキーワードはユーザーの質問の本質を捉えているか?
481
- 3. 分析AIの思考プロセスに誤った前提や論理的な誤りはないか?
482
-
483
- もし分析に問題があれば、どのように修正すべきかを<analysis_evaluation>タグ内に記述してください。
484
- 問題がなければ、「分析結果は適切です」と記述してください。
485
-
486
- <analysis_evaluation>
487
- ここに分析結果の評価を記述
488
- </analysis_evaluation>
489
-
490
- ### ステップ2: ユーザーへの回答
491
- 分析結果の評価を踏まえた上で、ユーザーの質問に対する回答を提供してください。以下の点に注意してください:
492
- 1. {'幅広い知識に基づいた丁寧で詳細な回答を提供してください。抽象的な説明よりも具体例を含めると良いでしょう。' if st.session_state.identified_type == 'general' else '高品質なコードと詳細な説明を提供してください。コードの各部分の動作原理と、なぜその実装方法を選んだのかを説明してください。'}
493
- 2. キーワードとして抽出された概念に特に注目して回答を構成してください。
494
- 3. ユーザーの意図を深く理解し、直接的な質問だけでなく潜在的なニーズにも対応してください。
495
- 4. もし分析結果に誤りがあると判断した場合は、修正した理解に基づいて回答してください。
496
-
497
- 注意: <analysis_evaluation>タグはユーザーに表示せず、あなたの内部的な評価のみに使用してください。
498
- """
499
-
500
- # 言語検出とシステムプロンプトの拡張
501
- enhanced_system_prompt, detected_lang = enhance_system_prompt_with_language(system_prompt + "\n\n" + analysis_summary, prompt)
502
- st.session_state.detected_language = detected_lang
503
-
504
- # 準備ができた旨を表示
505
- st.markdown(f"*{model}モデルで回答を生成中...*")
506
- time.sleep(0.5) # UIフィードバック用の短い遅延
507
-
508
- # 選択されたモデルで回答を生成
509
- messages = [
510
- {"role": "system", "content": enhanced_system_prompt},
511
- {"role": "user", "content": prompt}
512
- ]
513
-
514
- response_text = process_streaming_response(messages, model)
515
-
516
- # <analysis_evaluation>タグを削除する処理を追加
517
- response_text = re.sub(r'<analysis_evaluation>.*?</analysis_evaluation>', '', response_text, flags=re.DOTALL)
518
-
519
- # 応答をセッション状態に保存(実際の回答内容)
520
- st.session_state.initial_response_cache = response_text
521
- st.session_state.messages.append({"role": "assistant", "content": response_text})
522
-
523
- else:
524
- # 継続会話:特定されたタイプに基づいてモデルを使用(簡略化)
525
- if st.session_state.identified_type == "general":
526
- model = "qwen-2.5-32b"
527
- system_prompt = load_system_prompt("general")
528
- else:
529
- model = "qwen-2.5-coder-32b"
530
- system_prompt = load_system_prompt("coder")
531
-
532
- # 継続会話では評価ステップを省略し、シンプルな指示のみを使用
533
- keywords = st.session_state.keywords.replace("KEYWORDS:", "").strip() if st.session_state.keywords else "特になし"
534
-
535
- # 継続会話用の簡略化されたコンテキスト
536
- conversation_context = f"""
537
- あなたは{'一般的な質問' if st.session_state.identified_type == 'general' else 'コード生成の質問'}に回答するAIアシスタントです。
538
- 以前の会話履歴を考慮して回答してください。関連キーワード: {keywords}
539
- """
540
-
541
- # 言語検出とシステムプロンプトの拡張
542
- enhanced_system_prompt, detected_lang = enhance_system_prompt_with_language(system_prompt + "\n\n" + conversation_context, prompt)
543
- st.session_state.detected_language = detected_lang
544
-
545
- # メッセージ履歴から会話コンテキストを構築
546
- conversation_history = []
547
- for msg in st.session_state.messages[:-1]: # 最新のユーザーメッセージを除く
548
- conversation_history.append({"role": msg["role"], "content": msg["content"]})
549
-
550
- messages = [
551
- {"role": "system", "content": enhanced_system_prompt},
552
- *conversation_history,
553
- {"role": "user", "content": prompt}
554
- ]
555
-
556
- st.markdown(f"*{model}モデルで応答を生成中...*")
557
- time.sleep(0.5) # UIフィードバック用の短い遅延
558
-
559
- response_text = process_streaming_response(messages, model)
560
-
561
- # 応答をセッション状態に保存
562
- st.session_state.messages.append({"role": "assistant", "content": response_text})