Files
aiagent/智能体聊天助手记忆问题修复.md
2026-01-22 09:59:02 +08:00

13 KiB
Raw Permalink Blame History

智能体聊天助手记忆问题修复文档

问题描述

智能聊天助手无法记住用户信息,具体表现为:

  1. 第一次对话:用户输入 "我的名字叫老七"
  2. 第二次对话:用户输入 "你还记得我的名字吗?"
  3. 预期结果:助手应该回答 "是的,我记得你叫老七"
  4. 实际结果:助手无法记住用户名字,或者回答错误

额外问题:助手有时会回复两次相同的消息。

问题分析

工作流结构

智能聊天助手的工作流包含以下关键节点:

  1. 开始节点 (start-1) - 接收用户输入
  2. 查询记忆节点 (cache-query) - 从Redis查询对话历史
  3. 合并上下文节点 (transform-merge) - 合并用户输入和记忆
  4. 意图理解节点 (llm-intent) - 分析用户意图
  5. 意图路由节点 (switch-intent) - 根据意图分发到不同分支
  6. 问题回答节点 (llm-question) - 生成回答
  7. 合并回复节点 (merge-response) - 合并各分支结果
  8. 更新记忆节点 (cache-update) - 更新对话历史到Redis
  9. 格式化回复节点 (llm-format) - 格式化最终回复
  10. 结束节点 (end-1) - 返回最终结果

根本原因

经过深入调试,发现以下问题:

1. Cache节点数据存储问题

  • 问题cache-update 节点的 value_template 中,{{user_input}}{{output}}{{timestamp}} 等变量在Python表达式执行时被当作字符串 "null" 处理
  • 原因变量替换逻辑在Python表达式执行之后导致变量未正确替换

2. Cache节点数据读取问题

  • 问题cache-query 节点的 default_value 中,conversation_history 初始化为 null,导致后续 null + [...] 操作失败
  • 原因JSON解析后空值被解析为 None,而不是空列表 []

3. Transform节点数据丢失问题

  • 问题transform-merge 节点的 mapping 中,{{output}} 映射到 memory 字段,但 cache-query 的输出结构不包含完整的 memory 对象
  • 原因cache-query 返回的数据结构是 {"right": {...}},而 transform-merge 期望的是完整的 memory 对象

4. 边连接导致的数据丢失问题

  • 问题cache-query → transform-merge 的边设置了 sourceHandle='right',导致只有 right 字段被传递,其他内存相关字段丢失
  • 原因get_node_input 方法在处理 sourceHandle 时,只传递了指定字段,没有保留内存相关字段

