LLM情感标签生成逻辑分析.md 9.6 KB

LLM 情感标签生成逻辑分析

📋 问题描述

用户反馈:每次情感词都是 neutral,怀疑 LLM 没有生成情感标签。


🔍 现有代码逻辑梳理

1. LLM 提示词要求(backend/ai_service.py)

1.1 System Prompt Base(第1167-1180行)

[Emotion Tag - IMPORTANT]
At the BEGINNING of your response, on the FIRST line, add the emotion tag in this exact format:
[EMOTION:emotion_name]

Then ALWAYS continue with your spoken response (1-2 sentences).

Available emotions: neutral, happy, sad, excited, angry, surprised, thoughtful, encouraging, confused, worried, satisfied, friendly, sorry

CRITICAL: 
- NEVER return ONLY the emotion tag without spoken text
- ALWAYS include 1-2 sentences of actual spoken response after the emotion tag
- The emotion tag MUST be the FIRST line, before any other content
- DO NOT use parentheses () or brackets [] in your spoken response (they will be removed during speech synthesis)
- DO NOT use emojis or emoticons in your response

✅ 提示词明确要求生成情感标签


2. 情感标签检测逻辑(backend/ai_service.py)

2.1 流式生成中的情感检测(第341-369行)

初始化:

detected_emotion: EmotionType = "neutral"  # 默认值
emotion_yielded = False  # 标记是否已发送情感

检测逻辑:

# 🎯 优先检测情感标记(如果还没检测到)
if detected_emotion == "neutral" and "[EMOTION:" in full_response:
    emotion_start = full_response.find("[EMOTION:")
    emotion_end = full_response.find("]", emotion_start)
    if emotion_end > emotion_start:
        emotion_str = full_response[emotion_start + 9:emotion_end].strip().lower()
        valid_emotions = ["neutral", "happy", "sad", "excited", "angry", "surprised", 
                          "thoughtful", "encouraging", "confused", "worried", 
                          "satisfied", "friendly", "sorry", "gentle"]
        if emotion_str in valid_emotions:
            detected_emotion = emotion_str
        else:
            # 🔥 未知情感词,默认映射为 neutral
            logger.warning(f"[LLM] 未知情感词: {emotion_str},已映射为 neutral")
            detected_emotion = "neutral"
        
        # 🚀 立即 yield 空句子传递情感(减少延迟)
        if not emotion_yielded:
            yield ("", detected_emotion)  # 空句子,仅传递情感
            emotion_yielded = True

