fix(optimizer+daemon): 统计口径与日志刷屏 — cron读jobs键(57任务)、daemon逐行分类(error 60295→~50)、compaction空转刷10万行停刷、深度思考日志改单行
This commit is contained in:
parent
1b21f556f3
commit
5ce7390c2c
|
|
@ -118,6 +118,7 @@ def log_reasoning_step(step_type, message, data=None):
|
||||||
COMPACTION_MODEL = "glm-4-flash"
|
COMPACTION_MODEL = "glm-4-flash"
|
||||||
COMPACTION_RATIO_THRESHOLD = 0.80 # Grok IntraCompactionTrigger 思路:token ratio > 80% 触发摘要
|
COMPACTION_RATIO_THRESHOLD = 0.80 # Grok IntraCompactionTrigger 思路:token ratio > 80% 触发摘要
|
||||||
COMPACTION_MIN_TURNS = 10 # 至少 10 条 pattern 才压缩
|
COMPACTION_MIN_TURNS = 10 # 至少 10 条 pattern 才压缩
|
||||||
|
COMPACTION_MIN_INTERVAL = 300 # 距上次实际压缩至少 5 分钟(2026-09-06 防每 30s tick 空转刷日志)
|
||||||
_compaction_last_run = 0 # 上次压缩时间戳
|
_compaction_last_run = 0 # 上次压缩时间戳
|
||||||
_compaction_count = 0 # 累计压缩次数
|
_compaction_count = 0 # 累计压缩次数
|
||||||
|
|
||||||
|
|
@ -2399,7 +2400,9 @@ def deep_think(ctx, state, changes, journal, solutions_lib):
|
||||||
if not result:
|
if not result:
|
||||||
return {"evaluation_previous_goal": "LLM调用失败", "memory": "上次调用失败", "next_goal": "重试"}, ""
|
return {"evaluation_previous_goal": "LLM调用失败", "memory": "上次调用失败", "next_goal": "重试"}, ""
|
||||||
|
|
||||||
log(f" 深度思考 ({tokens}t): {result[:200]}")
|
# 2026-09-06 修复:result 含换行时 log() 会把整段 LLM 输出折成多行续行
|
||||||
|
# (每 2 分钟一次 deep_tick → 数十万续行),改为压缩成单行再写日志。
|
||||||
|
log(f" 深度思考 ({tokens}t): {' '.join(result[:200].split())}")
|
||||||
|
|
||||||
# Parse JSON output
|
# Parse JSON output
|
||||||
reflection_dict = {"evaluation_previous_goal": "", "memory": "", "next_goal": ""}
|
reflection_dict = {"evaluation_previous_goal": "", "memory": "", "next_goal": ""}
|
||||||
|
|
@ -2712,7 +2715,13 @@ def main_loop():
|
||||||
# ── Grok Build Compaction 系统:自动压缩检查 ───────────────────
|
# ── Grok Build Compaction 系统:自动压缩检查 ───────────────────
|
||||||
state_for_compact = {"patterns": state.get("_patterns", []), "observations": state.get("_observations", [])}
|
state_for_compact = {"patterns": state.get("_patterns", []), "observations": state.get("_observations", [])}
|
||||||
should_cp, cp_reason = _should_compact(ctx, state_for_compact)
|
should_cp, cp_reason = _should_compact(ctx, state_for_compact)
|
||||||
if should_cp:
|
# 2026-09-06 修复:旧代码只要 _should_compact 返回 True(journal>150 常驻成立——
|
||||||
|
# journal_entry→trim_journal 裁剪到 JOURNAL_MAX=200,永远 >150)就 log+尝试压缩,
|
||||||
|
# 而 patterns 为空时 _trigger_compaction 必然早退 → 每 30s light tick 刷一行
|
||||||
|
# "🗜️ Compaction 触发",50 天刷了 10.3 万行(daemon.log 58MB 主源之一)。
|
||||||
|
# 现在:patterns 不足 10 条不空跑;距上次实际压缩 <5 分钟不重复尝试。
|
||||||
|
if should_cp and len(state_for_compact.get("patterns", [])) >= COMPACTION_MIN_TURNS \
|
||||||
|
and time.time() - _compaction_last_run >= COMPACTION_MIN_INTERVAL:
|
||||||
log(f" 🗜️ Compaction 触发: {cp_reason}")
|
log(f" 🗜️ Compaction 触发: {cp_reason}")
|
||||||
_trigger_compaction(ctx, state_for_compact)
|
_trigger_compaction(ctx, state_for_compact)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,17 +48,45 @@ def collect_daemon_metrics():
|
||||||
metrics["learned_solutions"] = ctx.get("learned_count", 0)
|
metrics["learned_solutions"] = ctx.get("learned_count", 0)
|
||||||
|
|
||||||
# 从 daemon.log 提取崩溃和错误
|
# 从 daemon.log 提取崩溃和错误
|
||||||
|
# ⚠️ 2026-09-06 修复:
|
||||||
|
# 1) 只统计日志尾部 2MB(≈最近1-2天),避免把历史(如 07-25 NVML 6145 次崩溃)当"当前崩溃";
|
||||||
|
# 2) 旧代码用子串 count("Error"/"error"/"失败") 会把 deep_think 的 LLM 输出文本
|
||||||
|
# (如 soulful 行"沮丧情绪(失败)") 也数进去 → 报出 60295 个假错误。
|
||||||
|
# 改为逐行分类:只统计 [DAEMON] 时间戳前缀行(daemon 自己的 log() 写的行),
|
||||||
|
# 跳过 LLM 输出续行;崩溃=行首"❌ 崩溃",错误=⚠️/[call_llm] 失败标记行。
|
||||||
log_file = D + "/daemon.log"
|
log_file = D + "/daemon.log"
|
||||||
if os.path.exists(log_file):
|
if os.path.exists(log_file):
|
||||||
with open(log_file) as f:
|
with open(log_file, "rb") as f:
|
||||||
content = f.read()
|
f.seek(0, 2) # 到末尾
|
||||||
metrics["crashes"] = content.count("❌ 崩溃")
|
size = f.tell()
|
||||||
metrics["errors"] = content.count("Error") + content.count("error") + content.count("失败")
|
f.seek(max(0, size - 2 * 1024 * 1024)) # 尾部 2MB
|
||||||
# 模型调用次数
|
content = f.read().decode("utf-8", errors="ignore")
|
||||||
metrics["model_calls"] = content.count("深度思考")
|
line_re = re.compile(r"^\[DAEMON\] \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} (.*)$")
|
||||||
# 从日志提取 token 数
|
call_re = re.compile(r"^\[DAEMON\] \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\s+深度思考 \((\d+)t\)")
|
||||||
token_matches = re.findall(r'\((\d+)t\)', content)
|
err_words = ("失败", "异常", "错误", "failed", "Failed", "超时", "timeout", "Timeout")
|
||||||
metrics["model_tokens"] = sum(int(t) for t in token_matches)
|
crashes = errors = model_calls = model_tokens = 0
|
||||||
|
for line in content.splitlines():
|
||||||
|
m = line_re.match(line)
|
||||||
|
if not m:
|
||||||
|
continue # LLM 输出续行等非 daemon 行,跳过
|
||||||
|
cm = call_re.match(line)
|
||||||
|
if cm:
|
||||||
|
model_calls += 1
|
||||||
|
model_tokens += int(cm.group(1))
|
||||||
|
continue
|
||||||
|
msg = m.group(1).lstrip()
|
||||||
|
if msg.startswith("❌ 崩溃"):
|
||||||
|
crashes += 1
|
||||||
|
elif (msg.startswith(("⚠️", "❌")) or msg.startswith("[call_llm]")) \
|
||||||
|
and any(w in msg for w in err_words):
|
||||||
|
errors += 1
|
||||||
|
elif "Traceback" in msg or re.search(r"\b(Error|ERROR|Exception)\b", msg):
|
||||||
|
errors += 1
|
||||||
|
metrics["crashes"] = crashes
|
||||||
|
metrics["errors"] = errors
|
||||||
|
# 模型调用次数 + token 数(只从"深度思考 (Nt)"调用行取,N=该次调用 tokens)
|
||||||
|
metrics["model_calls"] = model_calls
|
||||||
|
metrics["model_tokens"] = model_tokens
|
||||||
|
|
||||||
return metrics
|
return metrics
|
||||||
|
|
||||||
|
|
@ -74,24 +102,31 @@ def collect_cron_metrics():
|
||||||
}
|
}
|
||||||
|
|
||||||
# 从 cron/jobs.json 读取
|
# 从 cron/jobs.json 读取
|
||||||
|
# ⚠️ 2026-09-06 修复:jobs.json 顶层是 dict {"jobs": [57个任务], "updated_at": ...},
|
||||||
|
# 旧代码把 dict.values() 当任务列表 → total_jobs 只数到顶层键个数(2),
|
||||||
|
# 57 个真实任务全部漏计(报告显示 "2任务 0成功")。
|
||||||
jobs_file = HERMES + "/cron/jobs.json"
|
jobs_file = HERMES + "/cron/jobs.json"
|
||||||
if os.path.exists(jobs_file):
|
if os.path.exists(jobs_file):
|
||||||
with open(jobs_file) as f:
|
with open(jobs_file) as f:
|
||||||
try:
|
try:
|
||||||
jobs = json.load(f)
|
data = json.load(f)
|
||||||
# 处理不同格式
|
if isinstance(data, dict):
|
||||||
if isinstance(jobs, dict):
|
jobs = data.get("jobs", [])
|
||||||
jobs = [v for v in jobs.values()]
|
# 兜底: 兼容 {job_id: job} 形态
|
||||||
elif isinstance(jobs, list):
|
if not isinstance(jobs, list) and jobs:
|
||||||
pass
|
jobs = list(jobs.values())
|
||||||
|
elif isinstance(data, list):
|
||||||
|
jobs = data
|
||||||
|
else:
|
||||||
|
jobs = []
|
||||||
|
|
||||||
metrics["total_jobs"] = len(jobs)
|
metrics["total_jobs"] = len(jobs)
|
||||||
for j in jobs:
|
for j in jobs:
|
||||||
if isinstance(j, dict):
|
if isinstance(j, dict):
|
||||||
status = j.get("last_status", "")
|
status = j.get("last_status") or ""
|
||||||
if status == "ok":
|
if status == "ok":
|
||||||
metrics["ok_jobs"] += 1
|
metrics["ok_jobs"] += 1
|
||||||
elif status and status != "ok":
|
elif status in ("error", "failed"):
|
||||||
metrics["failed_jobs"] += 1
|
metrics["failed_jobs"] += 1
|
||||||
if j.get("no_agent"):
|
if j.get("no_agent"):
|
||||||
metrics["no_agent_jobs"] += 1
|
metrics["no_agent_jobs"] += 1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue