# Agent调参outTaskLimit上限修复V3.0.1.6
| | |
| | | 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(); |
| | | |
| | |
| | | .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) |
| | |
| | | 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 + " 范围内"); |
| | | } |
| | |
| | | |
| | | 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" + |
| | |
| | | "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" + |
| | |
| | | "- 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" + |
| | |
| | | # 系统版本信息 |
| | | app: |
| | | version: 3.0.1.5 |
| | | version: 3.0.1.6 |
| | | version-type: prd # prd 或 dev |
| | | i18n: |
| | | default-locale: zh-CN |
| | |
| | | |
| | | 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; |
| New file |
| | |
| | | -- 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; |
| | |
| | | } |
| | | |
| | | @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 |
| | |
| | | } |
| | | |
| | | @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 |
| | |
| | | |
| | | 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(); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void buildRuleSnapshotExposesStepRangeCooldownAndDynamicMaxSource() { |
| | | void buildRuleSnapshotExposesStepRangeCooldownAndRiskNote() { |
| | | List<AutoTuneRuleSnapshotItem> result = service.buildRuleSnapshot(); |
| | | |
| | | AutoTuneRuleSnapshotItem stationOutTaskRule = findRule(result, "station", "outTaskLimit"); |
| | |
| | | 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"); |
| New file |
| | |
| | | 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("不得突" + "破动态上限")); |
| | | } |
| | | } |