"""
Recursive Image Deconstruction Agent.

图片递归解构Agent：递归解构图片中的视觉元素。

优化记录:
- 2025-10: 提取JSON处理工具类,消除重复代码
- 2025-10: 提取权重归一化工具类,提高可维护性
- 2025-10: 提取LLM调用封装类,统一错误处理
- 2025-10: 采用方案A(视觉优先),移除阶段0自由描述,直接基于图片生成树状结构
- 2025-10: 优化Prompt设计,强化核心约束原则(意图驱动、真实可见、无主观推测)
- 2025-10: 优化阶段1实现,明确职责分离:
  * 阶段1(元素定位树): description轻量化(1句话10-20字),position详细化(核心重点)
  * 阶段2(详细分析): 基于position精准定位,生成详细多维度描述
  * 避免阶段1的重复劳动,提升Token利用效率
- 2025-10-29: 并行化优化,使用共享线程池并行处理同一层级的子节点,显著提升处理效率
  * 采用实例级共享ThreadPoolExecutor(避免递归创建导致的线程池爆炸问题)
  * 在__init__中创建线程池,整个Agent生命周期内复用
  * 保持结果顺序一致性
  * 添加异常处理、降级策略和资源清理逻辑
- 2025-10-29: 全树并行化优化,利用节点无状态特性一次性并行处理所有节点
  * 扁平化树结构,将所有节点展开为带路径的节点列表
  * 一次性提交所有节点处理任务,最大化并行度
  * 处理完成后根据路径信息重组回树结构
  * 结果与原树结构完全一一对应
- 2025-10-29: 优化阶段1 Prompt,强化"每层穷尽所有子元素"的要求
  * 增加🔴标记的关键约束,明确要求children必须包含该层级所有可见子元素
  * 新增穷尽性检查清单,引导LLM系统性扫描所有区域和元素类型
  * 在示例中明确展示多个同类元素(如芒果块1/2/3)的完整列举
  * 确保LLM不遗漏任何可见元素,提升拆分完整性
- 2025-10-30: 引入"XY轴拆分与Z轴拆分交织递归"的核心认知
  * 明确两种拆分维度：XY轴拆分(X/Y轴位置)和Z轴拆分(Z轴层叠关系)
  * 拆分顺序灵活化：可以是"XY轴→Z轴→重复"，也可以是"Z轴→XY轴→重复"
  * 提供判断标准：根据元素特征(空间分布 vs 层叠覆盖)选择优先顺序
  * 强调两种拆分都要用到，确保完整捕获图片的3D结构信息
  * 提供两个示例展示不同拆分顺序的实际应用场景
- 2025-10-30: Prompt精简优化，提升可读性和执行效率
  * 合并重复内容：将穷尽性约束统一到一个章节，减少冗余强调
  * 精简交织递归说明：从~30行压缩到~4行，移除冗长示例
  * 简化字段说明：position和description的说明更加简洁直接
  * 补充完整示例：添加完整的JSON树状结构示例，展示所有关键点
  * 整体减少~37%的Prompt长度(从~95行到~60行)，同时保留所有核心要点
- 2025-10-30: 添加split_dimension字段追踪拆分维度
  * 在阶段1的Prompt中要求LLM为每个子节点标注split_dimension字段("XY轴拆分"或"Z轴拆分")
  * 在_process_single_node中提取split_dimension并记录到日志
  * 在结果构建时保留split_dimension信息，使最终输出明确标注每个元素的拆分维度
  * 在所有降级/错误处理场景中也保留split_dimension字段
  * 便于追踪和理解递归拆分的决策路径
- 2025-10-30: 强化"同一层级规则"约束
  * 明确规定：同一元素的children中，所有子元素的split_dimension必须相同
  * 避免在同一层级混合使用XY轴拆分和Z轴拆分
  * 确保拆分逻辑清晰，层级结构更合理
  * 在Prompt中用🔴标记强调此规则，确保LLM严格遵守
- 2025-10-30: 统一命名规范，提升语义清晰度
  * 将"空间拆分"重命名为"XY轴拆分"，更准确描述空间位置划分
  * 将"图层拆分"重命名为"Z轴拆分"，更准确描述层叠关系
  * 使用XYZ三维坐标系统的直观表述，便于理解图片的3D结构分解
"""

from typing import Dict, Any, List, Optional
import json
from concurrent.futures import ThreadPoolExecutor, as_completed

