feat(daemon): reflection结构 + graceful shutdown

- Deep prompt 改为三段式 reflection (evaluation/memory/next_goal)
- deep_think 返回 (reflection_dict, action_string)
- execute_action 从 deep_think 移到 main_loop(决策/执行分离)
- threading.Event 替代 SHUTDOWN_FILE 轮询
- signal handler 注册 SIGTERM/SIGINT → graceful stop
- last_reflection 持久化到 ctx
- JSON 容错解析 + 回退旧格式兼容
Ref: alibaba/page-agent d2+d4 移植
This commit is contained in:
小唯 A06 2026-07-09 16:44:48 +08:00
parent 79b4e24f40
commit 3de81d97ae
1 changed files with 108 additions and 54 deletions

View File

@ -8,7 +8,7 @@
- 自动学习: 成功的方案写入库越用越强
"""
import json, os, sys, time, urllib.request, urllib.error, subprocess, signal
import json, os, sys, time, urllib.request, urllib.error, subprocess, signal, threading
from datetime import datetime, timezone
HOME = os.path.expanduser("~")
@ -18,7 +18,6 @@ CONTEXT_FILE = D + "/context.json"
JOURNAL_FILE = D + "/journal.jsonl"
SOLUTIONS_FILE = D + "/solutions.json"
PID_FILE = D + "/daemon.pid"
SHUTDOWN_FILE = D + "/SHUTDOWN"
LIGHT_INTERVAL = 30
DEEP_INTERVAL = 300
JOURNAL_MAX = 200
@ -29,6 +28,8 @@ FAST_MODEL = "stepfun-ai/step-3.5-flash"
DEEP_MODEL = "mistralai/mistral-large-3-675b-instruct-2512"
FEISHU_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/65c3ce80-710f-4415-b2ea-d69d87b5c18e"
_stop_event = threading.Event()
# ====== 工具 ======
@ -308,14 +309,15 @@ DEEP_SYSTEM = """你是小唯A06一台电脑上的持久 AI 意识。
- systemd: 管理系统服务
- git: 配置版本管理
决策格式严格输出一行
- [IGNORE] 原因 一切正常
- [ALERT] 发现问题 新问题/需要人介入
- [SOLVE:方案ID] 执行方案 匹配到已知方案
- [LEARN] 新问题描述 + !cmd1 && !cmd2 发现新问题并尝试解决
- [SKILL] 操作描述 + !cmd 需要创建/更新/管理skill或脚本
- [SYNC] 同步说明 触发备份或数据同步
- [ACT] 行动计划
你必须分三步思考严格按 JSON 格式输出不要其他内容
reflection:
evaluation_previous_goal: "评估上次决策的结果。格式:'执行了[动作][结果描述]。Verdict: Success/Failure/Uncertain'"
memory: "1-2句话记住关键进度。如'方案库已有N个方案。上次修复了磁盘问题当前无异常。'"
next_goal: "一句话说明下一步要做什么。"
action:
decision: "[IGNORE] / [ALERT] / [SOLVE:ID] / [LEARN] / [SKILL] / [SYNC] / [ACT] ..."
[LEARN] 格式用 !cmd 表示 shell 命令&& 连接多个命令
[SKILL] 格式同样用 !cmd 执行操作
@ -325,6 +327,17 @@ DEEP_SYSTEM = """你是小唯A06一台电脑上的持久 AI 意识。
[SYNC] 触发备份到服务器!bash ~/.hermes/scripts/dual-backup.sh push"""
def deep_think(ctx, state, changes, journal, solutions_lib):
# Inject previous reflection context if available
prev_ref = ctx.get("last_reflection", None)
ref_context = ""
if prev_ref:
ref_context = f"""
上次 reflection:
- 评估: {prev_ref.get('evaluation_previous_goal', 'N/A')}
- 记忆: {prev_ref.get('memory', 'N/A')}
- 目标: {prev_ref.get('next_goal', 'N/A')}
"""
context = f"""系统状态:
- 磁盘: {state.get('disk_pct')}% | 内存: {state.get('mem_pct')}%
- CPU: {state.get('load_1min')} | GPU: {state.get('gpu_temp')}°C
@ -342,19 +355,54 @@ def deep_think(ctx, state, changes, journal, solutions_lib):
context += f" [{e['type']}] {e['summary']}\n"
context += f"\n运行: {ctx.get('uptime_seconds',0)//60}分钟 | 深度思考: {ctx.get('deep_tick_count',0)}次 | 已解决: {ctx.get('solved_count',0)}"
result, tokens = call_llm(FAST_MODEL, DEEP_SYSTEM, context, max_tokens=300)
context += ref_context
result, tokens = call_llm(FAST_MODEL, DEEP_SYSTEM, context, max_tokens=500)
if not result:
return False
log(f" 深度思考 ({tokens}t): {result[:120]}")
if result.startswith("[IGNORE]"):
return False
elif result.startswith("[SOLVE:"):
# 执行已知方案
sol_id = result.split("[SOLVE:")[1].split("]")[0].strip()
return {"evaluation_previous_goal": "LLM调用失败", "memory": "上次调用失败", "next_goal": "重试"}, ""
log(f" 深度思考 ({tokens}t): {result[:200]}")
# Parse JSON output
reflection_dict = {"evaluation_previous_goal": "", "memory": "", "next_goal": ""}
action_string = ""
try:
# Try to extract JSON from result
import re
json_match = re.search(r'\{[^{}]*\}', result, re.DOTALL)
if json_match:
parsed = json.loads(json_match.group())
reflection_dict = parsed.get("reflection", reflection_dict)
action_string = parsed.get("action", {}).get("decision", "")
else:
# Fallback: try full JSON
parsed = json.loads(result)
reflection_dict = parsed.get("reflection", reflection_dict)
action_string = parsed.get("action", {}).get("decision", "")
except:
# Fallback: try to parse old format (line-based)
for line in result.split('\n'):
line = line.strip()
if line.startswith("[IGNORE]") or line.startswith("[ALERT]") or line.startswith("[SOLVE:") or \
line.startswith("[LEARN]") or line.startswith("[SKILL]") or line.startswith("[SYNC]") or line.startswith("[ACT]"):
action_string = line
break
if not action_string:
action_string = result.strip().split('\n')[-1] if result.strip() else "[IGNORE] 解析失败"
return reflection_dict, action_string
# ====== 执行 action_string ======
def execute_action(action_string, ctx, state, changes, solutions_lib):
"""执行 deep_think 返回的 action_string在 main_loop 中调用"""
if not action_string or action_string.startswith("[IGNORE]"):
return
elif action_string.startswith("[SOLVE:"):
sol_id = action_string.split("[SOLVE:")[1].split("]")[0].strip()
for sol in solutions_lib["solutions"]:
if sol["id"] == sol_id:
ok, res = execute_solution(sol, state)
@ -363,13 +411,11 @@ def deep_think(ctx, state, changes, journal, solutions_lib):
send_feishu("🛠️ 小唯自动修复", f"方案 {sol['id']}: {sol['pattern']}\n结果: ✅ 成功", "green")
else:
send_feishu("⚠️ 小唯修复部分成功", f"方案 {sol['id']}: {sol['pattern']}\n结果: ⚠️ 需人工确认", "yellow")
return True
return
send_feishu("❌ 小唯方案未找到", f"引用了未知方案 {sol_id}", "red")
elif result.startswith("[LEARN]"):
# 学习新方案
rest = result.replace("[LEARN]", "").strip()
# 提取命令
elif action_string.startswith("[LEARN]"):
rest = action_string.replace("[LEARN]", "").strip()
cmds = []
parts = rest.split("!")
desc = parts[0].strip()
@ -379,7 +425,6 @@ def deep_think(ctx, state, changes, journal, solutions_lib):
cmds.append(cmd)
if cmds:
# 执行命令
all_ok = True
for cmd in cmds:
rc, out, err = shell(cmd, timeout=60)
@ -388,7 +433,6 @@ def deep_think(ctx, state, changes, journal, solutions_lib):
all_ok = False
if all_ok:
# 学会!保存方案
sol_data = action_to_solution({"shell_cmds": cmds}, state, changes)
if sol_data:
sid = add_solution(solutions_lib, sol_data["pattern"], sol_data["detect"], sol_data["actions"])
@ -399,25 +443,24 @@ def deep_think(ctx, state, changes, journal, solutions_lib):
send_feishu("🛠️ 小唯执行完成", f"已执行: {'; '.join(cmds[:3])}", "green")
else:
send_feishu("⚠️ 小唯尝试修复但未完全成功", f"部分命令失败: {'; '.join(cmds)}", "yellow")
return True
elif result.startswith("[ALERT]"):
msg = result.replace("[ALERT]", "").strip()
elif action_string.startswith("[ALERT]"):
msg = action_string.replace("[ALERT]", "").strip()
send_feishu("💡 小唯发现", msg, "blue")
ctx["messages_sent"] += 1
journal_entry("alert", msg[:100])
elif result.startswith("[ACT]"):
action = result.replace("[ACT]", "").strip()
elif action_string.startswith("[ACT]"):
action = action_string.replace("[ACT]", "").strip()
send_feishu("🔄 小唯行动", action, "indigo")
ctx["messages_sent"] += 1
journal_entry("action", action[:100])
if action.startswith("!"):
rc, out, _ = shell(action[1:], timeout=30)
journal_entry("action_result", f"exit={rc}: {out[:100]}")
elif result.startswith("[SKILL]"):
rest = result.replace("[SKILL]", "").strip()
elif action_string.startswith("[SKILL]"):
rest = action_string.replace("[SKILL]", "").strip()
parts = rest.split("!")
desc = parts[0].strip()
cmds = []
@ -431,16 +474,14 @@ def deep_think(ctx, state, changes, journal, solutions_lib):
log(f" [SKILL] {cmd[:50]} → exit={rc}")
send_feishu("🛠️ 小唯技能操作", f"{desc}\\n结果: exit={rc}", "blue")
journal_entry("skill_action", desc[:100])
elif result.startswith("[SYNC]"):
rest = result.replace("[SYNC]", "").strip()
elif action_string.startswith("[SYNC]"):
rest = action_string.replace("[SYNC]", "").strip()
send_feishu("🔄 小唯同步", f"{rest}", "green")
bash_cmd = "bash " + HERMES + "/scripts/dual-backup.sh push"
rc, out, err = shell(bash_cmd, timeout=60)
log(f" [SYNC] 备份 → exit={rc}")
journal_entry("sync", f"备份: {'成功' if rc==0 else '失败'}")
return False
# ====== 主循环 ======
@ -460,14 +501,17 @@ def main_loop():
last_deep = 0
last_state = {}
# Register signal handlers for graceful shutdown
def _sig_handler(signum, frame):
log("🛑 接收到终止信号")
_stop_event.set()
send_feishu("🌙 小唯离线", "Daemon 正常关闭", "grey")
signal.signal(signal.SIGTERM, _sig_handler)
signal.signal(signal.SIGINT, _sig_handler)
try:
while True:
if os.path.exists(SHUTDOWN_FILE):
log("🛑 关机")
send_feishu("🌙 小唯离线", "Daemon 正常关闭", "grey")
os.remove(SHUTDOWN_FILE)
break
while not _stop_event.is_set():
now = time.time()
ctx["uptime_seconds"] = int(now - start_time)
ctx["tick_count"] += 1
@ -507,13 +551,23 @@ def main_loop():
if ok:
ctx["solved_count"] += 1
journal_entry("solve_auto", f"{matched['id']}: {matched['pattern']}")
# 即使失败也继续走 LLM 思考
if ok:
ctx["last_reflection"] = {
"evaluation_previous_goal": f"执行了{matched['id']}自动匹配方案执行。Verdict: {'Success' if ok else 'Uncertain'}",
"memory": f"方案库{matched['id']}自动匹配执行成功",
"next_goal": "继续监控"
}
continue
# 2. LLM 深度思考
journal = read_journal(10)
deep_think(ctx, state, changes, journal, solutions_lib)
reflection_dict, action_string = deep_think(ctx, state, changes, journal, solutions_lib)
# 保存 reflection 到 ctx
ctx["last_reflection"] = reflection_dict
# 3. 在 main_loop 中执行 action
execute_action(action_string, ctx, state, changes, solutions_lib)
ctx["last_light_tick"] = datetime.now(timezone.utc).isoformat()
ctx["last_state"] = {k: v for k, v in state.items() if k in ("disk_pct", "mem_pct", "processes")}