Kaynağa Gözat

基本完成对Recat Redis的调试,准备进行异步改造。

wangxq 1 ay önce
ebeveyn
işleme
989d3b37bb

+ 43 - 5
test/custom_react_agent/agent.py

@@ -61,6 +61,8 @@ class CustomReactAgent:
             base_url=config.QWEN_BASE_URL,
             model=config.QWEN_MODEL,
             temperature=0.1,
+            timeout=config.NETWORK_TIMEOUT,  # 添加超时配置
+            max_retries=config.MAX_RETRIES,  # 添加重试配置
             extra_body={
                 "enable_thinking": False,
                 "misc": {
@@ -148,11 +150,47 @@ class CustomReactAgent:
             instruction = f"提示:建议下一步使用工具 '{state['suggested_next_step']}'。"
             messages_for_llm.append(SystemMessage(content=instruction))
 
-        response = self.llm_with_tools.invoke(messages_for_llm)
-        logger.info(f"   LLM Response: {response.pretty_print()}")
-        
-        # 只返回消息,不承担其他职责
-        return {"messages": [response]}
+        # 添加重试机制处理网络连接问题
+        import time
+        max_retries = config.MAX_RETRIES
+        for attempt in range(max_retries):
+            try:
+                response = self.llm_with_tools.invoke(messages_for_llm)
+                logger.info(f"   LLM Response: {response.pretty_print()}")
+                # 只返回消息,不承担其他职责
+                return {"messages": [response]}
+                
+            except Exception as e:
+                error_msg = str(e)
+                logger.warning(f"   ⚠️ LLM调用失败 (尝试 {attempt + 1}/{max_retries}): {error_msg}")
+                
+                # 检查是否是网络连接错误
+                if any(keyword in error_msg for keyword in [
+                    "Connection error", "APIConnectionError", "ConnectError", 
+                    "timeout", "远程主机强迫关闭", "网络连接"
+                ]):
+                    if attempt < max_retries - 1:
+                        wait_time = config.RETRY_BASE_DELAY ** attempt  # 指数退避:2, 4, 8秒
+                        logger.info(f"   🔄 网络错误,{wait_time}秒后重试...")
+                        time.sleep(wait_time)
+                        continue
+                    else:
+                        # 所有重试都失败了,返回一个降级的回答
+                        logger.error(f"   ❌ 网络连接持续失败,返回降级回答")
+                        
+                        # 检查是否有SQL执行结果可以利用
+                        sql_data = self._extract_latest_sql_data(state["messages"])
+                        if sql_data:
+                            fallback_content = "抱歉,由于网络连接问题,无法生成完整的文字总结。不过查询已成功执行,结果如下:\n\n" + sql_data
+                        else:
+                            fallback_content = "抱歉,由于网络连接问题,无法完成此次请求。请稍后重试或检查网络连接。"
+                            
+                        fallback_response = AIMessage(content=fallback_content)
+                        return {"messages": [fallback_response]}
+                else:
+                    # 非网络错误,直接抛出
+                    logger.error(f"   ❌ LLM调用出现非网络错误: {error_msg}")
+                    raise e
     
     def _print_state_info(self, state: AgentState, node_name: str) -> None:
         """

+ 250 - 4
test/custom_react_agent/api.py

@@ -285,7 +285,7 @@ def chat_endpoint():
             "error": "系统异常,请稍后重试"
         }), 500
 
-@app.route('/api/users/<user_id>/conversations', methods=['GET'])
+@app.route('/api/v0/react/users/<user_id>/conversations', methods=['GET'])
 def get_user_conversations(user_id: str):
     """获取用户的聊天记录列表"""
     global _agent_instance
@@ -329,7 +329,7 @@ def get_user_conversations(user_id: str):
             "timestamp": datetime.now().isoformat()
         }), 500
 
-@app.route('/api/users/<user_id>/conversations/<thread_id>', methods=['GET'])
+@app.route('/api/v0/react/users/<user_id>/conversations/<thread_id>', methods=['GET'])
 def get_user_conversation_detail(user_id: str, thread_id: str):
     """获取特定对话的详细历史"""
     global _agent_instance
@@ -622,7 +622,7 @@ def test_redis_connection():
             "timestamp": datetime.now().isoformat()
         }), 500
 
-@app.route('/api/test/users/<user_id>/conversations', methods=['GET'])
+@app.route('/api/v0/react/direct/users/<user_id>/conversations', methods=['GET'])
 def test_get_user_conversations_simple(user_id: str):
     """测试简单Redis查询获取用户对话列表"""
     try:
@@ -653,6 +653,252 @@ def test_get_user_conversations_simple(user_id: str):
             "error": str(e),
             "timestamp": datetime.now().isoformat()
         }), 500
+    
+
+# 在 api.py 文件顶部的导入部分添加:
+from enhanced_redis_api import get_conversation_detail_from_redis
+
+# 在 api.py 文件中添加以下新路由:
+
+@app.route('/api/v0/react/direct/conversations/<thread_id>', methods=['GET'])
+def get_conversation_detail_api(thread_id: str):
+    """
+    获取特定对话的详细信息 - 支持include_tools开关参数
+    
+    Query Parameters:
+        - include_tools: bool, 是否包含工具调用信息,默认false
+                        true: 返回完整对话(human/ai/tool/system)
+                        false: 只返回human/ai消息,清理工具调用信息
+        - user_id: str, 可选的用户ID验证
+        
+    Examples:
+        GET /api/conversations/wang:20250709195048728?include_tools=true   # 完整模式
+        GET /api/conversations/wang:20250709195048728?include_tools=false  # 简化模式(默认)
+        GET /api/conversations/wang:20250709195048728                      # 简化模式(默认)
+    """
+    try:
+        # 获取查询参数
+        include_tools = request.args.get('include_tools', 'false').lower() == 'true'
+        user_id = request.args.get('user_id')
+        
+        # 验证thread_id格式
+        if ':' not in thread_id:
+            return jsonify({
+                "success": False,
+                "error": "Invalid thread_id format. Expected format: user_id:timestamp",
+                "timestamp": datetime.now().isoformat()
+            }), 400
+        
+        # 如果提供了user_id,验证thread_id是否属于该用户
+        thread_user_id = thread_id.split(':')[0]
+        if user_id and thread_user_id != user_id:
+            return jsonify({
+                "success": False,
+                "error": f"Thread ID {thread_id} does not belong to user {user_id}",
+                "timestamp": datetime.now().isoformat()
+            }), 400
+        
+        logger.info(f"📖 获取对话详情 - Thread: {thread_id}, Include Tools: {include_tools}")
+        
+        # 从Redis获取对话详情(使用我们的新函数)
+        result = get_conversation_detail_from_redis(thread_id, include_tools)
+        
+        if not result['success']:
+            logger.warning(f"⚠️ 获取对话详情失败: {result['error']}")
+            return jsonify({
+                "success": False,
+                "error": result['error'],
+                "timestamp": datetime.now().isoformat()
+            }), 404
+        
+        # 添加API元数据
+        result['data']['api_metadata'] = {
+            "timestamp": datetime.now().isoformat(),
+            "api_version": "v1",
+            "endpoint": "get_conversation_detail",
+            "query_params": {
+                "include_tools": include_tools,
+                "user_id": user_id
+            }
+        }
+        
+        mode_desc = "完整模式" if include_tools else "简化模式"
+        logger.info(f"✅ 成功获取对话详情 - Messages: {result['data']['message_count']}, Mode: {mode_desc}")
+        
+        return jsonify({
+            "success": True,
+            "data": result['data'],
+            "timestamp": datetime.now().isoformat()
+        }), 200
+        
+    except Exception as e:
+        import traceback
+        logger.error(f"❌ 获取对话详情异常: {e}")
+        logger.error(f"❌ 详细错误信息: {traceback.format_exc()}")
+        
+        return jsonify({
+            "success": False,
+            "error": str(e),
+            "timestamp": datetime.now().isoformat()
+        }), 500
+
+@app.route('/api/v0/react/direct/conversations/<thread_id>/compare', methods=['GET'])
+def compare_conversation_modes_api(thread_id: str):
+    """
+    比较完整模式和简化模式的对话内容
+    用于调试和理解两种模式的差异
+    
+    Examples:
+        GET /api/conversations/wang:20250709195048728/compare
+    """
+    try:
+        logger.info(f"🔍 比较对话模式 - Thread: {thread_id}")
+        
+        # 获取完整模式
+        full_result = get_conversation_detail_from_redis(thread_id, include_tools=True)
+        
+        # 获取简化模式
+        simple_result = get_conversation_detail_from_redis(thread_id, include_tools=False)
+        
+        if not (full_result['success'] and simple_result['success']):
+            return jsonify({
+                "success": False,
+                "error": "无法获取对话数据进行比较",
+                "timestamp": datetime.now().isoformat()
+            }), 404
+        
+        # 构建比较结果
+        comparison = {
+            "thread_id": thread_id,
+            "full_mode": {
+                "message_count": full_result['data']['message_count'],
+                "stats": full_result['data']['stats'],
+                "sample_messages": full_result['data']['messages'][:3]  # 只显示前3条作为示例
+            },
+            "simple_mode": {
+                "message_count": simple_result['data']['message_count'],
+                "stats": simple_result['data']['stats'],
+                "sample_messages": simple_result['data']['messages'][:3]  # 只显示前3条作为示例
+            },
+            "comparison_summary": {
+                "message_count_difference": full_result['data']['message_count'] - simple_result['data']['message_count'],
+                "tools_filtered_out": full_result['data']['stats'].get('tool_messages', 0),
+                "ai_messages_with_tools": full_result['data']['stats'].get('messages_with_tools', 0),
+                "filtering_effectiveness": "有效" if (full_result['data']['message_count'] - simple_result['data']['message_count']) > 0 else "无差异"
+            },
+            "metadata": {
+                "timestamp": datetime.now().isoformat(),
+                "note": "sample_messages 只显示前3条消息作为示例,完整消息请使用相应的详情API"
+            }
+        }
+        
+        logger.info(f"✅ 模式比较完成 - 完整: {comparison['full_mode']['message_count']}, 简化: {comparison['simple_mode']['message_count']}")
+        
+        return jsonify({
+            "success": True,
+            "data": comparison,
+            "timestamp": datetime.now().isoformat()
+        }), 200
+        
+    except Exception as e:
+        logger.error(f"❌ 对话模式比较失败: {e}")
+        return jsonify({
+            "success": False,
+            "error": str(e),
+            "timestamp": datetime.now().isoformat()
+        }), 500
+
+@app.route('/api/v0/react/direct/conversations/<thread_id>/summary', methods=['GET'])
+def get_conversation_summary_api(thread_id: str):
+    """
+    获取对话摘要信息(只包含基本统计,不返回具体消息)
+    
+    Query Parameters:
+        - include_tools: bool, 影响统计信息的计算方式
+        
+    Examples:
+        GET /api/conversations/wang:20250709195048728/summary?include_tools=true
+    """
+    try:
+        include_tools = request.args.get('include_tools', 'false').lower() == 'true'
+        
+        # 验证thread_id格式
+        if ':' not in thread_id:
+            return jsonify({
+                "success": False,
+                "error": "Invalid thread_id format. Expected format: user_id:timestamp",
+                "timestamp": datetime.now().isoformat()
+            }), 400
+        
+        logger.info(f"📊 获取对话摘要 - Thread: {thread_id}, Include Tools: {include_tools}")
+        
+        # 获取完整对话信息
+        result = get_conversation_detail_from_redis(thread_id, include_tools)
+        
+        if not result['success']:
+            return jsonify({
+                "success": False,
+                "error": result['error'],
+                "timestamp": datetime.now().isoformat()
+            }), 404
+        
+        # 只返回摘要信息,不包含具体消息
+        data = result['data']
+        summary = {
+            "thread_id": data['thread_id'],
+            "user_id": data['user_id'],
+            "include_tools": data['include_tools'],
+            "message_count": data['message_count'],
+            "stats": data['stats'],
+            "metadata": data['metadata'],
+            "first_message_preview": None,
+            "last_message_preview": None,
+            "conversation_preview": None
+        }
+        
+        # 添加消息预览
+        messages = data.get('messages', [])
+        if messages:
+            # 第一条human消息预览
+            for msg in messages:
+                if msg['type'] == 'human':
+                    content = str(msg['content'])
+                    summary['first_message_preview'] = content[:100] + "..." if len(content) > 100 else content
+                    break
+            
+            # 最后一条ai消息预览
+            for msg in reversed(messages):
+                if msg['type'] == 'ai' and msg.get('content', '').strip():
+                    content = str(msg['content'])
+                    summary['last_message_preview'] = content[:100] + "..." if len(content) > 100 else content
+                    break
+            
+            # 生成对话预览(第一条human消息)
+            summary['conversation_preview'] = summary['first_message_preview']
+        
+        # 添加API元数据
+        summary['api_metadata'] = {
+            "timestamp": datetime.now().isoformat(),
+            "api_version": "v1",
+            "endpoint": "get_conversation_summary"
+        }
+        
+        logger.info(f"✅ 成功获取对话摘要")
+        
+        return jsonify({
+            "success": True,
+            "data": summary,
+            "timestamp": datetime.now().isoformat()
+        }), 200
+        
+    except Exception as e:
+        logger.error(f"❌ 获取对话摘要失败: {e}")
+        return jsonify({
+            "success": False,
+            "error": str(e),
+            "timestamp": datetime.now().isoformat()
+        }), 500
+
 
 # 为了支持独立运行
 if __name__ == "__main__":
@@ -669,4 +915,4 @@ if __name__ == "__main__":
         logger.error(f"❌ API 服务启动失败: {e}")
     
     # 启动Flask应用
-    app.run(host="0.0.0.0", port=8000, debug=False) 
+    app.run(host="0.0.0.0", port=8000, debug=False) 

+ 0 - 0
test/custom_react_agent/simple_redis_api.py → test/custom_react_agent/bak/simple_redis_api.py


+ 0 - 0
test/custom_react_agent/simple_redis_query.py → test/custom_react_agent/bak/simple_redis_query.py


+ 6 - 1
test/custom_react_agent/config.py

@@ -23,4 +23,9 @@ LOG_LEVEL = logging.INFO
 LOG_FORMAT = '%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
 
 # --- Agent 配置 ---
-DEFAULT_USER_ID = "default-user" 
+DEFAULT_USER_ID = "default-user"
+
+# --- 网络重试配置 ---
+MAX_RETRIES = 3                    # 最大重试次数
+RETRY_BASE_DELAY = 2               # 重试基础延迟(秒)
+NETWORK_TIMEOUT = 30               # 网络超时时间(秒) 

+ 9 - 9
test/custom_react_agent/doc/QUICKSTART.md

@@ -25,10 +25,10 @@ curl -X POST http://localhost:8000/api/chat \
 ### 4. 查看对话历史 ⭐ 新功能
 ```bash
 # 查看用户的对话列表
-curl "http://localhost:8000/api/users/doudou/conversations?limit=5"
+curl "http://localhost:8000/api/v0/react/users/doudou/conversations?limit=5"
 
 # 查看特定对话的详细内容
-curl "http://localhost:8000/api/users/doudou/conversations/doudou:20250115103000001"
+curl "http://localhost:8000/api/v0/react/users/doudou/conversations/doudou:20250115103000001"
 ```
 
 ## 📋 基本API用法
@@ -54,13 +54,13 @@ curl -X POST http://localhost:8000/api/chat \
 ### 对话历史管理 ⭐ 新功能
 ```bash
 # 获取用户对话列表
-curl "http://localhost:8000/api/users/alice/conversations"
+curl "http://localhost:8000/api/v0/react/users/alice/conversations"
 
 # 限制返回数量
-curl "http://localhost:8000/api/users/alice/conversations?limit=10"
+curl "http://localhost:8000/api/v0/react/users/alice/conversations?limit=10"
 
 # 获取特定对话详情
-curl "http://localhost:8000/api/users/alice/conversations/alice:20250115103000001"
+curl "http://localhost:8000/api/v0/react/users/alice/conversations/alice:20250115103000001"
 ```
 
 ## 💻 Python 客户端示例
@@ -92,7 +92,7 @@ import requests
 
 def get_user_conversations(user_id, limit=10):
     """获取用户对话列表"""
-    url = f"http://localhost:8000/api/users/{user_id}/conversations"
+    url = f"http://localhost:8000/api/v0/react/users/{user_id}/conversations"
     params = {"limit": limit}
     
     response = requests.get(url, params=params)
@@ -100,7 +100,7 @@ def get_user_conversations(user_id, limit=10):
 
 def get_conversation_detail(user_id, thread_id):
     """获取对话详情"""
-    url = f"http://localhost:8000/api/users/{user_id}/conversations/{thread_id}"
+    url = f"http://localhost:8000/api/v0/react/users/{user_id}/conversations/{thread_id}"
     
     response = requests.get(url)
     return response.json()
@@ -142,14 +142,14 @@ console.log("回答:", result.data.response);
 ```javascript
 async function getUserConversations(userId, limit = 10) {
     const response = await fetch(
-        `http://localhost:8000/api/users/${userId}/conversations?limit=${limit}`
+        `http://localhost:8000/api/v0/react/users/${userId}/conversations?limit=${limit}`
     );
     return await response.json();
 }
 
 async function getConversationDetail(userId, threadId) {
     const response = await fetch(
-        `http://localhost:8000/api/users/${userId}/conversations/${threadId}`
+        `http://localhost:8000/api/v0/react/users/${userId}/conversations/${threadId}`
     );
     return await response.json();
 }

+ 6 - 6
test/custom_react_agent/doc/README_API.md

@@ -58,7 +58,7 @@ python api.py
 ```
 
 ### 3. 获取用户对话列表 ⭐ 新增
-**GET** `/api/users/{user_id}/conversations`
+**GET** `/api/v0/react/users/{user_id}/conversations`
 
 获取指定用户的最近聊天记录列表
 
@@ -70,7 +70,7 @@ python api.py
 
 **请求示例:**
 ```bash
-curl "http://localhost:8000/api/users/doudou/conversations?limit=5"
+curl "http://localhost:8000/api/v0/react/users/doudou/conversations?limit=5"
 ```
 
 **响应示例:**
@@ -109,7 +109,7 @@ curl "http://localhost:8000/api/users/doudou/conversations?limit=5"
 ```
 
 ### 4. 获取对话详情 ⭐ 新增
-**GET** `/api/users/{user_id}/conversations/{thread_id}`
+**GET** `/api/v0/react/users/{user_id}/conversations/{thread_id}`
 
 获取特定对话的详细历史记录
 
@@ -119,7 +119,7 @@ curl "http://localhost:8000/api/users/doudou/conversations?limit=5"
 
 **请求示例:**
 ```bash
-curl "http://localhost:8000/api/users/doudou/conversations/doudou:20250115103000001"
+curl "http://localhost:8000/api/v0/react/users/doudou/conversations/doudou:20250115103000001"
 ```
 
 **响应示例:**
@@ -191,10 +191,10 @@ curl -X POST http://localhost:8000/api/chat \
   -d '{"question": "请问哪个高速服务区的档口数量最多?", "user_id": "doudou"}'
 
 # 2. 查看对话列表  
-curl "http://localhost:8000/api/users/doudou/conversations?limit=5"
+curl "http://localhost:8000/api/v0/react/users/doudou/conversations?limit=5"
 
 # 3. 查看特定对话详情
-curl "http://localhost:8000/api/users/doudou/conversations/doudou:20250115103000001"
+curl "http://localhost:8000/api/v0/react/users/doudou/conversations/doudou:20250115103000001"
 ```
 
 ## 📝 注意事项

+ 1 - 0
test/custom_react_agent/doc/network_troubleshooting.md

@@ -0,0 +1 @@
+ 

+ 495 - 0
test/custom_react_agent/enhanced_redis_api.py

@@ -0,0 +1,495 @@
+"""
+enhanced_redis_api.py - 完整的Redis直接访问API
+支持include_tools开关参数,可以控制是否包含工具调用信息
+"""
+import redis
+import json
+from typing import List, Dict, Any, Optional
+from datetime import datetime
+import logging
+
+logger = logging.getLogger(__name__)
+
+def get_conversation_detail_from_redis(thread_id: str, include_tools: bool = False) -> Dict[str, Any]:
+    """
+    直接从Redis获取对话详细信息
+    
+    Args:
+        thread_id: 线程ID,格式为 user_id:timestamp
+        include_tools: 是否包含工具调用信息
+                      - True: 返回所有消息(human/ai/tool/system)
+                      - False: 只返回human和ai消息,且清理ai消息中的工具调用信息
+        
+    Returns:
+        包含对话详细信息的字典
+    """
+    try:
+        # 创建Redis连接
+        redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
+        redis_client.ping()
+        
+        # 扫描该thread的所有checkpoint keys
+        pattern = f"checkpoint:{thread_id}:*"
+        logger.info(f"🔍 扫描模式: {pattern}, include_tools: {include_tools}")
+        
+        keys = []
+        cursor = 0
+        while True:
+            cursor, batch = redis_client.scan(cursor=cursor, match=pattern, count=1000)
+            keys.extend(batch)
+            if cursor == 0:
+                break
+        
+        logger.info(f"📋 找到 {len(keys)} 个keys")
+        
+        if not keys:
+            redis_client.close()
+            return {
+                "success": False,
+                "error": f"未找到对话 {thread_id}",
+                "data": None
+            }
+        
+        # 获取最新的checkpoint(按key排序,最大的是最新的)
+        latest_key = max(keys)
+        logger.info(f"🔍 使用最新key: {latest_key}")
+        
+        # 检查key类型并获取数据
+        key_type = redis_client.type(latest_key)
+        logger.info(f"🔍 Key类型: {key_type}")
+        
+        data = None
+        if key_type == 'string':
+            data = redis_client.get(latest_key)
+        elif key_type == 'ReJSON-RL':
+            # RedisJSON类型
+            try:
+                data = redis_client.execute_command('JSON.GET', latest_key)
+            except Exception as json_error:
+                logger.error(f"❌ JSON.GET 失败: {json_error}")
+                redis_client.close()
+                return {
+                    "success": False,
+                    "error": f"无法读取RedisJSON数据: {json_error}",
+                    "data": None
+                }
+        else:
+            redis_client.close()
+            return {
+                "success": False,
+                "error": f"不支持的key类型: {key_type}",
+                "data": None
+            }
+        
+        if not data:
+            redis_client.close()
+            return {
+                "success": False,
+                "error": "没有找到有效数据",
+                "data": None
+            }
+        
+        # 解析JSON数据
+        try:
+            checkpoint_data = json.loads(data)
+            logger.info(f"🔍 JSON顶级keys: {list(checkpoint_data.keys())}")
+        except json.JSONDecodeError as e:
+            redis_client.close()
+            return {
+                "success": False,
+                "error": f"JSON解析失败: {e}",
+                "data": None
+            }
+        
+        # 提取消息数据
+        messages = extract_messages_from_checkpoint(checkpoint_data)
+        logger.info(f"🔍 找到 {len(messages)} 条原始消息")
+        
+        # 解析并过滤消息 - 这里是关键的开关逻辑
+        parsed_messages = parse_and_filter_messages(messages, include_tools)
+        
+        # 提取用户ID
+        user_id = thread_id.split(':')[0] if ':' in thread_id else 'unknown'
+        
+        # 生成对话统计信息
+        stats = generate_conversation_stats(parsed_messages, include_tools)
+        
+        redis_client.close()
+        
+        return {
+            "success": True,
+            "data": {
+                "thread_id": thread_id,
+                "user_id": user_id,
+                "include_tools": include_tools,
+                "message_count": len(parsed_messages),
+                "messages": parsed_messages,
+                "stats": stats,
+                "metadata": {
+                    "latest_checkpoint_key": latest_key,
+                    "total_raw_messages": len(messages),
+                    "filtered_message_count": len(parsed_messages),
+                    "filter_mode": "full_conversation" if include_tools else "human_ai_only"
+                }
+            }
+        }
+        
+    except Exception as e:
+        logger.error(f"❌ 获取对话详情失败: {e}")
+        import traceback
+        traceback.print_exc()
+        return {
+            "success": False,
+            "error": str(e),
+            "data": None
+        }
+
+def extract_messages_from_checkpoint(checkpoint_data: Dict[str, Any]) -> List[Any]:
+    """
+    从checkpoint数据中提取消息列表
+    """
+    messages = []
+    
+    # 尝试不同的数据结构路径
+    if 'checkpoint' in checkpoint_data:
+        checkpoint = checkpoint_data['checkpoint']
+        if isinstance(checkpoint, dict) and 'channel_values' in checkpoint:
+            channel_values = checkpoint['channel_values']
+            if isinstance(channel_values, dict) and 'messages' in channel_values:
+                messages = channel_values['messages']
+    
+    # 如果没有找到,尝试直接路径
+    if not messages and 'channel_values' in checkpoint_data:
+        channel_values = checkpoint_data['channel_values']
+        if isinstance(channel_values, dict) and 'messages' in channel_values:
+            messages = channel_values['messages']
+    
+    return messages
+
+def parse_and_filter_messages(raw_messages: List[Any], include_tools: bool) -> List[Dict[str, Any]]:
+    """
+    解析和过滤消息列表 - 关键的开关逻辑实现
+    
+    Args:
+        raw_messages: 原始消息列表
+        include_tools: 是否包含工具消息
+                      - True: 返回所有消息类型
+                      - False: 只返回human/ai,且清理ai消息中的工具信息
+        
+    Returns:
+        解析后的消息列表
+    """
+    parsed_messages = []
+    
+    for msg in raw_messages:
+        try:
+            parsed_msg = parse_single_message(msg)
+            if not parsed_msg:
+                continue
+            
+            msg_type = parsed_msg['type']
+            
+            if include_tools:
+                # 完整模式:包含所有消息类型
+                parsed_messages.append(parsed_msg)
+                logger.debug(f"✅ [完整模式] 包含消息: {msg_type}")
+                
+            else:
+                # 简化模式:只包含human和ai消息
+                if msg_type == 'human':
+                    parsed_messages.append(parsed_msg)
+                    logger.debug(f"✅ [简化模式] 包含human消息")
+                    
+                elif msg_type == 'ai':
+                    # 清理AI消息,移除工具调用信息
+                    cleaned_msg = clean_ai_message_for_simple_mode(parsed_msg)
+                    
+                    # 只包含有实际内容的AI消息
+                    if cleaned_msg['content'].strip() and not cleaned_msg.get('is_intermediate_step', False):
+                        parsed_messages.append(cleaned_msg)
+                        logger.debug(f"✅ [简化模式] 包含有内容的ai消息")
+                    else:
+                        logger.debug(f"⏭️ [简化模式] 跳过空的ai消息或中间步骤")
+                
+                else:
+                    # 跳过tool、system等消息
+                    logger.debug(f"⏭️ [简化模式] 跳过 {msg_type} 消息")
+                    
+        except Exception as e:
+            logger.warning(f"⚠️ 解析消息失败: {e}")
+            continue
+    
+    logger.info(f"📊 解析结果: {len(parsed_messages)} 条消息 (include_tools={include_tools})")
+    return parsed_messages
+
+def parse_single_message(msg: Any) -> Optional[Dict[str, Any]]:
+    """
+    解析单个消息,支持LangChain序列化格式
+    """
+    if isinstance(msg, dict):
+        # LangChain序列化格式
+        if (msg.get('lc') == 1 and 
+            msg.get('type') == 'constructor' and 
+            'id' in msg and 
+            isinstance(msg['id'], list) and 
+            'kwargs' in msg):
+            
+            kwargs = msg['kwargs']
+            msg_class = msg['id'][-1] if msg['id'] else 'Unknown'
+            
+            # 确定消息类型
+            if msg_class == 'HumanMessage':
+                msg_type = 'human'
+            elif msg_class == 'AIMessage':
+                msg_type = 'ai'
+            elif msg_class == 'ToolMessage':
+                msg_type = 'tool'
+            elif msg_class == 'SystemMessage':
+                msg_type = 'system'
+            else:
+                msg_type = 'unknown'
+            
+            # 构建基础消息对象
+            parsed_msg = {
+                "type": msg_type,
+                "content": kwargs.get('content', ''),
+                "id": kwargs.get('id'),
+                "timestamp": datetime.now().isoformat()
+            }
+            
+            # 处理AI消息的特殊字段
+            if msg_type == 'ai':
+                # 工具调用信息
+                tool_calls = kwargs.get('tool_calls', [])
+                parsed_msg['tool_calls'] = tool_calls
+                parsed_msg['has_tool_calls'] = len(tool_calls) > 0
+                
+                # 额外的AI消息元数据
+                additional_kwargs = kwargs.get('additional_kwargs', {})
+                if additional_kwargs:
+                    parsed_msg['additional_kwargs'] = additional_kwargs
+                
+                response_metadata = kwargs.get('response_metadata', {})
+                if response_metadata:
+                    parsed_msg['response_metadata'] = response_metadata
+            
+            # 处理工具消息的特殊字段
+            elif msg_type == 'tool':
+                parsed_msg['tool_name'] = kwargs.get('name')
+                parsed_msg['tool_call_id'] = kwargs.get('tool_call_id')
+                parsed_msg['status'] = kwargs.get('status', 'unknown')
+            
+            return parsed_msg
+            
+        # 简单字典格式
+        elif 'type' in msg:
+            return {
+                "type": msg.get('type', 'unknown'),
+                "content": msg.get('content', ''),
+                "id": msg.get('id'),
+                "timestamp": datetime.now().isoformat()
+            }
+    
+    return None
+
+def clean_ai_message_for_simple_mode(ai_msg: Dict[str, Any]) -> Dict[str, Any]:
+    """
+    调试版本:清理AI消息用于简化模式
+    """
+    original_content = ai_msg.get("content", "")
+    logger.info(f"🔍 清理AI消息,原始内容: '{original_content}', 长度: {len(original_content)}")
+    
+    cleaned_msg = {
+        "type": ai_msg["type"],
+        "content": original_content,
+        "id": ai_msg.get("id"),
+        "timestamp": ai_msg.get("timestamp")
+    }
+    
+    # 处理内容格式化
+    content = original_content.strip()
+    
+    # 调试:检查 [Formatted Output] 处理
+    if '[Formatted Output]' in content:
+        logger.info(f"🔍 发现 [Formatted Output] 标记")
+        
+        if content.startswith('[Formatted Output]\n'):
+            # 去掉标记,保留后面的实际内容
+            actual_content = content.replace('[Formatted Output]\n', '')
+            logger.info(f"🔍 去除标记后的内容: '{actual_content}', 长度: {len(actual_content)}")
+            cleaned_msg["content"] = actual_content
+            content = actual_content
+        elif content == '[Formatted Output]' or content == '[Formatted Output]\n':
+            # 如果只有标记没有内容
+            logger.info(f"🔍 只有标记没有实际内容")
+            cleaned_msg["content"] = ""
+            cleaned_msg["is_intermediate_step"] = True
+            content = ""
+    
+    # 如果清理后内容为空或只有空白,标记为中间步骤
+    if not content.strip():
+        logger.info(f"🔍 内容为空,标记为中间步骤")
+        cleaned_msg["is_intermediate_step"] = True
+        cleaned_msg["content"] = ""
+    
+    # 添加简化模式标记
+    cleaned_msg["simplified"] = True
+    
+    logger.info(f"🔍 清理结果: '{cleaned_msg['content']}', 是否中间步骤: {cleaned_msg.get('is_intermediate_step', False)}")
+    
+    return cleaned_msg
+
+def generate_conversation_stats(messages: List[Dict[str, Any]], include_tools: bool) -> Dict[str, Any]:
+    """
+    生成对话统计信息
+    
+    Args:
+        messages: 解析后的消息列表
+        include_tools: 是否包含工具信息(影响统计内容)
+        
+    Returns:
+        统计信息字典
+    """
+    stats = {
+        "total_messages": len(messages),
+        "human_messages": 0,
+        "ai_messages": 0,
+        "conversation_rounds": 0,
+        "include_tools_mode": include_tools
+    }
+    
+    # 添加工具相关统计(仅在include_tools=True时)
+    if include_tools:
+        stats.update({
+            "tool_messages": 0,
+            "system_messages": 0,
+            "messages_with_tools": 0,
+            "unique_tools_used": set()
+        })
+    
+    for msg in messages:
+        msg_type = msg.get('type', 'unknown')
+        
+        if msg_type == 'human':
+            stats["human_messages"] += 1
+        elif msg_type == 'ai':
+            stats["ai_messages"] += 1
+            
+            # 工具相关统计
+            if include_tools and msg.get('has_tool_calls', False):
+                stats["messages_with_tools"] += 1
+                
+                # 统计使用的工具
+                tool_calls = msg.get('tool_calls', [])
+                for tool_call in tool_calls:
+                    if isinstance(tool_call, dict) and 'name' in tool_call:
+                        stats["unique_tools_used"].add(tool_call['name'])
+                        
+        elif include_tools:
+            if msg_type == 'tool':
+                stats["tool_messages"] += 1
+                
+                # 记录工具名称
+                tool_name = msg.get('tool_name')
+                if tool_name:
+                    stats["unique_tools_used"].add(tool_name)
+                    
+            elif msg_type == 'system':
+                stats["system_messages"] += 1
+    
+    # 计算对话轮次
+    stats["conversation_rounds"] = stats["human_messages"]
+    
+    # 转换set为list(JSON序列化)
+    if include_tools and "unique_tools_used" in stats:
+        stats["unique_tools_used"] = list(stats["unique_tools_used"])
+    
+    return stats
+
+def format_timestamp_readable(timestamp: str) -> str:
+    """格式化时间戳为可读格式"""
+    try:
+        if len(timestamp) >= 14:
+            year = timestamp[:4]
+            month = timestamp[4:6]
+            day = timestamp[6:8]
+            hour = timestamp[8:10]
+            minute = timestamp[10:12]
+            second = timestamp[12:14]
+            return f"{year}-{month}-{day} {hour}:{minute}:{second}"
+    except Exception:
+        pass
+    return timestamp
+
+
+# =================== 测试函数 ===================
+
+def test_conversation_detail_with_switch():
+    """
+    测试对话详情获取功能,重点测试include_tools开关
+    """
+    print("🧪 测试对话详情获取(开关参数测试)...")
+    
+    # 测试thread_id(请替换为实际存在的thread_id)
+    test_thread_id = "wang:20250709195048728323"
+    
+    print(f"\n1. 测试完整模式(include_tools=True)...")
+    result_full = get_conversation_detail_from_redis(test_thread_id, include_tools=True)
+    
+    if result_full['success']:
+        data = result_full['data']
+        print(f"   ✅ 成功获取完整对话")
+        print(f"   📊 消息数量: {data['message_count']}")
+        print(f"   📈 统计信息: {data['stats']}")
+        print(f"   🔧 包含工具: {data['stats'].get('tool_messages', 0)} 条工具消息")
+        
+        # 显示消息类型分布
+        message_types = {}
+        for msg in data['messages']:
+            msg_type = msg['type']
+            message_types[msg_type] = message_types.get(msg_type, 0) + 1
+        print(f"   📋 消息类型分布: {message_types}")
+        
+    else:
+        print(f"   ❌ 获取失败: {result_full['error']}")
+    
+    print(f"\n2. 测试简化模式(include_tools=False)...")
+    result_simple = get_conversation_detail_from_redis(test_thread_id, include_tools=False)
+    
+    if result_simple['success']:
+        data = result_simple['data']
+        print(f"   ✅ 成功获取简化对话")
+        print(f"   📊 消息数量: {data['message_count']}")
+        print(f"   📈 统计信息: {data['stats']}")
+        
+        # 显示消息类型分布
+        message_types = {}
+        for msg in data['messages']:
+            msg_type = msg['type']
+            message_types[msg_type] = message_types.get(msg_type, 0) + 1
+        print(f"   📋 消息类型分布: {message_types}")
+        
+        # 显示前几条消息示例
+        print(f"   💬 消息示例:")
+        for i, msg in enumerate(data['messages'][:4]):
+            content_preview = str(msg['content'])[:50] + "..." if len(str(msg['content'])) > 50 else str(msg['content'])
+            simplified_mark = " [简化]" if msg.get('simplified') else ""
+            print(f"      [{i+1}] {msg['type']}: {content_preview}{simplified_mark}")
+            
+    else:
+        print(f"   ❌ 获取失败: {result_simple['error']}")
+    
+    # 比较两种模式
+    if result_full['success'] and result_simple['success']:
+        full_count = result_full['data']['message_count']
+        simple_count = result_simple['data']['message_count']
+        difference = full_count - simple_count
+        
+        print(f"\n3. 模式比较:")
+        print(f"   📊 完整模式消息数: {full_count}")
+        print(f"   📊 简化模式消息数: {simple_count}")
+        print(f"   📊 过滤掉的消息数: {difference}")
+        print(f"   🎯 过滤效果: {'有效' if difference > 0 else '无差异'}")
+
+if __name__ == "__main__":
+    test_conversation_detail_with_switch()

+ 21 - 1
test/custom_react_agent/shell.py

@@ -103,7 +103,27 @@ class CustomAgentShell:
                 # 更新 thread_id 以便在同一会话中继续
                 self.thread_id = result.get("thread_id")
             else:
-                print(f"❌ 发生错误: {result.get('error')}")
+                error_msg = result.get('error', '未知错误')
+                print(f"❌ 发生错误: {error_msg}")
+                
+                # 提供针对性的建议
+                if "Connection error" in error_msg or "网络" in error_msg:
+                    print("💡 建议:")
+                    print("   - 检查网络连接是否正常")
+                    print("   - 稍后重试该问题")
+                    print("   - 如果问题持续,可以尝试重新启动程序")
+                elif "timeout" in error_msg.lower():
+                    print("💡 建议:")
+                    print("   - 当前网络较慢,建议稍后重试")
+                    print("   - 尝试简化问题复杂度")
+                else:
+                    print("💡 建议:")
+                    print("   - 请检查问题格式是否正确")
+                    print("   - 尝试重新描述您的问题")
+                
+                # 保持thread_id,用户可以继续对话
+                if not self.thread_id and result.get("thread_id"):
+                    self.thread_id = result.get("thread_id")
 
     async def _show_current_history(self):
         """显示当前会话的历史记录。"""

+ 5 - 5
test/custom_react_agent/test_conversation_api.py

@@ -78,7 +78,7 @@ def test_get_user_conversations(user_id: str, limit: int = 5):
     print(f"\n📋 测试获取用户 {user_id} 的对话列表 (limit={limit})...")
     
     try:
-        response = requests.get(f"{API_BASE}/api/users/{user_id}/conversations?limit={limit}")
+        response = requests.get(f"{API_BASE}/api/v0/react/users/{user_id}/conversations?limit={limit}")
         
         print(f"   状态码: {response.status_code}")
         
@@ -125,7 +125,7 @@ def test_get_conversation_detail(user_id: str, thread_id: str):
     print(f"\n📖 测试获取对话详情: {thread_id}...")
     
     try:
-        response = requests.get(f"{API_BASE}/api/users/{user_id}/conversations/{thread_id}")
+        response = requests.get(f"{API_BASE}/api/v0/react/users/{user_id}/conversations/{thread_id}")
         
         print(f"   状态码: {response.status_code}")
         
@@ -179,17 +179,17 @@ def test_invalid_cases(user_id: str):
     
     # 测试1: 不存在的用户
     print("   测试不存在的用户...")
-    response = requests.get(f"{API_BASE}/api/users/nonexistent_user/conversations")
+    response = requests.get(f"{API_BASE}/api/v0/react/users/nonexistent_user/conversations")
     print(f"   状态码: {response.status_code} (应该是200,返回空列表)")
     
     # 测试2: 不匹配的thread_id
     print("   测试不匹配的thread_id...")
-    response = requests.get(f"{API_BASE}/api/users/{user_id}/conversations/wronguser:20250115103000001")
+    response = requests.get(f"{API_BASE}/api/v0/react/users/{user_id}/conversations/wronguser:20250115103000001")
     print(f"   状态码: {response.status_code} (应该是400)")
     
     # 测试3: 超出限制的limit参数
     print("   测试超出限制的limit参数...")
-    response = requests.get(f"{API_BASE}/api/users/{user_id}/conversations?limit=100")
+    response = requests.get(f"{API_BASE}/api/v0/react/users/{user_id}/conversations?limit=100")
     if response.status_code == 200:
         result = response.json()
         actual_limit = result.get("data", {}).get("limit", 0)

+ 1 - 1
test/custom_react_agent/test_fix.py

@@ -17,7 +17,7 @@ def test_fixed_api():
     # 1. 测试对话列表
     print("\n1. 对话列表API...")
     try:
-        response = requests.get('http://localhost:8000/api/users/doudou/conversations')
+        response = requests.get('http://localhost:8000/api/v0/react/users/doudou/conversations')
         print(f"   状态: {response.status_code}")
         
         if response.status_code == 200:

+ 1 - 1
test/custom_react_agent/test_simple_api.py

@@ -16,7 +16,7 @@ def test_api():
     
     print("\n=== 测试标准版本 ===")
     try:
-        response = requests.get(f"{base_url}/api/users/wang/conversations?limit=5")
+        response = requests.get(f"{base_url}/api/v0/react/users/wang/conversations?limit=5")
         print(f"状态码: {response.status_code}")
         print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
     except Exception as e: