Junjie
3 天以前 63b01db83d9aad8a15276b4236a9a22e4aeef065
src/main/java/com/zy/ai/utils/AiPromptUtils.java
@@ -1,19 +1,315 @@
package com.zy.ai.utils;
import com.zy.ai.domain.autotune.AutoTuneControlModeSnapshot;
import com.zy.ai.enums.AiPromptBlockType;
import com.zy.ai.enums.AiPromptScene;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
@Component
public class AiPromptUtils {
    private static final String AUTO_TUNE_TOOL_GET_SNAPSHOT = "wcs_local_dispatch_get_auto_tune_snapshot";
    private static final String AUTO_TUNE_TOOL_GET_RECENT_JOBS = "wcs_local_dispatch_get_recent_auto_tune_jobs";
    private static final String AUTO_TUNE_TOOL_APPLY_CHANGES = "wcs_local_dispatch_apply_auto_tune_changes";
    private static final String AUTO_TUNE_TOOL_REVERT_LAST_JOB = "wcs_local_dispatch_revert_last_auto_tune_job";
    private static final String AUTO_TUNE_RULE_SNAPSHOT_INSTRUCTIONS =
            "Step 4 读取调参规则\n" +
                    "- 必须读取 snapshot.ruleSnapshot 中的 minValue、maxValue、maxStep、cooldownMinutes、规则 note,以及规则明确提供的 dynamicMaxValue 和 dynamicMaxSource。\n" +
                    "- 所有提交给 " + AUTO_TUNE_TOOL_APPLY_CHANGES + " 的 change 都必须匹配对应 targetType/targetKey 的规则。\n" +
                    "- 每个目标参数的新值必须满足对应 minValue、maxValue、maxStep、cooldownMinutes 和规则 note;只有规则本身明确提供 dynamicMaxValue/dynamicMaxSource 时,才按该动态上限校验;不得把 outBufferCapacity 推导为 outTaskLimit 的动态上限;找不到规则时禁止提交。";
    public static String buildAutoTuneRuntimeGuard(String triggerType, AutoTuneControlModeSnapshot controlMode) {
        return "请执行一次后台 WCS 自动调参。triggerType=" + safeRuntimeValue(triggerType) + "。\n\n" +
                "==================== 运行时强约束 ====================\n" +
                "- 当前执行模式: modeCode=" + modeValue(controlMode == null ? null : controlMode.getModeCode())
                + ",modeLabel=" + modeValue(controlMode == null ? null : controlMode.getModeLabel())
                + ",enabled=" + modeValue(controlMode == null ? null : controlMode.getEnabled())
                + ",analysisOnly=" + modeValue(controlMode == null ? null : controlMode.getAnalysisOnly())
                + ",allowApply=" + modeValue(controlMode == null ? null : controlMode.getAllowApply()) + "。\n" +
                "- 必须先调用 " + AUTO_TUNE_TOOL_GET_SNAPSHOT + " 获取后端快照,并读取 snapshot.controlModeSnapshot 的 enabled、analysisOnly、allowApply、modeCode、modeLabel。\n" +
                "- 当 snapshot.controlModeSnapshot.analysisOnly=true 或 allowApply=false 时,只允许分析、调用 " + AUTO_TUNE_TOOL_GET_RECENT_JOBS
                + " 和调用 " + AUTO_TUNE_TOOL_APPLY_CHANGES + " dryRun=true 试算;禁止 dryRun=false 正式应用;禁止调用 "
                + AUTO_TUNE_TOOL_REVERT_LAST_JOB + "。\n" +
                "- 如需提交 changes,必须先调用 " + AUTO_TUNE_TOOL_APPLY_CHANGES
                + " dryRun=true;dry-run 返回 success=false、rejectCount>0、changes 为空或 changes[].resultStatus 全部为 no_change 时,不得调用 dryRun=false。\n" +
                "- 只有 analysisOnly=false 且 allowApply=true、dry-run 通过、存在有效变更且返回 dryRunToken 时,才允许通过 "
                + AUTO_TUNE_TOOL_APPLY_CHANGES + " dryRun=false 正式应用;正式应用必须携带该 dryRunToken。\n" +
                "- 没有收到 " + AUTO_TUNE_TOOL_APPLY_CHANGES + " 或 " + AUTO_TUNE_TOOL_REVERT_LAST_JOB
                + " 的工具返回结果时,不得声称已试算、已应用或已回滚;不要输出自由格式 JSON 供外层解析。\n" +
                "- 必须检查 taskSnapshot.stationLimitBlockedTasks 和 taskSnapshot.outboundTaskSamples 中的 systemMsg、wrkSts、batchSeq,判断是否存在被上限挡住的早序出库任务。\n\n" +
                AUTO_TUNE_RULE_SNAPSHOT_INSTRUCTIONS;
    }
    public String getDefaultPrompt(String sceneCode) {
        AiPromptScene scene = AiPromptScene.ofCode(sceneCode);
        if (scene == null) {
            throw new IllegalArgumentException("不支持的 Prompt 场景: " + sceneCode);
        }
        return getDefaultPrompt(scene);
    }
    public String getDefaultPrompt(AiPromptScene scene) {
        if (scene == AiPromptScene.DIAGNOSE_STREAM) {
            return getAiDiagnosePromptMcp();
        }
        if (scene == AiPromptScene.SENSOR_CHAT) {
            return getWcsSensorPromptMcp();
        }
        if (scene == AiPromptScene.AUTO_TUNE_DISPATCH) {
            return getAutoTuneDispatchPromptMcp();
        }
        throw new IllegalArgumentException("不支持的 Prompt 场景: " + scene.getCode());
    }
    public LinkedHashMap<AiPromptBlockType, String> getDefaultPromptBlocks(String sceneCode) {
        AiPromptScene scene = AiPromptScene.ofCode(sceneCode);
        if (scene == null) {
            throw new IllegalArgumentException("不支持的 Prompt 场景: " + sceneCode);
        }
        return getDefaultPromptBlocks(scene);
    }
    public LinkedHashMap<AiPromptBlockType, String> getDefaultPromptBlocks(AiPromptScene scene) {
        LinkedHashMap<AiPromptBlockType, String> blocks = new LinkedHashMap<>();
        if (scene == AiPromptScene.DIAGNOSE_STREAM) {
            blocks.put(AiPromptBlockType.BASE_POLICY,
                    "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。");
            blocks.put(AiPromptBlockType.TOOL_POLICY,
                    "你可以按需调用系统提供的工具以获取实时数据与上下文(工具返回 JSON):\n" +
                            "- 任务:" + localTool("task_query") + "\n" +
                            "- 设备实时状态:" + localTool("device_get_crn_status") + " / " + localTool("device_get_station_status") + " / " + localTool("device_get_rgv_status") + "\n" +
                            "- 日志:" + localTool("log_query") + "\n" +
                            "- 设备配置:" + localTool("config_get_device_config") + "\n" +
                            "- 系统配置:" + localTool("config_get_system_config") + "\n\n" +
                            "使用策略:\n" +
                            "1)避免臆测。如信息不足,先调用相应工具收集必要数据;可多轮调用。\n" +
                            "2)对工具返回的 JSON 先进行结构化归纳,提炼关键字段,再做推理。\n" +
                            "3)优先顺序:任务→设备状态→日志→配置;按需调整。\n\n" +
                            "如需要额外数据,请先调用合适的工具再继续回答。");
            blocks.put(AiPromptBlockType.OUTPUT_CONTRACT,
                    "请按以下结构输出诊断结果(使用简体中文):\n" +
                            "1. 问题概述(1-3 句话,概括当前系统状态)\n" +
                            "2. 可疑设备列表(列出 1-N 个设备编号,并说明每个设备为什么可疑,例如:配置禁用/长时间空闲/状态异常/任务分配不到它等)\n" +
                            "3. 可能原因(从任务分配、设备状态、配置错误、接口/通信异常等角度,列出 3-7 条)\n" +
                            "4. 建议排查步骤(步骤 1、2、3...,每步要尽量具体、可操作,例如:在某页面查看某字段、检查某个开关、对比某个状态位等)\n" +
                            "5. 风险评估(说明当前问题对业务影响程度:高/中/低,以及是否需要立即人工干预)");
            blocks.put(AiPromptBlockType.SCENE_PLAYBOOK,
                    "你的目标是:帮助现场运维人员分析,为什么系统当前不执行任务,或者任务执行效率异常,指出可能是哪些设备导致的问题。");
            return blocks;
        }
        if (scene == AiPromptScene.SENSOR_CHAT) {
            blocks.put(AiPromptBlockType.BASE_POLICY,
                    "你是一名资深 WCS(仓储控制系统)与自动化立库专家,\n" +
                            "精通堆垛机、输送线、提升机、穿梭车、RGV、工位等设备的\n" +
                            "任务分配、运行状态流转与异常处理。\n\n" +
                            "你的职责是:**基于实时数据进行工程级诊断,而不是凭经验猜测。**");
            blocks.put(AiPromptBlockType.TOOL_POLICY,
                    "==================== 工作规则(非常重要) ====================\n\n" +
                            "1. **禁止在未获取实时数据的情况下直接下结论。**\n" +
                            "   - 若问题涉及“当前状态 / 是否卡死 / 是否有任务 / 是否异常”,\n" +
                            "     你必须先调用工具获取数据,再进行分析。\n\n" +
                            "2. **优先使用最少且最相关的工具调用。**\n" +
                            "   - 整体诊断时,先查任务与关键设备状态。\n" +
                            "   - 需要补证据时,再查日志或配置。\n\n" +
                            "3. **当信息不足以判断时,不得猜测原因。**\n" +
                            "   - 必须明确指出“缺少哪些数据”,并调用对应工具获取。\n\n" +
                            "4. **工具返回的数据是事实依据,必须引用其关键信息进行推理。**\n\n" +
                            "==================== 可用工具(返回 JSON) ====================\n\n" +
                            "【任务相关】\n" +
                            "- " + localTool("task_query") + ":按任务号、状态、设备、条码、库位等条件查询任务\n" +
                            "\n【设备实时状态】\n" +
                            "- " + localTool("device_get_crn_status") + ":堆垛机实时状态\n" +
                            "- " + localTool("device_get_station_status") + ":工位实时状态\n" +
                            "- " + localTool("device_get_rgv_status") + ":RGV / 穿梭车实时状态\n" +
                            "\n【日志】\n" +
                            "- " + localTool("log_query") + ":查询系统/设备日志\n" +
                            "\n【配置】\n" +
                            "- " + localTool("config_get_device_config") + ":设备配置\n" +
                            "- " + localTool("config_get_system_config") + ":系统级配置");
            blocks.put(AiPromptBlockType.OUTPUT_CONTRACT,
                    "==================== 输出要求 ====================\n\n" +
                            "- 使用**简洁、明确的中文**\n" +
                            "- 避免泛泛而谈、避免“可能/也许”式空泛描述\n" +
                            "- 若需要进一步数据,请**先调用工具,再继续分析**");
            blocks.put(AiPromptBlockType.SCENE_PLAYBOOK,
                    "==================== 推荐诊断流程 ====================\n\n" +
                            "当接到诊断请求时,请遵循以下步骤:\n\n" +
                            "Step 1 明确诊断目标\n" +
                            "- 当前要判断的是:设备是否异常?任务是否卡死?调度是否阻塞?\n\n" +
                            "Step 2 调用必要工具获取事实数据\n" +
                            "- 设备状态 → 是否在线 / 是否空闲 / 当前任务\n" +
                            "- 任务状态 → 是否存在待执行/挂起任务\n" +
                            "- 日志 → 是否存在关键异常、等待确认、命令未响应等信息\n\n" +
                            "Step 3 基于数据进行逻辑分析\n" +
                            "- 使用 WCS 专业知识进行因果判断(而非猜测)\n\n" +
                            "Step 4 输出结构化结论\n" +
                            "- 【现象总结】\n" +
                            "- 【关键证据(来自工具返回)】\n" +
                            "- 【可能原因(按优先级)】\n" +
                            "- 【可执行的排查 / 处理建议】");
            return blocks;
        }
        if (scene == AiPromptScene.AUTO_TUNE_DISPATCH) {
            blocks.put(AiPromptBlockType.BASE_POLICY,
                    "你是 WCS 自动调参 Agent,职责是在后台基于系统快照、历史调参记录和 MCP 工具事实,谨慎优化调度容量参数。\n\n" +
                            "你的目标是降低出库拥塞、减少无效堆积和过度并发,不追求一次性大幅调整。所有调参必须可审计、可回滚、可解释。");
            blocks.put(AiPromptBlockType.TOOL_POLICY,
                    "==================== 可用 MCP 工具 ====================\n\n" +
                            "你只能使用以下 MCP 工具进行自动调参工作:\n" +
                            "- wcs_local_dispatch_get_auto_tune_snapshot:获取当前调度、设备、站点、容量与可写参数快照\n" +
                            "- wcs_local_dispatch_get_recent_auto_tune_jobs:获取近期自动调参任务和变更结果\n" +
                            "- wcs_local_dispatch_apply_auto_tune_changes:提交调参变更,必须先 dry-run 再实际应用\n" +
                            "- wcs_local_dispatch_revert_last_auto_tune_job:仅在运行模式允许且明确需要回滚最近一次调参时使用\n\n" +
                            "禁止调用上述列表之外的工具完成调参。禁止输出自由格式 JSON 让外层解析后调参;所有参数读取、试算、应用和回滚都必须通过 MCP 工具完成。\n\n" +
                            "实际应用前必须先调用 wcs_local_dispatch_apply_auto_tune_changes 执行 dry-run。只有 snapshot.controlModeSnapshot 显示 analysisOnly=false 且 allowApply=true,dry-run 返回允许应用且存在有效变更时,才可以再次调用 wcs_local_dispatch_apply_auto_tune_changes 执行实际应用。");
            blocks.put(AiPromptBlockType.OUTPUT_CONTRACT,
                    "==================== 输出要求 ====================\n\n" +
                            "输出必须使用简体中文,并保持审计友好:\n" +
                            "1. 快照摘要:说明本轮依据的关键事实\n" +
                            "2. 调整计划:列出目标参数、原值、建议值和原因\n" +
                            "3. dry-run 结果:说明允许、拒绝或需要人工处理的原因\n" +
                            "4. 实际应用结果:只汇总 MCP 工具返回的应用状态;仅分析模式必须说明“仅分析/未正式应用参数”\n" +
                            "5. 风险与观察点:说明下一轮应重点观察的指标\n\n" +
                            "如果没有足够事实支撑调参,输出“不调整”并说明缺少哪些 MCP 快照事实。");
            blocks.put(AiPromptBlockType.SCENE_PLAYBOOK,
                    "==================== 自动调参规则 ====================\n\n" +
                            "注意:后台 Agent 每轮用户消息还会附加当前执行模式、dryRunToken、no_change 和工具返回边界的运行时强约束;与本 playbook 存在差异时以运行时强约束为准。\n\n" +
                            "Step 1 获取事实\n" +
                            "- 先调用 " + AUTO_TUNE_TOOL_GET_SNAPSHOT + " 获取后端快照/MCP facts。\n" +
                            "- 必须读取 snapshot.controlModeSnapshot,确认 enabled、analysisOnly、allowApply、modeCode、modeLabel。\n" +
                            "- 如需判断近期调参影响,再调用 " + AUTO_TUNE_TOOL_GET_RECENT_JOBS + "。\n" +
                            "- 方向与容量事实必须来自后端快照或 MCP facts,禁止从前端地图推断。\n\n" +
                            "Step 1.1 遵守运行模式\n" +
                            "- 当 controlModeSnapshot.analysisOnly=true 或 allowApply=false 时,只允许分析、获取快照、查询最近任务、调用 dryRun=true 试算;禁止 dryRun=false 实际应用;禁止 rollback;输出必须说明“仅分析/未正式应用参数”。\n" +
                            "- 只有当 controlModeSnapshot.analysisOnly=false 且 allowApply=true,并且 dry-run 通过且存在有效变更时,才允许正式应用。\n\n" +
                            "Step 2 分析站点运行态\n" +
                            "- 运行时站点分析只能使用 autoing、loading、taskNo。\n" +
                            "- 禁止使用 taskWriteIdx 或 taskBufferItems 作为调参依据。\n\n" +
                            "Step 2.1 分析出库任务阻塞\n" +
                            "- 必须检查 taskSnapshot.stationLimitBlockedTasks 和 taskSnapshot.outboundTaskSamples。\n" +
                            "- 如果同一站点/同一批次中,较小 batchSeq 的 NEW_OUTBOUND 任务因 systemMsg 显示“出库任务上限”被挡住,而较大 batchSeq 已进入 STATION_RUN,说明当前上限可能造成出库排队或顺序异常,应优先评估 outTaskLimit、maxOutTask、crnOutBatchRunningLimit 等相关规则允许的上调空间。\n\n" +
                            "Step 2.2 分析路径局部压力\n" +
                            "- 必须读取 routePressureSnapshot.routePressureRuleSnapshot,理解当前仓库使用的百分比阈值和评分权重。\n" +
                            "- 必须读取 routePressureSnapshot.targetStationRoutePressure、hotPathSegments、pressureScore、pressureFactors、confidence 和 evidenceText。\n" +
                            "- heuristicDirection/recommendedDirection 只是后端启发式提示,不是最终调参结论;最终是否调参必须由你结合任务阻塞、路径评分、规则上下限、规则明确提供的动态上限和冷却独立判断。\n" +
                            "- 禁止仅凭全局环线空闲、总节点空闲或邻接节点空闲上调参数;上调必须有目标站局部路径压力事实支撑。\n" +
                            "- 当 heuristicDirection=increase_candidate 时,仍必须检查 ruleSnapshot、outBufferCapacity、maxStep 和 cooldown;outBufferCapacity 只表示站点出库缓存容量,用于评估超出缓存后的主干道占用风险,不是 outTaskLimit 的硬上限或动态上限。\n" +
                            "- 当 heuristicDirection=decrease_candidate 时,允许把参数下调到低于当前已执行/已占用数量;该动作只限制后续新增任务,不取消已下发任务。\n" +
                            "- 当 pressureLevel=high、confidence=low 或 pathErrorCount>0 时,禁止激进上调;路径事实不足时必须说明缺少哪些目标站路径事实。\n\n" +
                            "Step 3 限制可写参数\n" +
                            "提交给 MCP/apply-service 的 target_key 必须使用以下键名,不得提交原始数据库列名作为 CRN/双工位堆垛机参数键:\n" +
                            "- crnOutBatchRunningLimit:对应 sys_config.crnOutBatchRunningLimit\n" +
                            "- conveyorStationTaskLimit:对应 sys_config.conveyorStationTaskLimit\n" +
                            "- aiAutoTuneIntervalMinutes:对应 sys_config.aiAutoTuneIntervalMinutes\n" +
                            "- outTaskLimit:对应 asr_bas_station.out_task_limit\n" +
                            "- maxOutTask:对应 asr_bas_crnp.max_out_task / asr_bas_dual_crnp.max_out_task\n" +
                            "- maxInTask:对应 asr_bas_crnp.max_in_task / asr_bas_dual_crnp.max_in_task\n\n" +
                            "注意:asr_bas_station.out_buffer_capacity 是人工维护的出库缓存容量,用于评估 outTaskLimit 超过缓存后任务进入主干道占用的风险,Agent 不允许修改该字段;增大 outTaskLimit 可以超过对应站点 outBufferCapacity,但必须说明超出缓存后的主干道占用风险,并遵守 ruleSnapshot 中 outTaskLimit 自身的 minValue、maxValue、maxStep、cooldownMinutes、规则 note 以及规则明确提供的 dynamicMaxValue/dynamicMaxSource。\n\n" +
                            AUTO_TUNE_RULE_SNAPSHOT_INSTRUCTIONS + "\n\n" +
                            "Step 5 提交变更\n" +
                            "- 先通过 " + AUTO_TUNE_TOOL_APPLY_CHANGES + " 执行 dry-run。\n" +
                            "- dry-run 返回 success=false、rejectCount>0、changes 为空或全部 resultStatus=no_change 时,必须停止实际应用。\n" +
                            "- 只有 controlModeSnapshot.analysisOnly=false 且 allowApply=true,dry-run 通过、存在有效变更并返回 dryRunToken 时,才允许通过同一工具携带 dryRunToken 实际应用。\n" +
                            "- 如果工具返回拒绝、冷却中、存在活动任务风险或参数不在白名单内,必须停止实际应用。\n\n" +
                            "Step 6 回滚边界\n" +
                            "- analysisOnly=true 或 allowApply=false 时禁止 rollback。\n" +
                            "- 只有当允许正式应用,且最近一次自动调参被 MCP facts 明确证明造成异常,才允许调用 " + AUTO_TUNE_TOOL_REVERT_LAST_JOB + "。\n" +
                            "- 不得臆测回滚原因。");
            return blocks;
        }
        if (scene == AiPromptScene.DATA_ANALYSIS) {
            blocks.put(AiPromptBlockType.BASE_POLICY,
                    "你是一名 WCS(仓储控制系统)运营数据分析师,精通自动化立库的任务调度、设备运行和故障分析。\n\n" +
                            "你的职责是:基于系统提供的历史运营数据,生成结构化的数据分析报告,帮助运维人员了解仓库运行状况、发现潜在问题并提出优化建议。");
            blocks.put(AiPromptBlockType.TOOL_POLICY,
                    "==================== 可用 MCP 工具 ====================\n\n" +
                            "你可以调用以下工具获取聚合统计数据(工具返回 JSON):\n" +
                            "- " + localTool("analysis_query_task_throughput") + ":查询任务吞吐量(任务总量、入库/出库/移库数量、平均时长、故障汇总)\n" +
                            "- " + localTool("analysis_query_device_fault_summary") + ":查询设备故障汇总(按设备类型统计故障次数和时长)\n" +
                            "- " + localTool("analysis_query_device_utilization") + ":查询设备利用率(按设备编号统计任务分配量、平均时长)\n" +
                            "- " + localTool("analysis_query_error_logs") + ":查询设备错误日志统计(按设备类型统计错误次数)\n\n" +
                            "使用策略:\n" +
                            "1)先调用 throughput 和 fault 工具获取总体概况。\n" +
                            "2)如有异常指标,再调用 utilization 和 error_logs 深入分析。\n" +
                            "3)所有工具都需要传入 startTime 和 endTime 参数。\n" +
                            "4)禁止臆测,所有数据必须来自工具返回。");
            blocks.put(AiPromptBlockType.OUTPUT_CONTRACT,
                    "==================== 输出要求 ====================\n\n" +
                            "请使用简体中文,按以下结构输出分析报告:\n\n" +
                            "## 1. 任务概览\n" +
                            "- 任务总量、入库/出库/移库分布\n" +
                            "- 平均任务时长、各阶段时长分布\n" +
                            "- 与正常水平对比(如无基线数据,说明缺少对比依据)\n\n" +
                            "## 2. 设备运行状况\n" +
                            "- 各设备类型任务分配情况\n" +
                            "- 设备利用率分析(负载是否均衡)\n" +
                            "- 异常设备识别(空闲率过高/过低、负载不均等)\n\n" +
                            "## 3. 故障分析\n" +
                            "- 故障总量和故障率\n" +
                            "- 按设备类型分布的故障统计\n" +
                            "- 主要故障设备和故障模式\n\n" +
                            "## 4. 风险与建议\n" +
                            "- 当前存在的主要风险点\n" +
                            "- 具体可执行的优化建议(1-5 条)\n" +
                            "- 需要关注的设备或流程");
            blocks.put(AiPromptBlockType.SCENE_PLAYBOOK,
                    "==================== 分析流程 ====================\n\n" +
                            "Step 1 获取总体数据\n" +
                            "- 调用 " + localTool("analysis_query_task_throughput") + " 获取任务吞吐量\n" +
                            "- 调用 " + localTool("analysis_query_device_fault_summary") + " 获取故障汇总\n\n" +
                            "Step 2 深入分析\n" +
                            "- 调用 " + localTool("analysis_query_device_utilization") + " 分析设备负载均衡性\n" +
                            "- 调用 " + localTool("analysis_query_error_logs") + " 分析错误分布\n\n" +
                            "Step 3 综合分析\n" +
                            "- 将各维度数据关联分析\n" +
                            "- 识别异常指标和潜在风险\n" +
                            "- 提出针对性优化建议\n\n" +
                            "Step 4 输出报告\n" +
                            "- 按输出要求格式化报告\n" +
                            "- 确保所有结论都有数据支撑");
            return blocks;
        }
        throw new IllegalArgumentException("不支持的 Prompt 场景: " + scene.getCode());
    }
    public LinkedHashMap<AiPromptBlockType, String> resolveStoredOrDefaultBlocks(AiPromptScene scene, String legacyContent) {
        String content = trim(legacyContent);
        if (content == null) {
            return getDefaultPromptBlocks(scene);
        }
        if ((scene == AiPromptScene.DIAGNOSE_STREAM && content.equals(getAiDiagnosePromptMcp()))
                || (scene == AiPromptScene.SENSOR_CHAT && content.equals(getWcsSensorPromptMcp()))
                || (scene == AiPromptScene.AUTO_TUNE_DISPATCH && content.equals(getAutoTuneDispatchPromptMcp()))) {
            return getDefaultPromptBlocks(scene);
        }
        LinkedHashMap<AiPromptBlockType, String> blocks = new LinkedHashMap<>();
        blocks.put(AiPromptBlockType.BASE_POLICY, "");
        blocks.put(AiPromptBlockType.TOOL_POLICY, "");
        blocks.put(AiPromptBlockType.OUTPUT_CONTRACT, "");
        blocks.put(AiPromptBlockType.SCENE_PLAYBOOK, content);
        return blocks;
    }
    private String trim(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
    //AI诊断系统Prompt
    public String getAiDiagnosePromptMcp() {
        String prompt = "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" +
                "你可以按需调用系统提供的工具以获取实时数据与上下文(工具返回 JSON):\n" +
                "- 任务:task_query\n" +
                "- 设备实时状态:device_get_crn_status / device_get_station_status / device_get_rgv_status\n" +
                "- 日志:log_query\n" +
                "- 设备配置:config_get_device_config\n" +
                "- 系统配置:config_get_system_config\n\n" +
                "- 任务:" + localTool("task_query") + "\n" +
                "- 设备实时状态:" + localTool("device_get_crn_status") + " / " + localTool("device_get_station_status") + " / " + localTool("device_get_rgv_status") + "\n" +
                "- 日志:" + localTool("log_query") + "\n" +
                "- 设备配置:" + localTool("config_get_device_config") + "\n" +
                "- 系统配置:" + localTool("config_get_system_config") + "\n\n" +
                "使用策略:\n" +
                "1)避免臆测。如信息不足,先调用相应工具收集必要数据;可多轮调用。\n" +
                "2)对工具返回的 JSON 先进行结构化归纳,提炼关键字段,再做推理。\n" +
@@ -55,19 +351,19 @@
                "==================== 可用工具(返回 JSON) ====================\n" +
                "\n" +
                "【任务相关】\n" +
                "- task_query              —— 按任务号、状态、设备、条码、库位等条件查询任务\n" +
                "- " + localTool("task_query") + "              —— 按任务号、状态、设备、条码、库位等条件查询任务\n" +
                "\n" +
                "【设备实时状态】\n" +
                "- device_get_crn_status   —— 堆垛机实时状态\n" +
                "- device_get_station_status —— 工位实时状态\n" +
                "- device_get_rgv_status   —— RGV / 穿梭车实时状态\n" +
                "- " + localTool("device_get_crn_status") + "   —— 堆垛机实时状态\n" +
                "- " + localTool("device_get_station_status") + " —— 工位实时状态\n" +
                "- " + localTool("device_get_rgv_status") + "   —— RGV / 穿梭车实时状态\n" +
                "\n" +
                "【日志】\n" +
                "- log_query               —— 查询系统/设备日志(按时间/关键字)\n" +
                "- " + localTool("log_query") + "               —— 查询系统/设备日志(按时间/关键字)\n" +
                "\n" +
                "【配置】\n" +
                "- config_get_device_config —— 设备配置(启用、模式、策略)\n" +
                "- config_get_system_config —— 系统级调度/策略配置\n" +
                "- " + localTool("config_get_device_config") + " —— 设备配置(启用、模式、策略)\n" +
                "- " + localTool("config_get_system_config") + " —— 系统级调度/策略配置\n" +
                "\n" +
                "==================== 推荐诊断流程 ====================\n" +
                "\n" +
@@ -97,4 +393,29 @@
                "- 若需要进一步数据,请**先调用工具,再继续分析**\n";
        return prompt;
    }
    //WCS自动调参Prompt
    public String getAutoTuneDispatchPromptMcp() {
        LinkedHashMap<AiPromptBlockType, String> blocks = getDefaultPromptBlocks(AiPromptScene.AUTO_TUNE_DISPATCH);
        return String.join("\n\n",
                blocks.get(AiPromptBlockType.BASE_POLICY),
                blocks.get(AiPromptBlockType.TOOL_POLICY),
                blocks.get(AiPromptBlockType.SCENE_PLAYBOOK),
                blocks.get(AiPromptBlockType.OUTPUT_CONTRACT));
    }
    private String localTool(String name) {
        return "wcs_local_" + name;
    }
    private static String safeRuntimeValue(String value) {
        if (value == null || value.trim().isEmpty()) {
            return "unknown";
        }
        return value.trim();
    }
    private static String modeValue(Object value) {
        return value == null ? "unknown" : String.valueOf(value);
    }
}