13 KiB
智能体聊天助手记忆问题修复文档
问题描述
智能聊天助手无法记住用户信息,具体表现为:
- 第一次对话:用户输入 "我的名字叫老七"
- 第二次对话:用户输入 "你还记得我的名字吗?"
- 预期结果:助手应该回答 "是的,我记得你叫老七"
- 实际结果:助手无法记住用户名字,或者回答错误
额外问题:助手有时会回复两次相同的消息。
问题分析
工作流结构
智能聊天助手的工作流包含以下关键节点:
- 开始节点 (
start-1) - 接收用户输入 - 查询记忆节点 (
cache-query) - 从Redis查询对话历史 - 合并上下文节点 (
transform-merge) - 合并用户输入和记忆 - 意图理解节点 (
llm-intent) - 分析用户意图 - 意图路由节点 (
switch-intent) - 根据意图分发到不同分支 - 问题回答节点 (
llm-question) - 生成回答 - 合并回复节点 (
merge-response) - 合并各分支结果 - 更新记忆节点 (
cache-update) - 更新对话历史到Redis - 格式化回复节点 (
llm-format) - 格式化最终回复 - 结束节点 (
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表达式执行之前,先替换变量
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
测试验证
测试步骤
-
第一次对话:
- 输入:
我的名字叫老七 - 预期:助手正常回复,并将信息存储到记忆
- 输入:
-
第二次对话:
- 输入:
你还记得我的名字吗? - 预期:助手回答
是的,我记得你叫老七
- 输入:
-
验证重复回复:
- 观察是否只收到一次回复
测试结果
✅ 记忆功能:正常工作,能正确记住用户名字
✅ 重复回复:已修复,每次对话只回复一次
关键代码文件
-
后端核心逻辑:
backend/app/services/workflow_engine.py- 工作流执行引擎backend/scripts/generate_chat_agent.py- 智能聊天助手工作流定义
-
前端组件:
frontend/src/components/AgentChatPreview.vue- 聊天预览组件
-
测试脚本:
test_memory_functionality.py- 记忆功能测试脚本test_output_variable_extraction.py- 输出变量提取测试脚本
修复时间线
- 问题发现:用户报告无法记住名字
- 初步分析:检查工作流定义和节点配置
- 深入调试:添加详细日志,追踪数据流
- 修复Cache节点:修复数据存储和读取逻辑
- 修复Transform节点:修复数据合并逻辑
- 修复LLM节点:修复变量替换和嵌套路径支持
- 修复前端:修复重复回复问题
- 测试验证:确认所有问题已解决
经验总结
- 数据流追踪:在复杂工作流中,需要仔细追踪数据在每个节点之间的传递
- 变量替换时机:变量替换必须在表达式执行之前完成
- 数据结构一致性:确保上下游节点对数据结构的期望一致
- 边界情况处理:注意处理
null、空值、嵌套结构等边界情况 - 前端防重复:轮询逻辑中需要添加防重复机制
后续优化建议
- 统一数据结构:定义统一的数据结构规范,避免节点间数据格式不一致
- 增强日志:添加更详细的调试日志,方便问题排查
- 单元测试:为关键节点添加单元测试,确保修复的稳定性
- 性能优化:优化轮询频率,减少不必要的API调用
- 错误处理:增强错误处理机制,提供更友好的错误提示
修复完成时间:2024年(根据实际时间填写)
修复人员:AI Assistant
文档版本:v1.0