# 智能体聊天助手记忆问题修复文档 ## 问题描述 智能聊天助手无法记住用户信息,具体表现为: 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节点变量替换问题 - **问题1**:LLM节点的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 # 在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` 处理逻辑 **关键修改**: ```python # 确保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` 模式) **关键修改**: ```python # 如果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` 方法 **关键修改**: ```python # 即使有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` 节点处理逻辑(变量替换部分) **关键修改**: ```python # 支持嵌套路径,如 {{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}}` 格式化 ```python # 如果是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}}` 变量处理) **关键修改**: ```python # 特殊处理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` 函数 **关键修改**: ```typescript // 添加标志位,防止重复添加回复 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