Spaces:
Running
Running
Commit
·
d8085e0
1
Parent(s):
fa7dc28
Latest
Browse files- core/pipeline.py +22 -4
- features/mcp/agent_bridge.py +5 -6
- services/ai_service.py +6 -2
- static/frontend/js/websocket.js +15 -0
core/pipeline.py
CHANGED
|
@@ -124,11 +124,15 @@ class ChatPipeline:
|
|
| 124 |
collect_texts(translated_data)
|
| 125 |
|
| 126 |
if not texts_to_translate:
|
|
|
|
| 127 |
return tool_data
|
| 128 |
|
| 129 |
# 批量翻譯(讓 GPT 自動判斷目標語言)
|
| 130 |
import services.ai_service as ai_service
|
| 131 |
|
|
|
|
|
|
|
|
|
|
| 132 |
combined_text = "\n---\n".join(texts_to_translate)
|
| 133 |
messages = [
|
| 134 |
{
|
|
@@ -138,12 +142,14 @@ class ChatPipeline:
|
|
| 138 |
{"role": "user", "content": combined_text}
|
| 139 |
]
|
| 140 |
|
|
|
|
| 141 |
translated = await ai_service.generate_response_async(
|
| 142 |
messages=messages,
|
| 143 |
-
model="gpt-
|
| 144 |
-
reasoning_effort=
|
| 145 |
max_tokens=800,
|
| 146 |
)
|
|
|
|
| 147 |
|
| 148 |
if translated:
|
| 149 |
translated_parts = translated.strip().split("---")
|
|
@@ -219,7 +225,11 @@ class ChatPipeline:
|
|
| 219 |
|
| 220 |
# 提取情緒(雙軌制:音頻情緒優先,文字情緒輔助)
|
| 221 |
text_emotion = intent_data.get("emotion", "neutral") if intent_data else "neutral"
|
| 222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
# 情緒融合邏輯
|
| 224 |
if audio_emotion and audio_emotion.get("success"):
|
| 225 |
audio_emotion_label = audio_emotion.get("emotion", "neutral")
|
|
@@ -319,8 +329,16 @@ class ChatPipeline:
|
|
| 319 |
return PipelineResult(text="抱歉,功能處理沒有產出結果。", is_fallback=True, reason="feature-empty")
|
| 320 |
|
| 321 |
# 簡化翻譯:非中文用戶 → 翻譯工具卡片
|
| 322 |
-
|
|
|
|
|
|
|
|
|
|
| 323 |
tool_data = await self._translate_tool_data(tool_data, user_message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
# 返回帶有工具元數據的結果(包含情緒)
|
| 326 |
meta_dict = {}
|
|
|
|
| 124 |
collect_texts(translated_data)
|
| 125 |
|
| 126 |
if not texts_to_translate:
|
| 127 |
+
logger.info(f"🌐 無需翻譯的文字,直接返回原始資料")
|
| 128 |
return tool_data
|
| 129 |
|
| 130 |
# 批量翻譯(讓 GPT 自動判斷目標語言)
|
| 131 |
import services.ai_service as ai_service
|
| 132 |
|
| 133 |
+
logger.info(f"🌐 收集到 {len(texts_to_translate)} 個需要翻譯的文字")
|
| 134 |
+
logger.debug(f"🌐 待翻譯文字: {texts_to_translate[:3]}...") # 只顯示前3個
|
| 135 |
+
|
| 136 |
combined_text = "\n---\n".join(texts_to_translate)
|
| 137 |
messages = [
|
| 138 |
{
|
|
|
|
| 142 |
{"role": "user", "content": combined_text}
|
| 143 |
]
|
| 144 |
|
| 145 |
+
logger.info(f"🌐 呼叫 GPT 翻譯,模型: gpt-4o-mini")
|
| 146 |
translated = await ai_service.generate_response_async(
|
| 147 |
messages=messages,
|
| 148 |
+
model="gpt-4o-mini", # 升級到 gpt-4o-mini 以提升翻譯品質
|
| 149 |
+
reasoning_effort=None, # gpt-4o-mini 不支援此參數
|
| 150 |
max_tokens=800,
|
| 151 |
)
|
| 152 |
+
logger.info(f"🌐 GPT 翻譯完成,結果長度: {len(translated) if translated else 0}")
|
| 153 |
|
| 154 |
if translated:
|
| 155 |
translated_parts = translated.strip().split("---")
|
|
|
|
| 225 |
|
| 226 |
# 提取情緒(雙軌制:音頻情緒優先,文字情緒輔助)
|
| 227 |
text_emotion = intent_data.get("emotion", "neutral") if intent_data else "neutral"
|
| 228 |
+
|
| 229 |
+
# DEBUG: 顯示 audio_emotion 的完整內容
|
| 230 |
+
logger.info(f"🐛 [DEBUG] audio_emotion = {audio_emotion}")
|
| 231 |
+
logger.info(f"🐛 [DEBUG] text_emotion = {text_emotion}")
|
| 232 |
+
|
| 233 |
# 情緒融合邏輯
|
| 234 |
if audio_emotion and audio_emotion.get("success"):
|
| 235 |
audio_emotion_label = audio_emotion.get("emotion", "neutral")
|
|
|
|
| 329 |
return PipelineResult(text="抱歉,功能處理沒有產出結果。", is_fallback=True, reason="feature-empty")
|
| 330 |
|
| 331 |
# 簡化翻譯:非中文用戶 → 翻譯工具卡片
|
| 332 |
+
is_chinese = self._is_chinese_message(user_message)
|
| 333 |
+
logger.info(f"🌐 語言檢測: user_message='{user_message}', is_chinese={is_chinese}")
|
| 334 |
+
if not is_chinese and tool_data:
|
| 335 |
+
logger.info(f"🌐 開始翻譯工具卡片: {len(str(tool_data))} chars")
|
| 336 |
tool_data = await self._translate_tool_data(tool_data, user_message)
|
| 337 |
+
logger.info(f"🌐 翻譯完成: {len(str(tool_data))} chars")
|
| 338 |
+
elif is_chinese:
|
| 339 |
+
logger.info(f"🌐 用戶使用中文,不翻譯工具卡片")
|
| 340 |
+
elif not tool_data:
|
| 341 |
+
logger.info(f"🌐 無工具資料,跳過翻譯")
|
| 342 |
|
| 343 |
# 返回帶有工具元數據的結果(包含情緒)
|
| 344 |
meta_dict = {}
|
features/mcp/agent_bridge.py
CHANGED
|
@@ -1150,7 +1150,8 @@ YouBike 查詢(重要!參數提取規則):
|
|
| 1150 |
"【核心原則】\n"
|
| 1151 |
"⭐ 只回答使用者問的問題,不要把所有數據都說出來\n"
|
| 1152 |
"⭐ 分析使用者的核心意圖(問溫度?天氣?時間?地點?數量?)\n"
|
| 1153 |
-
"⭐ 從工具數據中只提取相關資訊,無關資訊一律省略\n
|
|
|
|
| 1154 |
"【回應要求】\n"
|
| 1155 |
"1. 使用口語化、親切的語氣(可以用「喔」「呢」「哦」等語氣詞)\n"
|
| 1156 |
"2. 不要列表式的羅列數據,而是用對話方式描述\n"
|
|
@@ -1181,15 +1182,13 @@ YouBike 查詢(重要!參數提取規則):
|
|
| 1181 |
{"role": "user", "content": user_prompt}
|
| 1182 |
]
|
| 1183 |
|
| 1184 |
-
# 格式化回應使用
|
| 1185 |
-
optimal_effort = get_optimal_reasoning_effort("format_response")
|
| 1186 |
-
|
| 1187 |
response = await ai_service.generate_response_for_user(
|
| 1188 |
messages=messages,
|
| 1189 |
user_id="format_response",
|
| 1190 |
-
model="gpt-
|
| 1191 |
chat_id=None,
|
| 1192 |
-
reasoning_effort=
|
| 1193 |
)
|
| 1194 |
|
| 1195 |
return response
|
|
|
|
| 1150 |
"【核心原則】\n"
|
| 1151 |
"⭐ 只回答使用者問的問題,不要把所有數據都說出來\n"
|
| 1152 |
"⭐ 分析使用者的核心意圖(問溫度?天氣?時間?地點?數量?)\n"
|
| 1153 |
+
"⭐ 從工具數據中只提取相關資訊,無關資訊一律省略\n"
|
| 1154 |
+
"⭐ **注意:用什麼語言提問,就用什麼語言回答**(日文問→日文答,英文問→英文答)\n\n"
|
| 1155 |
"【回應要求】\n"
|
| 1156 |
"1. 使用口語化、親切的語氣(可以用「喔」「呢」「哦」等語氣詞)\n"
|
| 1157 |
"2. 不要列表式的羅列數據,而是用對話方式描述\n"
|
|
|
|
| 1182 |
{"role": "user", "content": user_prompt}
|
| 1183 |
]
|
| 1184 |
|
| 1185 |
+
# 格式化回應使用 gpt-4o-mini(支援多語言,不需 reasoning_effort)
|
|
|
|
|
|
|
| 1186 |
response = await ai_service.generate_response_for_user(
|
| 1187 |
messages=messages,
|
| 1188 |
user_id="format_response",
|
| 1189 |
+
model="gpt-4o-mini", # 升級到 gpt-4o-mini 以支援多語言
|
| 1190 |
chat_id=None,
|
| 1191 |
+
reasoning_effort=None # gpt-4o-mini 不支援此參數
|
| 1192 |
)
|
| 1193 |
|
| 1194 |
return response
|
services/ai_service.py
CHANGED
|
@@ -505,10 +505,14 @@ async def generate_response_async(
|
|
| 505 |
"max_completion_tokens": max_tokens if max_tokens else 2000, # 關懷模式可自訂 tokens
|
| 506 |
}
|
| 507 |
|
| 508 |
-
# 加入 reasoning_effort
|
| 509 |
-
|
|
|
|
|
|
|
| 510 |
request_kwargs["reasoning_effort"] = reasoning_effort
|
| 511 |
logger.info(f"🧠 設定 reasoning_effort: {reasoning_effort}")
|
|
|
|
|
|
|
| 512 |
|
| 513 |
# 優先使用 Structured Outputs(2025年最佳實踐)
|
| 514 |
if use_structured_outputs and response_schema:
|
|
|
|
| 505 |
"max_completion_tokens": max_tokens if max_tokens else 2000, # 關懷模式可自訂 tokens
|
| 506 |
}
|
| 507 |
|
| 508 |
+
# 加入 reasoning_effort 控制(僅 o1 系列和 gpt-5 系列支援)
|
| 509 |
+
# gpt-4o-mini 等模型不支援此參數,需要過濾
|
| 510 |
+
reasoning_models = model.startswith("o1") or model.startswith("gpt-5")
|
| 511 |
+
if reasoning_effort and reasoning_models:
|
| 512 |
request_kwargs["reasoning_effort"] = reasoning_effort
|
| 513 |
logger.info(f"🧠 設定 reasoning_effort: {reasoning_effort}")
|
| 514 |
+
elif reasoning_effort and not reasoning_models:
|
| 515 |
+
logger.debug(f"⚠️ 模型 {model} 不支援 reasoning_effort,已忽略")
|
| 516 |
|
| 517 |
# 優先使用 Structured Outputs(2025年最佳實踐)
|
| 518 |
if use_structured_outputs and response_schema:
|
static/frontend/js/websocket.js
CHANGED
|
@@ -629,6 +629,21 @@ function initializeWebSocket(token) {
|
|
| 629 |
}
|
| 630 |
break;
|
| 631 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
default:
|
| 633 |
console.log('🔍 未處理的訊息類型:', data.type);
|
| 634 |
}
|
|
|
|
| 629 |
}
|
| 630 |
break;
|
| 631 |
|
| 632 |
+
case 'audio_emotion_detected':
|
| 633 |
+
// 音頻情緒檢測結果(新增)
|
| 634 |
+
console.log('🎭 檢測到音頻情緒:', data.emotion, '置信度:', data.confidence, 'source:', data.source);
|
| 635 |
+
|
| 636 |
+
// 如果置信度 >= 0.5,應用情緒主題
|
| 637 |
+
if (data.emotion && data.confidence >= 0.5 && typeof applyEmotion === 'function') {
|
| 638 |
+
applyEmotion(data.emotion);
|
| 639 |
+
console.log('✅ 音頻情緒主題已套用:', data.emotion, `(置信度: ${data.confidence.toFixed(2)})`);
|
| 640 |
+
} else if (data.confidence < 0.5) {
|
| 641 |
+
console.log('⚠️ 音頻情緒置信度過低,不套用主題:', data.confidence.toFixed(2));
|
| 642 |
+
} else {
|
| 643 |
+
console.warn('⚠️ applyEmotion 函數未定義或情緒值無效');
|
| 644 |
+
}
|
| 645 |
+
break;
|
| 646 |
+
|
| 647 |
default:
|
| 648 |
console.log('🔍 未處理的訊息類型:', data.type);
|
| 649 |
}
|