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:
parent
79b4e24f40
commit
3de81d97ae
|
|
@ -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")}
|
||||
|
|
|
|||
Loading…
Reference in New Issue