sirochild commited on
Commit
53dab97
·
verified ·
1 Parent(s): 00b6d12

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +25 -66
app.py CHANGED
@@ -12,41 +12,13 @@ logger = logging.getLogger(__name__)
12
  load_dotenv()
13
 
14
 
15
- # --- 2. 安全機構(保険)の実装 (辞書と関数ベース) ---
16
 
17
  # グローバルな安全設定
18
- RATE_LIMIT_MAX_REQUESTS = 15
19
- RATE_LIMIT_IN_SECONDS = 60
20
  MAX_INPUT_LENGTH = 1000
21
  MAX_HISTORY_TURNS = 100
22
 
23
- def create_limiter_state():
24
- """レートリミッターの初期状態(辞書)を作成する関数"""
25
- return {
26
- "timestamps": [],
27
- "is_blocked": False
28
- }
29
-
30
- def check_limiter(limiter_state):
31
- """
32
- リミッターの状態をチェックし、呼び出し可否を判断して状態を更新する関数。
33
- 呼び出しが許可されればTrueを、拒否されればFalseを返す。
34
- """
35
- if limiter_state["is_blocked"]:
36
- logger.warning("API呼び出しは永続的にブロックされています。")
37
- return False
38
-
39
- now = time.time()
40
- limiter_state["timestamps"] = [t for t in limiter_state["timestamps"] if now - t < RATE_LIMIT_IN_SECONDS]
41
-
42
- if len(limiter_state["timestamps"]) >= RATE_LIMIT_MAX_REQUESTS:
43
- logger.error(f"レートリミット超過! {RATE_LIMIT_IN_SECONDS}秒以内に{RATE_LIMIT_MAX_REQUESTS}回を超えました。このセッションのAPI呼び出しを永久にブロックします。")
44
- limiter_state["is_blocked"] = True
45
- return False
46
-
47
- limiter_state["timestamps"].append(now)
48
- return True
49
-
50
 
51
  # --- 3. APIクライアント初期化 ---
52
  try:
@@ -54,7 +26,6 @@ try:
54
  if not TOGETHER_API_KEY:
55
  raise ValueError("環境変数 TOGETHER_API_KEY が設定されていません。Hugging Face SpaceのSecretsに設定してください。")
56
 
57
- # Together AIはOpenAIのライブラリと互換性がある
58
  client = OpenAI(
59
  api_key=TOGETHER_API_KEY,
60
  base_url="https://api.together.xyz/v1",
@@ -107,11 +78,8 @@ def get_sentiment_analyzer():
107
  logger.error(f"感情分析モデルのロードに失敗: {e}")
108
  return sentiment_analyzer
109
 
110
- def call_llm(system_prompt, user_prompt, limiter_state, is_json_output=False):
111
- """Together AIを呼び出す共通関数。必ずリミッターを通過させる。"""
112
- if not check_limiter(limiter_state):
113
- return None
114
-
115
  messages = [
116
  {"role": "system", "content": system_prompt},
117
  {"role": "user", "content": user_prompt}
@@ -128,11 +96,9 @@ def call_llm(system_prompt, user_prompt, limiter_state, is_json_output=False):
128
  return chat_completion.choices[0].message.content
129
  except Exception as e:
130
  logger.error(f"Together AIのAPI呼び出し中に致命的なエラー: {e}", exc_info=True)
131
- limiter_state["is_blocked"] = True
132
- logger.error("APIエラーのため、このセッションのAPI呼び出しをブロックします。")
133
  return None
134
 
135
- def detect_scene_change(history, message, limiter_state):
136
  history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history[-3:]])
137
  available_keywords = ", ".join(THEME_URLS.keys())
138
  system_prompt = "あなたは会話分析のエキスパートです。ユーザーの提案とキャラクターの反応から、シーン(場所)が変更されるか判断し、指定されたキーワードでJSON形式で出力してください。"
@@ -146,7 +112,7 @@ def detect_scene_change(history, message, limiter_state):
146
  合意していない場合は {{"scene": "none"}} と出力してください。
147
  キーワード: {available_keywords}
148
  """
149
- response_text = call_llm(system_prompt, user_prompt, limiter_state, is_json_output=True)
150
  if response_text:
151
  try:
152
  result = json.loads(response_text)
@@ -158,7 +124,7 @@ def detect_scene_change(history, message, limiter_state):
158
  logger.error(f"シーン検出のJSON解析に失敗")
159
  return None
160
 
161
- def generate_dialogue(history, message, affection, stage_name, scene_params, limiter_state, instruction=None):
162
  history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history[-5:]])
163
  user_prompt = f"""
164
  # 現在の状況
