"""
LLM 调用节点

支持普通文本输出和结构化输出（基于 Function Calling）
"""
import json
import logging
import time
from dataclasses import dataclass
from typing import Any, Dict, Generator, List

from ..base import BaseNode, NodeContext, NodeResult
from ..registry import NodeRegistry

logger = logging.getLogger(__name__)


@dataclass
class LLMStreamChunkEvent:
    """LLM 流式输出事件"""
    content: str = ''  # 增量内容
    is_finished: bool = False  # 是否完成
    accumulated_content: str = ''  # 累积内容
    tokens_used: int = 0  # Token 使用量（完成时才有）


@NodeRegistry.register
class LLMNode(BaseNode):
    """
    LLM 调用节点
    
    调用大语言模型进行对话或生成
    """
    
    node_type = 'llm'
    node_name = 'LLM'
    node_category = 'llm'
    node_icon = 'bot'
    node_description = '调用大语言模型进行对话或生成'
    
    inputs = [
        {
            'name': 'prompt',
            'type': 'string',
            'description': '提示词',
        },
        {
            'name': 'context',
            'type': 'string',
            'description': '上下文（可选）',
        },
    ]
    
    outputs = [
        {
            'name': 'response',
            'type': 'string',
            'description': 'LLM 响应',
        },
        {
            'name': 'tokens',
            'type': 'number',
            'description': '使用的 Token 数',
        },
    ]
    
    def execute(self, context: NodeContext) -> NodeResult:
        """执行 LLM 调用（同步方法，通过运行异步方法实现）"""
        import asyncio
        
        # 在同步方法中运行异步代码
        loop = asyncio.get_event_loop()
        if loop.is_running():
            # 如果已有事件循环在运行，创建新任务
            import concurrent.futures
            with concurrent.futures.ThreadPoolExecutor() as executor:
                future = executor.submit(asyncio.run, self.execute_async(context))
                return future.result()
        else:
            return loop.run_until_complete(self.execute_async(context))
    
    async def execute_async(self, context: NodeContext) -> NodeResult:
        """异步执行 LLM 调用"""
        start_time = time.time()
        
        try:
            from ai_platform.services.llm_service import LLMService
            
            # 获取配置
            model_id = self.config.get('model_id', '')
            system_prompt = self.config.get('system_prompt', '')
            user_prompt = self.config.get('user_prompt', '')
            temperature = self.config.get('temperature', 0.7)
            max_tokens = self.config.get('max_tokens', 2048)
            
            # 结构化输出配置
            output_mode = self.config.get('output_mode', 'text')
            output_schema_source = self.config.get('output_schema_source', 'static')
            output_schema = self.config.get('output_schema', [])
            
            # 如果结构来源是变量，从上下文中获取
            if output_schema_source == 'variable':
                output_schema_variable = self.config.get('output_schema_variable', '')
                if output_schema_variable:
                    # 直接从上下文变量中获取原始值（不使用 resolve_template，因为它会将对象转为字符串）
                    # 支持格式：{{node_id.variable_name}} 或 variable_name
                    # 从 {{node.var}} 中提取最后一个部分作为变量名
                    var_name = output_schema_variable.strip()
                    if var_name.startswith('{{') and var_name.endswith('}}'):
                        var_name = var_name[2:-2].strip()
                    
                    # 如果是 node.variable 格式，提取 variable 部分
                    if '.' in var_name:
                        var_name = var_name.split('.')[-1]
                    
                    # 尝试从上下文变量中获取
                    resolved = context.variables.get(var_name)
                    
                    if resolved is not None:
                        try:
                            import json
                            # 优先检查是否已经是 list 类型（直接从上下文变量获取）
                            if isinstance(resolved, list):
                                output_schema = resolved
                            elif isinstance(resolved, str):
                                # 尝试解析 JSON 字符串
                                output_schema = json.loads(resolved)
                            logger.info(f'LLM 从变量 {var_name} 获取结构化输出配置: {len(output_schema)} 个字段')
                        except (json.JSONDecodeError, TypeError) as e:
                            logger.warning(f'解析结构化输出变量失败: {e}')
                            logger.warning(f'var_name: {var_name}, resolved 类型: {type(resolved)}, 值: {str(resolved)[:200]}')
            
            logger.info(f'LLM execute_async - 原始 user_prompt: {user_prompt[:200] if user_prompt else "无"}')
            logger.info(f'LLM execute_async - 上下文变量: {context.variables}')
            
            # 解析模板变量
            if system_prompt:
                system_prompt = context.resolve_template(system_prompt)
            if user_prompt:
                user_prompt = context.resolve_template(user_prompt)
            
            logger.info(f'LLM execute_async - 解析后 user_prompt: {user_prompt[:200] if user_prompt else "无"}')
            
            # 至少需要配置 system_prompt 或 user_prompt 之一
            if not system_prompt and not user_prompt:
                return NodeResult(
                    success=False,
                    error='请至少配置系统提示词或用户提示词',
                )
            
            # 构建消息
            messages = []
            if system_prompt:
                messages.append({'role': 'system', 'content': system_prompt})
            
            # 添加对话历史
            if self.config.get('use_conversation_history', False):
                messages.extend(context.conversation_history)
            
            # 只有配置了 user_prompt 才添加用户消息
            if user_prompt:
                messages.append({'role': 'user', 'content': user_prompt})
            
            # 调用 LLM（异步）
            llm_service = LLMService(context.db_session)
            output_var = self.config.get('output_variable', 'llm_response')
            
            # 根据输出模式选择执行方式
            if output_mode == 'structured' and output_schema:
                # 结构化输出模式：使用 Function Calling（异步）
                result = await self._execute_structured_output_async(
                    llm_service, model_id, messages, temperature, max_tokens,
                    output_schema, output_var, start_time
                )
                return result
            else:
                # 普通文本输出模式（异步）
                response = await llm_service.chat_async(
                    model_id=model_id,
                    messages=messages,
                    temperature=temperature,
                    max_tokens=max_tokens,
                )
                
                elapsed_time = int((time.time() - start_time) * 1000)
                
                return NodeResult(
                    success=True,
                    output=response.content,
                    output_variables={
                        output_var: response.content,
                        f'{output_var}_tokens': response.total_tokens,
                    },
                    tokens_used=response.total_tokens,
                    elapsed_time=elapsed_time,
                    metadata={
                        'model': response.model,
                        'prompt_tokens': response.prompt_tokens,
                        'completion_tokens': response.completion_tokens,
                    },
                )
            
        except Exception as e:
            logger.exception(f'LLM 节点异步执行失败: {e}')
            elapsed_time = int((time.time() - start_time) * 1000)
            return NodeResult(
                success=False,
                error=str(e),
                elapsed_time=elapsed_time,
            )
    
    def execute_stream(self, context: NodeContext) -> Generator[LLMStreamChunkEvent, None, NodeResult]:
        """
        流式执行 LLM 调用
        
        Yields:
            LLMStreamChunkEvent: 流式输出事件
            
        Returns:
            NodeResult: 最终执行结果
        """
        start_time = time.time()
        
        try:
            from ai_platform.services.llm_service import LLMService
            
            # 获取配置
            model_id = self.config.get('model_id', '')
            system_prompt = self.config.get('system_prompt', '')
            user_prompt = self.config.get('user_prompt', '')
            temperature = self.config.get('temperature', 0.7)
            max_tokens = self.config.get('max_tokens', 2048)
            
            # 结构化输出配置
            output_mode = self.config.get('output_mode', 'text')  # text 或 structured
            output_schema_source = self.config.get('output_schema_source', 'static')
            output_schema = self.config.get('output_schema', [])
            
            # 如果结构来源是变量，从上下文中获取
            if output_schema_source == 'variable':
                output_schema_variable = self.config.get('output_schema_variable', '')
                if output_schema_variable:
                    # 直接从上下文变量中获取原始值（不使用 resolve_template，因为它会将对象转为字符串）
                    # 支持格式：{{node_id.variable_name}} 或 variable_name
                    # 从 {{node.var}} 中提取最后一个部分作为变量名
                    var_name = output_schema_variable.strip()
                    if var_name.startswith('{{') and var_name.endswith('}}'):
                        var_name = var_name[2:-2].strip()
                    
                    # 如果是 node.variable 格式，提取 variable 部分
                    if '.' in var_name:
                        var_name = var_name.split('.')[-1]
                    
                    # 尝试从上下文变量中获取
                    resolved = context.variables.get(var_name)
                    
                    if resolved is not None:
                        try:
                            import json
                            # 优先检查是否已经是 list 类型（直接从上下文变量获取）
                            if isinstance(resolved, list):
                                output_schema = resolved
                            elif isinstance(resolved, str):
                                # 尝试解析 JSON 字符串
                                output_schema = json.loads(resolved)
                            logger.info(f'LLM stream 从变量 {var_name} 获取结构化输出配置: {len(output_schema)} 个字段')
                        except (json.JSONDecodeError, TypeError) as e:
                            logger.warning(f'解析结构化输出变量失败: {e}')
                            logger.warning(f'var_name: {var_name}, resolved 类型: {type(resolved)}, 值: {str(resolved)[:200]}')
            
            logger.info(f'LLM stream - 原始 user_prompt: {user_prompt[:200] if user_prompt else "无"}')
            logger.info(f'LLM stream - 上下文变量: {context.variables}')
            
            # 解析模板变量
            if system_prompt:
                system_prompt = context.resolve_template(system_prompt)
            if user_prompt:
                user_prompt = context.resolve_template(user_prompt)
            
            logger.info(f'LLM stream - 解析后 user_prompt: {user_prompt[:200] if user_prompt else "无"}')
            
            # 至少需要配置 system_prompt 或 user_prompt 之一
            if not system_prompt and not user_prompt:
                yield LLMStreamChunkEvent(
                    content='',
                    is_finished=True,
                    accumulated_content='',
                )
                return NodeResult(
                    success=False,
                    error='请至少配置系统提示词或用户提示词',
                )
            
            # 构建消息
            messages = []
            if system_prompt:
                messages.append({'role': 'system', 'content': system_prompt})
            
            # 添加对话历史
            if self.config.get('use_conversation_history', False):
                messages.extend(context.conversation_history)
            
            # 只有配置了 user_prompt 才添加用户消息
            if user_prompt:
                messages.append({'role': 'user', 'content': user_prompt})
            
            llm_service = LLMService(context.db_session)
            output_var = self.config.get('output_variable', 'llm_response')
            
            # 根据输出模式选择执行方式
            if output_mode == 'structured' and output_schema:
                # 结构化输出模式：使用 Function Calling 流式调用
                result = yield from self._execute_stream_structured_output(
                    llm_service, model_id, messages, temperature, max_tokens,
                    output_schema, output_var, start_time
                )
                return result
            else:
                # 普通文本输出模式
                accumulated_content = ''
                total_tokens = 0
                
                for chunk in llm_service.chat_stream_sync(
                    model_id=model_id,
                    messages=messages,
                    temperature=temperature,
                    max_tokens=max_tokens,
                ):
                    accumulated_content += chunk.content
                    
                    # 如果是最后一个 chunk，获取 token 信息
                    if chunk.is_finished and chunk.total_tokens > 0:
                        total_tokens = chunk.total_tokens
                    
                    yield LLMStreamChunkEvent(
                        content=chunk.content,
                        is_finished=chunk.is_finished,
                        accumulated_content=accumulated_content,
                        tokens_used=total_tokens,
                    )
                
                elapsed_time = int((time.time() - start_time) * 1000)
                
                return NodeResult(
                    success=True,
                    output=accumulated_content,
                    output_variables={
                        output_var: accumulated_content,
                        f'{output_var}_tokens': total_tokens,
                    },
                    tokens_used=total_tokens,
                    elapsed_time=elapsed_time,
                )
            
        except Exception as e:
            logger.exception(f'LLM 节点流式执行失败: {e}')
            elapsed_time = int((time.time() - start_time) * 1000)
            yield LLMStreamChunkEvent(
                content='',
                is_finished=True,
                accumulated_content='',
            )
            return NodeResult(
                success=False,
                error=str(e),
                elapsed_time=elapsed_time,
            )
    
    def _execute_stream_structured_output(
        self,
        llm_service,
        model_id: str,
        messages: List[Dict],
        temperature: float,
        max_tokens: int,
        output_schema: List[Dict],
        output_var: str,
        start_time: float,
    ) -> Generator[LLMStreamChunkEvent, None, NodeResult]:
        """
        使用 Function Calling 流式执行结构化输出
        
        Args:
            llm_service: LLM 服务实例
            model_id: 模型 ID
            messages: 消息列表
            temperature: 温度
            max_tokens: 最大 token
            output_schema: 输出字段定义列表（支持多层嵌套）
            output_var: 输出变量名
            start_time: 开始时间
            
        Yields:
            LLMStreamChunkEvent: 流式输出事件
            
        Returns:
            NodeResult: 执行结果
        """
        # 使用递归方法构建多层嵌套的 JSON Schema
        properties, required_fields = self._build_schema_properties(output_schema)
        
        if not properties:
            yield LLMStreamChunkEvent(
                content='',
                is_finished=True,
                accumulated_content='',
            )
            return NodeResult(
                success=False,
                error='结构化输出模式需要至少定义一个输出字段',
                elapsed_time=int((time.time() - start_time) * 1000),
            )
        
        # 构建 Function Calling 工具定义
        tools = [{
            'name': 'extract_structured_data',
            'description': '从文本中提取结构化数据并返回 JSON 格式',
            'parameters': {
                'type': 'object',
                'properties': properties,
                'required': required_fields,
            },
        }]
        
        # 检查模型是否支持 Function Calling
        # TODO: 需要改造为异步版本，当前默认支持
        supports_fc = True
        
        accumulated_content = ''
        total_tokens = 0
        tool_call_arguments = ''
        
        extracted_data = None
        
        if supports_fc:
            # 使用 Function Calling 流式调用
            for chunk in llm_service.chat_stream_sync(
                model_id=model_id,
                messages=messages,
                temperature=temperature,
                max_tokens=max_tokens,
                tools=tools,
                tool_choice='required',
            ):
                # 处理 tool_calls 流式数据
                if chunk.tool_calls:
                    for tc in chunk.tool_calls:
                        if tc.arguments:
                            # arguments 可能是 dict（已解析）或 str（流式增量）
                            if isinstance(tc.arguments, dict):
                                # 已解析的完整参数，直接使用
                                extracted_data = tc.arguments
                                accumulated_content = json.dumps(tc.arguments, ensure_ascii=False)
                            else:
                                # 字符串形式的增量参数
                                tool_call_arguments += str(tc.arguments)
                                accumulated_content = tool_call_arguments
                elif chunk.tool_call_delta:
                    # 处理增量的 tool_call 数据
                    if chunk.tool_call_delta.get('arguments'):
                        args = chunk.tool_call_delta['arguments']
                        if isinstance(args, str):
                            tool_call_arguments += args
                        else:
                            tool_call_arguments += json.dumps(args, ensure_ascii=False)
                        accumulated_content = tool_call_arguments
                elif chunk.content:
                    accumulated_content += chunk.content
                
                if chunk.is_finished and chunk.total_tokens > 0:
                    total_tokens = chunk.total_tokens
                
                yield LLMStreamChunkEvent(
                    content=chunk.content or '',
                    is_finished=chunk.is_finished,
                    accumulated_content=accumulated_content,
                    tokens_used=total_tokens,
                )
            
            elapsed_time = int((time.time() - start_time) * 1000)
            
            # 解析 tool_call 参数
            if extracted_data:
                # 已经有解析好的数据（dict 类型）
                json_output = json.dumps(extracted_data, ensure_ascii=False, indent=2)
                
                output_variables = {
                    output_var: json_output,
                    f'{output_var}_data': extracted_data,
                    f'{output_var}_tokens': total_tokens,
                }
                
                for key, value in extracted_data.items():
                    output_variables[f'{output_var}_{key}'] = value
                
                logger.info(f'LLM 结构化输出成功 (Function Calling 流式), tokens: {total_tokens}')
                return NodeResult(
                    success=True,
                    output=json_output,
                    output_variables=output_variables,
                    tokens_used=total_tokens,
                    elapsed_time=elapsed_time,
                    metadata={
                        'output_mode': 'structured',
                        'use_function_calling': True,
                        'call_method': 'Function Calling Stream (tool_choice=required)',
                    },
                )
            elif tool_call_arguments:
                # 从字符串解析 JSON
                try:
                    extracted_data = json.loads(tool_call_arguments)
                    json_output = json.dumps(extracted_data, ensure_ascii=False, indent=2)
                    
                    output_variables = {
                        output_var: json_output,
                        f'{output_var}_data': extracted_data,
                        f'{output_var}_tokens': total_tokens,
                    }
                    
                    for key, value in extracted_data.items():
                        output_variables[f'{output_var}_{key}'] = value
                    
                    logger.info(f'LLM 结构化输出成功 (Function Calling 流式解析), tokens: {total_tokens}')
                    return NodeResult(
                        success=True,
                        output=json_output,
                        output_variables=output_variables,
                        tokens_used=total_tokens,
                        elapsed_time=elapsed_time,
                        metadata={
                            'output_mode': 'structured',
                            'use_function_calling': True,
                            'call_method': 'Function Calling Stream (parsed from string)',
                        },
                    )
                except json.JSONDecodeError as e:
                    logger.warning(f'Function Calling 参数解析失败: {e}')
                    return self._parse_json_from_text(
                        accumulated_content, output_var, total_tokens, elapsed_time
                    )
            else:
                # 没有 tool_call 结果，尝试从文本解析
                return self._parse_json_from_text(
                    accumulated_content, output_var, total_tokens, elapsed_time
                )
        else:
            # 模型不支持 Function Calling，使用普通流式调用后解析文本
            for chunk in llm_service.chat_stream_sync(
                model_id=model_id,
                messages=messages,
                temperature=temperature,
                max_tokens=max_tokens,
            ):
                accumulated_content += chunk.content
                
                if chunk.is_finished and chunk.total_tokens > 0:
                    total_tokens = chunk.total_tokens
                
                yield LLMStreamChunkEvent(
                    content=chunk.content,
                    is_finished=chunk.is_finished,
                    accumulated_content=accumulated_content,
                    tokens_used=total_tokens,
                )
            
            elapsed_time = int((time.time() - start_time) * 1000)
            
            return self._parse_json_from_text(
                accumulated_content, output_var, total_tokens, elapsed_time
            )
    
    def _execute_structured_output(
        self,
        llm_service,
        model_id: str,
        messages: List[Dict],
        temperature: float,
        max_tokens: int,
        output_schema: List[Dict],
        output_var: str,
        start_time: float,
    ) -> NodeResult:
        """
        使用 Function Calling 执行结构化输出
        
        Args:
            llm_service: LLM 服务实例
            model_id: 模型 ID
            messages: 消息列表
            temperature: 温度
            max_tokens: 最大 token
            output_schema: 输出字段定义列表（支持多层嵌套）
            output_var: 输出变量名
            start_time: 开始时间
            
        Returns:
            NodeResult: 执行结果
        """
        # 使用递归方法构建多层嵌套的 JSON Schema
        properties, required_fields = self._build_schema_properties(output_schema)
        
        if not properties:
            return NodeResult(
                success=False,
                error='结构化输出模式需要至少定义一个输出字段',
                elapsed_time=int((time.time() - start_time) * 1000),
            )
        
        # 构建 Function Calling 工具定义
        tools = [{
            'name': 'extract_structured_data',
            'description': '从文本中提取结构化数据并返回 JSON 格式',
            'parameters': {
                'type': 'object',
                'properties': properties,
                'required': required_fields,
            },
        }]
        
        # 检查模型是否支持 Function Calling
        # TODO: 需要改造为异步版本，当前默认支持
        supports_fc = True
        
        if supports_fc:
            # 使用 Function Calling
            response = llm_service.chat(
                model_id=model_id,
                messages=messages,
                temperature=temperature,
                max_tokens=max_tokens,
                tools=tools,
                tool_choice='required',
            )
            
            elapsed_time = int((time.time() - start_time) * 1000)
            
            if response.has_tool_calls and response.tool_calls:
                # 从 tool_calls 中提取结构化数据
                extracted_data = response.tool_calls[0].arguments
                
                # 处理双重序列化问题：如果 extracted_data 是 {"raw": "..."} 格式，提取 raw 字段
                if isinstance(extracted_data, dict) and len(extracted_data) == 1 and 'raw' in extracted_data:
                    raw_value = extracted_data['raw']
                    logger.info(f'检测到 raw 包装格式，提取 raw 字段: {raw_value[:200] if isinstance(raw_value, str) else raw_value}')
                    if isinstance(raw_value, str):
                        try:
                            extracted_data = json.loads(raw_value)
                            logger.info(f'成功解析 raw 字段为 JSON 对象')
                        except json.JSONDecodeError as e:
                            logger.warning(f'raw 字段不是有效的 JSON: {e}')
                            # 保持原样
                    else:
                        extracted_data = raw_value
                
                json_output = json.dumps(extracted_data, ensure_ascii=False, indent=2)
                logger.info(f'LLM 结构化输出成功 (Function Calling 同步), tokens: {response.total_tokens}')
                
                # 构建输出变量：既有 JSON 字符串，也有各个字段
                output_variables = {
                    output_var: json_output,
                    f'{output_var}_data': extracted_data,
                    f'{output_var}_tokens': response.total_tokens,
                    f'{output_var}_raw': response.tool_calls[0].arguments,  # 保留原始响应
                }
                
                # 将每个字段也作为独立变量输出
                for key, value in extracted_data.items():
                    output_variables[f'{output_var}_{key}'] = value
                
                return NodeResult(
                    success=True,
                    output=json_output,
                    output_variables=output_variables,
                    tokens_used=response.total_tokens,
                    elapsed_time=elapsed_time,
                    metadata={
                        'model': response.model,
                        'output_mode': 'structured',
                        'use_function_calling': True,
                        'call_method': 'Function Calling (tool_choice=required)',
                    },
                )
            else:
                # Function Calling 未返回结果，回退到文本解析
                logger.warning('Function Calling 未返回 tool_calls，尝试解析文本响应')
                return self._parse_json_from_text(
                    response.content, output_var, response.total_tokens, elapsed_time
                )
        else:
            # 模型不支持 Function Calling，使用文本解析
            response = llm_service.chat(
                model_id=model_id,
                messages=messages,
                temperature=temperature,
                max_tokens=max_tokens,
            )
            
            elapsed_time = int((time.time() - start_time) * 1000)
            
            return self._parse_json_from_text(
                response.content, output_var, response.total_tokens, elapsed_time
            )
    
    def _build_schema_properties(self, output_schema: List[Dict]) -> tuple:
        """
        递归构建多层嵌套的 JSON Schema properties
        
        Args:
            output_schema: 输出字段定义列表，支持嵌套结构
            
        Returns:
            tuple: (properties dict, required fields list)
        """
        properties = {}
        required_fields = []
        
        type_mapping = {
            'string': 'string',
            'number': 'number',
            'integer': 'integer',
            'boolean': 'boolean',
            'array': 'array',
            'object': 'object',
        }
        
        for field in output_schema:
            field_name = field.get('name', '')
            field_type = field.get('type', 'string')
            field_desc = field.get('description', '')
            field_required = field.get('required', False)
            
            if not field_name:
                continue
            
            prop = {
                'type': type_mapping.get(field_type, 'string'),
            }
            
            if field_desc:
                prop['description'] = field_desc
            
            # 处理 object 类型的嵌套属性
            if field_type == 'object' and field.get('properties'):
                nested_props, nested_required = self._build_schema_properties(field['properties'])
                prop['properties'] = nested_props
                if nested_required:
                    prop['required'] = nested_required
            
            # 处理 array 类型的元素定义
            # 支持两种格式：items（标准格式）和 children（表单转 LLM 节点使用的格式）
            array_items = field.get('items') or field.get('children')
            if field_type == 'array' and array_items:
                # 如果 children 是列表（表单字段列表），转换为 object 的 properties
                if isinstance(array_items, list):
                    # 表单转 LLM 节点格式：children 是字段列表
                    nested_props, nested_required = self._build_schema_properties(array_items)
                    prop['items'] = {
                        'type': 'object',
                        'properties': nested_props,
                    }
                    if nested_required:
                        prop['items']['required'] = nested_required
                elif isinstance(array_items, dict):
                    # 标准格式：items 是单个对象定义
                    items_type = array_items.get('type', 'string')
                    
                    if items_type == 'object' and array_items.get('properties'):
                        # 数组元素是对象类型
                        nested_props, nested_required = self._build_schema_properties(array_items['properties'])
                        prop['items'] = {
                            'type': 'object',
                            'properties': nested_props,
                        }
                        if nested_required:
                            prop['items']['required'] = nested_required
                    else:
                        # 数组元素是基本类型
                        prop['items'] = {
                            'type': type_mapping.get(items_type, 'string'),
                        }
            
            properties[field_name] = prop
            
            if field_required:
                required_fields.append(field_name)
        
        # 调试日志
        import json
        logger.info(f'_build_schema_properties 结果: properties={json.dumps(properties, ensure_ascii=False)}, required={required_fields}')
        
        return properties, required_fields
    
    def _parse_json_from_text(
        self,
        content: str,
        output_var: str,
        total_tokens: int,
        elapsed_time: int,
    ) -> NodeResult:
        """从文本中解析 JSON（回退方案）"""
        try:
            # 尝试提取 JSON
            text = content.strip()
            
            # 处理 markdown 代码块
            if '```' in text:
                import re
                json_match = re.search(r'```(?:json)?\s*([\s\S]*?)```', text)
                if json_match:
                    text = json_match.group(1).strip()
            
            # 尝试找到 JSON 对象
            start_idx = text.find('{')
            end_idx = text.rfind('}')
            if start_idx != -1 and end_idx != -1:
                text = text[start_idx:end_idx + 1]
            
            extracted_data = json.loads(text)
            json_output = json.dumps(extracted_data, ensure_ascii=False, indent=2)
            
            output_variables = {
                output_var: json_output,
                f'{output_var}_data': extracted_data,
                f'{output_var}_tokens': total_tokens,
            }
            
            for key, value in extracted_data.items():
                output_variables[f'{output_var}_{key}'] = value
            
            logger.info(f'LLM 结构化输出成功 (文本解析), tokens: {total_tokens}')
            return NodeResult(
                success=True,
                output=json_output,
                output_variables=output_variables,
                tokens_used=total_tokens,
                elapsed_time=elapsed_time,
                metadata={
                    'output_mode': 'structured',
                    'use_function_calling': False,
                    'call_method': 'Text Parsing (JSON extraction from response)',
                },
            )
            
        except (json.JSONDecodeError, ValueError) as e:
            logger.warning(f'JSON 解析失败: {e}, content: {content[:200]}')
            return NodeResult(
                success=False,
                error=f'无法从 LLM 响应中解析 JSON: {str(e)}',
                output=content,
                output_variables={
                    output_var: content,
                    f'{output_var}_tokens': total_tokens,
                },
                tokens_used=total_tokens,
                elapsed_time=elapsed_time,
            )
    
    async def _execute_structured_output_async(
        self,
        llm_service,
        model_id: str,
        messages: List[Dict],
        temperature: float,
        max_tokens: int,
        output_schema: List[Dict],
        output_var: str,
        start_time: float,
    ) -> NodeResult:
        """
        异步使用 Function Calling 执行结构化输出（支持多层嵌套）
        """
        # 使用递归方法构建多层嵌套的 JSON Schema
        properties, required_fields = self._build_schema_properties(output_schema)
        
        if not properties:
            return NodeResult(
                success=False,
                error='结构化输出模式需要至少定义一个输出字段',
                elapsed_time=int((time.time() - start_time) * 1000),
            )
        
        # 构建 Function Calling 工具定义
        tools = [{
            'name': 'extract_structured_data',
            'description': '从文本中提取结构化数据并返回 JSON 格式',
            'parameters': {
                'type': 'object',
                'properties': properties,
                'required': required_fields,
            },
        }]
        
        # 使用 Function Calling（异步）
        response = await llm_service.chat_async(
            model_id=model_id,
            messages=messages,
            temperature=temperature,
            max_tokens=max_tokens,
            tools=tools,
            tool_choice='required',
        )
        
        elapsed_time = int((time.time() - start_time) * 1000)
        
        if response.has_tool_calls and response.tool_calls:
            # 从 tool_calls 中提取结构化数据
            extracted_data = response.tool_calls[0].arguments
            
            # 处理双重序列化问题：如果 extracted_data 是 {"raw": "..."} 格式，提取 raw 字段
            if isinstance(extracted_data, dict) and len(extracted_data) == 1 and 'raw' in extracted_data:
                raw_value = extracted_data['raw']
                logger.info(f'检测到 raw 包装格式，提取 raw 字段: {raw_value[:200] if isinstance(raw_value, str) else raw_value}')
                if isinstance(raw_value, str):
                    try:
                        extracted_data = json.loads(raw_value)
                        logger.info(f'成功解析 raw 字段为 JSON 对象')
                    except json.JSONDecodeError as e:
                        logger.warning(f'raw 字段不是有效的 JSON: {e}')
                        # 保持原样
                else:
                    extracted_data = raw_value
            
            json_output = json.dumps(extracted_data, ensure_ascii=False, indent=2)
            logger.info(f'LLM 结构化输出成功 (Function Calling), tokens: {response.total_tokens}')
            
            output_variables = {
                output_var: json_output,
                f'{output_var}_data': extracted_data,
                f'{output_var}_tokens': response.total_tokens,
                f'{output_var}_raw': response.tool_calls[0].arguments,  # 保留原始响应
            }
            
            for key, value in extracted_data.items():
                output_variables[f'{output_var}_{key}'] = value
            
            return NodeResult(
                success=True,
                output=json_output,
                output_variables=output_variables,
                tokens_used=response.total_tokens,
                elapsed_time=elapsed_time,
                metadata={
                    'model': response.model,
                    'output_mode': 'structured',
                    'use_function_calling': True,
                    'call_method': 'Function Calling (tool_choice=required)',
                },
            )
        else:
            # Function Calling 未返回结果，回退到文本解析
            logger.warning('Function Calling 未返回 tool_calls，尝试解析文本响应')
            return self._parse_json_from_text(
                response.content, output_var, response.total_tokens, elapsed_time
            )
    
    @classmethod
    def get_config_schema(cls) -> Dict[str, Any]:
        """获取配置 Schema"""
        return {
            'type': 'object',
            'properties': {
                'model_id': {
                    'type': 'string',
                    'title': '模型',
                    'description': '选择要使用的 LLM 模型',
                },
                'system_prompt': {
                    'type': 'string',
                    'title': '系统提示词',
                    'description': '设置 AI 的角色和行为',
                    'format': 'textarea',
                },
                'user_prompt': {
                    'type': 'string',
                    'title': '用户提示词',
                    'description': '支持变量引用，如 {{user_input}}',
                    'format': 'textarea',
                },
                'temperature': {
                    'type': 'number',
                    'title': '温度',
                    'description': '控制输出的随机性，0-2',
                    'default': 0.7,
                    'minimum': 0,
                    'maximum': 2,
                },
                'max_tokens': {
                    'type': 'integer',
                    'title': '最大 Token',
                    'description': '限制输出长度',
                    'default': 2048,
                },
                'use_conversation_history': {
                    'type': 'boolean',
                    'title': '使用对话历史',
                    'default': False,
                },
                'use_streaming': {
                    'type': 'boolean',
                    'title': '流式输出',
                    'description': '启用后将实时输出 LLM 响应',
                    'default': False,
                },
                'output_mode': {
                    'type': 'string',
                    'title': '输出模式',
                    'description': 'text: 普通文本输出; structured: 结构化 JSON 输出（使用 Function Calling）',
                    'enum': ['text', 'structured'],
                    'enumNames': ['文本输出', '结构化输出'],
                    'default': 'text',
                },
                'output_schema': {
                    'type': 'array',
                    'title': '输出字段定义',
                    'description': '结构化输出模式下，定义要提取的字段',
                    'items': {
                        'type': 'object',
                        'properties': {
                            'name': {
                                'type': 'string',
                                'title': '字段名',
                                'description': '英文字段名，如 country, project_name',
                            },
                            'type': {
                                'type': 'string',
                                'title': '类型',
                                'enum': ['string', 'number', 'integer', 'boolean', 'array', 'object'],
                                'default': 'string',
                            },
                            'description': {
                                'type': 'string',
                                'title': '字段描述',
                                'description': '描述该字段的含义，帮助 LLM 理解',
                            },
                            'required': {
                                'type': 'boolean',
                                'title': '必填',
                                'default': False,
                            },
                        },
                        'required': ['name'],
                    },
                },
                'output_variable': {
                    'type': 'string',
                    'title': '输出变量名',
                    'default': 'llm_response',
                },
            },
            'required': ['model_id'],
        }
