package tech.easyflow.agent.runtime;

import com.easyagents.agent.runtime.agentscope.AgentHttpTransportProvider;
import com.easyagents.agent.runtime.agentscope.AgentScopeMessageAdapter;
import com.easyagents.agent.runtime.agentscope.AgentScopeModelFactory;
import com.easyagents.agent.runtime.message.AgentContentBlock;
import com.easyagents.agent.runtime.message.AgentMediaBlock;
import com.easyagents.agent.runtime.message.AgentMessage;
import com.easyagents.agent.runtime.message.AgentMessageRole;
import com.easyagents.agent.runtime.message.AgentTextBlock;
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
import com.easyagents.agent.runtime.model.AgentMessageContentFormat;
import com.easyagents.agent.runtime.model.AgentModelFactory;
import com.easyagents.agent.runtime.model.AgentModelProviderType;
import com.easyagents.agent.runtime.model.AgentModelSpec;
import io.agentscope.core.message.ContentBlock;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.message.ToolUseBlock;
import io.agentscope.core.model.ChatResponse;
import io.agentscope.core.model.ExecutionConfig;
import io.agentscope.core.model.GenerateOptions;
import io.agentscope.core.model.ToolChoice;
import io.agentscope.core.model.ToolSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.service.support.VlmVerificationImage;
import tech.easyflow.ai.service.verification.ChatModelConnectivityVerifier;
import tech.easyflow.ai.service.verification.ChatModelVerificationResult;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;

/**
 * 使用 AgentScope 真实运行链路验证 Chat Model 与 VLM 连通性。
 */
@Component
public class AgentScopeChatModelConnectivityVerifier implements ChatModelConnectivityVerifier {

    private static final Logger LOG = LoggerFactory.getLogger(AgentScopeChatModelConnectivityVerifier.class);
    private static final String PROBE_TOOL_NAME = "easyflow_capability_probe";
    private static final String PROBE_NONCE_FIELD = "nonce";
    private static final String PROBE_IMAGE_FIELD = "imageCode";
    private static final String VERIFICATION_SYSTEM_PROMPT = "You are a model connectivity verification assistant.";
    /** 首次能力探测使用的低成本输出预算。 */
    private static final int MAX_TOKENS = 128;
    /** 首次响应被输出上限截断时使用的单次重试预算。 */
    private static final int TRUNCATION_RETRY_MAX_TOKENS = 512;
    private static final Duration DEFAULT_PHASE_TIMEOUT = Duration.ofSeconds(60);

    /** AgentScope 模型工厂。 */
    private final AgentModelFactory<io.agentscope.core.model.Model> modelFactory;
    /** EasyAgents 与 AgentScope 的消息适配器。 */
    private final AgentScopeMessageAdapter messageAdapter;
    /** 单阶段最大等待时间。 */
    private final Duration phaseTimeout;
    /** 为每次工具能力探测生成随机校验值。 */
    private final Supplier<String> nonceSupplier;

    /**
     * 使用生产运行时组件创建验证器。
     */
    public AgentScopeChatModelConnectivityVerifier() {
        this(new AgentScopeModelFactory(), new AgentScopeMessageAdapter(), DEFAULT_PHASE_TIMEOUT,
                () -> UUID.randomUUID().toString());
    }

    /**
     * 使用指定依赖创建验证器，供测试和受控运行环境使用。
     *
     * @param modelFactory AgentScope 模型工厂
     * @param messageAdapter 消息适配器
     * @param phaseTimeout 单阶段最大等待时间
     */
    AgentScopeChatModelConnectivityVerifier(
            AgentModelFactory<io.agentscope.core.model.Model> modelFactory,
            AgentScopeMessageAdapter messageAdapter,
            Duration phaseTimeout) {
        this(modelFactory, messageAdapter, phaseTimeout, () -> UUID.randomUUID().toString());
    }

    /**
     * 使用可控随机值创建验证器，供能力探测测试使用。
     *
     * @param modelFactory AgentScope 模型工厂
     * @param messageAdapter 消息适配器
     * @param phaseTimeout 单阶段最大等待时间
     * @param nonceSupplier 探测随机值生成器
     */
    AgentScopeChatModelConnectivityVerifier(
            AgentModelFactory<io.agentscope.core.model.Model> modelFactory,
            AgentScopeMessageAdapter messageAdapter,
            Duration phaseTimeout,
            Supplier<String> nonceSupplier) {
        this.modelFactory = modelFactory;
        this.messageAdapter = messageAdapter;
        this.phaseTimeout = phaseTimeout;
        this.nonceSupplier = nonceSupplier;
    }

