Junjie
2 天以前 d7d7e0edf4d8dc422402be9a1fbb6e535ae3761e
# Agent调参outTaskLimit上限修复V3.0.1.6
2个文件已添加
7个文件已修改
200 ■■■■ 已修改文件
src/main/java/com/zy/ai/domain/autotune/AutoTuneRuleDefinition.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/utils/AiPromptUtils.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/20260428_ai_auto_tune_consolidated.sql 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/20260505_ai_auto_tune_out_buffer_capacity_semantics.sql 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/ai/service/impl/AutoTuneSnapshotServiceImplTest.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/ai/utils/AiPromptUtilsTest.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/domain/autotune/AutoTuneRuleDefinition.java
@@ -7,10 +7,8 @@
public final class AutoTuneRuleDefinition {
    private static final String DEFAULT_RULE_NOTE = "单次调整幅度不能超过 maxStep。";
    private static final String STATION_OUT_TASK_LIMIT_DYNAMIC_MAX_SOURCE =
            "currentParameterSnapshot.stationOutBufferCapacities[targetId]";
    private static final String STATION_OUT_TASK_LIMIT_NOTE =
            "单次调整幅度不能超过 maxStep;增大时不得超过对应站点 outBufferCapacity。";
            "单次调整幅度不能超过 maxStep;outBufferCapacity 代表出库站缓存位置数量,超过该容量可能使任务出现在主干道,仅作为风险参考,不是合法性上限。";
    private static final Map<String, Rule> RULE_MAP = buildRuleMap();
@@ -49,7 +47,6 @@
                .minValue(0)
                .maxStep(3)
                .cooldownMinutes(10)
                .dynamicMaxSource(STATION_OUT_TASK_LIMIT_DYNAMIC_MAX_SOURCE)
                .note(STATION_OUT_TASK_LIMIT_NOTE));
        add(ruleMap, rule(AutoTuneTargetType.CRN, "maxOutTask")
                .minValue(0)
src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
@@ -338,11 +338,14 @@
        validatedChange.setOldValue(currentValue.getOldValue());
        Integer maxValue = resolveMaxValue(validatedChange, rule, requestedValue);
        if (maxValue == null) {
            return validatedChange.reject("站点 " + validatedChange.getTargetId()
                    + " 缺少 outBufferCapacity,无法证明 outTaskLimit 上限");
        if (requestedValue < rule.getMinValue()) {
            if (maxValue != null) {
                return validatedChange.reject(validatedChange.getTargetKey() + " 必须在 "
                        + rule.getMinValue() + "~" + maxValue + " 范围内");
            }
            return validatedChange.reject(validatedChange.getTargetKey() + " 必须不小于 " + rule.getMinValue());
        }
        if (requestedValue < rule.getMinValue() || requestedValue > maxValue) {
        if (maxValue != null && requestedValue > maxValue) {
            return validatedChange.reject(validatedChange.getTargetKey() + " 必须在 "
                    + rule.getMinValue() + "~" + maxValue + " 范围内");
        }
src/main/java/com/zy/ai/utils/AiPromptUtils.java
@@ -17,9 +17,9 @@
    private static final String AUTO_TUNE_RULE_SNAPSHOT_INSTRUCTIONS =
            "Step 4 读取调参规则\n" +
                    "- 必须读取 snapshot.ruleSnapshot 中的 minValue、maxValue、maxStep、cooldownMinutes、dynamicMaxValue 和 dynamicMaxSource。\n" +
                    "- 必须读取 snapshot.ruleSnapshot 中的 minValue、maxValue、maxStep、cooldownMinutes、规则 note,以及规则明确提供的 dynamicMaxValue 和 dynamicMaxSource。\n" +
                    "- 所有提交给 " + AUTO_TUNE_TOOL_APPLY_CHANGES + " 的 change 都必须匹配对应 targetType/targetKey 的规则。\n" +
                    "- 每个目标参数的新值必须满足对应 minValue、maxValue 或 dynamicMaxValue、maxStep、cooldownMinutes 和规则 note;找不到规则或无法证明动态上限时禁止提交。";
                    "- 每个目标参数的新值必须满足对应 minValue、maxValue、maxStep、cooldownMinutes 和规则 note;只有规则本身明确提供 dynamicMaxValue/dynamicMaxSource 时,才按该动态上限校验;不得把 outBufferCapacity 推导为 outTaskLimit 的动态上限;找不到规则时禁止提交。";
    public static String buildAutoTuneRuntimeGuard(String triggerType, AutoTuneControlModeSnapshot controlMode) {
        return "请执行一次后台 WCS 自动调参。triggerType=" + safeRuntimeValue(triggerType) + "。\n\n" +
@@ -194,9 +194,9 @@
                            "Step 2.2 分析路径局部压力\n" +
                            "- 必须读取 routePressureSnapshot.routePressureRuleSnapshot,理解当前仓库使用的百分比阈值和评分权重。\n" +
                            "- 必须读取 routePressureSnapshot.targetStationRoutePressure、hotPathSegments、pressureScore、pressureFactors、confidence 和 evidenceText。\n" +
                            "- heuristicDirection/recommendedDirection 只是后端启发式提示,不是最终调参结论;最终是否调参必须由你结合任务阻塞、路径评分、规则步长、动态上限和冷却独立判断。\n" +
                            "- heuristicDirection/recommendedDirection 只是后端启发式提示,不是最终调参结论;最终是否调参必须由你结合任务阻塞、路径评分、规则上下限、规则明确提供的动态上限和冷却独立判断。\n" +
                            "- 禁止仅凭全局环线空闲、总节点空闲或邻接节点空闲上调参数;上调必须有目标站局部路径压力事实支撑。\n" +
                            "- 当 heuristicDirection=increase_candidate 时,仍必须检查 ruleSnapshot、outBufferCapacity、maxStep 和 cooldown,且不得突破动态上限。\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" +
@@ -207,7 +207,7 @@
                            "- 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。\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" +
src/main/resources/application.yml
@@ -1,6 +1,6 @@
# 系统版本信息
app:
  version: 3.0.1.5
  version: 3.0.1.6
  version-type: prd  # prd 或 dev
  i18n:
    default-locale: zh-CN
src/main/resources/sql/20260428_ai_auto_tune_consolidated.sql
@@ -200,7 +200,7 @@
SET @add_out_buffer_capacity_sql := IF(
  @out_buffer_capacity_exists = 0,
  'ALTER TABLE asr_bas_station ADD COLUMN out_buffer_capacity INT NULL COMMENT ''出库缓存容量,用于证明 out_task_limit 上限'' AFTER out_task_limit',
  'ALTER TABLE asr_bas_station ADD COLUMN out_buffer_capacity INT NULL COMMENT ''出库缓存容量,用于评估 out_task_limit 超过缓存后的主干道占用风险'' AFTER out_task_limit',
  'SELECT ''column out_buffer_capacity already exists'' '
);
PREPARE stmt_out_buffer_capacity FROM @add_out_buffer_capacity_sql;
src/main/resources/sql/20260505_ai_auto_tune_out_buffer_capacity_semantics.sql
New file
@@ -0,0 +1,95 @@
-- Clarify out_buffer_capacity semantics for AI auto-tune.
-- out_buffer_capacity is an outbound buffer capacity and risk reference, not an out_task_limit cap.
SET @current_db := DATABASE();
SET @out_buffer_capacity_exists := (
  SELECT COUNT(1)
  FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = @current_db
    AND TABLE_NAME = 'asr_bas_station'
    AND COLUMN_NAME = 'out_buffer_capacity'
);
SET @alter_out_buffer_capacity_comment_sql := IF(
  @out_buffer_capacity_exists > 0,
  'ALTER TABLE asr_bas_station MODIFY COLUMN out_buffer_capacity INT NULL COMMENT ''出库缓存容量,用于评估 out_task_limit 超过缓存后的主干道占用风险''',
  'SELECT ''column asr_bas_station.out_buffer_capacity missing, skip comment update'' '
);
PREPARE stmt_out_buffer_capacity_comment FROM @alter_out_buffer_capacity_comment_sql;
EXECUTE stmt_out_buffer_capacity_comment;
DEALLOCATE PREPARE stmt_out_buffer_capacity_comment;
SET @prompt_template_table_exists := (
  SELECT COUNT(1)
  FROM information_schema.TABLES
  WHERE TABLE_SCHEMA = @current_db
    AND TABLE_NAME = 'sys_ai_prompt_template'
);
SET @prompt_block_table_exists := (
  SELECT COUNT(1)
  FROM information_schema.TABLES
  WHERE TABLE_SCHEMA = @current_db
    AND TABLE_NAME = 'sys_ai_prompt_block'
);
SET @old_rule_read_line := '- 必须读取 snapshot.ruleSnapshot 中的 minValue、maxValue、maxStep、cooldownMinutes、dynamicMaxValue 和 dynamicMaxSource。';
SET @new_rule_read_line := '- 必须读取 snapshot.ruleSnapshot 中的 minValue、maxValue、maxStep、cooldownMinutes、规则 note,以及规则明确提供的 dynamicMaxValue 和 dynamicMaxSource。';
SET @old_rule_limit_line := CONCAT(
  '- 每个目标参数的新值必须满足对应 minValue、maxValue 或 dynamicMaxValue、maxStep、cooldownMinutes 和规则 note;找不到规则或无法证',
  '明动态上限时禁止提交。'
);
SET @new_rule_limit_line := '- 每个目标参数的新值必须满足对应 minValue、maxValue、maxStep、cooldownMinutes 和规则 note;只有规则本身明确提供 dynamicMaxValue/dynamicMaxSource 时,才按该动态上限校验;不得把 outBufferCapacity 推导为 outTaskLimit 的动态上限;找不到规则时禁止提交。';
SET @old_decision_line := '- heuristicDirection/recommendedDirection 只是后端启发式提示,不是最终调参结论;最终是否调参必须由你结合任务阻塞、路径评分、规则步长、动态上限和冷却独立判断。';
SET @new_decision_line := '- heuristicDirection/recommendedDirection 只是后端启发式提示,不是最终调参结论;最终是否调参必须由你结合任务阻塞、路径评分、规则上下限、规则明确提供的动态上限和冷却独立判断。';
SET @old_increase_line := CONCAT(
  '- 当 heuristicDirection=increase_candidate 时,仍必须检查 ruleSnapshot、outBufferCapacity、maxStep 和 cooldown,且不得突',
  '破动态上限。'
);
SET @new_increase_line := '- 当 heuristicDirection=increase_candidate 时,仍必须检查 ruleSnapshot、outBufferCapacity、maxStep 和 cooldown;outBufferCapacity 只表示站点出库缓存容量,用于评估超出缓存后的主干道占用风险,不是 outTaskLimit 的硬上限或动态上限。';
SET @old_buffer_note := CONCAT(
  '注意:asr_bas_station.out_buffer_capacity 是人工维护的出库缓存容量,只用于证',
  '明 outTaskLimit 可上调上限,Agent 不允许修改该字段;增大 outTaskLimit 时建议值不得超过',
  '对应站点 outBufferCapacity。'
);
SET @new_buffer_note := '注意:asr_bas_station.out_buffer_capacity 是人工维护的出库缓存容量,用于评估 outTaskLimit 超过缓存后任务进入主干道占用的风险,Agent 不允许修改该字段;增大 outTaskLimit 可以超过对应站点 outBufferCapacity,但必须说明超出缓存后的主干道占用风险,并遵守 ruleSnapshot 中 outTaskLimit 自身的 minValue、maxValue、maxStep、cooldownMinutes、规则 note 以及规则明确提供的 dynamicMaxValue/dynamicMaxSource。';
SET @update_prompt_block_sql := IF(
  @prompt_template_table_exists > 0 AND @prompt_block_table_exists > 0,
  'UPDATE sys_ai_prompt_block b
     JOIN sys_ai_prompt_template t ON t.id = b.template_id
      SET b.content = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(b.content,
            @old_rule_read_line, @new_rule_read_line),
            @old_rule_limit_line, @new_rule_limit_line),
            @old_decision_line, @new_decision_line),
            @old_increase_line, @new_increase_line),
            @old_buffer_note, @new_buffer_note)
    WHERE t.scene_code = ''wcs_auto_tune_dispatch''
      AND t.published = 1',
  'SELECT ''prompt template/block table missing, skip published prompt block update'' '
);
PREPARE stmt_update_prompt_block FROM @update_prompt_block_sql;
EXECUTE stmt_update_prompt_block;
DEALLOCATE PREPARE stmt_update_prompt_block;
SET @update_prompt_template_sql := IF(
  @prompt_template_table_exists > 0,
  'UPDATE sys_ai_prompt_template
      SET content = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(content,
            @old_rule_read_line, @new_rule_read_line),
            @old_rule_limit_line, @new_rule_limit_line),
            @old_decision_line, @new_decision_line),
            @old_increase_line, @new_increase_line),
            @old_buffer_note, @new_buffer_note)
    WHERE scene_code = ''wcs_auto_tune_dispatch''
      AND published = 1',
  'SELECT ''prompt template table missing, skip published prompt content update'' '
);
PREPARE stmt_update_prompt_template FROM @update_prompt_template_sql;
EXECUTE stmt_update_prompt_template;
DEALLOCATE PREPARE stmt_update_prompt_template;
src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
@@ -237,14 +237,20 @@
    }
    @Test
    void rejectStationOutTaskLimitAboveDirectionalBufferCapacity() {
        when(basStationService.getById(101)).thenReturn(station(101, 1, 2));
    void stationOutTaskLimitAllowsAboveBufferCapacityButRejectsOverStep() {
        when(basStationService.getById(101)).thenReturn(station(101, 5, 3));
        service.apply(request(true, command("station", "101", "outTaskLimit", "3")));
        service.apply(request(true,
                command("station", "101", "outTaskLimit", "6"),
                command("station", "101", "outTaskLimit", "9")
        ));
        List<AiAutoTuneChange> changes = savedChanges();
        assertEquals("rejected", changes.get(0).getResultStatus());
        assertTrue(changes.get(0).getRejectReason().contains("0~2"));
        assertEquals("dry_run", changes.get(0).getResultStatus());
        assertEquals("5", changes.get(0).getOldValue());
        assertEquals("6", changes.get(0).getRequestedValue());
        assertEquals("rejected", changes.get(1).getResultStatus());
        assertTrue(changes.get(1).getRejectReason().contains("步长不能超过 3"));
    }
    @Test
@@ -283,14 +289,15 @@
    }
    @Test
    void rejectStationOutTaskLimitWithoutOutBufferCapacity() {
    void allowStationOutTaskLimitWithoutOutBufferCapacity() {
        when(basStationService.getById(101)).thenReturn(station(101, 0));
        service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
        AutoTuneApplyResult result = service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
        List<AiAutoTuneChange> changes = savedChanges();
        assertEquals("rejected", changes.get(0).getResultStatus());
        assertTrue(changes.get(0).getRejectReason().contains("缺少 outBufferCapacity"));
        assertTrue(result.getSuccess());
        assertEquals("dry_run", changes.get(0).getResultStatus());
        assertEquals("1", changes.get(0).getRequestedValue());
    }
    @Test
@@ -315,7 +322,7 @@
        AutoTuneApplyResult result = service.apply(request(false,
                command("sys_config", null, "conveyorStationTaskLimit", "15"),
                command("station", "101", "outTaskLimit", "3")
                command("station", "101", "outTaskLimit", "5")
        ));
        List<AiAutoTuneChange> changes = savedChanges();
src/test/java/com/zy/ai/service/impl/AutoTuneSnapshotServiceImplTest.java
@@ -83,7 +83,7 @@
    }
    @Test
    void buildRuleSnapshotExposesStepRangeCooldownAndDynamicMaxSource() {
    void buildRuleSnapshotExposesStepRangeCooldownAndRiskNote() {
        List<AutoTuneRuleSnapshotItem> result = service.buildRuleSnapshot();
        AutoTuneRuleSnapshotItem stationOutTaskRule = findRule(result, "station", "outTaskLimit");
@@ -91,10 +91,9 @@
        assertNull(stationOutTaskRule.getMaxValue());
        assertEquals(3, stationOutTaskRule.getMaxStep());
        assertEquals(10, stationOutTaskRule.getCooldownMinutes());
        assertEquals(Boolean.TRUE, stationOutTaskRule.getDynamicMaxValue());
        assertEquals("currentParameterSnapshot.stationOutBufferCapacities[targetId]",
                stationOutTaskRule.getDynamicMaxSource());
        assertEquals("单次调整幅度不能超过 maxStep;增大时不得超过对应站点 outBufferCapacity。",
        assertEquals(Boolean.FALSE, stationOutTaskRule.getDynamicMaxValue());
        assertNull(stationOutTaskRule.getDynamicMaxSource());
        assertEquals("单次调整幅度不能超过 maxStep;outBufferCapacity 代表出库站缓存位置数量,超过该容量可能使任务出现在主干道,仅作为风险参考,不是合法性上限。",
                stationOutTaskRule.getNote());
        AutoTuneRuleSnapshotItem crnMaxOutRule = findRule(result, "crn", "maxOutTask");
src/test/java/com/zy/ai/utils/AiPromptUtilsTest.java
New file
@@ -0,0 +1,39 @@
package com.zy.ai.utils;
import com.zy.ai.domain.autotune.AutoTuneControlModeSnapshot;
import com.zy.ai.enums.AiPromptScene;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AiPromptUtilsTest {
    @Test
    void defaultAutoTunePromptTreatsOutBufferCapacityAsRiskReferenceNotHardLimit() {
        AiPromptUtils promptUtils = new AiPromptUtils();
        String prompt = promptUtils.getDefaultPrompt(AiPromptScene.AUTO_TUNE_DISPATCH);
        assertOutBufferCapacityRiskReferenceSemantics(prompt);
    }
    @Test
    void runtimeAutoTuneGuardDoesNotRestoreOutBufferCapacityHardLimitSemantics() {
        String prompt = AiPromptUtils.buildAutoTuneRuntimeGuard("auto", new AutoTuneControlModeSnapshot());
        assertFalse(prompt.contains("找不到规则或无法证" + "明动态上限时禁止提交"));
        assertTrue(prompt.contains("不得把 outBufferCapacity 推导为 outTaskLimit 的动态上限"));
    }
    private void assertOutBufferCapacityRiskReferenceSemantics(String prompt) {
        assertTrue(prompt.contains("outBufferCapacity 只表示站点出库缓存容量"));
        assertTrue(prompt.contains("主干道占用风险"));
        assertTrue(prompt.contains("不是 outTaskLimit 的硬上限或动态上限"));
        assertTrue(prompt.contains("增大 outTaskLimit 可以超过对应站点 outBufferCapacity"));
        assertFalse(prompt.contains("增大 outTaskLimit 时建议值不得超过"
                + "对应站点 outBufferCapacity"));
        assertFalse(prompt.contains("只用于证" + "明 outTaskLimit 可上调上限"));
        assertFalse(prompt.contains("不得突" + "破动态上限"));
    }
}