关键点:

  • ✅ 检测条件:detected_emotion == "neutral" 且 "[EMOTION:" in full_response
  • ✅ 检测时机:每个 chunk 都会检查 full_response(累积的完整响应)
  • ✅ 提取逻辑:从 [EMOTION: 后第9个字符开始,到 ] 结束
  • ⚠️ 问题点1:如果 LLM 没有生成 [EMOTION: 标签,detected_emotion 会一直保持 "neutral"

3. 流式处理流程(backend/websocket_handler.py)

3.1 主循环(第704-721行)

async for sentence, emotion in ai_service.generate_response_stream(...):
    # 🎯 优先处理情感:第一时间发送给前端
    if not emotion_sent and emotion:
        await self.send_emotion(request_id, emotion)
        emotion_sent = True
        final_emotion = emotion
    
    # ⚡ 如果是空句子(仅传递情感),跳过 TTS 处理
    if not sentence:
        continue

关键点:

  • ✅ 第一个非空 emotion 会被发送给前端
  • ✅ final_emotion 会被更新为最后一个 emotion 值

4. 登录问候语处理(backend/ai_service.py)

4.1 生成登录问候(第181-231行)

greeting_text = response.choices[0].message.content.strip()
# 🔥 确保返回的问候语包含情感标签
if not greeting_text.startswith("[EMOTION:"):
    greeting_text = f"[EMOTION:friendly]\n{greeting_text}"
return greeting_text

✅ 登录问候有兜底逻辑:如果没有情感标签,自动添加 [EMOTION:friendly]


🔎 问题分析

可能的原因

原因1:LLM 没有生成情感标签

  • 现象:full_response 中不包含 [EMOTION: 字符串
  • 结果:detected_emotion 保持默认值 "neutral"
  • 验证方法:检查日志中 LLM 的原始输出(full_response)

原因2:情感标签格式不正确

  • 现象:LLM 生成了情感标签,但格式不是 [EMOTION:xxx]
  • 可能格式:
    • (EMOTION: happy) ❌
    • EMOTION: happy ❌
    • [emotion: happy] ❌(大小写问题)
    • [EMOTION: happy ] ✅(但可能有空格问题)
  • 结果:检测逻辑找不到标签,detected_emotion 保持 "neutral"

原因3:情感标签不在第一行

  • 现象:LLM 生成了情感标签,但不在响应的第一行
  • 结果:检测逻辑仍然能找到标签(因为检查的是 full_response),但可能不符合提示词要求

原因4:流式处理中标签被分割(已排除)

  • 现象:[EMOTION: 和 ] 被分割在不同的 chunk 中
  • 分析:
    • 第一个 chunk:full_response = "[EMOTION:",emotion_end = -1,条件 emotion_end > emotion_start 不满足,不设置 detected_emotion
    • 第二个 chunk:full_response = "[EMOTION:happy]",emotion_end > emotion_start,可以正确检测
    • ✅ 结论:检测逻辑可以处理标签被分割的情况,因为每次都会检查完整的 full_response

📊 检测逻辑的潜在问题

问题1:检测条件分析

if detected_emotion == "neutral" and "[EMOTION:" in full_response:
  • ✅ 优点:只检测一次,避免重复检测
  • ✅ 实际行为:每次 chunk 都会检查 full_response(累积的完整响应),所以即使标签被分割,也能在后续 chunk 中检测到
  • ⚠️ 真正的问题:如果 LLM 根本没有生成 [EMOTION: 标签,detected_emotion 会一直保持 "neutral"

问题2:提取逻辑可能失败

emotion_start = full_response.find("[EMOTION:")
emotion_end = full_response.find("]", emotion_start)
if emotion_end > emotion_start:
    emotion_str = full_response[emotion_start + 9:emotion_end].strip().lower()
  • ✅ 优点:简单直接
  • ⚠️ 缺点:如果 ] 在 [EMOTION: 之前(不太可能),或者有多个 ],可能提取错误

问题3:没有日志记录原始输出

  • ⚠️ 当前代码没有记录 LLM 的原始输出(full_response),无法验证 LLM 是否真的生成了情感标签

🎯 建议的调试方法

1. 添加日志记录 ✅ 已完成

已在 backend/ai_service.py 中添加以下日志:

1.1 检测到情感标签时记录(第360行)

if emotion_str in valid_emotions:
    detected_emotion = emotion_str
    logger.info(f"[LLM情感检测] ✅ 检测到情感标签: {emotion_str}, 原始片段: {full_response[:200]}")

1.2 流式处理完成后记录完整响应(第396-407行)

# 🔍 调试日志:记录LLM完整响应和情感检测结果
logger.debug(
    f"[LLM完整响应] 总chunk数={chunk_count}, "
    f"检测到的情感={detected_emotion}, "
    f"是否包含[EMOTION:={'[EMOTION:' in full_response}, "
    f"完整响应长度={len(full_response)}, "
    f"响应前500字符={full_response[:500]}"
)

# ⚠️ 警告:如果没有检测到情感标签
if detected_emotion == "neutral" and "[EMOTION:" not in full_response:
    logger.warning(
        f"[LLM情感检测] ⚠️ 未检测到情感标签,LLM可能没有生成。"
        f"完整响应长度={len(full_response)}, "
        f"响应前500字符={full_response[:500]}"
    )

1.3 WebSocket处理中记录情感发送(backend/websocket_handler.py 第718行)

if not emotion_sent and emotion:
    logger.info(f"[{request_id}] 🎭 发送情感标签给前端: {emotion}, 句子内容: {sentence[:100] if sentence else '(空句子)'}")
    await self.send_emotion(request_id, emotion)
    emotion_sent = True
    final_emotion = emotion

2. 检查 LLM 实际输出

  • 查看日志中是否有 [LLM] 未知情感词 的警告
  • 如果有,说明 LLM 生成了情感标签,但格式不对

3. 验证提示词是否生效

  • 检查发送给 LLM 的 messages 数组,确认 system_prompt 中包含情感标签要求

📝 总结

当前逻辑流程

  1. ✅ LLM 提示词明确要求生成情感标签
  2. ✅ 代码中有检测逻辑(流式处理中)
  3. ✅ 检测到的情感会传递给前端和 TTS
  4. ⚠️ 问题:如果 LLM 没有生成标签,detected_emotion 会一直保持 "neutral"

最可能的原因

LLM 没有按照提示词要求生成情感标签,或者生成的格式不正确。

下一步行动

✅ 已完成

  1. ✅ 添加日志记录,验证 LLM 是否生成了情感标签
    • 检测到情感标签时记录(INFO级别)
    • 流式处理完成后记录完整响应(DEBUG级别)
    • 未检测到情感标签时警告(WARNING级别)
    • WebSocket处理中记录情感发送(INFO级别)

🔍 待验证

  1. 运行测试,查看日志输出:
    • 如果看到 [LLM情感检测] ✅ 检测到情感标签,说明LLM生成了标签,检测逻辑正常
    • 如果看到 [LLM情感检测] ⚠️ 未检测到情感标签,说明LLM没有生成标签
    • 查看 [LLM完整响应] 日志,检查LLM的原始输出格式

🔧 后续修复(根据日志结果)

  1. 如果 LLM 确实没有生成情感标签:

    • 强化提示词(在 System Prompt 中更明确地要求)
    • 检查 LLM 模型是否支持结构化输出
    • 考虑使用 Function Calling 或其他方式强制输出格式
  2. 如果 LLM 生成了但格式不对:

    • 调整检测逻辑,支持更多格式变体
    • 使用正则表达式更灵活地匹配