from src.components.agents.base import BaseLLMAgent
from src.states.what_deconstruction_state import WhatDeconstructionState
from src.utils.logger import get_logger

logger = get_logger(__name__)


# ============================================================================
# 常量配置
# ============================================================================

# 默认描述维度
DEFAULT_DIMENSIONS = [
    "主体内容",
    "构图方式",
    "色彩风格",
    "视觉焦点",
    "氛围感",
    "细节特征"
]

# 维度生成配置
MIN_DIMENSION_COUNT = 3  # 最小维度数
TARGET_DIMENSION_RANGE = (5, 8)  # 目标维度范围

# 日志分隔符
LOG_SEPARATOR = "=" * 80


# ============================================================================
# 工具类定义
# ============================================================================

class JSONExtractor:
    """JSON提取和解析工具类

    负责从LLM响应中提取JSON内容并安全解析
    """

    @staticmethod
    def extract_json_from_response(content: str) -> str:
        """从LLM响应中提取JSON内容

        Args:
            content: LLM响应文本

        Returns:
            提取的JSON字符串
        """
        content = content.strip()

        # 提取代码块中的JSON
        if "```json" in content:
            content = content.split("```json")[1].split("```")[0].strip()
        elif "```" in content:
            content = content.split("```")[1].split("```")[0].strip()

        return content

    @staticmethod
    def safe_parse_json(content: str, default: Any = None) -> Any:
        """安全解析JSON,失败时返回默认值

        Args:
            content: JSON字符串
            default: 解析失败时的默认值

        Returns:
            解析后的对象或默认值
        """
        try:
            return json.loads(content)
        except json.JSONDecodeError as e:
            logger.error(
                f"JSON解析失败: {e}\n"
                f"内容前100字符: {content[:100]}..."
            )
            return default if default is not None else {}

    @staticmethod
    def extract_and_parse(content: str, default: Any = None) -> Any:
        """一步完成提取和解析

        Args:
            content: LLM响应文本
            default: 解析失败时的默认值

        Returns:
            解析后的对象或默认值
        """
        json_str = JSONExtractor.extract_json_from_response(content)
        return JSONExtractor.safe_parse_json(json_str, default)


class WeightNormalizer:
    """权重归一化工具类

    负责子节点权重的归一化处理
    """

    @staticmethod
    def normalize_weights(children: List[Dict[str, Any]]) -> None:
        """归一化子节点权重(原地修改)

        Args:
            children: 子节点列表
        """
        if not children:
            return

        # 计算总权重
        total_weight = sum(
            child.get("importance_weight", 0)
            for child in children
        )

        if total_weight > 0:
            # 按比例归一化
            for child in children:
                original_weight = child.get("importance_weight", 0)
                normalized_weight = original_weight / total_weight
                child["importance_weight"] = round(normalized_weight, 3)
                logger.debug(
                    f"权重归一化 - 原始: {original_weight:.3f}, "
                    f"归一化: {normalized_weight:.3f}"
                )
        else:
            # 均分权重
            equal_weight = 1.0 / len(children)
            for child in children:
                child["importance_weight"] = round(equal_weight, 3)
            logger.warning(
                f"所有子节点权重为0,均分权重: {equal_weight:.3f}"
            )


class LLMInvoker:
    """LLM调用封装类

    提供统一的LLM调用接口,包含错误处理和日志记录
    """

    @staticmethod
    def safe_invoke(
        agent: Any,
        operation_name: str,
        messages: List[Dict[str, Any]],
        fallback: Any = None
    ) -> Any:
        """安全的LLM调用

        Args:
            agent: Agent实例
            operation_name: 操作名称(用于日志)
            messages: 消息列表
            fallback: 调用失败时的降级返回值

        Returns:
            解析后的JSON对象或降级值
        """
        try:
            if not agent.is_initialized:
                agent.initialize()

            response = agent.model.invoke(messages)
            content = response.content.strip()

            # 提取和解析JSON
            result = JSONExtractor.extract_and_parse(content, {})

            if not result:
                logger.warning(
                    f"{operation_name} - LLM返回内容为空或解析失败\n"
                    f"原始内容前200字符: {content[:200]}..."
                )
                return fallback if fallback is not None else {}

            return result

        except json.JSONDecodeError as e:
            logger.error(
                f"{operation_name} - JSON解析失败: {e}\n"
                f"原始内容前200字符: {content[:200] if 'content' in locals() else 'N/A'}..."
            )
            return fallback if fallback is not None else {}

        except Exception as e:
            logger.error(
                f"{operation_name} - 调用失败: {e}",
                exc_info=True
            )
            return fallback if fallback is not None else {}


