codebee 0.1.49 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +4 -150
- package/app/core/aiflavor.py +14 -0
- package/app/core/builtin_agent.py +567 -74
- package/app/core/jobs.py +170 -42
- package/app/core/knowledge.py +432 -405
- package/app/core/manager.py +100 -10
- package/app/core/market.py +27 -1
- package/app/core/modelhub.py +53 -5
- package/app/core/pipeline.py +193 -77
- package/app/core/planner.py +14 -5
- package/app/core/preview.py +293 -0
- package/app/core/router.py +189 -104
- package/app/core/runner.py +370 -144
- package/app/core/settings.py +11 -4
- package/app/core/skills.py +36 -12
- package/app/core/store.py +74 -2
- package/app/core/zentao.py +189 -30
- package/app/main.py +95 -7
- package/app/ui/app.js +13256 -12876
- package/app/ui/i18n.js +97 -34
- package/app/ui/index.html +55 -12
- package/app/ui/style.css +217 -26
- package/package.json +1 -1
package/app/core/pipeline.py
CHANGED
|
@@ -419,7 +419,25 @@ def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=Non
|
|
|
419
419
|
def _log(line):
|
|
420
420
|
lines.append(str(line))
|
|
421
421
|
|
|
422
|
-
|
|
422
|
+
# 思考过程可视化(2026-09-22 用户诉求):模型的思维链/正文/工具活动边收边
|
|
423
|
+
# 落进步骤记录(store.stream_step 内部节流),对话时间线 2s 轮询即能读到
|
|
424
|
+
# 「它正在想什么」——此前整轮只有三点打字动画。
|
|
425
|
+
live = {"thinking": "", "text": ""}
|
|
426
|
+
|
|
427
|
+
def _on_reason(chunk):
|
|
428
|
+
live["thinking"] += chunk or ""
|
|
429
|
+
store.stream_step(run_id, step["n"], thinking=live["thinking"])
|
|
430
|
+
|
|
431
|
+
def _on_stream(acc):
|
|
432
|
+
live["text"] = acc or ""
|
|
433
|
+
store.stream_step(run_id, step["n"], text=live["text"])
|
|
434
|
+
|
|
435
|
+
def _on_activity(line):
|
|
436
|
+
store.stream_step(run_id, step["n"], activity=str(line or "")[:200])
|
|
437
|
+
|
|
438
|
+
res = builtin_agent.run(bi, prompt, workdir, cancel_event=ev, log=_log, images=images,
|
|
439
|
+
on_reason=_on_reason, on_stream=_on_stream,
|
|
440
|
+
on_activity=_on_activity)
|
|
423
441
|
if followups and res.get("ok"):
|
|
424
442
|
clean, fups = _parse_followups(res.get("text") or "")
|
|
425
443
|
if fups:
|
|
@@ -427,6 +445,10 @@ def _run_builtin_step(run_id, role, bi, prompt, workdir, ev, note="", images=Non
|
|
|
427
445
|
res["followups"] = fups
|
|
428
446
|
if log_abs:
|
|
429
447
|
try:
|
|
448
|
+
# 思考过程同时进步骤日志(日志抽屉与对话气泡同源,便于事后复盘)
|
|
449
|
+
think = (res.get("reasoning") or "").strip()
|
|
450
|
+
if think:
|
|
451
|
+
lines.append("[思考过程]\n" + think)
|
|
430
452
|
log_abs.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
431
453
|
except Exception:
|
|
432
454
|
pass
|
|
@@ -550,7 +572,9 @@ def _finish_step_result(run_id, step, res, role, agent, start):
|
|
|
550
572
|
# 智能体的最终回答(runner 已从 JSONL 事件流里抽出 agent_message)。
|
|
551
573
|
# 对话视图直读这个;日志文件是全量事件流,塞进气泡就成了「看日志」。
|
|
552
574
|
output=(res.get("text") or ""),
|
|
553
|
-
followups=res.get("followups")
|
|
575
|
+
followups=res.get("followups"),
|
|
576
|
+
# 思考过程(内置智能体流式抓取):落进步骤记录,对话气泡折叠展示
|
|
577
|
+
thinking=res.get("reasoning") or None)
|
|
554
578
|
# 错误台账:失败/超时各记一条结构化记录(遥测与诊断包的数据源)。
|
|
555
579
|
# 用户主动取消不入账——那不是产品问题;detail 只存脱敏后的失败摘录。
|
|
556
580
|
if status in ("failed", "timeout"):
|
|
@@ -1090,29 +1114,53 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
1090
1114
|
and _code_bestof(run, task, impl_agent, difficulty, ev)):
|
|
1091
1115
|
return True
|
|
1092
1116
|
# 实现步失败不立刻判死:2026-09-16 实测配额烧干时 5 连跑全在同一条 CLI 上
|
|
1093
|
-
# 失败收场,而健康的 opencode 一直在旁观望——跨 CLI
|
|
1094
|
-
# (mode=manual 尊重用户指定,不换)。
|
|
1117
|
+
# 失败收场,而健康的 opencode 一直在旁观望——跨 CLI 换将重试
|
|
1118
|
+
# (mode=manual 尊重用户指定,不换)。2026-09-22 续修:配额/限流类死亡
|
|
1119
|
+
# 是确定性秒死,允许继续走查候选名单(同上游让位异上游);其余死因
|
|
1120
|
+
# (超时等)仍只换一次,防着在坏候选上再烧一整个超时。
|
|
1095
1121
|
if mode == "auto" and impl_agent.get("mode") == "real":
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1122
|
+
tried = {impl_agent["id"], "mock-a", "mock-b"}
|
|
1123
|
+
notes = []
|
|
1124
|
+
dead_ups = [] # 配额死亡候选的上游集合:换将优先异上游
|
|
1125
|
+
prev_id = impl_agent["id"]
|
|
1126
|
+
other = None
|
|
1127
|
+
while True:
|
|
1128
|
+
other, other_reason = router.pick_switch_candidate(
|
|
1129
|
+
agents, "implement", "code", stats,
|
|
1130
|
+
exclude=tried, dead_upstreams=dead_ups)
|
|
1131
|
+
if other is None or other.get("mode") != "real":
|
|
1132
|
+
other = None
|
|
1133
|
+
break
|
|
1100
1134
|
note = "实现步失败自动换将 %s → %s:%s。失败原因:%s" % (
|
|
1101
|
-
|
|
1135
|
+
prev_id, other["id"], other_reason,
|
|
1102
1136
|
(res.get("error") or "")[:200])
|
|
1103
1137
|
ok2, res = _run_one(other)
|
|
1104
1138
|
if ok2:
|
|
1105
|
-
|
|
1139
|
+
route_note = ";".join(notes + [note])
|
|
1140
|
+
store.update_run(run_id, error="", route_note=route_note)
|
|
1106
1141
|
_record_actual_route(
|
|
1107
1142
|
run_id, task, agents, stats, other,
|
|
1108
1143
|
implementers=[other], reviewer=reviewer,
|
|
1109
|
-
implement_reason=
|
|
1144
|
+
implement_reason=route_note,
|
|
1110
1145
|
review_reason=route.get("reviewer", ""),
|
|
1111
1146
|
direct=not workflow["review_required"])
|
|
1112
1147
|
return True
|
|
1113
|
-
|
|
1148
|
+
notes.append(note)
|
|
1149
|
+
tried.add(other["id"])
|
|
1150
|
+
prev_id = other["id"]
|
|
1151
|
+
if runner._quota_error(res.get("error") or ""):
|
|
1152
|
+
ups = router.agent_upstreams(other["id"])
|
|
1153
|
+
if ups:
|
|
1154
|
+
dead_ups.append(ups)
|
|
1155
|
+
else:
|
|
1156
|
+
break # 非配额死因:只换一次就收手
|
|
1157
|
+
tail_err = (res.get("error") or "")[:200]
|
|
1158
|
+
if notes:
|
|
1159
|
+
res_err = ";".join(notes) + ";换将后仍失败:%s" % tail_err
|
|
1160
|
+
elif other is None:
|
|
1161
|
+
res_err = "实现步骤失败(无其他真实 CLI 可换将): %s" % tail_err
|
|
1114
1162
|
else:
|
|
1115
|
-
res_err = "
|
|
1163
|
+
res_err = "实现步骤失败: %s" % tail_err
|
|
1116
1164
|
else:
|
|
1117
1165
|
res_err = "实现步骤失败: %s" % res.get("error")
|
|
1118
1166
|
store.update_run(run_id, expected_status="running", status="failed",
|
|
@@ -2085,20 +2133,6 @@ def _weakest_chapters(chapter_scores, global_means, threshold, limit=2):
|
|
|
2085
2133
|
return cand[:limit]
|
|
2086
2134
|
|
|
2087
2135
|
|
|
2088
|
-
def _inject_detection(tpl, text, task):
|
|
2089
|
-
"""把确定性检测行(AI 味/叙事架构/节奏)插进评审提示词。
|
|
2090
|
-
|
|
2091
|
-
命中才插,位置固定为「## 待评审稿件」之前——与技能包/圣经/知识库注入
|
|
2092
|
-
同一位置,保持前缀稳定(§07 T1.2' 前缀缓存)。单稿件评审与连载逐章
|
|
2093
|
-
评审共用本函数:此前连载逐章完全没有这条参考线(旗舰场景漏挂)。
|
|
2094
|
-
"""
|
|
2095
|
-
det = aiflavor.report_line(text, task.get("type"))
|
|
2096
|
-
if not det:
|
|
2097
|
-
return tpl
|
|
2098
|
-
block = "## 确定性检测结果(供评审参考)\n%s\n\n" % det
|
|
2099
|
-
return tpl.replace("## 待评审稿件", block + "## 待评审稿件", 1)
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
2136
|
def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route, resume_ctx, difficulty):
|
|
2103
2137
|
"""连载流水线:大纲 → 逐章起草/评审/修订 → 全局一致性评审 → 合并成书。"""
|
|
2104
2138
|
import json as _json
|
|
@@ -2175,9 +2209,8 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2175
2209
|
if kb:
|
|
2176
2210
|
tpl = tpl.replace("## 待评审稿件", "%s\n\n## 待评审稿件" % kb, 1)
|
|
2177
2211
|
# 确定性检测(AI 味/叙事架构/节奏)此前只挂在单稿件评审上,连载逐章
|
|
2178
|
-
#
|
|
2179
|
-
|
|
2180
|
-
tpl = _inject_detection(tpl, text, task)
|
|
2212
|
+
# 从未拿到——逐章节奏与钩子恰恰最需要这条参考线(命中才追加,不扣分)
|
|
2213
|
+
tpl = aiflavor.inject_into_prompt(tpl, text, task.get("type"))
|
|
2181
2214
|
return tpl.replace("__DIMKEYS__", dimkey).replace(
|
|
2182
2215
|
"__MANUSCRIPT__", text or "(稿件为空!)")
|
|
2183
2216
|
|
|
@@ -2338,50 +2371,92 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2338
2371
|
|
|
2339
2372
|
def run_critique(text, rnd, note_extra="", critic_sids=None, event_check=""):
|
|
2340
2373
|
"""一轮多维评审:返回 (cj_by_agent, scored)。变体赛马与主循环共用。
|
|
2341
|
-
event_check:本章大纲核对块(逐项目标审稿),随评审下发并回收标记块。
|
|
2374
|
+
event_check:本章大纲核对块(逐项目标审稿),随评审下发并回收标记块。
|
|
2375
|
+
|
|
2376
|
+
多评审并发跑(2026-09-22):评审占运行总时长的六成以上,而各评审之间
|
|
2377
|
+
互不依赖(同一份稿件、各自视角),串行等待纯属浪费——每位评审的墙上
|
|
2378
|
+
时间直接叠加。与「同章多稿赛马」「best-of 候选」同一套线程范式(那两处
|
|
2379
|
+
早已多线程调用 _run_step)。共享结构的写入全部收敛到 join 之后按
|
|
2380
|
+
critics 原顺序合并,保证与串行版逐字节同结果(ev_check 取第一份非空、
|
|
2381
|
+
issues/ledger 的先后顺序都不变),只在耗时上取并行收益。"""
|
|
2342
2382
|
cj_map, sids = {}, dict(critic_sids or {})
|
|
2343
2383
|
scored = 0 # 真正给出分数的评审数;失败/不可解析不得当成 0 分计入
|
|
2344
|
-
|
|
2345
|
-
|
|
2384
|
+
role = "critique-c%d" % i
|
|
2385
|
+
results = {}
|
|
2386
|
+
# 绑定解析提前到主线程:bind_agent 会把链首同步进 CLI 自家配置(写盘),
|
|
2387
|
+
# 多线程同时调它有写盘竞争。解析结果随线程参数传入,线程内只做调用。
|
|
2388
|
+
bound = [modelhub.bind_agent(a, difficulty) for a in critics]
|
|
2389
|
+
|
|
2390
|
+
def _critique_one(idx, agent):
|
|
2391
|
+
"""单评审执行体(线程内只做调用与解析,不改共享结构)。
|
|
2392
|
+
|
|
2393
|
+
异常不吞:记进 results 由主线程按原顺序重抛——串行版的
|
|
2394
|
+
Cancelled/管道异常语义(上抛打断本 run)必须原样保留。"""
|
|
2395
|
+
try:
|
|
2396
|
+
_critique_one_inner(idx, agent)
|
|
2397
|
+
except BaseException as exc: # noqa: BLE001(含 Cancelled)
|
|
2398
|
+
results[idx] = {"error": exc}
|
|
2399
|
+
|
|
2400
|
+
def _critique_one_inner(idx, agent):
|
|
2346
2401
|
if agent.get("mode") == "mock":
|
|
2347
2402
|
step, log_abs = store.add_step(run_id, role, agent["id"], agent.get("label"))
|
|
2348
2403
|
time.sleep(0.15)
|
|
2349
2404
|
cj = mocks.critique(agent["id"], rnd, dims, threshold_ch)
|
|
2350
|
-
scored += 1
|
|
2351
2405
|
store.finish_step(run_id, step["n"], "done",
|
|
2352
2406
|
summary="均分 %.1f:%s" % (
|
|
2353
2407
|
sum(cj["scores"].values()) / max(1, len(dims)),
|
|
2354
2408
|
cj["summary"]),
|
|
2355
2409
|
duration_s=0.15)
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
#
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
if evl and not ev_check_lines[0]:
|
|
2376
|
-
ev_check_lines[0] = evl
|
|
2377
|
-
ledger_lines.extend(_parse_tagged_lines(res.get("text"), "ledger"))
|
|
2410
|
+
results[idx] = {"agent": agent, "cj": cj, "evl": [],
|
|
2411
|
+
"ledger": [], "sid": None}
|
|
2412
|
+
return
|
|
2413
|
+
lens = _critic_lens(critics, agent)
|
|
2414
|
+
res = _run_step(run_id, role, bound[idx],
|
|
2415
|
+
crit_prompt_for(
|
|
2416
|
+
text,
|
|
2417
|
+
note=("小说第 %d 章" % i) + (
|
|
2418
|
+
"|你的专属评审视角:%s(其他评审会覆盖其余视角,"
|
|
2419
|
+
"请深挖你的镜头,但所有维度仍需打分)" % lens)
|
|
2420
|
+
if lens else "",
|
|
2421
|
+
event_check=event_check) + note_extra,
|
|
2422
|
+
workdir, readonly=True, ev=ev,
|
|
2423
|
+
resume=sids.get(agent["id"]))
|
|
2424
|
+
results[idx] = {
|
|
2425
|
+
"agent": agent, "cj": _critique_json(res, dims),
|
|
2426
|
+
# 逐项目标审稿 + 资源账本:标记块先各存各的,join 后按序合并
|
|
2427
|
+
"evl": _parse_tagged_lines(res.get("text"), "event_check"),
|
|
2428
|
+
"ledger": _parse_tagged_lines(res.get("text"), "ledger"),
|
|
2378
2429
|
# §07 T1.1:记录该评审的会话 id(第 2 轮复用)
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2430
|
+
"sid": _resume_sid(agent, res.get("sid")),
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
threads = []
|
|
2434
|
+
for idx, agent in enumerate(critics):
|
|
2435
|
+
th = threading.Thread(target=_critique_one, args=(idx, agent),
|
|
2436
|
+
name="crit-%s-c%d-%d" % (run_id, i, idx), daemon=True)
|
|
2437
|
+
threads.append(th)
|
|
2438
|
+
th.start()
|
|
2439
|
+
for th in threads:
|
|
2440
|
+
th.join(3000)
|
|
2441
|
+
_check_cancel(ev)
|
|
2442
|
+
|
|
2443
|
+
# join 后按原顺序合并(确定性:与串行版结果一致)
|
|
2444
|
+
for idx in range(len(critics)):
|
|
2445
|
+
r = results.get(idx)
|
|
2446
|
+
if r is None:
|
|
2447
|
+
continue # 该评审线程超时未回,按「没出分」处理
|
|
2448
|
+
if "error" in r:
|
|
2449
|
+
raise r["error"] # 还原串行版的异常上抛语义
|
|
2450
|
+
agent, cj = r["agent"], r["cj"]
|
|
2451
|
+
if cj.get("scores"):
|
|
2452
|
+
scored += 1
|
|
2453
|
+
if r["evl"] and not ev_check_lines[0]:
|
|
2454
|
+
ev_check_lines[0] = r["evl"]
|
|
2455
|
+
ledger_lines.extend(r["ledger"])
|
|
2456
|
+
if r["sid"]:
|
|
2457
|
+
sids[agent["id"]] = r["sid"]
|
|
2382
2458
|
cj_map[agent["id"]] = cj
|
|
2383
2459
|
issues_all.extend({"chapter": i, **it} for it in (cj.get("issues") or [])[:6])
|
|
2384
|
-
_check_cancel(ev)
|
|
2385
2460
|
|
|
2386
2461
|
# 评审者级 fallback:名单内评审全挂(网关抖动/CLI 故障)时,
|
|
2387
2462
|
# 从其它已启用真实智能体补位至多 2 个(排除 mock 与已试过的),
|
|
@@ -2496,6 +2571,8 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2496
2571
|
chapter_reason = route.get("author", "")
|
|
2497
2572
|
last_attempt_impl = impl
|
|
2498
2573
|
last_attempt_reason = chapter_reason
|
|
2574
|
+
prev_prompt = None # 上一次实际下发的提示词
|
|
2575
|
+
prev_timed_out = False # 上一次是否「超时/停滞」收场
|
|
2499
2576
|
for draft_attempt in range(3):
|
|
2500
2577
|
if draft_attempt:
|
|
2501
2578
|
# 30s / 60s 退避;ev.wait 睡等可被取消即刻唤醒
|
|
@@ -2511,11 +2588,20 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2511
2588
|
# 30KB 对讯飞必挂)
|
|
2512
2589
|
sk2, bible2, _note = _shrink_context_block(sk_block, bible, budget=12000)
|
|
2513
2590
|
use_prompt = prompt.replace(sk_block, sk2).replace(bible, bible2)
|
|
2591
|
+
# 超时重试必须有新变量才值得做(2026-09-22):提示词与上次逐字节
|
|
2592
|
+
# 相同、上次又是超时/停滞收场时,重试只是把同一个超时再烧一遍
|
|
2593
|
+
# (2400s × N)。此时直接跳出交替换将——换 CLI/模型才是新机会。
|
|
2594
|
+
# 注意保留「缩上下文重试」:提示词真的变小了(缩块生效)就照试,
|
|
2595
|
+
# 那是针对「提示词过大挂起」的有效降级。
|
|
2596
|
+
if prev_timed_out and use_prompt == prev_prompt:
|
|
2597
|
+
break
|
|
2514
2598
|
res = _run_step(run_id, "draft-c%d" % i, modelhub.bind_agent(impl, difficulty), use_prompt,
|
|
2515
2599
|
step_wd, readonly=False, ev=ev, timeout=2400,
|
|
2516
2600
|
resume=resume_ctx["session"] if resume_ctx else None,
|
|
2517
2601
|
images=_task_images(task, workdir),
|
|
2518
2602
|
note=("起草重试 %d/2(网关限流退避)" % draft_attempt) if draft_attempt else "")
|
|
2603
|
+
prev_prompt = use_prompt
|
|
2604
|
+
prev_timed_out = bool((res.get("raw") or {}).get("timed_out"))
|
|
2519
2605
|
good, txt = _chapter_state()
|
|
2520
2606
|
if good:
|
|
2521
2607
|
break
|
|
@@ -2836,9 +2922,23 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2836
2922
|
global_issues = []
|
|
2837
2923
|
|
|
2838
2924
|
def run_global_round(agent_list):
|
|
2839
|
-
"""一轮全局评审:返回 (出分评审数, 按维累计分)。失败/不可解析不得当成低分计入。
|
|
2925
|
+
"""一轮全局评审:返回 (出分评审数, 按维累计分)。失败/不可解析不得当成低分计入。
|
|
2926
|
+
|
|
2927
|
+
多评审并发(2026-09-22,同章级评审并发):各自读同一份全书文本、互不依赖,
|
|
2928
|
+
串行只是把等待时间叠起来。共享结构仍在 join 后按原顺序合并,结果与串行一致。"""
|
|
2840
2929
|
gmeans_acc, scored = {}, 0
|
|
2841
|
-
|
|
2930
|
+
results = {}
|
|
2931
|
+
# 绑定解析提前到主线程(线程内不做会写盘的 bind_agent)
|
|
2932
|
+
bound = [modelhub.bind_agent(a, difficulty) for a in agent_list]
|
|
2933
|
+
|
|
2934
|
+
def _global_one(idx, agent):
|
|
2935
|
+
"""异常不吞:记进 results 由主线程按原顺序重抛(保留串行语义)。"""
|
|
2936
|
+
try:
|
|
2937
|
+
_global_one_inner(idx, agent)
|
|
2938
|
+
except BaseException as exc: # noqa: BLE001(含 Cancelled)
|
|
2939
|
+
results[idx] = {"error": exc}
|
|
2940
|
+
|
|
2941
|
+
def _global_one_inner(idx, agent):
|
|
2842
2942
|
if agent.get("mode") == "mock":
|
|
2843
2943
|
step, _ = store.add_step(run_id, "global-critique", agent["id"],
|
|
2844
2944
|
agent.get("label"))
|
|
@@ -2847,25 +2947,41 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2847
2947
|
"issues": [], "summary": "(mock)全书结构完整,达到可签约水平"}
|
|
2848
2948
|
store.finish_step(run_id, step["n"], "done", summary="均分 8.0:(mock)全书达标",
|
|
2849
2949
|
duration_s=0.15)
|
|
2950
|
+
results[idx] = gj
|
|
2951
|
+
return
|
|
2952
|
+
gtpl = SERIAL_GLOBAL_PROMPT
|
|
2953
|
+
if bible:
|
|
2954
|
+
gtpl = gtpl.replace("## 全书目标", bible + "\n\n## 全书目标", 1)
|
|
2955
|
+
res = _run_step(run_id, "global-critique", bound[idx],
|
|
2956
|
+
(gtpl.replace("__DIMKEYS__", dimkey)
|
|
2957
|
+
.replace("__GOAL__", task["goal"])
|
|
2958
|
+
.replace("__MANUSCRIPT__", full_text[:60000])),
|
|
2959
|
+
workdir, readonly=True, ev=ev, timeout=2400)
|
|
2960
|
+
results[idx] = _critique_json(res, dims)
|
|
2961
|
+
|
|
2962
|
+
threads = []
|
|
2963
|
+
for idx, agent in enumerate(agent_list):
|
|
2964
|
+
th = threading.Thread(target=_global_one, args=(idx, agent),
|
|
2965
|
+
name="gcrit-%s-%d" % (run_id, idx), daemon=True)
|
|
2966
|
+
threads.append(th)
|
|
2967
|
+
th.start()
|
|
2968
|
+
for th in threads:
|
|
2969
|
+
th.join(3000)
|
|
2970
|
+
_check_cancel(ev)
|
|
2971
|
+
|
|
2972
|
+
for idx in range(len(agent_list)):
|
|
2973
|
+
gj = results.get(idx)
|
|
2974
|
+
if gj is None:
|
|
2975
|
+
continue
|
|
2976
|
+
if "error" in gj:
|
|
2977
|
+
raise gj["error"] # 还原串行版的异常上抛语义
|
|
2978
|
+
if gj.get("scores"):
|
|
2850
2979
|
scored += 1
|
|
2851
|
-
else:
|
|
2852
|
-
gtpl = SERIAL_GLOBAL_PROMPT
|
|
2853
|
-
if bible:
|
|
2854
|
-
gtpl = gtpl.replace("## 全书目标", bible + "\n\n## 全书目标", 1)
|
|
2855
|
-
res = _run_step(run_id, "global-critique", modelhub.bind_agent(agent, difficulty),
|
|
2856
|
-
(gtpl.replace("__DIMKEYS__", dimkey)
|
|
2857
|
-
.replace("__GOAL__", task["goal"])
|
|
2858
|
-
.replace("__MANUSCRIPT__", full_text[:60000])),
|
|
2859
|
-
workdir, readonly=True, ev=ev, timeout=2400)
|
|
2860
|
-
gj = _critique_json(res, dims)
|
|
2861
|
-
if gj.get("scores"):
|
|
2862
|
-
scored += 1
|
|
2863
2980
|
global_issues.extend({"chapter": "全书", **it} for it in (gj.get("issues") or [])[:8])
|
|
2864
2981
|
for d in dims:
|
|
2865
2982
|
v = gj.get("scores", {}).get(d)
|
|
2866
2983
|
if v is not None:
|
|
2867
2984
|
gmeans_acc.setdefault(d, []).append(float(v))
|
|
2868
|
-
_check_cancel(ev)
|
|
2869
2985
|
return scored, gmeans_acc
|
|
2870
2986
|
|
|
2871
2987
|
gscored, gmeans_acc = run_global_round(critics)
|
|
@@ -3359,8 +3475,8 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
3359
3475
|
if task.get("context"):
|
|
3360
3476
|
crit_prompt += "\n\n## 原始任务背景与附件参考\n" + task["context"]
|
|
3361
3477
|
# AI 味确定性检测(借鉴 oh-story 去AI味):客观参考线随评审下发,
|
|
3362
|
-
#
|
|
3363
|
-
crit_prompt =
|
|
3478
|
+
# 命中才追加——评审官结合上下文判断是否真问题,脚本不直接扣分
|
|
3479
|
+
crit_prompt = aiflavor.inject_into_prompt(crit_prompt, manuscript, task.get("type"))
|
|
3364
3480
|
for agent in critics:
|
|
3365
3481
|
role = "critique-r%d" % r
|
|
3366
3482
|
if agent.get("mode") == "mock":
|
package/app/core/planner.py
CHANGED
|
@@ -114,13 +114,16 @@ def _log_usage(source, role, task, res, agent=None, tool="", model="", provider=
|
|
|
114
114
|
except Exception:
|
|
115
115
|
pass
|
|
116
116
|
|
|
117
|
-
CODE_PLAN_PROMPT = """
|
|
118
|
-
|
|
117
|
+
CODE_PLAN_PROMPT = """你是技术负责人。请先判定任务难度,再按难度拆解为按顺序执行的子任务,
|
|
118
|
+
并给出后续质量步骤。只输出一个 ```json 代码块,不要输出其他内容。JSON 结构:
|
|
119
119
|
{"difficulty": "easy 或 hard", "workflow": {"review_required": true, "max_repair_rounds": 0, "allow_switch": false}, "subtasks": [{"title": "简短标题", "detail": "具体要做什么,给执行工程师的直接指令", "files": ["涉及的文件路径"]}]}
|
|
120
|
-
|
|
120
|
+
难度判定:宁易勿难,绝大多数任务都是 easy——常规增删改查/小函数/格式调整/单点 bug 修复/
|
|
121
|
+
配置调整 = easy;只有跨模块改动/架构调整/复杂算法/安全相关才算 hard,拿不准时一律按 easy。
|
|
121
122
|
质量步骤判定:涉及安全、数据迁移、并发、权限、公共 API 或跨模块改动时必须评审;
|
|
122
123
|
有明确自动验收的低风险小改可不做模型评审。max_repair_rounds 取 0-2,只有复杂任务才建议换将。
|
|
123
|
-
|
|
124
|
+
子任务拆分:easy 一律只拆 1 个子任务——一个子任务把目标完整做完并自检通过即收尾,
|
|
125
|
+
禁止拆出独立的核查/联调/收尾子任务;只有 hard 才允许多个子任务(至多 __N__ 个),
|
|
126
|
+
粒度要可独立验证,且最后一个子任务必须包含整体联调/收尾。
|
|
124
127
|
|
|
125
128
|
## 先探索再计划(重要,借鉴 OpenSpec explore)
|
|
126
129
|
拆解之前先用你的读文件/搜索工具**实际查看工作目录**,找到目标相关的真实文件与函数,再据此拆解。
|
|
@@ -527,7 +530,13 @@ def _norm_subtasks(data):
|
|
|
527
530
|
if clean:
|
|
528
531
|
step["files"] = clean
|
|
529
532
|
steps.append(step)
|
|
530
|
-
|
|
533
|
+
if not steps:
|
|
534
|
+
return None
|
|
535
|
+
# easy 任务执行层钉死单子任务:编排者提示词不服从多拆时,截掉只留首步
|
|
536
|
+
# (联调/收尾要求仅对 hard 生效,easy 单步自检即收尾)。
|
|
537
|
+
if _plan_difficulty(data) == "easy":
|
|
538
|
+
steps = steps[:1]
|
|
539
|
+
return steps
|
|
531
540
|
|
|
532
541
|
|
|
533
542
|
def _plan_difficulty(data):
|