    /**
     * 使用非流式 Chat 请求验证连接、视觉与工具调用能力。
     *
     * <p>当兼容接口明确拒绝工具参数时，追加一次不带工具的连接兜底请求，
     * 避免将可用的普通对话模型误判为连接失败。严格内容块数组模式会先
     * 追加一次多轮上下文验证。</p>
     *
     * @param model 已补齐供应商默认配置的模型
     * @return 连接和工具能力验证结果
     * @throws BusinessException 连接或视觉验证失败时抛出
     */
    @Override
    public ChatModelVerificationResult verify(Model model) {
        AgentModelSpec modelSpec = AgentModelSpecMapper.fromModel(model);
        String effectiveHttpVersion = AgentHttpTransportProvider.resolveEffectivePolicy(
                modelSpec.getHttpVersionPolicy(), modelSpec.getBaseUrl()).name();
        String nonce = nonceSupplier.get();

        try {
            if (modelSpec.getMessageContentFormat() == AgentMessageContentFormat.TEXT_PARTS) {
                verifyMessageContentCompatibility(modelSpec);
            }
            boolean supportTool = verifyProbeRequest(modelSpec, nonce);
            return ChatModelVerificationResult.passed(effectiveHttpVersion, supportTool);
        } catch (Exception exception) {
            if (isToolCapabilityRejection(exception)) {
                LOG.info("Model endpoint rejected tool probe, fallback to plain connectivity, modelId={}",
                        model.getId());
                try {
                    verifyPlainRequest(modelSpec);
                    return ChatModelVerificationResult.passed(effectiveHttpVersion, false);
                } catch (Exception fallbackException) {
                    LOG.error("AgentScope model fallback connectivity verification failed, modelId={}, httpPolicy={}",
                            model.getId(), effectiveHttpVersion, fallbackException);
                    throw new BusinessException(
                            400, 1, "模型连接验证失败，请查看后端日志", fallbackException);
                }
            }
            LOG.error("AgentScope model connectivity verification failed, modelId={}, httpPolicy={}",
                    model.getId(), effectiveHttpVersion, exception);
            if (exception instanceof BusinessException businessException) {
                throw businessException;
            }
            throw new BusinessException(400, 1, "模型连接验证失败，请查看后端日志", exception);
        }
    }

    /**
     * 执行携带无副作用工具 Schema 的能力探测请求。
     *
     * @param modelSpec 运行时模型声明
     * @param nonce 本次探测随机值
     * @return 模型是否正确返回指定工具调用
     * @throws BusinessException 响应为空或视觉识别失败时抛出
     */
    private boolean verifyProbeRequest(AgentModelSpec modelSpec, String nonce) {
        AgentMessage message = buildProbeMessage(modelSpec.isSupportImage(), nonce);
        List<ToolSchema> tools = List.of(buildProbeToolSchema(modelSpec.isSupportImage()));
        ToolChoice toolChoice = new ToolChoice.Specific(PROBE_TOOL_NAME);
        List<ChatResponse> responses = request(
                modelSpec,
                message,
                tools,
                toolChoice);
        if (hasLengthLimitedResponse(responses)) {
            LOG.info("Model probe response reached token limit, retry with larger budget, modelName={}, maxTokens={}",
                    modelSpec.getModelName(), TRUNCATION_RETRY_MAX_TOKENS);
            responses = request(
                    modelSpec,
                    message,
                    tools,
                    toolChoice,
                    TRUNCATION_RETRY_MAX_TOKENS);
        }
        boolean validToolCall = hasValidProbeToolCall(
                responses, nonce, modelSpec.isSupportImage());
        validateProbeResponse(modelSpec.isSupportImage(), responses, validToolCall);
        return validToolCall;
    }