class RecursiveImageDeconstructionAgent(BaseLLMAgent):
    """图片递归解构Agent

    功能：
    - 递归解构图片视觉元素（最多10层）
    - 粗理解图片生成what描述
    - 使用大模型自带知识获取描述维度
    - 根据大模型判断是否继续分割
    - 递归处理子图片元素

    """

    def __init__(
        self,
        name: str = "recursive_image_deconstruction_agent",
        description: str = "递归解构图片元素",
        model_provider: str = "google_genai",
        temperature: float = 0.1,
        max_tokens: int = 40960,
        max_depth: int = 10,
        max_workers: int = 4
    ):
        system_prompt = """你是一个专业的视觉内容分析专家。你的任务是深入分析图片内容，提取视觉特征。

你需要：
1. 理解图片的整体构图、主体、风格
2. 根据提供的描述维度，分析图片特征
3. 识别图片中的视觉子元素（如果需要细分）
4. 判断图片是否需要进一步分割为子元素

要求：
- 客观准确，基于视觉呈现
- 描述要具体详细
- 判断要合理
"""

        super().__init__(
            name=name,
            description=description,
            model_provider=model_provider,
            system_prompt=system_prompt,
            temperature=temperature,
            max_tokens=max_tokens
        )

        self.max_depth = max_depth
        # 创建共享线程池，避免递归时重复创建
        self.max_workers = max_workers
        self._executor = ThreadPoolExecutor(max_workers=max_workers)
        logger.info(f"初始化共享线程池 - max_workers: {max_workers}")

    def __del__(self):
        """析构函数：清理线程池资源"""
        self.cleanup()

    def cleanup(self):
        """显式清理线程池资源（建议在使用完Agent后调用）"""
        if hasattr(self, '_executor') and self._executor:
            try:
                self._executor.shutdown(wait=True)
                logger.info("共享线程池已关闭")
            except Exception as e:
                logger.warning(f"关闭线程池时出错: {e}")

    def process(self, state: WhatDeconstructionState, config: Optional[Any] = None) -> Dict[str, Any]:
        """处理图片递归解构（入口方法）

        两阶段流程（优化版 - 职责分离）：
        1. 阶段1：生成元素定位树（轻量级description + 详细position）
        2. 阶段2：基于定位树逐层进行维度提取和详细理解
        """
        current_image = state.get("current_image", "")

        if not current_image:
            logger.warning("图片URL为空")
            return {}

        logger.info(f"开始图片解构（两阶段流程 - 优化版）: {current_image}")

        # ===== 阶段1: 生成元素定位树 =====
        logger.info(LOG_SEPARATOR)
        logger.info("【阶段1】生成元素定位树（轻量description + 详细position）...")
        logger.info(LOG_SEPARATOR)

        tree_structure = self._generate_tree_structure(current_image)

        logger.info(LOG_SEPARATOR)
        logger.info("【阶段1】元素定位树生成完成")
        logger.info(LOG_SEPARATOR)

        # 输出树状结构（包含轻量description和详细position）
        logger.info("\n【元素定位树】:")
        self._print_tree_structure(tree_structure)

        logger.info(LOG_SEPARATOR)

        # ===== 阶段2: 逐层维度理解 =====
        logger.info(LOG_SEPARATOR)
        logger.info("【阶段2】开始逐层维度理解（生成详细描述）...")
        logger.info(LOG_SEPARATOR)

        deconstruction_dimensions = state.get("deconstruction_dimensions", [])
        result = self._enrich_tree_with_dimensions(
            tree_structure,
            current_image,
            deconstruction_dimensions
        )

        logger.info(LOG_SEPARATOR)
        logger.info("【阶段2】逐层维度理解完成")
        logger.info(LOG_SEPARATOR)

        return result

    def _generate_tree_structure(self, image_url: str) -> Dict[str, Any]:
        """【阶段1】生成完整的树状结构（元素定位树）

        基于图片直接生成整个图片的层级结构，主要用于元素定位导航。
        每个节点包含：
        - element: 元素名称（what值，简洁名词）
        - description: 简要识别描述（1句话，用于确认元素身份）
        - position: 精准位置坐标（关键！用于阶段2定位元素）
        - children: 子节点列表

        注意：详细描述在阶段2完成，此阶段重点是构建精准的定位树。

        Args:
            image_url: 图片URL

        Returns:
            树状结构（嵌套字典）
        """
        try:
            prompt = """请生成图片的完整元素定位树（用于后续详细分析的导航坐标）。

**目标**：构建层级清晰的树状结构，最多10层深度，一次性识别所有层级。

**节点格式**：
根节点：`{"element": "图片", "description": "简要概括", "children": [...]}`
子节点：`{"element": "元素名", "description": "识别特征", "position": "精准位置坐标", "split_dimension": "拆分维度", "children": [...]}`

**split_dimension说明**（子节点必需字段）：
- "XY轴拆分"：X/Y轴位置划分（如左/右、上/下、前景/背景位置）
- "Z轴拆分"：Z轴层叠关系（如背景层→主体层→文字层）
- 根节点不需要此字段

**关键要求**：

1. **position（核心）**：必须包含方位词、相对位置、区分性特征，确保能精准定位
   示例："碗的左上角区域，靠近碗边缘，是三块芒果中最大的一块，切面朝上"

2. **description（轻量）**：仅用于识别"这是什么"，不包含详细特征和位置信息

3. **穷尽性约束**：
   - 🔴 每层children必须包含该层级**所有**可见子元素，不能遗漏
   - 系统性扫描所有区域：空间上的不同位置、视觉上的不同图层
   - 同类元素有多个实例时，全部列出（如芒果块1/2/3）

4. **拆分策略**：
   - **意图驱动**：只拆分对理解帖子意图有帮助的元素
   - **交织递归**：灵活运用XY轴拆分和Z轴拆分，可以是"XY轴→Z轴"或"Z轴→XY轴"
     * 空间分布明显（如左右分栏）→ 优先XY轴拆分
     * 层叠覆盖明显（如背景+主体+文字）→ 优先Z轴拆分
   - 🔴 **同一层级规则**：同一元素的children中，所有子元素的split_dimension必须相同（要么全是"XY轴拆分"，要么全是"Z轴拆分"）
   - **真实可见**：只识别清晰可见的元素，不主观推测
   - **终止条件**：达到10层深度 或 无法再有意义细分 → children为空数组

🔴 **最重要**：
1. 确保每一层children包含该层级所有可见子元素，不要遗漏！
2. 同一元素的所有子元素必须使用相同的split_dimension（不能混合XY轴拆分和Z轴拆分）！"""

            messages = [
                {"role": "system", "content": self.system_prompt},
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": prompt},
                        {"type": "image_url", "image_url": {"url": image_url}}
                    ]
                }
            ]

            # 使用统一的LLM调用器
            result = LLMInvoker.safe_invoke(
                self,
                "树状结构生成",
                messages,
                fallback={
                    "element": "图片",
                    "description": "图片内容",
                    "children": []
                }
            )

            logger.info(f"树状结构生成成功 - 根节点: {result.get('element')}")
            return result

        except Exception as e:
            logger.error(f"树状结构生成失败: {e}", exc_info=True)
            return {
                "element": "图片",
                "description": "图片内容",
                "children": []
            }

    def _flatten_tree_nodes(
        self,
        tree_node: Dict[str, Any],
        path: List[int] = None,
        depth: int = 0
    ) -> List[Dict[str, Any]]:
        """将树结构扁平化为节点列表（带路径信息）

        Args:
            tree_node: 树节点
            path: 当前节点路径（用于重组树结构时定位）
            depth: 当前深度

        Returns:
            扁平化的节点列表，每个节点包含原始数据和路径信息
        """
        if path is None:
            path = []

        # 当前节点信息
        flat_nodes = [{
            "node": tree_node,
            "path": path.copy(),
            "depth": depth
        }]

        # 递归处理子节点
        children = tree_node.get("children", [])
        for idx, child in enumerate(children):
            child_path = path + [idx]
            flat_nodes.extend(
                self._flatten_tree_nodes(child, child_path, depth + 1)
            )

        return flat_nodes

    def _rebuild_tree_from_results(
        self,
        flat_results: Dict[str, Dict[str, Any]],
        tree_node: Dict[str, Any],
        path: List[int] = None
    ) -> Dict[str, Any]:
        """根据处理结果重组树结构

        Args:
            flat_results: 扁平化处理结果字典（key为路径字符串）
            tree_node: 原始树节点
            path: 当前路径

        Returns:
            重组后的节点
        """
        if path is None:
            path = []

        # 获取当前节点的处理结果
        path_key = "_".join(map(str, path)) if path else "root"
        result = flat_results.get(path_key)

        if not result:
            logger.warning(f"路径 {path_key} 没有找到处理结果")
            return {
                "what": tree_node.get("element", "未知元素"),
                "position": tree_node.get("position", ""),
                "split_dimension": tree_node.get("split_dimension", ""),
                "description": tree_node.get("description", ""),
                "image_path": "",
                "relationships": [],
                "importance_weight": 0.5,
                "weight_reason": "默认权重",
                "children": []
            }

        # 递归处理子节点
        children = tree_node.get("children", [])
        enriched_children = []
        for idx, child in enumerate(children):
            child_path = path + [idx]
            enriched_child = self._rebuild_tree_from_results(
                flat_results, child, child_path
            )
            enriched_children.append(enriched_child)

        # 归一化子节点权重
        if enriched_children:
            WeightNormalizer.normalize_weights(enriched_children)

        # 构建最终结果
        result["children"] = enriched_children
        result["relationships"] = self._extract_relationships(enriched_children) if enriched_children else []

        return result

    def _process_single_node(
        self,
        node_data: Dict[str, Any],
        image_url: str,
        deconstruction_dimensions: List[Dict[str, Any]]
    ) -> Dict[str, Any]:
        """处理单个节点（无状态，可并行）

        Args:
            node_data: 扁平化节点数据（包含node, path, depth）
            image_url: 图片URL
            deconstruction_dimensions: 解构维度列表

        Returns:
            处理后的节点结果
        """
        tree_node = node_data["node"]
        depth = node_data["depth"]
        path = node_data["path"]

        try:
            element = tree_node.get("element", "")
            description = tree_node.get("description", "")
            position = tree_node.get("position", "")
            split_dimension = tree_node.get("split_dimension", "")  # 获取拆分维度

            path_key = "_".join(map(str, path)) if path else "root"
            logger.info(f"处理节点 - 路径: {path_key}, 深度: {depth}, 元素: {element}, 拆分维度: {split_dimension}")

            # Step 1: 生成描述维度
            if depth == 0:
                dimensions = self._generate_dimensions_for_root(
                    image_url,
                    description,
                    deconstruction_dimensions
                )
            else:
                dimensions = self._generate_dimensions_for_element(
                    image_url,
                    element,
                    description,
                    deconstruction_dimensions
                )

            # Step 2: 根据维度进行详细理解
            description_values = self._detailed_understanding(
                image_url,
                element,
                position,
                dimensions
            )

            # Step 3: 构建结果（不包含children，后续重组时添加）
            result = {
                "what": element,
                "position": position,
                "split_dimension": split_dimension,  # 添加拆分维度信息
                "description": description_values,
                "image_path": image_url,
                "relationships": [],  # 后续重组时计算
                "importance_weight": 0.5,
                "weight_reason": "默认权重",
                "children": []  # 后续重组时添加
            }

            logger.info(f"节点处理完成 - 路径: {path_key}, 元素: {element}")
            return result

        except Exception as e:
            logger.error(f"节点处理失败 - 路径: {path}: {e}", exc_info=True)
            return {
                "what": tree_node.get("element", "未知元素"),
                "position": tree_node.get("position", ""),
                "split_dimension": tree_node.get("split_dimension", ""),
                "description": tree_node.get("description", ""),
                "image_path": image_url,
                "relationships": [],
                "importance_weight": 0.5,
                "weight_reason": "默认权重",
                "children": []
            }

    def _batch_process_nodes(
        self,
        flat_nodes: List[Dict[str, Any]],
        image_url: str,
        deconstruction_dimensions: List[Dict[str, Any]]
    ) -> Dict[str, Dict[str, Any]]:
        """批量并行处理所有节点

        Args:
            flat_nodes: 扁平化的节点列表
            image_url: 图片URL
            deconstruction_dimensions: 解构维度列表

        Returns:
            处理结果字典（key为路径字符串，value为处理后的节点）
        """
        logger.info(f"开始批量并行处理 - 节点总数: {len(flat_nodes)}")

        # 提交所有节点处理任务
        future_to_node = {
            self._executor.submit(
                self._process_single_node,
                node_data,
                image_url,
                deconstruction_dimensions
            ): node_data for node_data in flat_nodes
        }

        # 收集结果
        results = {}
        completed_count = 0
        for future in as_completed(future_to_node):
            node_data = future_to_node[future]
            path = node_data["path"]
            path_key = "_".join(map(str, path)) if path else "root"

            try:
                result = future.result()
                results[path_key] = result
                completed_count += 1

                if completed_count % 10 == 0 or completed_count == len(flat_nodes):
                    logger.info(f"处理进度: {completed_count}/{len(flat_nodes)}")

            except Exception as e:
                logger.error(f"节点处理失败 - 路径: {path_key}: {e}", exc_info=True)
                # 使用降级策略
                tree_node = node_data["node"]
                results[path_key] = {
                    "what": tree_node.get("element", "未知元素"),
                    "position": tree_node.get("position", ""),
                    "split_dimension": tree_node.get("split_dimension", ""),
                    "description": tree_node.get("description", ""),
                    "image_path": image_url,
                    "relationships": [],
                    "importance_weight": 0.5,
                    "weight_reason": "默认权重",
                    "children": []
                }

        logger.info(f"批量并行处理完成 - 成功: {len(results)}/{len(flat_nodes)}")
        return results

    def _enrich_tree_with_dimensions(
        self,
        tree_node: Dict[str, Any],
        image_url: str,
        deconstruction_dimensions: List[Dict[str, Any]],
        depth: int = 0  # type: ignore  # Compatibility parameter, not used in parallel implementation
    ) -> Dict[str, Any]:
        """【阶段2】基于树状结构进行维度理解（全树并行优化版）

        新架构：
        1. 将树结构扁平化为带路径的节点列表
        2. 批量并行处理所有节点（利用节点无状态特性）
        3. 根据路径信息重组回树结构

        优势：
        - 一次性并行处理所有节点，最大化并行度
        - 不受树深度限制，所有节点同时处理
        - 结果与原树结构完全一一对应

        Args:
            tree_node: 树节点
            image_url: 图片URL
            deconstruction_dimensions: 外部传入的解构维度
            depth: 当前深度（兼容参数，实际不使用）

        Returns:
            丰富后的节点
        """
        try:
            logger.info(LOG_SEPARATOR)
            logger.info("【全树并行处理】开始...")

            # Step 1: 扁平化树结构
            logger.info("Step 1: 扁平化树结构")
            flat_nodes = self._flatten_tree_nodes(tree_node)
            logger.info(f"扁平化完成 - 节点总数: {len(flat_nodes)}")

            # Step 2: 批量并行处理所有节点
            logger.info("Step 2: 批量并行处理所有节点")
            flat_results = self._batch_process_nodes(
                flat_nodes,
                image_url,
                deconstruction_dimensions
            )
            logger.info(f"批量处理完成 - 成功节点数: {len(flat_results)}")

            # Step 3: 重组树结构
            logger.info("Step 3: 重组树结构")
            result = self._rebuild_tree_from_results(flat_results, tree_node)
            logger.info("重组完成")

            logger.info("【全树并行处理】完成")
            logger.info(LOG_SEPARATOR)

            return result

        except Exception as e:
            logger.error(f"全树并行处理失败: {e}", exc_info=True)
            # 返回基础结构
            return {
                "what": tree_node.get("element", "未知元素"),
                "position": tree_node.get("position", ""),
                "split_dimension": tree_node.get("split_dimension", ""),
                "description": tree_node.get("description", ""),
                "image_path": image_url,
                "relationships": [],
                "importance_weight": 0.5,
                "weight_reason": "默认权重",
                "children": []
            }

    def _build_dimension_info(self, deconstruction_dimensions: List[Dict[str, Any]]) -> str:
        """构建解构维度信息字符串

        Args:
            deconstruction_dimensions: 外部传入的解构维度列表

        Returns:
            格式化的维度信息字符串
        """
        if not deconstruction_dimensions:
            return ""

        dimensions_list = []
        for idx, dimension in enumerate(deconstruction_dimensions):
            dimension_name = dimension.get("dimension_name", "")
            dimension_description = dimension.get("dimension_description", "")
            if dimension_name:
                dimensions_list.append(
                    f"{idx + 1}. 维度: {dimension_name}\n   说明: {dimension_description}"
                )

        if not dimensions_list:
            return ""

        min_dim, max_dim = TARGET_DIMENSION_RANGE
        return f"""

**可选的解构维度列表（从评论中提取）：**
{chr(10).join(dimensions_list)}

请在生成分析维度时：
1. 首先判断这些解构维度是否适用于当前元素
2. 筛选出相关的维度
3. 在此基础上补充其他重要的视觉描述维度
4. 总共生成{min_dim}-{max_dim}个分析维度"""

    def _generate_dimensions_for_element(
        self,
        image_url: str,
        element: str,
        description: str,
        deconstruction_dimensions: List[Dict[str, Any]] = None
    ) -> List[str]:
        """为普通元素生成描述维度

        Args:
            image_url: 图片URL
            element: 元素名称
            description: 元素描述
            deconstruction_dimensions: 外部传入的解构维度

        Returns:
            维度列表
        """
        try:
            # 构建解构维度信息
            dimensions_info = self._build_dimension_info(deconstruction_dimensions or [])
            min_dim, max_dim = TARGET_DIMENSION_RANGE

            prompt = f"""请为图片中的指定元素生成用于分析的描述维度。

**图片元素**: {element}
**元素描述**: {description}{dimensions_info}

**任务**：
生成{min_dim}-{max_dim}个用于分析该元素的描述维度。

请输出JSON格式：
{{{{
    "dimensions": ["维度1", "维度2", "维度3", ...],
    "relevant_count": 从解构维度中筛选出的相关维度数量,
    "reason": "简要说明筛选和生成维度的理由"
}}}}"""

            messages = [
                {"role": "system", "content": self.system_prompt},
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": prompt},
                        {"type": "image_url", "image_url": {"url": image_url}}
                    ]
                }
            ]

            # 使用统一的LLM调用器
            result = LLMInvoker.safe_invoke(
                self,
                "元素维度生成",
                messages,
                fallback={"dimensions": self._get_default_dimensions()}
            )

            dimensions = result.get("dimensions", [])
            relevant_count = result.get("relevant_count", 0)
            reason = result.get("reason", "未提供理由")

            # 验证维度列表
            if not dimensions or len(dimensions) < MIN_DIMENSION_COUNT:
                logger.warning(f"LLM返回的维度列表不符合要求，使用默认维度")
                dimensions = self._get_default_dimensions()
            else:
                logger.info(
                    f"元素维度生成完成 - 元素: {element}, "
                    f"维度数: {len(dimensions)}, 相关维度: {relevant_count}, 理由: {reason}"
                )

            return dimensions

        except Exception as e:
            logger.error(f"元素维度生成失败: {e}", exc_info=True)
            return self._get_default_dimensions()


    def _generate_dimensions_for_root(
        self,
        original_image: str,
        overall_description: str,
        deconstruction_dimensions: List[Dict[str, Any]] = None
    ) -> List[str]:
        """为根节点生成描述维度

        Args:
            original_image: 完整原图路径
            overall_description: 整体描述
            deconstruction_dimensions: 外部传入的解构维度（从评论中提取）

        Returns:
            维度列表
        """
        try:
            # 构建解构维度信息
            dimensions_info = self._build_dimension_info(deconstruction_dimensions or [])
            min_dim, max_dim = TARGET_DIMENSION_RANGE

            prompt = f"""请为图片生成用于分析的描述维度。

**图片整体描述**：
{overall_description}{dimensions_info}

**任务**：
生成{min_dim}-{max_dim}个用于分析图片的描述维度。

请输出JSON格式：
{{{{
    "dimensions": ["维度1", "维度2", "维度3", ...],
    "relevant_count": 从解构维度中筛选出的相关维度数量,
    "reason": "简要说明筛选和生成维度的理由"
}}}}"""

            messages = [
                {"role": "system", "content": self.system_prompt},
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": prompt},
                        {"type": "image_url", "image_url": {"url": original_image}}
                    ]
                }
            ]

            # 使用统一的LLM调用器
            result = LLMInvoker.safe_invoke(
                self,
                "根节点维度生成",
                messages,
                fallback={"dimensions": self._get_default_dimensions()}
            )

            dimensions = result.get("dimensions", [])
            relevant_count = result.get("relevant_count", 0)
            reason = result.get("reason", "未提供理由")

            # 验证维度列表
            if not dimensions or len(dimensions) < MIN_DIMENSION_COUNT:
                logger.warning(f"LLM返回的维度列表不符合要求，使用默认维度")
                dimensions = self._get_default_dimensions()
            else:
                logger.info(
                    f"根节点维度生成完成 - "
                    f"维度数: {len(dimensions)}, 相关维度: {relevant_count}, 理由: {reason}"
                )

            return dimensions

        except Exception as e:
            logger.error(f"根节点维度生成失败: {e}", exc_info=True)
            return self._get_default_dimensions()

    def _get_default_dimensions(self) -> List[str]:
        """获取默认描述维度"""
        return DEFAULT_DIMENSIONS.copy()

    def _detailed_understanding(
        self,
        original_image: str,
        element_name: str,
        element_position: str,
        dimensions: List[str]
    ) -> Dict[str, Any]:
        """根据维度细致理解图片元素

        使用element（what值）和position（位置坐标）精准定位元素

        Args:
            original_image: 完整原图路径（提供上下文）
            element_name: 元素名称（what值，如"芒果块"）
            element_position: 元素位置坐标（如"位于碗的左上角，靠近碗边缘"）
            dimensions: 描述维度列表

        Returns:
            描述值字典
        """
        try:
            # 使用图片路径
            image_url = original_image

            # 构建精准的定位描述
            if element_position:
                location_info = f"""
【要分析的元素】: {element_name}
【元素位置】: {element_position}

请在图片中找到这个元素（根据上述元素名称和位置坐标精准定位），然后分析它的特征。"""
            else:
                # 根节点没有position
                location_info = f"""
【要分析的对象】: 整张图片"""

            prompt = f"""请基于完整原图，分析指定元素的特征。

{location_info}

请从以下维度分析该元素:
{chr(10).join(f'{i+1}. {dim}' for i, dim in enumerate(dimensions))}

**重要约束**：

1. **精准定位元素**：
   - 根据【元素位置】的坐标描述，在图片中精准找到该元素
   - 只分析这一个元素，不要分析其他元素

2. **只描述该元素本身的客观特征**：
   - 只描述直接观察到的物理特征（颜色、形状、质地、状态等）
   - 不要添加主观推测（如"看起来新鲜"、"似乎多汁"等）

3. **描述不包含位置信息**：
   - 不要重复位置信息（position已经单独记录）
   - 不要包含与其他元素的关系
   - 不要包含数量描述

4. **每个维度的值必须是元素的内在属性**：
   - 每个维度的描述应该是当前元素自身的特征
   - 不涉及与其他事物的比较或关系

请输出JSON格式：
{{{{
    "维度1": "值1（只包含该元素的客观特征）",
    "维度2": "值2（只包含该元素的客观特征）",
    ...
}}}}"""

            messages = [
                {"role": "system", "content": self.system_prompt},
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": prompt},
                        {"type": "image_url", "image_url": {"url": image_url}}
                    ]
                }
            ]

            # 使用统一的LLM调用器
            result = LLMInvoker.safe_invoke(
                self,
                "元素详细理解",
                messages,
                fallback={dim: "分析失败" for dim in dimensions}
            )

            return result

        except Exception as e:
            logger.error(f"图片详细分析失败: {e}", exc_info=True)
            return {dim: "分析失败" for dim in dimensions}

    def _extract_relationships(self, children: List[Dict[str, Any]]) -> List[str]:
        """从子节点中提取关系信息

        Args:
            children: 子节点列表

        Returns:
            关系列表
        """
        relationships = []
        for child in children:
            what = child.get("what", "")
            position = child.get("position", "")
            if position:
                relationships.append(f"{what}位于{position}")
        return relationships

    def _print_tree_structure(self, node: Dict[str, Any]) -> None:
        """打印树状结构(JSON格式)

        Args:
            node: 树节点
        """
        # 直接打印JSON格式
        tree_json = json.dumps(node, ensure_ascii=False, indent=2)
        logger.info(tree_json)

    # 实现BaseLLMAgent要求的抽象方法（虽然process方法已被重写，但仍需提供实现）
    def _build_messages(self, state: Dict[str, Any]) -> List[Dict[str, Any]]:
        """构建消息（占位实现，实际不使用）

        由于本Agent重写了process方法实现递归逻辑，此方法不会被调用。
        但作为BaseLLMAgent的子类，必须实现此抽象方法。
        """
        return []

    def _update_state(self, state: Dict[str, Any], response: Any) -> Dict[str, Any]:
        """更新状态（占位实现，实际不使用）

        由于本Agent重写了process方法实现递归逻辑，此方法不会被调用。
        但作为BaseLLMAgent的子类，必须实现此抽象方法。
        """
        return state
