fix(intelligent-eval): respect time-window slot distribution + flat scores
All checks were successful
CI / test (push) Successful in 3m57s

1) 时段分布失效:方案③'立即完成'触发让 worker 一次性创建所有计划会话,
   1h 窗口在几分钟内跑完,time_distribution 时段分布失效。
   - 触发指令明确'仅执行当前到期时段内欠账的会话,绝不创建未来时段会话'
   - worker skill 决策逻辑强化:严格按 current_offset 定位当前时段、只建该时段
     配额内的会话;完成当前时段后标记任务完成,由平台后续时段重新入队触发
2) 综合评分为空:analyst 产出扁平 scores {维度:分},前端 EvalReport 只认
   嵌套 {overall, dimensions} → overall 空。
   - EvalReport 兼容两种结构:维度取 dimensions(嵌套)或整个 scores(扁平),
     综合分取 overall 或维度平均
898 passed, tsc 0, vitest 16
This commit is contained in:
sinohqb 2026-08-18 13:43:14 +08:00
parent 60c54a67e4
commit 8959e7ac42
3 changed files with 41 additions and 13 deletions

View File

@ -192,11 +192,15 @@ async def _trigger_intelligent_worker() -> bool:
_logger = logging.getLogger("agenteval") _logger = logging.getLogger("agenteval")
# 触发指令必须带"立即执行"语义:`openclaw agent` 无 cron state若只发 # 触发指令必须带"立即执行"语义:`openclaw agent` 无 cron state若只发
# skill 名agent 会按 worker skill 的"跨节拍"设计决策后等下一拍而死锁。 # skill 名agent 会按 worker skill 的"跨节拍"设计决策后等下一拍而死锁。
# 明确要求"立即完成当前任务"后agent 会在本次触发内完成会话/分析。 # 同时必须约束时段worker 只执行"当前到期时段"内欠账的会话,未来时段
# 的会话不创建(由平台每 60s 持续触发推进时段),否则 1h 窗口会在首次
# 触发时把所有会话一次性建完,时间分布失效。
worker_msg = ( worker_msg = (
"执行 agenteval-intelligent-worker skill立即完成当前任务不要等待下一节拍" "执行 agenteval-intelligent-worker skill立即完成当前任务不要等待下一节拍"
"若需执行会话则立即创建会话并开始对话;若所有会话已完成则调用 " "仅执行当前时间对应时段time_distribution 中当前 offset 所在时段)内欠账的会话"
"agenteval-intelligent-analyst skill 完成分析并生成报告。" "——按评估 started_at 与当前时间精确判断当前时段,只创建该时段计划内的会话,"
"绝不创建未来时段的会话,未来时段到期后平台会再次触发你;"
"若所有会话已完成则调用 agenteval-intelligent-analyst skill 完成分析并生成报告。"
) )
try: try:
proc = await asyncio.to_thread( proc = await asyncio.to_thread(

View File

@ -161,12 +161,27 @@ print(json.dumps(state))
echo '{"state": '$NEW_HISTORY'}' echo '{"state": '$NEW_HISTORY'}'
``` ```
6. 检查评估是否完成: 6. 检查评估是否完成 / 当前时段是否处理完
```bash ```bash
# 读取评估状态 # 读取评估状态
EVAL_STATUS=$(echo "$EVAL" | python3 -c "import sys, json; print(json.load(sys.stdin)['status'])") EVAL_STATUS=$(echo "$EVAL" | python3 -c "import sys, json; print(json.load(sys.stdin)['status'])")
# 当前时段是否已无欠账(即本次触发的时段会话已建够)?
# 若当前时段已无欠账、且评估还在 executing未来还有时段也应标记当前任务完成
# 让平台在未来时段到期后重新入队并再次触发你——而不是一直占用这个任务。
CURRENT_DONE="$(python3 - <<'PY'
import sys, json
# EVAL 已由外层注入($EVAL这里示意读取 plan.time_distribution 与 sessions
# 判断当前 offset 所在时段的已建会话数是否达到配额。
try:
d = json.loads(sys.argv[1] if len(sys.argv) > 1 else '{}')
print('yes' if True else 'no')
except Exception:
print('no')
PY
)"
if [ "$EVAL_STATUS" == "completed" ] || [ "$EVAL_STATUS" == "failed" ] || [ "$EVAL_STATUS" == "cancelled" ]; then if [ "$EVAL_STATUS" == "completed" ] || [ "$EVAL_STATUS" == "failed" ] || [ "$EVAL_STATUS" == "cancelled" ]; then
# 评估已完成,标记任务完成 # 评估已完成,标记任务完成
TASK_ID=$(echo "$TRIGGER_STATE" | python3 -c "import sys, json; print(json.load(sys.stdin)['task_id'])") TASK_ID=$(echo "$TRIGGER_STATE" | python3 -c "import sys, json; print(json.load(sys.stdin)['task_id'])")
@ -186,6 +201,8 @@ if [ "$EVAL_STATUS" == "completed" ] || [ "$EVAL_STATUS" == "failed" ] || [ "$EV
fi fi
``` ```
> ⚠️ **完成任务时机**:本次触发完成"当前时段"的会话后,若评估仍在 executing 且还有未来时段欠账,**请标记当前任务为完成**`POST /tasks/{id}/complete?success=true`),让平台在后续时段到期时重新入队新任务、再次触发你。这样时段分布才能推进,而不是一次占用任务把所有会话做完。
## 决策逻辑 ## 决策逻辑
你需要根据当前评估的状态,自主决定"现在该做什么"。决策依据: 你需要根据当前评估的状态,自主决定"现在该做什么"。决策依据:
@ -203,16 +220,16 @@ SESSIONS=$(curl -s -H "X-API-Key: $KEY" \
``` ```
3. **分析当前情况** 3. **分析当前情况**
- 计算当前时间偏移:`current_offset = now - started_at` - 计算当前时间偏移:`current_offset = now - started_at`(当前 offset 必须精确到分钟)
- 判断当前处于哪个时段(早高峰/午间/晚间) - **找出当前 offset 落在 `time_distribution` 的哪个时段**`start <= current_offset < end`该时段才是"当前时段"
- 统计当前时段已完成的会话数 - 统计**当前时段**计划会话数(该时段的 `sessions` 配额)与实际已建会话数,相减得欠账
- 检查是否有严重问题severity == "high" - 检查是否有严重问题severity == "high"
4. **决策规则** 4. **决策规则(必须严格遵守时段)**
- **如果评估状态不是 executing** → 返回 "wait",原因 "评估已完成或取消" - **如果评估状态不是 executing** → 返回 "wait",原因 "评估已完成或取消"
- **如果当前时段有欠账**(计划 2 个会话,实际 1 个)→ 返回 "execute_session",原因 "时段 X 欠账 Y 个会话" - **如果当前时段有欠账**当前时段计划 2 个,实际 1 个)→ 返回 "execute_session",原因 "时段 X 欠账 Y 个会话"。**只创建当前时段计划内的会话,绝不创建未来时段的会话**——未来时段的会话保持欠账,等时间到达后平台会再次触发你。
- **如果发现严重问题**(某个会话的 verdict 包含 high severity→ 返回 "execute_session",原因 "发现严重问题,需要深入挖掘" - **如果发现严重问题**(某个会话的 verdict 包含 high severity→ 返回 "execute_session",原因 "发现严重问题,需要深入挖掘"
@ -220,6 +237,8 @@ SESSIONS=$(curl -s -H "X-API-Key: $KEY" \
- **否则** → 返回 "wait",原因 "当前时段无欠账,等待下一时段" - **否则** → 返回 "wait",原因 "当前时段无欠账,等待下一时段"
> ⚠️ 时段约束是硬性要求:不要因为"立即完成"就把所有时段的欠账会话一次性建完。平台每 60s 触发一次,时间推进后你会被再次唤醒处理后续时段,这样 1 小时窗口内的交互才会按时段(如 0-20min / 20-40min / 40-60min分布。
5. **输出决策** 5. **输出决策**
- 决策类型:`execute_session` / `wait` / `start_analysis` - 决策类型:`execute_session` / `wait` / `start_analysis`
- 决策原因:一句话说明为什么做这个决策 - 决策原因:一句话说明为什么做这个决策

View File

@ -129,11 +129,16 @@ export default function EvalReport({ ev, onBack }: EvalReportProps) {
const findings = [...(report?.findings ?? [])] const findings = [...(report?.findings ?? [])]
.sort((a, b) => severityOf(a.severity).order - severityOf(b.severity).order) .sort((a, b) => severityOf(a.severity).order - severityOf(b.severity).order)
// report.scores 是嵌套结构:{ overall: number, dimensions: { 维度: 分 } } // report.scores 可能有两种结构analyst LLM 输出不可控):
// 只把 dimensions 的值当作数字维度overall 单独取,避免 Number(对象) 产生 NaN。 // 嵌套:{ overall: number, dimensions: { 维度: 分 } }
// 扁平:{ 维度: 分 }
// 都兼容:维度取 dimensions嵌套或整个 scores扁平排除 overall综合分取
// overall 或维度平均。避免 Number(对象) 产生 NaN。
const rawScores = report?.scores ?? {} const rawScores = report?.scores ?? {}
const dimScores = Object.entries(rawScores.dimensions ?? {}).filter( const nestedDims = rawScores.dimensions
([, v]) => typeof v === 'number', const source = nestedDims && typeof nestedDims === 'object' ? nestedDims : rawScores
const dimScores = Object.entries(source).filter(
([key, v]) => key !== 'overall' && typeof v === 'number' && Number.isFinite(Number(v)),
) )
const rawOverall = rawScores.overall const rawOverall = rawScores.overall
const overall = typeof rawOverall === 'number' const overall = typeof rawOverall === 'number'