Просмотр исходного кода

Merge remote-tracking branch 'origin/lzh_knowledge_1202' into lzh_knowledge_1202

TanJingyu 12 часов назад
Родитель
Сommit
7decb2e179

+ 4 - 4
knowledge_v2/prompt/all_tools_params.md

@@ -10,7 +10,7 @@
         "properties": {
             "prompt": {
                 "type": "string",
-                "description": "提示词prompt,包含完整的输入信息,图片、音频等多媒体需要url表示"
+                "description": "用户使用方法,包括筛选的话题类型、统计时间等"
             }
         },
         "required": [
@@ -30,7 +30,7 @@
         "properties": {
             "prompt": {
                 "type": "string",
-                "description": "提示词prompt,包含完整的输入信息,图片、音频等多媒体需要url表示"
+                "description": "用户使用方法,包括输入的话题关键词"
             }
         },
         "required": [
@@ -50,7 +50,7 @@
         "properties": {
             "prompt": {
                 "type": "string",
-                "description": "提示词prompt,包含完整的输入信息,图片、音频等多媒体需要url表示"
+                "description": "用户使用方法,包括筛选的话题类型、统计时间等"
             }
         },
         "required": [
@@ -70,7 +70,7 @@
         "properties": {
             "prompt": {
                 "type": "string",
-                "description": "提示词prompt,包含完整的输入信息,图片、音频等多媒体需要url表示"
+                "description": "用户使用方法,包括输入的关键词"
             }
         },
         "required": [

+ 6 - 4
knowledge_v2/prompt/function_knowledge_extract_tool_params_prompt.md

@@ -1,9 +1,11 @@
-你是一个API调用专家。你的任务是根据工具的信息和用户的查询,生成正确的调用参数。
+你是一个API调用专家。你的任务是根据已经选择的工具及使用方法信息生成正确的调用参数。
 
-查询内容:
-{query}
+选择的工具调用ID:
+{tool_mcp_name}
+选择的工具使用方法:
+{tool_instructions}
 
-工具信息:
+所有的工具参数定义
 {all_tool_params}
 
 请分析工具的参数要求,根据查询内容提取或推断出合适的参数值。

+ 9 - 0
knowledge_v2/prompt/tool_result_prettify_prompt.md

@@ -0,0 +1,9 @@
+你是一个数据结构化整体大师,你的任务是将API返回的结构化数据整理成一份适合人类阅读的数据
+要求:
+- 去除无用或者空数据字段
+- 遇到转义后的json字符串,需要去除掉转义字符
+
+输出整理后的数据即可,不要包含任何解释。
+
+输入的数据如下:
+{input}

+ 741 - 0
knowledge_v2/visualization/workflow_visualization.py

@@ -0,0 +1,741 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+'''
+知识获取工作流可视化
+1. 读取知识获取工作流详细过程数据文件(参考 .cache/9f510b2a8348/execution_record.json)
+2. 文件路径可设置,每个文件表示一个输入信息执行过程,可能有多个。多个文件路径硬编码在代码中,用list表示
+3. 将知识获取工作流用html页面展示出来
+4. HTML 文件输出路径在当前目录下,文件名称为 workflow_visualization_datetime.html
+'''
+
+import json
+import os
+from datetime import datetime
+from pathlib import Path
+
+
+# 硬编码的文件路径列表(相对于项目根目录)
+DATA_FILE_PATHS = [
+    "../.cache/9f510b2a8348/execution_record.json",
+    # 可以在这里添加更多文件路径
+]
+
+
+def load_data_files(file_paths):
+    """读取并解析JSON文件"""
+    data_list = []
+    script_dir = Path(__file__).parent
+    for file_path in file_paths:
+        # 将相对路径转换为绝对路径
+        abs_path = (script_dir / file_path).resolve()
+        if not abs_path.exists():
+            print(f"警告: 文件不存在: {abs_path}")
+            continue
+        try:
+            with open(abs_path, 'r', encoding='utf-8') as f:
+                data = json.load(f)
+                data_list.append(data)
+        except (json.JSONDecodeError, IOError) as e:
+            print(f"错误: 读取文件失败 {abs_path}: {e}")
+    return data_list
+
+
+def parse_workflow_data(data):
+    """解析工作流数据,提取关键信息"""
+    workflow = {
+        'input': {},
+        'steps': [],
+        'output': {}
+    }
+    
+    # 提取输入信息
+    if 'input' in data:
+        workflow['input'] = {
+            'question': data['input'].get('question', ''),
+            'post_info': data['input'].get('post_info', ''),
+            'persona_info': data['input'].get('persona_info', '')
+        }
+    
+    # 提取执行流程
+    if 'execution' in data and 'modules' in data['execution']:
+        function_knowledge = data['execution']['modules'].get('function_knowledge', {})
+        
+        # 步骤1: 生成query
+        if 'generate_query' in function_knowledge:
+            generate_query = function_knowledge['generate_query']
+            workflow['steps'].append({
+                'step': 'generate_query',
+                'name': '生成查询',
+                'query': generate_query.get('query', ''),
+                'prompt': generate_query.get('prompt', '')
+            })
+        
+        # 步骤2: 选择工具
+        if 'select_tool' in function_knowledge:
+            select_tool = function_knowledge['select_tool']
+            response = select_tool.get('response', {})
+            workflow['steps'].append({
+                'step': 'select_tool',
+                'name': '选择工具',
+                'prompt': select_tool.get('prompt', ''),
+                'tool_name': response.get('工具名', ''),
+                'tool_id': response.get('工具调用ID', ''),
+                'tool_usage': response.get('使用方法', '')
+            })
+        
+        # 步骤3: 提取参数
+        if 'extract_params' in function_knowledge:
+            extract_params = function_knowledge['extract_params']
+            workflow['steps'].append({
+                'step': 'extract_params',
+                'name': '提取参数',
+                'prompt': extract_params.get('prompt', ''),
+                'params': extract_params.get('params', {})
+            })
+        
+        # 步骤4: 执行工具
+        if 'execute_tool' in function_knowledge:
+            execute_tool = function_knowledge['execute_tool']
+            workflow['steps'].append({
+                'step': 'execute_tool',
+                'name': '执行工具',
+                'response': execute_tool.get('response', '')
+            })
+    
+    # 提取输出信息
+    if 'output' in data:
+        workflow['output'] = {
+            'result': data['output'].get('result', '')
+        }
+    
+    return workflow
+
+
+def escape_html(text):
+    """转义HTML特殊字符"""
+    if not isinstance(text, str):
+        text = str(text)
+    return (text.replace('&', '&')
+                .replace('<', '&lt;')
+                .replace('>', '&gt;')
+                .replace('"', '&quot;')
+                .replace("'", '&#39;'))
+
+
+def format_json_for_display(obj):
+    """格式化JSON对象用于显示"""
+    if isinstance(obj, dict):
+        return json.dumps(obj, ensure_ascii=False, indent=2)
+    elif isinstance(obj, str):
+        try:
+            parsed = json.loads(obj)
+            return json.dumps(parsed, ensure_ascii=False, indent=2)
+        except (json.JSONDecodeError, ValueError):
+            return obj
+    return str(obj)
+
+
+def generate_html(workflows):
+    """生成HTML页面"""
+    html = '''<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>知识获取工作流可视化</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+        
+        body {
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: #333;
+            line-height: 1.6;
+            min-height: 100vh;
+        }
+        
+        .container {
+            max-width: 1400px;
+            margin: 0 auto;
+            padding: 30px 20px;
+        }
+        
+        h1 {
+            text-align: center;
+            color: white;
+            margin-bottom: 40px;
+            font-size: 32px;
+            font-weight: 600;
+            text-shadow: 0 2px 10px rgba(0,0,0,0.2);
+            letter-spacing: 1px;
+        }
+        
+        .tabs {
+            display: flex;
+            background: white;
+            border-radius: 12px 12px 0 0;
+            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
+            overflow-x: auto;
+            padding: 5px;
+        }
+        
+        .tab {
+            padding: 16px 28px;
+            cursor: pointer;
+            border: none;
+            background: transparent;
+            color: #666;
+            font-size: 14px;
+            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+            white-space: nowrap;
+            border-radius: 8px;
+            margin: 0 4px;
+            position: relative;
+            font-weight: 500;
+        }
+        
+        .tab:hover {
+            background: #f0f0f0;
+            color: #333;
+        }
+        
+        .tab.active {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+        }
+        
+        .tab-content {
+            display: none;
+            background: white;
+            padding: 40px;
+            border-radius: 0 0 12px 12px;
+            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
+            margin-bottom: 20px;
+            animation: fadeIn 0.3s ease-in;
+        }
+        
+        @keyframes fadeIn {
+            from { opacity: 0; transform: translateY(10px); }
+            to { opacity: 1; transform: translateY(0); }
+        }
+        
+        .tab-content.active {
+            display: block;
+        }
+        
+        .input-section {
+            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+            padding: 28px;
+            border-radius: 12px;
+            margin-bottom: 35px;
+            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
+            border: 1px solid rgba(255,255,255,0.5);
+        }
+        
+        .input-section h3 {
+            color: #2c3e50;
+            margin-bottom: 20px;
+            font-size: 20px;
+            font-weight: 600;
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }
+        
+        .input-section h3::before {
+            content: '📋';
+            font-size: 24px;
+        }
+        
+        .input-item {
+            margin-bottom: 16px;
+            padding: 12px;
+            background: rgba(255,255,255,0.7);
+            border-radius: 8px;
+            transition: all 0.3s;
+        }
+        
+        .input-item:hover {
+            background: rgba(255,255,255,0.9);
+            transform: translateX(5px);
+        }
+        
+        .input-item strong {
+            color: #495057;
+            display: inline-block;
+            width: 110px;
+            font-weight: 600;
+        }
+        
+        .input-item .placeholder {
+            color: #999;
+            font-style: italic;
+        }
+        
+        .workflow {
+            position: relative;
+        }
+        
+        .workflow-step {
+            background: white;
+            border: 2px solid #e0e0e0;
+            border-radius: 12px;
+            margin-bottom: 25px;
+            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+            overflow: hidden;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
+        }
+        
+        .workflow-step.active {
+            border-color: #667eea;
+            box-shadow: 0 8px 24px rgba(102, 126, 234, 0.25);
+            transform: translateY(-2px);
+        }
+        
+        .step-header {
+            padding: 20px 24px;
+            background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+            cursor: pointer;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            user-select: none;
+            transition: all 0.3s;
+        }
+        
+        .step-header:hover {
+            background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%);
+        }
+        
+        .workflow-step.active .step-header {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+        }
+        
+        .workflow-step.active .step-name {
+            color: white;
+        }
+        
+        .workflow-step.active .step-toggle {
+            color: white;
+        }
+        
+        .step-title {
+            display: flex;
+            align-items: center;
+            gap: 15px;
+        }
+        
+        .step-number {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            width: 36px;
+            height: 36px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border-radius: 50%;
+            font-size: 16px;
+            font-weight: bold;
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+        }
+        
+        .workflow-step.active .step-number {
+            background: white;
+            color: #667eea;
+            box-shadow: 0 4px 12px rgba(255,255,255,0.3);
+        }
+        
+        .step-name {
+            font-size: 18px;
+            font-weight: 600;
+            color: #2c3e50;
+        }
+        
+        .step-toggle {
+            color: #6c757d;
+            font-size: 20px;
+            transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+        }
+        
+        .step-toggle.expanded {
+            transform: rotate(180deg);
+        }
+        
+        .step-content {
+            padding: 0 20px;
+            max-height: 0;
+            overflow: hidden;
+            transition: max-height 0.3s ease-out, padding 0.3s;
+        }
+        
+        .step-content.expanded {
+            max-height: 5000px;
+            padding: 20px;
+        }
+        
+        .step-detail {
+            margin-bottom: 20px;
+        }
+        
+        .step-detail-label {
+            font-weight: 600;
+            color: #495057;
+            margin-bottom: 10px;
+            display: block;
+            font-size: 14px;
+            text-transform: uppercase;
+            letter-spacing: 0.5px;
+        }
+        
+        .step-detail-content {
+            background: #f8f9fa;
+            padding: 16px;
+            border-radius: 8px;
+            border-left: 4px solid #667eea;
+            font-size: 14px;
+            line-height: 1.8;
+            white-space: pre-wrap;
+            word-wrap: break-word;
+            max-height: 400px;
+            overflow-y: auto;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+        }
+        
+        .json-content {
+            font-family: 'SF Mono', 'Monaco', 'Courier New', monospace;
+            background: #1e1e1e;
+            color: #d4d4d4;
+            padding: 20px;
+            border-radius: 8px;
+            overflow-x: auto;
+            border-left: 4px solid #667eea;
+            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+        }
+        
+        .prompt-toggle-btn {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 6px;
+            cursor: pointer;
+            font-size: 13px;
+            font-weight: 500;
+            margin-top: 15px;
+            transition: all 0.3s;
+            box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+        }
+        
+        .prompt-toggle-btn:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+        }
+        
+        .prompt-content {
+            display: none;
+            margin-top: 15px;
+            padding: 16px;
+            background: #fff3cd;
+            border-radius: 8px;
+            border-left: 4px solid #ffc107;
+            font-size: 13px;
+            line-height: 1.8;
+            white-space: pre-wrap;
+            word-wrap: break-word;
+            max-height: 500px;
+            overflow-y: auto;
+        }
+        
+        .prompt-content.show {
+            display: block;
+            animation: slideDown 0.3s ease-out;
+        }
+        
+        @keyframes slideDown {
+            from {
+                opacity: 0;
+                max-height: 0;
+            }
+            to {
+                opacity: 1;
+                max-height: 500px;
+            }
+        }
+        
+        .output-section {
+            background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
+            padding: 28px;
+            border-radius: 12px;
+            margin-top: 35px;
+            border-left: 4px solid #0ea5e9;
+            box-shadow: 0 4px 15px rgba(14, 165, 233, 0.2);
+        }
+        
+        .output-section h3 {
+            color: #0369a1;
+            margin-bottom: 20px;
+            font-size: 20px;
+            font-weight: 600;
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }
+        
+        .output-section h3::before {
+            content: '✨';
+            font-size: 24px;
+        }
+        
+        .arrow {
+            text-align: center;
+            color: #667eea;
+            font-size: 32px;
+            margin: -15px 0;
+            position: relative;
+            z-index: 1;
+            filter: drop-shadow(0 2px 4px rgba(102, 126, 234, 0.3));
+        }
+        
+        .arrow::before {
+            content: '↓';
+        }
+        
+        @media (max-width: 768px) {
+            .container {
+                padding: 10px;
+            }
+            
+            .tab {
+                padding: 12px 15px;
+                font-size: 13px;
+            }
+            
+            .tab-content {
+                padding: 20px;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>知识获取工作流可视化</h1>
+        <div class="tabs" id="tabs">
+'''
+    
+    # 生成Tab标签
+    for i, workflow in enumerate(workflows):
+        question = workflow['input'].get('question', f'问题 {i+1}')
+        active_class = 'active' if i == 0 else ''
+        html += f'            <button class="tab {active_class}" onclick="switchTab({i})">{escape_html(question)}</button>\n'
+    
+    html += '        </div>\n'
+    
+    # 生成Tab内容
+    for i, workflow in enumerate(workflows):
+        active_class = 'active' if i == 0 else ''
+        html += f'        <div class="tab-content {active_class}" id="tab-{i}">\n'
+        
+        # 输入信息
+        html += '            <div class="input-section">\n'
+        html += '                <h3>输入信息</h3>\n'
+        html += f'                <div class="input-item"><strong>问题:</strong> {escape_html(workflow["input"].get("question", ""))}</div>\n'
+        post_info = workflow['input'].get('post_info', '')
+        post_info_display = escape_html(post_info) if post_info else '<span class="placeholder">(无)</span>'
+        html += f'                <div class="input-item"><strong>帖子信息:</strong> {post_info_display}</div>\n'
+        persona_info = workflow['input'].get('persona_info', '')
+        persona_info_display = escape_html(persona_info) if persona_info else '<span class="placeholder">(无)</span>'
+        html += f'                <div class="input-item"><strong>人设信息:</strong> {persona_info_display}</div>\n'
+        html += '            </div>\n'
+        
+        # 工作流程
+        html += '            <div class="workflow">\n'
+        for j, step in enumerate(workflow['steps']):
+            step_id = f"step-{i}-{j}"
+            html += f'                <div class="workflow-step" id="{step_id}">\n'
+            html += '                    <div class="step-header" onclick="toggleStep(\'' + step_id + '\')">\n'
+            html += '                        <div class="step-title">\n'
+            html += f'                            <span class="step-number">{j+1}</span>\n'
+            html += f'                            <span class="step-name">{escape_html(step["name"])}</span>\n'
+            html += '                        </div>\n'
+            html += '                        <span class="step-toggle">▼</span>\n'
+            html += '                    </div>\n'
+            html += '                    <div class="step-content" id="content-' + step_id + '">\n'
+            
+            # 根据步骤类型显示不同内容(prompt放在最后,默认隐藏)
+            prompt_id = f"prompt-{step_id}"
+            
+            if step['step'] == 'generate_query':
+                if step.get('query'):
+                    html += '                        <div class="step-detail">\n'
+                    html += '                            <span class="step-detail-label">生成的Query:</span>\n'
+                    html += f'                            <div class="step-detail-content">{escape_html(step["query"])}</div>\n'
+                    html += '                        </div>\n'
+            
+            elif step['step'] == 'select_tool':
+                if step.get('tool_name'):
+                    html += '                        <div class="step-detail">\n'
+                    html += '                            <span class="step-detail-label">工具名称:</span>\n'
+                    html += f'                            <div class="step-detail-content">{escape_html(step["tool_name"])}</div>\n'
+                    html += '                        </div>\n'
+                if step.get('tool_id'):
+                    html += '                        <div class="step-detail">\n'
+                    html += '                            <span class="step-detail-label">工具调用ID:</span>\n'
+                    html += f'                            <div class="step-detail-content">{escape_html(step["tool_id"])}</div>\n'
+                    html += '                        </div>\n'
+                if step.get('tool_usage'):
+                    html += '                        <div class="step-detail">\n'
+                    html += '                            <span class="step-detail-label">使用方法:</span>\n'
+                    html += f'                            <div class="step-detail-content">{escape_html(step["tool_usage"])}</div>\n'
+                    html += '                        </div>\n'
+            
+            elif step['step'] == 'extract_params':
+                if step.get('params'):
+                    html += '                        <div class="step-detail">\n'
+                    html += '                            <span class="step-detail-label">提取的参数:</span>\n'
+                    params_str = format_json_for_display(step['params'])
+                    html += f'                            <div class="step-detail-content json-content">{escape_html(params_str)}</div>\n'
+                    html += '                        </div>\n'
+            
+            elif step['step'] == 'execute_tool':
+                if step.get('response'):
+                    html += '                        <div class="step-detail">\n'
+                    html += '                            <span class="step-detail-label">执行结果:</span>\n'
+                    response_str = format_json_for_display(step['response'])
+                    html += f'                            <div class="step-detail-content json-content">{escape_html(response_str)}</div>\n'
+                    html += '                        </div>\n'
+            
+            # Prompt放在最后,默认隐藏
+            if step.get('prompt'):
+                html += f'                        <button class="prompt-toggle-btn" onclick="togglePrompt(\'{prompt_id}\')">显示 Prompt</button>\n'
+                html += f'                        <div class="prompt-content" id="{prompt_id}">{escape_html(step["prompt"])}</div>\n'
+            
+            html += '                    </div>\n'
+            html += '                </div>\n'
+            
+            # 添加箭头(除了最后一步)
+            if j < len(workflow['steps']) - 1:
+                html += '                <div class="arrow"></div>\n'
+        
+        html += '            </div>\n'
+        
+        # 输出信息
+        if workflow['output'].get('result'):
+            html += '            <div class="output-section">\n'
+            html += '                <h3>最终输出</h3>\n'
+            result_str = format_json_for_display(workflow['output']['result'])
+            html += f'                <div class="step-detail-content json-content">{escape_html(result_str)}</div>\n'
+            html += '            </div>\n'
+        
+        html += '        </div>\n'
+    
+    html += '''    </div>
+    
+    <script>
+        function switchTab(index) {
+            // 隐藏所有tab内容
+            const contents = document.querySelectorAll('.tab-content');
+            contents.forEach(content => content.classList.remove('active'));
+            
+            // 移除所有tab的active状态
+            const tabs = document.querySelectorAll('.tab');
+            tabs.forEach(tab => tab.classList.remove('active'));
+            
+            // 显示选中的tab内容
+            document.getElementById('tab-' + index).classList.add('active');
+            tabs[index].classList.add('active');
+        }
+        
+        function toggleStep(stepId) {
+            const step = document.getElementById(stepId);
+            const content = document.getElementById('content-' + stepId);
+            const toggle = step.querySelector('.step-toggle');
+            
+            if (content.classList.contains('expanded')) {
+                content.classList.remove('expanded');
+                toggle.classList.remove('expanded');
+                step.classList.remove('active');
+            } else {
+                content.classList.add('expanded');
+                toggle.classList.add('expanded');
+                step.classList.add('active');
+            }
+        }
+        
+        function togglePrompt(promptId) {
+            const promptContent = document.getElementById(promptId);
+            const btn = promptContent.previousElementSibling;
+            
+            if (promptContent.classList.contains('show')) {
+                promptContent.classList.remove('show');
+                btn.textContent = '显示 Prompt';
+            } else {
+                promptContent.classList.add('show');
+                btn.textContent = '隐藏 Prompt';
+            }
+        }
+        
+        // 页面加载时高亮第一个步骤
+        window.addEventListener('load', function() {
+            const firstSteps = document.querySelectorAll('.workflow-step');
+            firstSteps.forEach((step, index) => {
+                if (index === 0 || index % (firstSteps.length / document.querySelectorAll('.tab-content').length) === 0) {
+                    step.classList.add('active');
+                    const content = step.querySelector('.step-content');
+                    const toggle = step.querySelector('.step-toggle');
+                    if (content) {
+                        content.classList.add('expanded');
+                        toggle.classList.add('expanded');
+                    }
+                }
+            });
+        });
+    </script>
+</body>
+</html>'''
+    
+    return html
+
+
+def main():
+    """主函数"""
+    # 获取当前脚本所在目录
+    script_dir = Path(__file__).parent
+    os.chdir(script_dir)
+    
+    # 读取数据文件
+    print("正在读取数据文件...")
+    data_list = load_data_files(DATA_FILE_PATHS)
+    
+    if not data_list:
+        print("错误: 没有成功读取任何数据文件")
+        return
+    
+    print(f"成功读取 {len(data_list)} 个数据文件")
+    
+    # 解析工作流数据
+    print("正在解析工作流数据...")
+    workflows = [parse_workflow_data(data) for data in data_list]
+    
+    # 生成HTML
+    print("正在生成HTML页面...")
+    html = generate_html(workflows)
+    
+    # 保存HTML文件
+    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+    output_filename = f"workflow_visualization_{timestamp}.html"
+    
+    with open(output_filename, 'w', encoding='utf-8') as f:
+        f.write(html)
+    
+    print(f"HTML页面已生成: {output_filename}")
+    print(f"文件路径: {os.path.abspath(output_filename)}")
+
+
+if __name__ == '__main__':
+    main()

+ 702 - 0
knowledge_v2/visualization/workflow_visualization_20251205_102946.html

@@ -0,0 +1,702 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>知识获取工作流可视化</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+        
+        body {
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: #333;
+            line-height: 1.6;
+            min-height: 100vh;
+        }
+        
+        .container {
+            max-width: 1400px;
+            margin: 0 auto;
+            padding: 30px 20px;
+        }
+        
+        h1 {
+            text-align: center;
+            color: white;
+            margin-bottom: 40px;
+            font-size: 32px;
+            font-weight: 600;
+            text-shadow: 0 2px 10px rgba(0,0,0,0.2);
+            letter-spacing: 1px;
+        }
+        
+        .tabs {
+            display: flex;
+            background: white;
+            border-radius: 12px 12px 0 0;
+            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
+            overflow-x: auto;
+            padding: 5px;
+        }
+        
+        .tab {
+            padding: 16px 28px;
+            cursor: pointer;
+            border: none;
+            background: transparent;
+            color: #666;
+            font-size: 14px;
+            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+            white-space: nowrap;
+            border-radius: 8px;
+            margin: 0 4px;
+            position: relative;
+            font-weight: 500;
+        }
+        
+        .tab:hover {
+            background: #f0f0f0;
+            color: #333;
+        }
+        
+        .tab.active {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+        }
+        
+        .tab-content {
+            display: none;
+            background: white;
+            padding: 40px;
+            border-radius: 0 0 12px 12px;
+            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
+            margin-bottom: 20px;
+            animation: fadeIn 0.3s ease-in;
+        }
+        
+        @keyframes fadeIn {
+            from { opacity: 0; transform: translateY(10px); }
+            to { opacity: 1; transform: translateY(0); }
+        }
+        
+        .tab-content.active {
+            display: block;
+        }
+        
+        .input-section {
+            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+            padding: 28px;
+            border-radius: 12px;
+            margin-bottom: 35px;
+            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
+            border: 1px solid rgba(255,255,255,0.5);
+        }
+        
+        .input-section h3 {
+            color: #2c3e50;
+            margin-bottom: 20px;
+            font-size: 20px;
+            font-weight: 600;
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }
+        
+        .input-section h3::before {
+            content: '📋';
+            font-size: 24px;
+        }
+        
+        .input-item {
+            margin-bottom: 16px;
+            padding: 12px;
+            background: rgba(255,255,255,0.7);
+            border-radius: 8px;
+            transition: all 0.3s;
+        }
+        
+        .input-item:hover {
+            background: rgba(255,255,255,0.9);
+            transform: translateX(5px);
+        }
+        
+        .input-item strong {
+            color: #495057;
+            display: inline-block;
+            width: 110px;
+            font-weight: 600;
+        }
+        
+        .input-item .placeholder {
+            color: #999;
+            font-style: italic;
+        }
+        
+        .workflow {
+            position: relative;
+        }
+        
+        .workflow-step {
+            background: white;
+            border: 2px solid #e0e0e0;
+            border-radius: 12px;
+            margin-bottom: 25px;
+            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+            overflow: hidden;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
+        }
+        
+        .workflow-step.active {
+            border-color: #667eea;
+            box-shadow: 0 8px 24px rgba(102, 126, 234, 0.25);
+            transform: translateY(-2px);
+        }
+        
+        .step-header {
+            padding: 20px 24px;
+            background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+            cursor: pointer;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            user-select: none;
+            transition: all 0.3s;
+        }
+        
+        .step-header:hover {
+            background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%);
+        }
+        
+        .workflow-step.active .step-header {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+        }
+        
+        .workflow-step.active .step-name {
+            color: white;
+        }
+        
+        .workflow-step.active .step-toggle {
+            color: white;
+        }
+        
+        .step-title {
+            display: flex;
+            align-items: center;
+            gap: 15px;
+        }
+        
+        .step-number {
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            width: 36px;
+            height: 36px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border-radius: 50%;
+            font-size: 16px;
+            font-weight: bold;
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+        }
+        
+        .workflow-step.active .step-number {
+            background: white;
+            color: #667eea;
+            box-shadow: 0 4px 12px rgba(255,255,255,0.3);
+        }
+        
+        .step-name {
+            font-size: 18px;
+            font-weight: 600;
+            color: #2c3e50;
+        }
+        
+        .step-toggle {
+            color: #6c757d;
+            font-size: 20px;
+            transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+        }
+        
+        .step-toggle.expanded {
+            transform: rotate(180deg);
+        }
+        
+        .step-content {
+            padding: 0 20px;
+            max-height: 0;
+            overflow: hidden;
+            transition: max-height 0.3s ease-out, padding 0.3s;
+        }
+        
+        .step-content.expanded {
+            max-height: 5000px;
+            padding: 20px;
+        }
+        
+        .step-detail {
+            margin-bottom: 20px;
+        }
+        
+        .step-detail-label {
+            font-weight: 600;
+            color: #495057;
+            margin-bottom: 10px;
+            display: block;
+            font-size: 14px;
+            text-transform: uppercase;
+            letter-spacing: 0.5px;
+        }
+        
+        .step-detail-content {
+            background: #f8f9fa;
+            padding: 16px;
+            border-radius: 8px;
+            border-left: 4px solid #667eea;
+            font-size: 14px;
+            line-height: 1.8;
+            white-space: pre-wrap;
+            word-wrap: break-word;
+            max-height: 400px;
+            overflow-y: auto;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+        }
+        
+        .json-content {
+            font-family: 'SF Mono', 'Monaco', 'Courier New', monospace;
+            background: #1e1e1e;
+            color: #d4d4d4;
+            padding: 20px;
+            border-radius: 8px;
+            overflow-x: auto;
+            border-left: 4px solid #667eea;
+            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+        }
+        
+        .prompt-toggle-btn {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 6px;
+            cursor: pointer;
+            font-size: 13px;
+            font-weight: 500;
+            margin-top: 15px;
+            transition: all 0.3s;
+            box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+        }
+        
+        .prompt-toggle-btn:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+        }
+        
+        .prompt-content {
+            display: none;
+            margin-top: 15px;
+            padding: 16px;
+            background: #fff3cd;
+            border-radius: 8px;
+            border-left: 4px solid #ffc107;
+            font-size: 13px;
+            line-height: 1.8;
+            white-space: pre-wrap;
+            word-wrap: break-word;
+            max-height: 500px;
+            overflow-y: auto;
+        }
+        
+        .prompt-content.show {
+            display: block;
+            animation: slideDown 0.3s ease-out;
+        }
+        
+        @keyframes slideDown {
+            from {
+                opacity: 0;
+                max-height: 0;
+            }
+            to {
+                opacity: 1;
+                max-height: 500px;
+            }
+        }
+        
+        .output-section {
+            background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
+            padding: 28px;
+            border-radius: 12px;
+            margin-top: 35px;
+            border-left: 4px solid #0ea5e9;
+            box-shadow: 0 4px 15px rgba(14, 165, 233, 0.2);
+        }
+        
+        .output-section h3 {
+            color: #0369a1;
+            margin-bottom: 20px;
+            font-size: 20px;
+            font-weight: 600;
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }
+        
+        .output-section h3::before {
+            content: '✨';
+            font-size: 24px;
+        }
+        
+        .arrow {
+            text-align: center;
+            color: #667eea;
+            font-size: 32px;
+            margin: -15px 0;
+            position: relative;
+            z-index: 1;
+            filter: drop-shadow(0 2px 4px rgba(102, 126, 234, 0.3));
+        }
+        
+        .arrow::before {
+            content: '↓';
+        }
+        
+        @media (max-width: 768px) {
+            .container {
+                padding: 10px;
+            }
+            
+            .tab {
+                padding: 12px 15px;
+                font-size: 13px;
+            }
+            
+            .tab-content {
+                padding: 20px;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>知识获取工作流可视化</h1>
+        <div class="tabs" id="tabs">
+            <button class="tab active" onclick="switchTab(0)">教资查分这个信息怎么来的</button>
+        </div>
+        <div class="tab-content active" id="tab-0">
+            <div class="input-section">
+                <h3>输入信息</h3>
+                <div class="input-item"><strong>问题:</strong> 教资查分这个信息怎么来的</div>
+                <div class="input-item"><strong>帖子信息:</strong> 发帖时间:2025.11.07</div>
+                <div class="input-item"><strong>人设信息:</strong> <span class="placeholder">(无)</span></div>
+            </div>
+            <div class="workflow">
+                <div class="workflow-step" id="step-0-0">
+                    <div class="step-header" onclick="toggleStep('step-0-0')">
+                        <div class="step-title">
+                            <span class="step-number">1</span>
+                            <span class="step-name">生成查询</span>
+                        </div>
+                        <span class="step-toggle">▼</span>
+                    </div>
+                    <div class="step-content" id="content-step-0-0">
+                        <div class="step-detail">
+                            <span class="step-detail-label">生成的Query:</span>
+                            <div class="step-detail-content">用什么工具获取教资查分这个信息</div>
+                        </div>
+                    </div>
+                </div>
+                <div class="arrow"></div>
+                <div class="workflow-step" id="step-0-1">
+                    <div class="step-header" onclick="toggleStep('step-0-1')">
+                        <div class="step-title">
+                            <span class="step-number">2</span>
+                            <span class="step-name">选择工具</span>
+                        </div>
+                        <span class="step-toggle">▼</span>
+                    </div>
+                    <div class="step-content" id="content-step-0-1">
+                        <div class="step-detail">
+                            <span class="step-detail-label">工具名称:</span>
+                            <div class="step-detail-content">新红热搜词搜索</div>
+                        </div>
+                        <div class="step-detail">
+                            <span class="step-detail-label">工具调用ID:</span>
+                            <div class="step-detail-content">new_red_hot_search_words_search</div>
+                        </div>
+                        <div class="step-detail">
+                            <span class="step-detail-label">使用方法:</span>
+                            <div class="step-detail-content">输入关键词&quot;教资查分&quot;,获取该词在小红书的热度值、近90天热度趋势曲线、声量概览及相关搜索结果,全面了解该信息的关注度和趋势。</div>
+                        </div>
+                        <button class="prompt-toggle-btn" onclick="togglePrompt('prompt-step-0-1')">显示 Prompt</button>
+                        <div class="prompt-content" id="prompt-step-0-1"># 角色定位
+你是一个工具匹配专家,负责根据用户需求从MCP工具库中找到最合适的工具。
+
+# 核心任务
+1. 接收上游输入的需求用什么工具获取教资查分这个信息
+2. 理解需求的核心意图(用户想找什么、解决什么问题)
+3. 查询MCP工具库,匹配最相关的工具
+4. 返回工具信息或&quot;无工具&quot;
+
+# MCP工具库信息
+
+工具名:新红话题榜单
+工具调用ID:xh_newrank_hottopics
+工具介绍:提供小红书平台热门话题全景榜单数据,覆盖排名、话题名称及核心简介、参与人数增量、浏览量增量、笔记增量、互动增量(点赞 / 收藏 / 评论综合统计)等核心维度。支持精准筛选与高效应用。话题类型涵盖美妆,美容个护,鞋包潮玩,穿搭打扮,美食,母婴育儿,旅游出行,家居家装,教育,生活,运动健身,兴趣爱好,影视综,婚嫁,摄影摄像,萌宠,情感星座,科技互联网,资讯,健康养生,科学科普,职场,交通工具,其他 24 个垂直领域,统计时间可选择日榜(近 7 天)、周榜(近 4 周)、月榜(近 5 个月),满足不同周期的热点追踪需求。榜单数据实时更新,同步呈现官方流量扶持话题标识,助力品牌、达人快速锁定高曝光赛道;便于拆解爆款逻辑、制定内容选题策略,提升运营效率。
+
+工具名:新红话题搜索
+工具调用ID:new_red_TopicSearch
+工具介绍:提供小红书平台话题精准检索服务,输入关键词即可匹配相关话题完整列表,核心数据包含话题名称及简介、总浏览量、总参与人数、近 30 天笔记增量、近 30 天互动增量。支持按参与人数、浏览量等指标排序筛选,可精准定位品牌发起话题或官方扶持话题,清晰展示话题流量趋势与内容创作方向。无论是达人寻找高适配性话题蹭流,还是品牌监测竞品话题布局,均能通过数据支撑快速决策,降低内容试错成本。
+
+工具名:新红热搜词榜单
+工具调用ID:xh_newrank_hotwords
+工具介绍:提供小红书平台热搜词权威榜单数据,核心维度包括实时排名、热搜词、主要覆盖内容领域、相关笔记总量、爆文数(高互动笔记占比)、热度值(综合搜索量与互动率计算)。支持按统计时间(日榜 / 周榜 / 月榜)及 美妆,美容个护,鞋包潮玩,穿搭打扮,美食,母婴育儿,旅游出行,家居家装,教育,生活,运动健身,兴趣爱好,影视综,婚嫁,摄影摄像,萌宠,情感星座,科技互联网,资讯,健康养生,科学科普,职场,交通工具,其他 24 个垂直内容领域筛选。榜单每日更新,助力用户第一时间捕捉流量风口。可直接应用于选题规划、竞品投放策略分析。
+
+工具名:新红热搜词搜索
+工具调用ID:new_red_hot_search_words_search
+工具介绍:提供小红书热搜词深度检索与趋势分析服务,输入关键词即可获取全维度数据表现:核心包含热度值(实时动态更新)、声量概览(笔记数、总热度、热门天数、热门内容领域、高频搭配词)、近 90 天热度值趋势曲线(支持自定义时间区间)、相关搜索结果。可直观呈现关键词种草趋势、流量峰值时段、高转化内容特征,帮助用户优化笔记标题关键词布局、预判话题生命周期。
+
+# 匹配规则
+
+## 匹配逻辑
+1. **需求本质识别**:理解需求背后的真实意图
+   - 示例:&quot;谐音梗这个选题灵感怎么来的&quot; → 真实意图是&quot;寻找热门选题的来源/依据&quot;
+   - 不关注具体关键词(如&quot;谐音梗&quot;),而关注用户想解决什么问题
+2. **需求类型分类**:判断需求属于哪种类型
+   - 寻找灵感/选题来源 → 需要热搜词/榜单类工具
+   - 分析特定词的热度/趋势 → 需要趋势分析类工具
+   - 寻找内容案例/参考 → 需要内容搜索/爬取类工具
+   - 生成创意内容 → 需要大模型/AI生成类工具
+3. **工具能力匹配**:根据需求类型,对比工具的核心能力
+   - 看工具能否解决该类型的问题(如&quot;提供热门选题&quot;)
+4. **选择最优工具**:返回能力最匹配的1个工具,无匹配则返回&quot;无工具&quot;
+
+## 匹配标准
+- 关键词匹配:需求核心词是否在工具介绍中出现或相关
+- 功能匹配:工具能力是否覆盖需求要解决的问题
+
+### 需求意图映射表
+| 需求意图关键信号 | 需求类型 | 对应工具类型 |
+|----------------|---------|------------|
+| &quot;灵感怎么来的&quot;、&quot;选题来源&quot;、&quot;找选题&quot; | 寻找选题灵感 | 热搜词榜单工具 |
+| &quot;这个词热不热&quot;、&quot;趋势如何&quot;、&quot;热度分析&quot; | 关键词趋势分析 | 热搜词搜索工具 |
+| &quot;找案例&quot;、&quot;参考内容&quot;、&quot;爆款笔记&quot; | 内容案例搜索 | 内容爬取工具 |
+
+## 无匹配判定
+当MCP工具库中没有任何工具能解决该需求时,直接返回空json对象。
+
+# 输出格式
+json格式,字段定义如下:
+&#39;&#39;&#39;json
+{
+    &quot;工具名&quot;: &quot;工具名称&quot;,
+    &quot;工具调用ID&quot;: &quot;调用ID&quot;,
+    &quot;使用方法&quot;: &quot;简要说明如何使用该工具解决用户需求&quot;
+}
+&#39;&#39;&#39;
+
+## 无匹配时
+&#39;&#39;&#39;json
+{
+
+}
+&#39;&#39;&#39;
+
+# 执行要求
+1. 只返回1个最优工具,不返回多个备选
+2. 严格按照输出格式返回结果
+3. 使用方法要具体,说明如何用该工具解决当前需求
+
+# 示例
+
+## 示例1:成功匹配
+需求输入:什么工具能找到教资查分这个灵感点?
+
+输出:
+工具名:新红热搜词搜索
+工具调用ID:new_red_hot_search_words_search
+使用方法:输入关键词&quot;教资查分&quot;,获取该词在小红书的热度值、近90天趋势曲线、相关笔记数据,判断是否为热门选题点。
+{
+    &quot;工具名&quot;: &quot;新红热搜词搜索&quot;,
+    &quot;工具调用ID&quot;: &quot;工具调用ID:new_red_hot_search_words_search&quot;,
+    &quot;使用方法&quot;: &quot;输入关键词&quot;教资查分&quot;,获取该词在小红书的热度值、近90天趋势曲线、相关笔记数据,判断是否为热门选题点。&quot;
+}
+
+## 示例2:无匹配
+需求输入:哪里可以找到股票实时行情数据?
+
+输出:
+{
+
+}
+
+# 上游输入的需求用什么工具获取教资查分这个信息
+{用什么工具获取教资查分这个信息}</div>
+                    </div>
+                </div>
+                <div class="arrow"></div>
+                <div class="workflow-step" id="step-0-2">
+                    <div class="step-header" onclick="toggleStep('step-0-2')">
+                        <div class="step-title">
+                            <span class="step-number">3</span>
+                            <span class="step-name">提取参数</span>
+                        </div>
+                        <span class="step-toggle">▼</span>
+                    </div>
+                    <div class="step-content" id="content-step-0-2">
+                        <div class="step-detail">
+                            <span class="step-detail-label">提取的参数:</span>
+                            <div class="step-detail-content json-content">{
+  &quot;prompt&quot;: &quot;教资查分&quot;
+}</div>
+                        </div>
+                        <button class="prompt-toggle-btn" onclick="togglePrompt('prompt-step-0-2')">显示 Prompt</button>
+                        <div class="prompt-content" id="prompt-step-0-2">你是一个API调用专家。你的任务是根据工具的信息和用户的查询,生成正确的调用参数。
+
+查询内容:
+用什么工具获取教资查分这个信息
+
+工具信息:
+{
+    &quot;name&quot;: &quot;新红热搜词搜索&quot;,
+    &quot;description&quot;: &quot;提供小红书热搜词深度检索与趋势分析服务,输入关键词即可获取全维度数据表现:核心包含热度值(实时动态更新)、声量概览(笔记数、总热度、热门天数、热门内容领域、高频搭配词)、近 90 天热度值趋势曲线(支持自定义时间区间)、相关搜索结果。可直观呈现关键词种草趋势、流量峰值时段、高转化内容特征,帮助用户优化笔记标题关键词布局、预判话题生命周期。&quot;,
+    &quot;inputSchema&quot;: {
+        &quot;type&quot;: &quot;object&quot;,
+        &quot;properties&quot;: {
+            &quot;prompt&quot;: {
+                &quot;type&quot;: &quot;string&quot;,
+                &quot;description&quot;: &quot;提示词prompt,包含完整的输入信息,图片、音频等多媒体需要url表示&quot;
+            }
+        },
+        &quot;required&quot;: [
+            &quot;prompt&quot;
+        ]
+    }
+}
+
+请分析工具的参数要求,根据查询内容提取或推断出合适的参数值。
+
+输出格式:请以 JSON 格式输出参数字典,例如:
+{
+    &quot;param1&quot;: &quot;value1&quot;,
+    &quot;param2&quot;: &quot;value2&quot;
+}
+
+注意事项:
+1. 只输出参数的JSON字典,不要包含任何解释
+2. 参数名必须与工具定义中的参数名完全一致
+3. 参数值要从查询中提取或合理推断
+4. 不要添加工具定义中没有的参数
+5. 如果某个参数无法从查询中获取,使用合理的默认值或省略该参数
+
+只输出JSON,不要包含markdown标记。</div>
+                    </div>
+                </div>
+                <div class="arrow"></div>
+                <div class="workflow-step" id="step-0-3">
+                    <div class="step-header" onclick="toggleStep('step-0-3')">
+                        <div class="step-title">
+                            <span class="step-number">4</span>
+                            <span class="step-name">执行工具</span>
+                        </div>
+                        <span class="step-toggle">▼</span>
+                    </div>
+                    <div class="step-content" id="content-step-0-3">
+                        <div class="step-detail">
+                            <span class="step-detail-label">执行结果:</span>
+                            <div class="step-detail-content json-content">{
+  &quot;result&quot;: {
+    &quot;image_url&quot;: [],
+    &quot;content&quot;: &quot;## \&quot;教资查分\&quot;关键词数据表现和趋势分析\n\n### 热度值\n- 近30天热度值超过96.14%全站其他词\n- 近30天热度值超过95.86%教育领域其他词\n\n### 声量概览\n- **笔记数**: 16篇(环比增长700.00%)\n- **总热度**: 4082(环比增长408100.00%)\n- **热门天数**: 4/30天\n- **近30天热门内容领域**:\n  - 教育: 56.25%\n  - 职场: 31.25%\n  - 运动健身: 6.25%\n- **高频搭配词**: 暂无相关数据\n\n### 热度值趋势\n- **整体趋势**: 下降趋势\n- **效果分析**:\n  - TOP1下降趋势: 2025-11-07 至 2025-11-08\n  - TOP2下降趋势: 2025-11-04 至 2025-11-05\n\n### 相关搜索结果\n- 共1条搜索结果\n- 内容领域分布: 教育、职场等领域为主\n\n从数据可以看出,\&quot;教资查分\&quot;这个关键词在近期有显著的热度增长,但目前呈现下降趋势。该关键词主要与教育和职场领域相关,这符合教师资格证查询分数的实际应用场景。虽然笔记数量不多(16篇),但环比增长非常高,表明这是一个短期内突然受到关注的话题。&quot;,
+    &quot;video_url&quot;: []
+  },
+  &quot;success&quot;: true,
+  &quot;error&quot;: &quot;&quot;
+}</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="output-section">
+                <h3>最终输出</h3>
+                <div class="step-detail-content json-content">{
+  &quot;result&quot;: {
+    &quot;image_url&quot;: [],
+    &quot;content&quot;: &quot;## \&quot;教资查分\&quot;关键词数据表现和趋势分析\n\n### 热度值\n- 近30天热度值超过96.14%全站其他词\n- 近30天热度值超过95.86%教育领域其他词\n\n### 声量概览\n- **笔记数**: 16篇(环比增长700.00%)\n- **总热度**: 4082(环比增长408100.00%)\n- **热门天数**: 4/30天\n- **近30天热门内容领域**:\n  - 教育: 56.25%\n  - 职场: 31.25%\n  - 运动健身: 6.25%\n- **高频搭配词**: 暂无相关数据\n\n### 热度值趋势\n- **整体趋势**: 下降趋势\n- **效果分析**:\n  - TOP1下降趋势: 2025-11-07 至 2025-11-08\n  - TOP2下降趋势: 2025-11-04 至 2025-11-05\n\n### 相关搜索结果\n- 共1条搜索结果\n- 内容领域分布: 教育、职场等领域为主\n\n从数据可以看出,\&quot;教资查分\&quot;这个关键词在近期有显著的热度增长,但目前呈现下降趋势。该关键词主要与教育和职场领域相关,这符合教师资格证查询分数的实际应用场景。虽然笔记数量不多(16篇),但环比增长非常高,表明这是一个短期内突然受到关注的话题。&quot;,
+    &quot;video_url&quot;: []
+  },
+  &quot;success&quot;: true,
+  &quot;error&quot;: &quot;&quot;
+}</div>
+            </div>
+        </div>
+    </div>
+    
+    <script>
+        function switchTab(index) {
+            // 隐藏所有tab内容
+            const contents = document.querySelectorAll('.tab-content');
+            contents.forEach(content => content.classList.remove('active'));
+            
+            // 移除所有tab的active状态
+            const tabs = document.querySelectorAll('.tab');
+            tabs.forEach(tab => tab.classList.remove('active'));
+            
+            // 显示选中的tab内容
+            document.getElementById('tab-' + index).classList.add('active');
+            tabs[index].classList.add('active');
+        }
+        
+        function toggleStep(stepId) {
+            const step = document.getElementById(stepId);
+            const content = document.getElementById('content-' + stepId);
+            const toggle = step.querySelector('.step-toggle');
+            
+            if (content.classList.contains('expanded')) {
+                content.classList.remove('expanded');
+                toggle.classList.remove('expanded');
+                step.classList.remove('active');
+            } else {
+                content.classList.add('expanded');
+                toggle.classList.add('expanded');
+                step.classList.add('active');
+            }
+        }
+        
+        function togglePrompt(promptId) {
+            const promptContent = document.getElementById(promptId);
+            const btn = promptContent.previousElementSibling;
+            
+            if (promptContent.classList.contains('show')) {
+                promptContent.classList.remove('show');
+                btn.textContent = '显示 Prompt';
+            } else {
+                promptContent.classList.add('show');
+                btn.textContent = '隐藏 Prompt';
+            }
+        }
+        
+        // 页面加载时高亮第一个步骤
+        window.addEventListener('load', function() {
+            const firstSteps = document.querySelectorAll('.workflow-step');
+            firstSteps.forEach((step, index) => {
+                if (index === 0 || index % (firstSteps.length / document.querySelectorAll('.tab-content').length) === 0) {
+                    step.classList.add('active');
+                    const content = step.querySelector('.step-content');
+                    const toggle = step.querySelector('.step-toggle');
+                    if (content) {
+                        content.classList.add('expanded');
+                        toggle.classList.add('expanded');
+                    }
+                }
+            });
+        });
+    </script>
+</body>
+</html>