@@ -172,7 +138,7 @@ def generate_dialogue(history, message, affection, stage_name, scene_params, lim
172
  {f"【特別指示】{instruction}" if instruction else f"ユーザーの発言「{message}」に応答してください。"}
173
 
174
  麻理の応答:"""
175
- response_text = call_llm(SYSTEM_PROMPT_MARI, user_prompt, limiter_state)
176
  return response_text if response_text else "(…うまく言葉が出てこない。少し時間を置いてほしい)"
177
 
178
  def get_relationship_stage(affection):
@@ -193,22 +159,16 @@ def update_affection(message, affection):
193
 
194
 
195
  # --- 6. Gradio応答関数 ---
196
- def respond(message, chat_history, affection, history, scene_params, limiter_state):
197
  try:
198
- # 保険: ブロック状態、入力長、履歴長のチェック
199
- if limiter_state["is_blocked"]:
200
- bot_message = "(…少し混乱している。時間をおいてから、ページを再読み込みして試してくれないか?)"
201
- chat_history.append((message, bot_message))
202
- return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, limiter_state, gr.update()
203
-
204
  if not message.strip():
205
- return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, limiter_state, gr.update()
206
 
207
  if len(message) > MAX_INPUT_LENGTH:
208
  logger.warning(f"入力長超過: {len(message)}文字")
209
  bot_message = f"(…長すぎる。{MAX_INPUT_LENGTH}文字以内で話してくれないか?)"
210
  chat_history.append((message, bot_message))
211
- return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, limiter_state, gr.update()
212
 
213
  if len(history) > MAX_HISTORY_TURNS:
214
  logger.error("会話履歴が長すぎます。システム保護のため、会話をリセットします。")
@@ -216,25 +176,23 @@ def respond(message, chat_history, affection, history, scene_params, limiter_sta
216
  chat_history = []
217
  bot_message = "(…ごめん、少し話が長くなりすぎた。最初からやり直そう)"
218
  chat_history.append((message, bot_message))
219
- return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, limiter_state, gr.update()
220
 
221
- # 通常処理
222
  new_affection = update_affection(message, affection)
223
  stage_name = get_relationship_stage(new_affection)
224
  final_scene_params = scene_params.copy()
225
 
226
  bot_message = ""
227
- new_scene_name = detect_scene_change(history, message, limiter_state)
228
 
229
  if new_scene_name and new_scene_name != final_scene_params.get("theme"):
230
  logger.info(f"シーンチェンジ実行: {final_scene_params.get('theme')} -> {new_scene_name}")
231
  final_scene_params["theme"] = new_scene_name
232
  instruction = f"ユーザーと一緒に「{new_scene_name}」に来た。周囲の様子を見て、最初の感想をぶっきらぼうに一言つぶやいてください。"
233
- bot_message = generate_dialogue(history, message, new_affection, stage_name, final_scene_params, limiter_state, instruction)
234
  else:
235
- bot_message = generate_dialogue(history, message, new_affection, stage_name, final_scene_params, limiter_state)
236
 
237
- # bot_messageがNoneや空の場合のフォールバック
238
  if not bot_message:
239
  bot_message = "(…うまく言葉にできない)"
240
 
@@ -244,14 +202,13 @@ def respond(message, chat_history, affection, history, scene_params, limiter_sta
244
  theme_url = THEME_URLS.get(final_scene_params.get("theme"), THEME_URLS["default"])
245
  background_html = f'<div class="background-container" style="background-image: url({theme_url});"></div>'
246
 
247
- return "", chat_history, new_affection, stage_name, new_affection, new_history, final_scene_params, limiter_state, background_html
248
 
249
  except Exception as e:
250
  logger.critical(f"respond関数で予期せぬ致命的なエラーが発生: {e}", exc_info=True)
251
  bot_message = "(ごめん、システムに予期せぬ問題が起きたみたいだ。ページを再読み込みしてくれるか…?)"
252
  chat_history.append((message, bot_message))
253
- limiter_state["is_blocked"] = True
254
- return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, limiter_state, gr.update()
255
 
256
 
257
  # --- 7. Gradio UIの構築 ---
@@ -263,11 +220,9 @@ except FileNotFoundError:
263
  custom_css = ""
264
 
265
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="rose", secondary_hue="pink"), title="麻理チャット") as demo:
266
- # 内部状態管理用
267
  scene_state = gr.State({"theme": "default"})
268
  affection_state = gr.State(30)
269
  history_state = gr.State([])
270
- limiter_state = gr.State(create_limiter_state())
271
 
272
  background_display = gr.HTML(f'<div class="background-container" style="background-image: url({THEME_URLS["default"]});"></div>')
273
 
@@ -275,7 +230,12 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="rose", secondar
275
  gr.Markdown("# 麻理チャット", elem_classes="header")
276
  with gr.Row():
277
  with gr.Column(scale=3):
278
- chatbot = gr.Chatbot(label="麻理との会話", height=550, elem_classes="chatbot", avatar_images=(None, "https://cdn.pixabay.com/photo/2016/03/31/21/40/bot-1296595_1280.png"))
 
 
 
 
 
279
  with gr.Row():
280
  msg_input = gr.Textbox(placeholder="麻理に話しかけてみましょう...", lines=2, scale=4, container=False)
281
  submit_btn = gr.Button("送信", variant="primary", scale=1, min_width=100)
@@ -285,8 +245,8 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="rose", secondar
285
  affection_gauge = gr.Slider(minimum=0, maximum=100, label="麻理の好感度", value=30, interactive=False)
286
  gr.Markdown("""<div class='footer'>Background Images & Icons: <a href="https://pixabay.com" target="_blank">Pixabay</a></div>""", elem_classes="footer")
287
 
288
- outputs = [msg_input, chatbot, affection_gauge, stage_display, affection_state, history_state, scene_state, limiter_state, background_display]
289
- inputs = [msg_input, chatbot, affection_state, history_state, scene_state, limiter_state]
290
 
291
  submit_btn.click(respond, inputs, outputs)
292
  msg_input.submit(respond, inputs, outputs)
@@ -297,6 +257,5 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="rose", secondar
297
 
298
 
299
  if __name__ == "__main__":
300
- # 感情分析モデルのプレロード(任意)
301
  get_sentiment_analyzer()
302
  demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", 7860)))
 
12
  load_dotenv()
13
 
14
 
15
+ # --- 2. 安全機構(保険)の実装 (【デバッグのため一時的に無効化】) ---
16
 
17
  # グローバルな安全設定
 
 
18
  MAX_INPUT_LENGTH = 1000
19
  MAX_HISTORY_TURNS = 100
20
 
21
+ # 【デバッグのためAPILimiter関連の関数はすべて一旦無視します】
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  # --- 3. APIクライアント初期化 ---
24
  try:
 
26
  if not TOGETHER_API_KEY:
27
  raise ValueError("環境変数 TOGETHER_API_KEY が設定されていません。Hugging Face SpaceのSecretsに設定してください。")
28
 
 
29
  client = OpenAI(
30
  api_key=TOGETHER_API_KEY,
31
  base_url="https://api.together.xyz/v1",
 
78
  logger.error(f"感情分析モデルのロードに失敗: {e}")
79
  return sentiment_analyzer
80
 
81
+ def call_llm(system_prompt, user_prompt, is_json_output=False):
82
+ """Together AIを呼び出す共通関数"""
 
 
 
83
  messages = [
84
  {"role": "system", "content": system_prompt},
85
  {"role": "user", "content": user_prompt}
 
96
  return chat_completion.choices[0].message.content
97
  except Exception as e:
98
  logger.error(f"Together AIのAPI呼び出し中に致命的なエラー: {e}", exc_info=True)
 
 
99
  return None
100
 
101
+ def detect_scene_change(history, message):
102
  history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history[-3:]])
103
  available_keywords = ", ".join(THEME_URLS.keys())
104
  system_prompt = "あなたは会話分析のエキスパートです。ユーザーの提案とキャラクターの反応から、シーン(場所)が変更されるか判断し、指定されたキーワードでJSON形式で出力してください。"
 
112
  合意していない場合は {{"scene": "none"}} と出力してください。
113
  キーワード: {available_keywords}
114
  """
115
+ response_text = call_llm(system_prompt, user_prompt, is_json_output=True)
116
  if response_text:
117
  try:
118
  result = json.loads(response_text)
 
124
  logger.error(f"シーン検出のJSON解析に失敗")
125
  return None
126
 
127
+ def generate_dialogue(history, message, affection, stage_name, scene_params, instruction=None):
128
  history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history[-5:]])
