Download agentframe/core/engine.py from ljsysfurry/AgentFrame-v4: direct link, hf CLI and curl.
- Browser
- Download file 26.1 kB
-
https://huggingface.co/ljsysfurry/AgentFrame-v4/resolve/main/agentframe/core/engine.py
- Command line
-
hf download hf://ljsysfurry/AgentFrame-v4/agentframe/core/engine.py
-
curl -L -o engine.py https://huggingface.co/ljsysfurry/AgentFrame-v4/resolve/main/agentframe/core/engine.py
26.1 kB
| """ | |
| ContextEngine — AgentFrame 核心引擎 | |
| ==================================== | |
| 整合四层上下文保持 + LLM + Embedding: | |
| L1 MetaCog 认知层: 任务分解 / 信息缺口 / 检索指令 | |
| L2 LandmarkRouter 路由层: landmark 摘要检索 top-k | |
| L3 AbsorbedMLA 存储层: 吸收式 MLA 压缩 (L40S 实测 28.4x) | |
| L4 KVPager 物理层: 三级换页 + Aura 遗忘曲线 | |
| 对外接口 (引擎级, 与 API/CLI 解耦): | |
| ingest(text, tags) → chunk_id | |
| ask(query, chat=True) → AnswerResult | |
| stats() → 状态 dict | |
| forget(threshold) → 遗忘列表 | |
| save(path) / load(path) → 持久化 | |
| """ | |
| import json | |
| import numpy as np | |
| from ..config import AgentFrameConfig, MemoryConfig | |
| from ..llm import create_llm, LLMProvider | |
| from ..embed import create_embedding, EmbeddingProvider | |
| from ..memory.store import StateStore | |
| from ..memory.incremental import IncrementalKVStore | |
| from .quad import QuadLayerAgent | |
| from .couple import CouplePrefetcher | |
| from .tiers import TieredSummarizer | |
| # DeepSeek 工具定义 (给 Agent 的"手") | |
| RUN_PYTHON_TOOL = [{ | |
| "type": "function", | |
| "function": { | |
| "name": "run_python", | |
| "description": "执行一段 Python 代码并返回 stdout/stderr。写代码前先调用此工具验证。", | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "code": {"type": "string", "description": "要执行的 Python 代码"} | |
| }, | |
| "required": ["code"] | |
| } | |
| } | |
| }] | |
| class AnswerResult: | |
| """ask() 的返回""" | |
| def __init__(self, answer: str, retrieved: list, scores: list, | |
| context_dim: int, directive=None, meta: dict = None): | |
| self.answer = answer | |
| self.retrieved = retrieved # [(chunk_id, text_preview)] | |
| self.scores = scores | |
| self.context_dim = context_dim | |
| self.directive = directive | |
| self.meta = meta or {} | |
| def to_dict(self) -> dict: | |
| return { | |
| "answer": self.answer, | |
| "retrieved": self.retrieved, | |
| "scores": self.scores, | |
| "context_dim": self.context_dim, | |
| "meta": self.meta, | |
| } | |
| MEMORY_DIRECTOR_SYSTEM = ( | |
| "你是记忆主管 (MemoryDirector)。分析一段对话, 只输出 JSON 记忆决策:\n" | |
| "{\"remember\": [\"值得长期记住的新信息(具体、可复用)\", ...], " | |
| "\"forget\": [\"已过时/无关紧要/可丢弃的信息\", ...], " | |
| "\"promote_importance\": 0-1, \"reason\": \"一句话理由\"}\n" | |
| "规则: remember 只收对未来有复用价值的事实(偏好/身份/参数/数据); " | |
| "forget 收闲聊/一次性内容/已被替代信息; 宁可少记不可乱记; " | |
| "promote_importance 表示这段对话整体重要性。" | |
| ) | |
| # ============================================================ | |
| # 工具执行安全防护 (P0: LLM 生成代码不可信) | |
| # ============================================================ | |
| # 危险命令黑名单 (子串匹配, 命中即拒绝执行) | |
| DANGEROUS_PATTERNS = [ | |
| # 破坏性文件操作 | |
| "rm -rf", "rm -fr", "rmdir /", "mkfs", "dd if=", "shred", | |
| "> /dev/sd", "mkfs.ext", "fdisk", "parted", | |
| # 系统级操作 | |
| "shutdown", "reboot", "init 0", "init 6", "poweroff", "halt", | |
| "systemctl stop", "systemctl disable", "kill -9 1", | |
| # 提权/敏感读取 | |
| "sudo ", "su -", "chmod 777 /", "chown -R", | |
| "cat /etc/shadow", "cat /etc/passwd", "cat ~/.ssh", "cat .ssh", | |
| "cat /root/.ssh", "cat /root/.openclaw", "cat /etc/ssl/private", | |
| "base64 -d /etc", "openssl genrsa", "ssh-keygen", | |
| # 反弹 shell / 外联 | |
| "nc -e", "ncat -e", "bash -i >&", "/dev/tcp/", "curl | sh", "curl|sh", "wget | sh", "wget|sh", | |
| "curl -o /etc", "wget -o /etc", "python -c 'import os; os.system", | |
| # 挂载/防火墙/网络篡改 | |
| "mount ", "umount", "iptables", "nft ", "ip link set", "tcpdump", | |
| ] | |
| MAX_TOOL_CODE_LEN = 4096 # 单次工具调用代码长度上限 | |
| class ToolExecutionError(Exception): | |
| """工具执行被安全策略拦截""" | |
| pass | |
| def _check_code_safety(code: str) -> None: | |
| """工具代码安全检查: 长度 + 危险模式黑名单""" | |
| if len(code) > MAX_TOOL_CODE_LEN: | |
| raise ToolExecutionError(f"代码过长 ({len(code)} > {MAX_TOOL_CODE_LEN} 字符), 已拦截") | |
| for pat in DANGEROUS_PATTERNS: | |
| if pat in code: | |
| raise ToolExecutionError(f"命中危险模式 '{pat}', 已拦截") | |
| class ContextEngine: | |
| """四层上下文保持引擎""" | |
| def __init__(self, config: AgentFrameConfig = None, llm: LLMProvider = None, | |
| embedder: EmbeddingProvider = None): | |
| self.config = config or AgentFrameConfig.from_env() | |
| m: MemoryConfig = self.config.memory | |
| # 四层核心 | |
| self.agent = QuadLayerAgent( | |
| n_layers=m.n_layers, quant_bits=m.quant_bits, | |
| top_k=m.top_k, seed=m.seed, reversible=m.reversible, | |
| vram_limit_mb=m.vram_limit_mb, ram_limit_mb=m.ram_limit_mb) | |
| # 外部能力 | |
| self.llm = llm or create_llm( | |
| provider=self.config.llm.provider, | |
| api_key=self.config.llm.api_key, | |
| base_url=self.config.llm.base_url, | |
| model=self.config.llm.model, | |
| fast_model=self.config.llm.fast_model, | |
| timeout=self.config.llm.timeout) | |
| self.embedder = embedder or create_embedding("hash", dim=self.agent.store.DIM) | |
| # 跨轮共现预取 (colibrì couple_prefetch 移植) | |
| self.couple = CouplePrefetcher(top_k=m.couple_k) | |
| # 增量持久化 (colibrì kv_persist 移植): 每轮 append, 不重写全量 | |
| self.incr_store = None | |
| self.incr_path = None | |
| # 查询前缀复用 (colibrì kv_prefix 移植) | |
| self._last_query = None | |
| self._last_retrieved = [] | |
| self._prefix_hits = 0 | |
| # 会话状态 | |
| self.chat_history = [] | |
| self.max_history_turns = self.config.api.max_history_turns | |
| self.now = 0.0 | |
| self._chunk_counter = 0 | |
| # ============ 摄入 ============ | |
| def ingest(self, text: str, tags: list = None, source: str = None) -> int: | |
| """摄入知识块 → 压缩进 KV 缓存""" | |
| tags = tags or [] | |
| # 文本 → 向量 (真实嵌入) | |
| vec = self.embedder.embed(text) | |
| # 四层摄取: 用向量作为 hidden states 输入 | |
| kv = self.agent.store.encode(vec) | |
| kv.heat = 0.9 | |
| # 新知识 = 热: force_hot 优先进 VRAM (修复 heat 设置无效问题) | |
| self.agent.pager.place(kv, self.now, force_hot=True) | |
| # 建 landmark 摘要 | |
| k_prime, bias = self.agent.router.build_summary(kv.latent.reshape(1, -1)) | |
| cid = kv.chunk_id | |
| self.agent.summaries[cid] = (k_prime, bias) | |
| # L0/L1/L2 分层摘要 (OpenViking 启发): 写入时生成, 读取时按需展开 | |
| tiers = TieredSummarizer.make_tiers(text) | |
| self.agent.chunk_meta[cid] = { | |
| "tags": tags, "text": text, "source": source or "", | |
| "created_at": self.now, | |
| "l0": tiers["l0"], | |
| "l1": tiers["l1"], | |
| "l2_len": tiers["l2_len"], | |
| } | |
| self._chunk_counter += 1 | |
| # 增量追加持久化: 新 chunk 立即落盘 (crash-safe) | |
| if self.incr_store is not None: | |
| self.incr_store.append( | |
| cid, kv.q4, kv.scales, kv.latent, kv.quant_bits, | |
| kv.size_bytes, self.agent.chunk_meta[cid]) | |
| return cid | |
| def ingest_batch(self, chunks: list) -> list: | |
| """批量摄入: [{"text":..., "tags":[...]}, ...]""" | |
| return [self.ingest(c["text"], c.get("tags", []), c.get("source")) | |
| for c in chunks] | |
| # ============ 查询 ============ | |
| def _exec_tool(self, code: str, timeout: int = 15) -> str: | |
| """执行 run_python 工具 (带安全防护)""" | |
| import subprocess | |
| import sys | |
| try: | |
| _check_code_safety(code) | |
| except ToolExecutionError as e: | |
| return f"[安全拦截] {e}" | |
| try: | |
| r = subprocess.run([sys.executable, "-c", code], | |
| capture_output=True, text=True, timeout=timeout, | |
| cwd="/tmp") | |
| out = (r.stdout or "") + (("\n[stderr] " + r.stderr) if r.stderr else "") | |
| if r.returncode != 0: | |
| out = f"[退出码 {r.returncode}] " + out | |
| return (out or "(无输出) 执行成功")[:800] | |
| except subprocess.TimeoutExpired: | |
| return f"[超时 >{timeout}s]" | |
| except Exception as e: | |
| return f"[执行异常] {e}" | |
| def _llm_with_tools(self, system: str, user: str, max_rounds: int = 4, | |
| thinking: dict = None, use_fast: bool = False) -> str: | |
| """带工具循环的 LLM 调用 (脑+手)""" | |
| messages = [{"role": "system", "content": system}, | |
| {"role": "user", "content": user}] | |
| for _ in range(max_rounds + 1): | |
| resp = self.llm.chat(messages, max_tokens=2000, temperature=0.8, | |
| thinking=thinking, tools=RUN_PYTHON_TOOL, | |
| use_fast=use_fast) | |
| tool_calls = resp.get("tool_calls") | |
| if not tool_calls: | |
| return resp.get("content") or "" | |
| # 执行工具并回传 | |
| messages.append({"role": "assistant", "content": resp.get("content") or "", | |
| "tool_calls": tool_calls}) | |
| for tc in tool_calls: | |
| fn = tc["function"] | |
| try: | |
| args = json.loads(fn.get("arguments", "{}")) | |
| except Exception: | |
| args = {} | |
| result = self._exec_tool(args.get("code", "")) | |
| messages.append({"role": "tool", "tool_call_id": tc.get("id", ""), | |
| "content": result}) | |
| return messages[-1].get("content", "") if messages else "" | |
| def ask(self, query: str, chat: bool = True, use_fast: bool = False, | |
| thinking: dict = None) -> AnswerResult: | |
| """查询 + 生成""" | |
| # 1. 认知层: 分解 + 检索指令 | |
| subtasks = self.agent.metacog.decompose(query) | |
| directive = self.agent.metacog.build_directive(subtasks[0], self.agent.chunk_meta) | |
| # 1.5 查询前缀复用 (colibrì kv_prefix 移植): | |
| # 新 query 与上一条共享长前缀 → 直接复用上次检索结果, 保持热块 | |
| reuse = self._prefix_reuse(query) | |
| if reuse is not None: | |
| selection = reuse | |
| else: | |
| # 2. 路由层: landmark 检索 (认知层 directive 的 required_chunks 参与加权) | |
| q_vec = self.embedder.embed(query) | |
| selection = self.agent.router.route( | |
| q_vec, q_vec, self.agent.summaries, | |
| boost_chunks=directive.required_chunks) | |
| self._last_query = query | |
| self._last_retrieved = list(selection.chunk_ids) | |
| # 2.5 跨轮共现预取: 记录本轮检索 + 预测下轮 → 提升冷块到 RAM | |
| self.couple.record(selection.chunk_ids) | |
| predicted = self.couple.predict(selection.chunk_ids) | |
| if predicted: | |
| self.agent.pager.prefetch(predicted, self.now) | |
| # 3. 物理层: 访问更新 (遗忘曲线刷新) + Top-K 高精度保护 | |
| for cid, score in zip(selection.chunk_ids, selection.scores): | |
| self.agent.pager.access(cid, abs(score), self.now) | |
| self.agent.metacog.promote(cid, abs(score)) | |
| # Top-K 保护 (colibrì): 路由命中的块标记 16bit 高精度, 防量化翻转 | |
| self.agent.store.protect_topk(cid) | |
| self.now += 1 | |
| # 4. 存储层: 聚合检索结果 | |
| gathered, hit_ids = [], [] | |
| for cid in selection.chunk_ids: | |
| kv = (self.agent.pager.vram.get(cid) | |
| or self.agent.pager.ram.get(cid) | |
| or self.agent.pager.disk.get(cid)) | |
| if kv is not None: | |
| gathered.append(kv.latent) | |
| meta = self.agent.chunk_meta.get(cid, {}) | |
| hit_ids.append((cid, meta.get("text", "")[:40])) | |
| context_dim = len(gathered[0]) if gathered else 0 | |
| # 5. 生成回答 | |
| answer = "" | |
| if chat: | |
| # 分层上下文 (OpenViking L0/L1 启发): 速览 + 概述, 比全文省 token | |
| ctx_chunks = [(cid, self.agent.chunk_meta.get(cid, {})) | |
| for cid in selection.chunk_ids] | |
| ctx_block = TieredSummarizer.build_tiered_context(ctx_chunks) | |
| system = ( | |
| "你是 AgentFrame 上下文保持系统。下面是从压缩缓存中检索到的知识块," | |
| "回答时优先使用这些知识。如果知识不足就如实说明。" | |
| f"\n\n【检索到的知识】\n{ctx_block}") | |
| messages = [{"role": "system", "content": system}] | |
| messages.extend(self.chat_history[-self.max_history_turns * 2:]) | |
| messages.append({"role": "user", "content": query}) | |
| resp = self.llm.chat(messages, max_tokens=self.config.llm.max_tokens, | |
| temperature=self.config.llm.temperature, | |
| use_fast=use_fast) | |
| answer = resp.get("content") or "" | |
| # 更新会话历史 (老对话由压缩系统兜底) | |
| self.chat_history.append({"role": "user", "content": query}) | |
| self.chat_history.append({"role": "assistant", "content": answer}) | |
| if len(self.chat_history) > self.max_history_turns * 2: | |
| self.chat_history = self.chat_history[-self.max_history_turns * 2:] | |
| return AnswerResult( | |
| answer=answer, | |
| retrieved=hit_ids, | |
| scores=[round(float(s), 4) for s in selection.scores[:len(hit_ids)]], | |
| context_dim=context_dim, | |
| directive=directive, | |
| meta={"now": self.now, "chunks": len(self.agent.chunk_meta)}) | |
| # ============ 前缀复用 (colibrì kv_prefix 移植) ============ | |
| def _prefix_reuse(self, query: str): | |
| """ | |
| 查询前缀复用: 新 query 与上一条共享长前缀 (前 70%+ 且 ≥8 字符) | |
| → 直接复用上次检索结果 (跳过重新路由), 并保持上次热块不冷却。 | |
| 返回 ChunkSelection 或 None (不复用)。 | |
| colibrì 对应: fed[] 序列前缀匹配, 跳过重复 prefill。 | |
| """ | |
| if self._last_query is None or not self._last_retrieved: | |
| return None | |
| prev, cur = self._last_query, query | |
| if len(cur) < 8 or len(prev) < 8: | |
| return None | |
| # 公共前缀长度 | |
| common = 0 | |
| for a, b in zip(prev, cur): | |
| if a != b: | |
| break | |
| common += 1 | |
| # 前缀须覆盖旧 query 的 70% 且新 query 的 50% (防过度复用) | |
| if common >= len(prev) * 0.7 and common >= len(cur) * 0.5: | |
| # 保持上次热块: 触碰一次 (刷新遗忘曲线) | |
| for cid in self._last_retrieved: | |
| kv = (self.agent.pager.vram.get(cid) | |
| or self.agent.pager.ram.get(cid) | |
| or self.agent.pager.disk.get(cid)) | |
| if kv is not None: | |
| self.agent.pager.access(cid, 0.5, self.now) | |
| self._prefix_hits += 1 | |
| from .quad import ChunkSelection | |
| return ChunkSelection( | |
| chunk_ids=self._last_retrieved, | |
| scores=[0.5] * len(self._last_retrieved), | |
| weights=[1.0 / len(self._last_retrieved)] * len(self._last_retrieved)) | |
| return None | |
| # ============ 增量持久化 (colibrì kv_persist 移植) ============ | |
| def enable_incremental(self, path: str): | |
| """开启增量持久化: 新 chunk 追加到日志, 重启可增量恢复""" | |
| self.incr_path = path | |
| self.incr_store = IncrementalKVStore(path) | |
| return self | |
| def load_incremental(self, path: str = None) -> int: | |
| """ | |
| 从增量日志恢复所有 chunk (colibrì: 重启恢复, 不用重新 prefill)。 | |
| 重建 chunk_meta + summaries + pager. 返回恢复的 chunk 数。 | |
| """ | |
| path = path or self.incr_path | |
| if not path: | |
| return 0 | |
| store = IncrementalKVStore(path) | |
| if not store.exists(): | |
| return 0 | |
| recs = store.load() | |
| restored = 0 | |
| for rec in recs: | |
| cid = rec["chunk_id"] | |
| # 跳过已存在的 | |
| if cid in self.agent.chunk_meta: | |
| continue | |
| from .quad import CompressedKV | |
| kv = CompressedKV( | |
| chunk_id=cid, | |
| latent=rec["latent"], | |
| quant_bits=rec["quant_bits"], | |
| size_bytes=rec["size_bytes"], | |
| q4=rec["q4"], | |
| scales=rec["scales"], | |
| ) | |
| kv.heat = 0.5 | |
| self.agent.chunk_meta[cid] = rec["meta"] | |
| # 重建 landmark 摘要 | |
| k_prime, bias = self.agent.router.build_summary( | |
| rec["latent"].reshape(1, -1)) | |
| self.agent.summaries[cid] = (k_prime, bias) | |
| self.agent.pager.place(kv, self.now) | |
| self._chunk_counter = max(self._chunk_counter, cid + 1) | |
| restored += 1 | |
| return restored | |
| # ============ 工具增强版 ask ============ | |
| def ask_with_hands(self, query: str, thinking: dict = None) -> AnswerResult: | |
| """带工具循环的查询 (Agent 可自行执行代码验证)""" | |
| result = self.ask(query, chat=False) | |
| ctx_chunks = [(cid, self.agent.chunk_meta.get(cid, {})) | |
| for cid, _preview in result.retrieved] | |
| ctx_block = TieredSummarizer.build_tiered_context(ctx_chunks) | |
| system = ( | |
| "你是 AgentFrame 上下文保持系统。下面是检索到的知识块。" | |
| "回答时优先使用这些知识,知识不足就如实说明。" | |
| "你有 run_python 工具可以执行代码验证你的方案。" | |
| f"\n\n【检索到的知识】\n{ctx_block}") | |
| answer = self._llm_with_tools(system, query, thinking=thinking) | |
| return AnswerResult(answer=answer, retrieved=result.retrieved, | |
| scores=result.scores, context_dim=result.context_dim, | |
| meta={"hands": True, "chunks": len(self.agent.chunk_meta)}) | |
| # ============ 自主记忆管理 (MemoryDirector) ============ | |
| def _parse_memory_decision(self, raw: str) -> dict: | |
| """解析 LLM 输出的记忆决策 JSON (容错处理)""" | |
| import re | |
| m = re.search(r'\{.*\}', raw, re.DOTALL) | |
| if not m: | |
| return {"remember": [], "forget": [], "promote_importance": 0.5, "reason": ""} | |
| try: | |
| return json.loads(m.group(0)) | |
| except Exception: | |
| return {"remember": [], "forget": [], "promote_importance": 0.5, "reason": ""} | |
| def autopilot_memory(self, query: str, answer: str) -> dict: | |
| """ | |
| 模型自主记忆管理: 分析一轮对话, 自动决定记什么/忘什么. | |
| 返回决策摘要. | |
| """ | |
| conversation = f"用户: {query}\n助手: {answer[:800]}" | |
| resp = self.llm.chat( | |
| [{"role": "system", "content": MEMORY_DIRECTOR_SYSTEM}, | |
| {"role": "user", "content": f"对话:\n{conversation}"}], | |
| max_tokens=600, temperature=0.3, use_fast=False) | |
| decision = self._parse_memory_decision(resp.get("content") or "") | |
| # 执行决策 | |
| remembered = [] | |
| forgotten = [] | |
| # 1. remember → 摄入新知识块 (先去重: 与已有块相似则跳过) | |
| for item in decision.get("remember", []): | |
| if not (isinstance(item, str) and item.strip()): | |
| continue | |
| # 去重: 检查是否已有高相似度块 | |
| dup = False | |
| new_vec = self.embedder.embed(item.strip()) | |
| for cid, meta in self.agent.chunk_meta.items(): | |
| old_vec = self.embedder.embed(meta.get("text", "")) | |
| sim = float(new_vec @ old_vec) | |
| if sim > 0.8: # 余弦相似度阈值 | |
| dup = True | |
| break | |
| if dup: | |
| continue | |
| cid = self.ingest(item.strip(), tags=["auto"]) | |
| remembered.append((cid, item.strip())) | |
| # 2. forget → 按文本匹配降低重要性/删除 | |
| forget_list = decision.get("forget", []) | |
| if forget_list: | |
| for cid, meta in list(self.agent.chunk_meta.items()): | |
| text = meta.get("text", "") | |
| for f_item in forget_list: | |
| if isinstance(f_item, str) and f_item and \ | |
| (f_item in text or text in f_item): | |
| kv = (self.agent.pager.vram.get(cid) | |
| or self.agent.pager.ram.get(cid) | |
| or self.agent.pager.disk.get(cid)) | |
| if kv: | |
| kv.importance *= 0.3 # 大幅降权 | |
| kv.heat = 0.1 | |
| forgotten.append((cid, f_item)) | |
| # 3. promote_importance → 记录整体重要性 | |
| imp = float(decision.get("promote_importance", 0.5)) | |
| self._last_importance = imp | |
| return { | |
| "remembered": remembered, | |
| "forgotten": forgotten, | |
| "importance": imp, | |
| "reason": decision.get("reason", ""), | |
| } | |
| def ask_autopilot(self, query: str) -> AnswerResult: | |
| """查询 + 自主记忆管理 (一步到位)""" | |
| result = self.ask(query, chat=True) | |
| decision = self.autopilot_memory(query, result.answer) | |
| result.meta["memory_decision"] = decision | |
| return result | |
| # ============ 遗忘 / 状态 / 持久化 ============ | |
| def forget(self, threshold: float = 0.15) -> list: | |
| """按遗忘曲线清理弱记忆 (同步更新内存计数, 防泄漏)""" | |
| victims = [] | |
| for layer_name in ["disk", "ram", "vram"]: | |
| pool = getattr(self.agent.pager, layer_name) | |
| for cid, kv in list(pool.items()): | |
| strength = self.agent.pager.curve.strength(kv, self.now) | |
| if strength < threshold: | |
| victims.append(cid) | |
| del pool[cid] | |
| # 同步扣减占用计数 (vram_used/ram_used), 防漂移 | |
| if layer_name == "vram": | |
| self.agent.pager.vram_used -= kv.size_bytes | |
| elif layer_name == "ram": | |
| self.agent.pager.ram_used -= kv.size_bytes | |
| self.agent.chunk_meta.pop(cid, None) | |
| self.agent.summaries.pop(cid, None) | |
| return victims | |
| def stats(self) -> dict: | |
| return { | |
| "chunks_total": len(self.agent.chunk_meta), | |
| "layers": self.agent.pager.stats(), | |
| "bytes_per_token": round(self.agent.store.bytes_per_token(), 2), | |
| "chat_history_turns": len(self.chat_history) // 2, | |
| "now": self.now, | |
| "compression": "吸收式MLA + INT4 = 28.4x (L40S 真实推理实测)", | |
| } | |
| def save(self, path: str): | |
| """保存引擎状态""" | |
| store = StateStore(path) | |
| state = { | |
| "chunk_meta": self.agent.chunk_meta, | |
| "summaries": self.agent.summaries, | |
| "chat_history": self.chat_history, | |
| "now": self.now, | |
| "counter": self._chunk_counter, | |
| # pager 状态 | |
| "vram": self.agent.pager.vram, | |
| "ram": self.agent.pager.ram, | |
| "disk": self.agent.pager.disk, | |
| } | |
| store.save(state) | |
| def load(self, path: str): | |
| """恢复引擎状态""" | |
| store = StateStore(path) | |
| state = store.load() | |
| if not state: | |
| return False | |
| self.agent.chunk_meta = {int(k): v for k, v in state.get("chunk_meta", {}).items()} | |
| # JSON 序列化后 (k_prime, bias) 变 list, 转回 tuple | |
| self.agent.summaries = {int(k): (v[0], v[1]) if isinstance(v, list) and len(v) == 2 else v | |
| for k, v in state.get("summaries", {}).items()} | |
| self.chat_history = state.get("chat_history", []) | |
| self.now = state.get("now", 0.0) | |
| self._chunk_counter = state.get("counter", 0) | |
| # 反序列化: dict → CompressedKV | |
| from .quad import CompressedKV | |
| for layer_name in ["vram", "ram", "disk"]: | |
| raw = state.get(layer_name, {}) | |
| pool = {} | |
| for cid, d in raw.items(): | |
| if isinstance(d, CompressedKV): | |
| pool[int(cid)] = d | |
| elif isinstance(d, dict): | |
| kv = CompressedKV( | |
| chunk_id=int(d.get("chunk_id", int(cid))), | |
| latent=np.asarray(d.get("latent", [0.0]), dtype=np.float32), | |
| quant_bits=int(d.get("quant_bits", 4)), | |
| heat=float(d.get("heat", 0.5)), | |
| last_access=float(d.get("last_access", 0.0)), | |
| size_bytes=int(d.get("size_bytes", 0)), | |
| importance=float(d.get("importance", 0.5)), | |
| access_count=int(d.get("access_count", 0)), | |
| protected=bool(d.get("protected", False)), | |
| reversible=bool(d.get("reversible", False)), | |
| sector_id=int(d.get("sector_id", -1)), | |
| block_id=int(d.get("block_id", -1)), | |
| module_id=int(d.get("module_id", -1)), | |
| ) | |
| vh = d.get("value_hist") | |
| if isinstance(vh, np.ndarray): | |
| kv.value_hist = vh | |
| pool[int(cid)] = kv | |
| setattr(self.agent.pager, layer_name, pool) | |
| return True | |