    /**
     * 执行不带工具的兼容性兜底请求。
     *
     * @param modelSpec 运行时模型声明
     * @throws BusinessException 响应为空或视觉识别失败时抛出
     */
    private void verifyPlainRequest(AgentModelSpec modelSpec) {
        List<ChatResponse> responses = request(
                modelSpec,
                buildPlainVerificationMessage(modelSpec.isSupportImage()),
                List.of(),
                null);
        validateTextResponse(modelSpec.isSupportImage(), aggregateText(responses));
    }

    /**
     * 验证严格内容块数组模式可以携带完整多轮上下文。
     *
     * <p>支持图片时将图片放在首轮用户消息中，再追加助手历史和纯文本追问，
     * 覆盖 VLM 首轮成功后继续对话的请求格式。</p>
     *
     * @param modelSpec 运行时模型声明
     * @throws BusinessException 响应为空时抛出
     */
    private void verifyMessageContentCompatibility(AgentModelSpec modelSpec) {
        List<ChatResponse> responses = request(
                modelSpec,
                buildMessageContentCompatibilityHistory(modelSpec.isSupportImage()),
                List.of(),
                null);
        validateTextResponse(false, aggregateText(responses));
    }

    /**
     * 使用统一低成本参数执行一次非流式模型请求。
     *
     * @param modelSpec 运行时模型声明
     * @param verificationMessage 验证消息
     * @param tools 工具 Schema
     * @param toolChoice 工具选择策略
     * @return 模型响应片段
     */
    private List<ChatResponse> request(AgentModelSpec modelSpec,
                                       AgentMessage verificationMessage,
                                       List<ToolSchema> tools,
                                       ToolChoice toolChoice) {
        return request(modelSpec, verificationMessage, tools, toolChoice, MAX_TOKENS);
    }

    /**
     * 使用指定输出预算执行一次非流式模型请求。
     *
     * @param modelSpec 运行时模型声明
     * @param verificationMessage 验证消息
     * @param tools 工具 Schema
     * @param toolChoice 工具选择策略
     * @param maxTokens 最大输出 Token 数
     * @return 模型响应片段
     */
    private List<ChatResponse> request(AgentModelSpec modelSpec,
                                       AgentMessage verificationMessage,
                                       List<ToolSchema> tools,
                                       ToolChoice toolChoice,
                                       int maxTokens) {
        List<Msg> messages = List.of(
                messageAdapter.toMsg(AgentMessage.text(
                        AgentMessageRole.SYSTEM, VERIFICATION_SYSTEM_PROMPT)),
                messageAdapter.toMsg(verificationMessage));
        return request(modelSpec, messages, tools, toolChoice, maxTokens);
    }

    /**
     * 使用统一低成本参数执行一次指定上下文的非流式模型请求。
     *
     * @param modelSpec 运行时模型声明
     * @param messages 完整上下文消息
     * @param tools 工具 Schema
     * @param toolChoice 工具选择策略
     * @return 模型响应片段
     */
    private List<ChatResponse> request(AgentModelSpec modelSpec,
                                       List<Msg> messages,
                                       List<ToolSchema> tools,
                                       ToolChoice toolChoice) {
        return request(modelSpec, messages, tools, toolChoice, MAX_TOKENS);
    }

    /**
     * 使用指定输出预算执行一次指定上下文的非流式模型请求。
     *
     * @param modelSpec 运行时模型声明
     * @param messages 完整上下文消息
     * @param tools 工具 Schema
     * @param toolChoice 工具选择策略
     * @param maxTokens 最大输出 Token 数
     * @return 模型响应片段
     */
    private List<ChatResponse> request(AgentModelSpec modelSpec,
                                       List<Msg> messages,
                                       List<ToolSchema> tools,
                                       ToolChoice toolChoice,
                                       int maxTokens) {
        AgentGenerationOptions generationOptions = new AgentGenerationOptions();
        generationOptions.setStream(false);
        generationOptions.setThinkingEnabled(false);
        disableOpenAiCompatibleThinking(modelSpec, generationOptions);
        generationOptions.setMaxTokens(maxTokens);
        io.agentscope.core.model.Model agentScopeModel = modelFactory.create(modelSpec, generationOptions);

        GenerateOptions.Builder requestBuilder = GenerateOptions.builder()
                .stream(false)
                .maxTokens(maxTokens)
                .executionConfig(ExecutionConfig.builder()
                        .timeout(phaseTimeout)
                        .maxAttempts(1)
                        .build());
        if (toolChoice != null) {
            requestBuilder.toolChoice(toolChoice);
        }
        return agentScopeModel
                .stream(messages, tools, requestBuilder.build())
                .timeout(phaseTimeout)
                .collectList()
                .block(phaseTimeout.plusSeconds(1));
    }