129
  user_prompt = f"""
130
  # 現在の状況
 
138
  {f"【特別指示】{instruction}" if instruction else f"ユーザーの発言「{message}」に応答してください。"}
139
 
140
  麻理の応答:"""
141
+ response_text = call_llm(SYSTEM_PROMPT_MARI, user_prompt)
142
  return response_text if response_text else "(…うまく言葉が出てこない。少し時間を置いてほしい)"
143
 
144
  def get_relationship_stage(affection):
 
159
 
160
 
161
  # --- 6. Gradio応答関数 ---
162
+ def respond(message, chat_history, affection, history, scene_params):
163
  try:
 
 
 
 
 
 
164
  if not message.strip():
165
+ return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, gr.update()
166
 
167
  if len(message) > MAX_INPUT_LENGTH:
168
  logger.warning(f"入力長超過: {len(message)}文字")
169
  bot_message = f"(…長すぎる。{MAX_INPUT_LENGTH}文字以内で話してくれないか?)"
170
  chat_history.append((message, bot_message))
171
+ return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, gr.update()
172
 
173
  if len(history) > MAX_HISTORY_TURNS:
174
  logger.error("会話履歴が長すぎます。システム保護のため、会話をリセットします。")
 
176
  chat_history = []
177
  bot_message = "(…ごめん、少し話が長くなりすぎた。最初からやり直そう)"