5. LLM节点变量替换问题

  • 问题1LLM节点的prompt模板中{{user_input}}{{memory.conversation_history}} 无法正确替换
  • 原因:变量替换逻辑不支持嵌套路径(如 {{memory.conversation_history}}
  • 问题2{{output}} 变量无法从嵌套的 right.right.right 结构中提取
  • 原因:变量提取逻辑只检查顶层字段,没有递归查找

6. 前端重复回复问题

  • 问题:助手有时会回复两次相同的消息
  • 原因:轮询逻辑中,checkStatus 函数在状态为 completed 时可能被多次调用,导致重复添加消息

修复方案

1. 修复Cache节点的变量替换逻辑

文件backend/app/services/workflow_engine.py

修改位置execute_node 方法中的 cache 节点处理逻辑

关键修改

# 在Python表达式执行之前先替换变量
if '{{user_input}}' in value_template:
    user_input_value = input_data.get('user_input') or input_data.get('query') or input_data.get('input') or input_data.get('USER_INPUT') or ''
    user_input_escaped = json_module.dumps(user_input_value, ensure_ascii=False)[1:-1]  # 移除外层引号
    value_template = value_template.replace('{{user_input}}', user_input_escaped)

if '{{output}}' in value_template:
    output_value = self._extract_output_value(input_data)
    output_escaped = json_module.dumps(output_value, ensure_ascii=False)[1:-1]  # 移除外层引号
    value_template = value_template.replace('{{output}}', output_escaped)

if '{{timestamp}}' in value_template:
    timestamp_value = input_data.get('timestamp') or datetime.now().isoformat()
    timestamp_escaped = json_module.dumps(timestamp_value, ensure_ascii=False)[1:-1]  # 移除外层引号
    value_template = value_template.replace('{{timestamp}}', timestamp_escaped)

说明

  • 使用 json.dumps()[1:-1] 来正确转义字符串,同时移除外层引号
  • 在Python表达式执行之前完成变量替换

2. 修复Cache节点的默认值初始化

文件backend/app/services/workflow_engine.py

修改位置execute_node 方法中的 cache-query 处理逻辑

关键修改

# 确保default_value中的conversation_history初始化为空列表
default_value_str = node_data.get('default_value', '{}')
try:
    default_value = json_module.loads(default_value_str)
    # 确保conversation_history是列表而不是null
    if 'conversation_history' not in default_value or default_value.get('conversation_history') is None:
        default_value['conversation_history'] = []
except:
    default_value = {"conversation_history": [], "user_profile": {}, "context": {}}

3. 修复Transform节点的Memory字段构建

文件backend/app/services/workflow_engine.py

修改位置execute_node 方法中的 transform 节点处理逻辑(merge 模式)

关键修改

# 如果memory字段为空尝试从顶层字段构建
if key == 'memory' and (value is None or value == '' or value == '{{output}}'):
    # 尝试从expanded_input中构建memory对象
    memory = {}
    for field in ['conversation_history', 'user_profile', 'context']:
        if field in expanded_input:
            memory[field] = expanded_input[field]
    if memory:
        result[key] = memory
    else:
        # 如果还是找不到保留原有的memory字段如果有
        if 'memory' in expanded_input:
            result[key] = expanded_input['memory']
else:
    result[key] = value

4. 修复边连接导致的数据丢失

文件backend/app/services/workflow_engine.py

修改位置get_node_input 方法

关键修改

# 即使有sourceHandle也要保留内存相关字段
if edge.get('sourceHandle'):
    input_data[edge['sourceHandle']] = source_output
    # 显式保留内存相关字段
    if isinstance(source_output, dict):
        for field in ['conversation_history', 'user_profile', 'context', 'memory']:
            if field in source_output:
                input_data[field] = source_output[field]
else:
    # ... 原有逻辑

5. 修复LLM节点的嵌套路径变量支持

文件backend/app/services/workflow_engine.py

修改位置execute_node 方法中的 llm 节点处理逻辑(变量替换部分)

关键修改

# 支持嵌套路径,如 {{memory.conversation_history}}
if '.' in var_name:
    value = self._get_nested_value(input_data, var_name)
else:
    # 原有逻辑:检查别名和直接字段
    value = input_data.get(var_name)
    if value is None:
        # 检查别名
        aliases = {
            'user_input': ['query', 'input', 'USER_INPUT', 'user_input'],
            'output': ['result', 'response', 'text', 'content']
        }
        for alias_key, alias_list in aliases.items():
            if var_name == alias_key:
                for alias in alias_list:
                    value = input_data.get(alias)
                    if value is not None:
                        break

特殊处理{{memory.conversation_history}} 格式化

# 如果是conversation_history格式化为可读格式
if var_name == 'memory.conversation_history' and isinstance(value, list):
    formatted_history = []
    for msg in value:
        role = msg.get('role', 'unknown')
        content = msg.get('content', '')
        if role == 'user':
            formatted_history.append(f"用户: {content}")
        elif role == 'assistant':
            formatted_history.append(f"助手: {content}")
    value = '\n'.join(formatted_history) if formatted_history else '无对话历史'

6. 修复{{output}}变量的递归提取

文件backend/app/services/workflow_engine.py

修改位置execute_node 方法中的 llm 节点处理逻辑({{output}} 变量处理)

关键修改

# 特殊处理output变量递归查找right字段
if var_path == 'output':
    right_value = input_data.get('right')
    if right_value:
        # 递归查找字符串值
        def extract_string_from_right(obj, depth=0):
            if isinstance(obj, str):
                return obj
            elif isinstance(obj, dict):
                # 优先检查常见字段
                for key in ['content', 'text', 'message', 'output']:
                    if key in obj and isinstance(obj[key], str):
                        return obj[key]
                # 递归查找right字段
                if 'right' in obj:
                    return extract_string_from_right(obj['right'], depth + 1)
            return None
        
        value = extract_string_from_right(right_value)
        if value:
            logger.info(f"[rjb] LLM节点从right字段提取output: {value[:100]}")

7. 修复前端重复回复问题

文件frontend/src/components/AgentChatPreview.vue

修改位置handleSendMessage 方法中的 checkStatus 函数

关键修改

// 添加标志位,防止重复添加回复
let replyAdded = false

const checkStatus = async () => {
  try {
    // 如果已经添加过回复,直接返回
    if (replyAdded) {
      return
    }
    
    // ... 获取执行状态 ...
    
    if (exec.status === 'completed') {
      // 防止重复添加
      if (replyAdded) {
        return
      }
      
      // 标记已添加回复
      replyAdded = true
      
      // 添加回复消息
      messages.value.push({
        role: 'agent',
        content: agentReply || '执行完成',
        timestamp: Date.now()
      })
      
      // ... 其他逻辑 ...
    }
  } catch (error) {
    // 同样添加防重复逻辑
    if (replyAdded) {
      return
    }
    replyAdded = true
    // ... 错误处理 ...
  }
}

// 每次发送新消息时重置标志位
replyAdded = false

测试验证

测试步骤

  1. 第一次对话

    • 输入:我的名字叫老七
    • 预期:助手正常回复,并将信息存储到记忆
  2. 第二次对话

    • 输入:你还记得我的名字吗?
    • 预期:助手回答 是的,我记得你叫老七
  3. 验证重复回复

    • 观察是否只收到一次回复

测试结果

记忆功能:正常工作,能正确记住用户名字
重复回复:已修复,每次对话只回复一次

关键代码文件

  1. 后端核心逻辑

    • backend/app/services/workflow_engine.py - 工作流执行引擎
    • backend/scripts/generate_chat_agent.py - 智能聊天助手工作流定义
  2. 前端组件

    • frontend/src/components/AgentChatPreview.vue - 聊天预览组件
  3. 测试脚本

    • test_memory_functionality.py - 记忆功能测试脚本
    • test_output_variable_extraction.py - 输出变量提取测试脚本

修复时间线

  1. 问题发现:用户报告无法记住名字
  2. 初步分析:检查工作流定义和节点配置
  3. 深入调试:添加详细日志,追踪数据流
  4. 修复Cache节点:修复数据存储和读取逻辑
  5. 修复Transform节点:修复数据合并逻辑
  6. 修复LLM节点:修复变量替换和嵌套路径支持
  7. 修复前端:修复重复回复问题
  8. 测试验证:确认所有问题已解决

经验总结

  1. 数据流追踪:在复杂工作流中,需要仔细追踪数据在每个节点之间的传递
  2. 变量替换时机:变量替换必须在表达式执行之前完成
  3. 数据结构一致性:确保上下游节点对数据结构的期望一致
  4. 边界情况处理:注意处理 null、空值、嵌套结构等边界情况
  5. 前端防重复:轮询逻辑中需要添加防重复机制

后续优化建议

  1. 统一数据结构:定义统一的数据结构规范,避免节点间数据格式不一致
  2. 增强日志:添加更详细的调试日志,方便问题排查
  3. 单元测试:为关键节点添加单元测试,确保修复的稳定性
  4. 性能优化优化轮询频率减少不必要的API调用
  5. 错误处理:增强错误处理机制,提供更友好的错误提示

修复完成时间2024年根据实际时间填写
修复人员AI Assistant
文档版本v1.0