    /**
     * 构造包含首轮用户消息、助手历史和文本追问的验证上下文。
     *
     * @param supportImage 是否在首轮用户消息中附加验证图片
     * @return AgentScope 多轮消息
     */
    private List<Msg> buildMessageContentCompatibilityHistory(boolean supportImage) {
        AgentMessage firstUserMessage = buildVerificationMessage(
                supportImage ? "请阅读图片，等待下一条消息。" : "这是第一轮消息。",
                supportImage);
        return List.of(
                messageAdapter.toMsg(AgentMessage.text(
                        AgentMessageRole.SYSTEM, VERIFICATION_SYSTEM_PROMPT)),
                messageAdapter.toMsg(firstUserMessage),
                messageAdapter.toMsg(AgentMessage.text(
                        AgentMessageRole.ASSISTANT, "已收到。")),
                messageAdapter.toMsg(AgentMessage.text(
                        AgentMessageRole.USER, "请直接回复“你好”，不要补充其他内容。")));
    }

    /**
     * 为支持该扩展字段的 OpenAI-compatible 服务显式关闭思考。
     *
     * <p>AgentScope 的通用 OpenAI Formatter 不会读取中立的
     * {@code thinkingEnabled} 字段，需通过扩展请求体传递；原生 OpenAI、
     * DeepSeek 等服务不接收该非标准字段，因此仅对已知兼容入口设置。</p>
     *
     * @param modelSpec 运行时模型声明
     * @param generationOptions 验证生成参数
     */
    private void disableOpenAiCompatibleThinking(
            AgentModelSpec modelSpec,
            AgentGenerationOptions generationOptions) {
        AgentModelProviderType providerType = modelSpec.getProviderType();
        if (providerType == AgentModelProviderType.OPENAI_COMPATIBLE
                || providerType == AgentModelProviderType.CUSTOM
                || providerType == AgentModelProviderType.SILICONFLOW) {
            generationOptions.getAdditionalBodyParams().put("enable_thinking", false);
        }
        Object sourceProviderType = modelSpec.getMetadata().get("sourceProviderType");
        if (usesChatTemplateThinkingControl(sourceProviderType)) {
            // vLLM 与 SGLang 通过聊天模板参数控制 Qwen 思考模式。
            generationOptions.getAdditionalBodyParams().put(
                    "chat_template_kwargs",
                    Map.of("enable_thinking", false));
        }
    }

    /**
     * 判断供应商是否通过聊天模板参数控制思考模式。
     *
     * @param sourceProviderType 模型配置中的原始供应商类型
     * @return GPUStack 或自部署 vLLM/SGLang 入口返回 true
     */
    private boolean usesChatTemplateThinkingControl(Object sourceProviderType) {
        if (sourceProviderType == null) {
            return false;
        }
        String normalizedProviderType = String.valueOf(sourceProviderType)
                .trim()
                .toLowerCase(Locale.ROOT)
                .replace('_', '-');
        return "gpustack".equals(normalizedProviderType)
                || "self-hosted".equals(normalizedProviderType)
                || "vllm".equals(normalizedProviderType)
                || "sglang".equals(normalizedProviderType);
    }

    /**
     * 创建同时验证连接、视觉和工具调用的消息。
     *
     * @param supportImage 是否验证图片理解能力
     * @param nonce 本次探测随机值
     * @return 工具能力探测消息
     */
    private AgentMessage buildProbeMessage(boolean supportImage, String nonce) {
        String prompt = supportImage
                ? "请调用 " + PROBE_TOOL_NAME + " 工具，将 nonce 设置为“" + nonce
                + "”，并将图片中的验证码设置为 imageCode。不要直接回答。"
                : "请调用 " + PROBE_TOOL_NAME + " 工具，并将 nonce 设置为“" + nonce
                + "”。不要直接回答。";
        return buildVerificationMessage(prompt, supportImage);
    }