178
  chat_history.append((message, bot_message))
179
+ return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, gr.update()
180
 
 
181
  new_affection = update_affection(message, affection)
182
  stage_name = get_relationship_stage(new_affection)
183
  final_scene_params = scene_params.copy()
184
 
185
  bot_message = ""
186
+ new_scene_name = detect_scene_change(history, message)
187
 
188
  if new_scene_name and new_scene_name != final_scene_params.get("theme"):
189
  logger.info(f"シーンチェンジ実行: {final_scene_params.get('theme')} -> {new_scene_name}")
190
  final_scene_params["theme"] = new_scene_name
191
  instruction = f"ユーザーと一緒に「{new_scene_name}」に来た。周囲の様子を見て、最初の感想をぶっきらぼうに一言つぶやいてください。"
192
+ bot_message = generate_dialogue(history, message, new_affection, stage_name, final_scene_params, instruction)
193
  else:
194
+ bot_message = generate_dialogue(history, message, new_affection, stage_name, final_scene_params)
195
 
 
196
  if not bot_message:
197
  bot_message = "(…うまく言葉にできない)"
198
 
 
202
  theme_url = THEME_URLS.get(final_scene_params.get("theme"), THEME_URLS["default"])
203
  background_html = f'<div class="background-container" style="background-image: url({theme_url});"></div>'
204
 
205
+ return "", chat_history, new_affection, stage_name, new_affection, new_history, final_scene_params, background_html
206
 
207
  except Exception as e:
208
  logger.critical(f"respond関数で予期せぬ致命的なエラーが発生: {e}", exc_info=True)
209
  bot_message = "(ごめん、システムに予期せぬ問題が起きたみたいだ。ページを再読み込みしてくれるか…?)"
210
  chat_history.append((message, bot_message))
211
+ return "", chat_history, affection, get_relationship_stage(affection), affection, history, scene_params, gr.update()
 
212
 
213
 
214
  # --- 7. Gradio UIの構築 ---
 
220
  custom_css = ""
221
 
222
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="rose", secondary_hue="pink"), title="麻理チャット") as demo:
 
223
  scene_state = gr.State({"theme": "default"})
224
  affection_state = gr.State(30)
225
  history_state = gr.State([])
 
226
 
227
  background_display = gr.HTML(f'<div class="background-container" style="background-image: url({THEME_URLS["default"]});"></div>')
228
 
 
230
  gr.Markdown("# 麻理チャット", elem_classes="header")
231
  with gr.Row():
232
  with gr.Column(scale=3):
233
+ chatbot = gr.Chatbot(
234
+ label="麻理との会話",
235
+ height=550,
236
+ elem_classes="chatbot",
237
+ avatar_images=(None, "https://cdn.pixabay.com/photo/2016/03/31/21/40/bot-1296595_1280.png"),
238
+ )
239
  with gr.Row():
240
  msg_input = gr.Textbox(placeholder="麻理に話しかけてみましょう...", lines=2, scale=4, container=False)
241
  submit_btn = gr.Button("送信", variant="primary", scale=1, min_width=100)
 
245
  affection_gauge = gr.Slider(minimum=0, maximum=100, label="麻理の好感度", value=30, interactive=False)
246
  gr.Markdown("""<div class='footer'>Background Images & Icons: <a href="https://pixabay.com" target="_blank">Pixabay</a></div>""", elem_classes="footer")
247
 
248
+ outputs = [msg_input, chatbot, affection_gauge, stage_display, affection_state, history_state, scene_state, background_display]
249
+ inputs = [msg_input, chatbot, affection_state, history_state, scene_state]
250
 
251
  submit_btn.click(respond, inputs, outputs)
252
  msg_input.submit(respond, inputs, outputs)
 
257
 
258
 
259
  if __name__ == "__main__":
 
260
  get_sentiment_analyzer()
261
  demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", 7860)))