    /**
     * 创建不带工具的兼容性兜底消息。
     *
     * @param supportImage 是否验证图片理解能力
     * @return 普通连接验证消息
     */
    private AgentMessage buildPlainVerificationMessage(boolean supportImage) {
        String prompt = supportImage
                ? "请直接输出图片中的内容，不要补充其他内容。"
                : "请直接回复“你好”，不要补充其他内容。";
        return buildVerificationMessage(prompt, supportImage);
    }

    /**
     * 创建文字或 VLM 验证消息。
     *
     * @param prompt 验证提示词
     * @param supportImage 是否附加验证图片
     * @return AgentScope 消息
     */
    private AgentMessage buildVerificationMessage(String prompt, boolean supportImage) {
        if (!supportImage) {
            return AgentMessage.text(AgentMessageRole.USER, prompt);
        }
        List<AgentContentBlock> blocks = new ArrayList<>();
        blocks.add(new AgentTextBlock(prompt));
        AgentMediaBlock image = new AgentMediaBlock("image");
        image.setMimeType("image/png");
        image.setData(Base64.getEncoder().encodeToString(VlmVerificationImage.pngBytes()));
        blocks.add(image);
        AgentMessage message = new AgentMessage();
        message.setRole(AgentMessageRole.USER);
        message.setContentBlocks(blocks);
        return message;
    }

    /**
     * 创建只声明参数、不绑定执行逻辑的探测工具 Schema。
     *
     * @param supportImage 是否包含图片验证码参数
     * @return 探测工具 Schema
     */
    private ToolSchema buildProbeToolSchema(boolean supportImage) {
        Map<String, Object> properties = new LinkedHashMap<>();
        properties.put(PROBE_NONCE_FIELD, Map.of(
                "type", "string",
                "description", "原样返回用户提供的 nonce"));
        List<String> required = new ArrayList<>();
        required.add(PROBE_NONCE_FIELD);
        if (supportImage) {
            properties.put(PROBE_IMAGE_FIELD, Map.of(
                    "type", "string",
                    "description", "图片中的验证码"));
            required.add(PROBE_IMAGE_FIELD);
        }
        return ToolSchema.builder()
                .name(PROBE_TOOL_NAME)
                .description("验证模型是否能生成结构化工具调用")
                .parameters(Map.of(
                        "type", "object",
                        "properties", properties,
                        "required", required))
                .strict(false)
                .build();
    }

    /**
     * 判断响应是否包含参数正确的探测工具调用。
     *
     * @param responses AgentScope 响应片段
     * @param nonce 本次探测随机值
     * @param supportImage 是否同时验证图片
     * @return 工具名和参数均正确返回 true
     */
    private boolean hasValidProbeToolCall(List<ChatResponse> responses,
                                          String nonce,
                                          boolean supportImage) {
        if (responses == null) {
            return false;
        }
        for (ChatResponse response : responses) {
            if (response == null || response.getContent() == null) {
                continue;
            }
            for (ContentBlock block : response.getContent()) {
                if (!(block instanceof ToolUseBlock toolUse)
                        || !PROBE_TOOL_NAME.equals(toolUse.getName())
                        || toolUse.getInput() == null
                        || !nonce.equals(String.valueOf(toolUse.getInput().get(PROBE_NONCE_FIELD)))) {
                    continue;
                }
                if (!supportImage || VlmVerificationImage.VERIFICATION_CODE.equals(
                        normalizeVerificationText(String.valueOf(
                                toolUse.getInput().get(PROBE_IMAGE_FIELD))))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 判断响应是否因为达到输出 Token 上限而结束。
     *
     * @param responses AgentScope 响应片段
     * @return 任一响应的结束原因为 length 时返回 true
     */
    private boolean hasLengthLimitedResponse(List<ChatResponse> responses) {
        if (responses == null) {
            return false;
        }
        for (ChatResponse response : responses) {
            if (response != null && "length".equalsIgnoreCase(response.getFinishReason())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 校验探测响应是否足以证明连接和视觉能力。
     *
     * @param supportImage 是否验证图片理解能力
     * @param responses AgentScope 响应片段
     * @param validToolCall 是否包含正确工具调用
     * @throws BusinessException 响应为空或图片识别错误时抛出
     */
    private void validateProbeResponse(boolean supportImage,
                                       List<ChatResponse> responses,
                                       boolean validToolCall) {
        if (validToolCall) {
            return;
        }
        String responseText = aggregateText(responses);
        if (responseText != null && !responseText.isBlank()) {
            validateTextResponse(supportImage, responseText);
            return;
        }
        if (hasAnyContent(responses) && !supportImage) {
            return;
        }
        throw new BusinessException("模型未返回有效内容");
    }

    /**
     * 判断模型是否返回任意内容块。
     *
     * @param responses AgentScope 响应片段
     * @return 存在内容块返回 true
     */
    private boolean hasAnyContent(List<ChatResponse> responses) {
        if (responses == null) {
            return false;
        }
        return responses.stream()
                .filter(response -> response != null && response.getContent() != null)
                .anyMatch(response -> !response.getContent().isEmpty());
    }

    /**
     * 聚合流式响应中的全部文本增量。
     *
     * @param responses AgentScope 响应片段
     * @return 聚合后的文本
     */
    private String aggregateText(List<ChatResponse> responses) {
        StringBuilder result = new StringBuilder();
        if (responses == null) {
            return result.toString();
        }
        for (ChatResponse response : responses) {
            if (response == null || response.getContent() == null) {
                continue;
            }
            for (ContentBlock block : response.getContent()) {
                if (block instanceof TextBlock textBlock && textBlock.getText() != null) {
                    result.append(textBlock.getText());
                }
            }
        }
        return result.toString();
    }

    /**
     * 校验模型返回内容。
     *
     * @param supportImage 是否执行 VLM 图片识别校验
     * @param responseText 聚合后的响应文本
     * @throws BusinessException 响应为空或图片识别结果不匹配时抛出
     */
    private void validateTextResponse(boolean supportImage, String responseText) {
        if (responseText == null || responseText.isBlank()) {
            throw new BusinessException("模型未返回有效内容");
        }
        if (supportImage
                && !normalizeVerificationText(responseText)
                .contains(VlmVerificationImage.VERIFICATION_CODE)) {
            LOG.warn("VLM verification response did not contain expected code, responseSummary={}",
                    responseSummary(responseText));
            throw new BusinessException("多模态校验未通过，模型未正确识别验证图片");
        }
    }

    /**
     * 归一化 VLM 对固定验证码的常见排版输出。
     *
     * <p>模型可能将连续数字输出为 {@code 5 8 3 9} 或 {@code 5,8,3,9}，
     * 这不影响图片识别结论，因此移除非字母数字字符后再校验。</p>
     *
     * @param responseText 模型验证回复
     * @return 仅保留字母与数字的响应文本
     */
    private String normalizeVerificationText(String responseText) {
        return responseText == null
                ? ""
                : responseText.replaceAll("[^\\p{L}\\p{N}]", "");
    }

    /**
     * 生成固定验证回复的安全日志摘要。
     *
     * @param responseText 模型验证回复
     * @return 移除控制字符且最长 160 字符的摘要
     */
    private String responseSummary(String responseText) {
        String normalized = responseText == null
                ? ""
                : responseText.replaceAll("[\\p{Cntrl}]", " ").trim();
        return normalized.length() <= 160
                ? normalized
                : normalized.substring(0, 160) + "...";
    }

    /**
     * 判断异常是否明确来自工具或工具选择参数不兼容。
     *
     * @param exception 模型调用异常
     * @return 工具参数被拒绝返回 true
     */
    private boolean isToolCapabilityRejection(Throwable exception) {
        Throwable current = exception;
        while (current != null) {
            String message = current.getMessage();
            if (message != null) {
                String normalized = message.toLowerCase(Locale.ROOT);
                if (normalized.contains("tool_choice")
                        || normalized.contains("tool choice")
                        || normalized.contains("tool_call")
                        || normalized.contains("tool call")
                        || normalized.contains("tools parameter")
                        || normalized.contains("function calling")
                        || (normalized.contains("tools")
                                && (normalized.contains("unsupported")
                                || normalized.contains("not support")
                                || normalized.contains("invalid")))) {
                    return true;
                }
            }
            current = current.getCause();
        }
        return false;
    }
}
