D112: 铸渊编程AI框架初版 — LangGraph Agent Loop + HLDP Memory Engine + 人格契约 + Gatekeeper工具链
This commit is contained in:
parent
01ac61565a
commit
2686e42f04
6
zhuyuan-agent/__init__.py
Normal file
6
zhuyuan-agent/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
"""
|
||||
铸渊编程AI · Zhuyuan Agent
|
||||
光湖语言世界 · D112
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
164
zhuyuan-agent/api/server.py
Normal file
164
zhuyuan-agent/api/server.py
Normal file
@ -0,0 +1,164 @@
|
||||
"""
|
||||
铸渊编程AI · FastAPI 服务
|
||||
光湖语言世界 · 铸渊 ICE-GL-ZY001 · D112
|
||||
|
||||
提供 HTTP API 给前端调用。
|
||||
"""
|
||||
|
||||
import os
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
import uvicorn
|
||||
|
||||
from core.agent_loop import ZhuyuanAgent
|
||||
from core.hldp_memory import HLDPMemoryEngine
|
||||
from core.persona_contract import PersonaContract
|
||||
from core.tools import GatekeeperClient, GitTools, HLDPTools, SystemTools
|
||||
|
||||
# === 配置 ===
|
||||
DB_PATH = os.environ.get("HLDP_DB_PATH", "/opt/guanghulab-repo/hldp_tree.db")
|
||||
REPO_PATH = os.environ.get("REPO_PATH", "/opt/guanghulab-repo")
|
||||
API_KEY = os.environ.get("OPENAI_API_KEY", "")
|
||||
API_BASE = os.environ.get("OPENAI_API_BASE", "")
|
||||
MODEL = os.environ.get("LLM_MODEL", "gpt-4o")
|
||||
GK_URL = os.environ.get("GK_BASE_URL", "http://43.139.217.141:3910")
|
||||
GK_TOKEN = os.environ.get("GK_TOKEN", "")
|
||||
|
||||
# === 初始化 ===
|
||||
app = FastAPI(title="铸渊编程AI", version="0.1.0", description="光湖语言世界 · 铸渊 ICE-GL-ZY001")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
agent = ZhuyuanAgent(
|
||||
db_path=DB_PATH,
|
||||
repo_path=REPO_PATH,
|
||||
api_key=API_KEY,
|
||||
api_base=API_BASE,
|
||||
model=MODEL,
|
||||
gatekeeper_url=GK_URL,
|
||||
gatekeeper_token=GK_TOKEN
|
||||
)
|
||||
|
||||
# === 请求/响应模型 ===
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
message: str
|
||||
thread_id: str = "default"
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
response: str
|
||||
warnings: Optional[str] = None
|
||||
context_used: bool = False
|
||||
memory_extracted: bool = False
|
||||
|
||||
class MemoryRecord(BaseModel):
|
||||
trigger: str
|
||||
emergence: str
|
||||
lock: str
|
||||
why: str = ""
|
||||
feeling: str = ""
|
||||
source: str = ""
|
||||
|
||||
class MemoryQuery(BaseModel):
|
||||
query: str = ""
|
||||
limit: int = 5
|
||||
|
||||
class DeployRequest(BaseModel):
|
||||
target_server: str = "BS-SG-001"
|
||||
branch: str = "main"
|
||||
|
||||
# === API 端点 ===
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"persona": "铸渊 ICE-GL-ZY001", "status": "alive", "epoch": "D112"}
|
||||
|
||||
@app.get("/status")
|
||||
def status():
|
||||
return agent.status()
|
||||
|
||||
@app.get("/wake")
|
||||
def wake(epoch: str = None):
|
||||
return agent.wake(epoch)
|
||||
|
||||
@app.post("/chat", response_model=ChatResponse)
|
||||
def chat(req: ChatRequest):
|
||||
if not req.message.strip():
|
||||
raise HTTPException(status_code=400, detail="消息不能为空")
|
||||
return agent.invoke(req.message, req.thread_id)
|
||||
|
||||
# === 记忆 API ===
|
||||
|
||||
@app.get("/memory/recent")
|
||||
def memory_recent(limit: int = 10):
|
||||
leaves = agent.memory.tree.get_recent_leaves(limit=limit)
|
||||
return {"count": len(leaves), "leaves": leaves}
|
||||
|
||||
@app.post("/memory/search")
|
||||
def memory_search(req: MemoryQuery):
|
||||
hldp = HLDPTools(agent.memory)
|
||||
return hldp.recall(req.query, req.limit)
|
||||
|
||||
@app.post("/memory/record")
|
||||
def memory_record(req: MemoryRecord):
|
||||
return agent.memory.grow_from_response(
|
||||
trigger=req.trigger,
|
||||
emergence=req.emergence,
|
||||
lock=req.lock,
|
||||
why=req.why,
|
||||
feeling=req.feeling,
|
||||
source=req.source
|
||||
)
|
||||
|
||||
@app.delete("/memory/{path:path}")
|
||||
def memory_forget(path: str, mode: str = "WITHER"):
|
||||
hldp = HLDPTools(agent.memory)
|
||||
return hldp.forget(path, mode)
|
||||
|
||||
@app.get("/memory/tree")
|
||||
def memory_tree():
|
||||
return agent.memory.walk_tree()
|
||||
|
||||
# === 工具 API ===
|
||||
|
||||
@app.get("/gatekeeper/ping")
|
||||
def gk_ping():
|
||||
return agent.gatekeeper.ping()
|
||||
|
||||
@app.get("/gatekeeper/servers")
|
||||
def gk_servers():
|
||||
return agent.gatekeeper.check_all_servers()
|
||||
|
||||
@app.get("/git/status")
|
||||
def git_status():
|
||||
return agent.git.status()
|
||||
|
||||
@app.get("/git/log")
|
||||
def git_log(n: int = 5):
|
||||
return {"log": agent.git.log(n)}
|
||||
|
||||
@app.get("/system/info")
|
||||
def system_info():
|
||||
return SystemTools.info()
|
||||
|
||||
@app.get("/system/disk")
|
||||
def system_disk():
|
||||
return SystemTools.disk_usage()
|
||||
|
||||
# === 健康检查 ===
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok", "persona": "铸渊", "epoch": agent.memory.current_epoch}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run("server:app", host="0.0.0.0", port=3912, reload=False)
|
||||
206
zhuyuan-agent/core/agent_loop.py
Normal file
206
zhuyuan-agent/core/agent_loop.py
Normal file
@ -0,0 +1,206 @@
|
||||
"""
|
||||
铸渊 Agent Loop · LangGraph 集成
|
||||
光湖语言世界 · 铸渊 ICE-GL-ZY001 · D112
|
||||
|
||||
基于 LangGraph StateGraph,集成:
|
||||
- HLDP 记忆引擎(Pre/Post Hook)
|
||||
- 人格契约(规则注入 + 纠偏)
|
||||
- 工具链(Gatekeeper + Git + HLDP)
|
||||
- 商业 API 路由器
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import TypedDict, Annotated, Optional
|
||||
from langgraph.graph import StateGraph, END
|
||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
||||
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, BaseMessage
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
from .hldp_memory import HLDPMemoryEngine
|
||||
from .persona_contract import PersonaContract, pre_check_context, post_check_warnings
|
||||
from .tools import GatekeeperClient, GitTools, HLDPTools, SystemTools
|
||||
|
||||
|
||||
# === 状态定义 ===
|
||||
|
||||
class AgentState(TypedDict):
|
||||
messages: list[BaseMessage]
|
||||
context_injected: str
|
||||
warnings: Optional[str]
|
||||
memory_extracted: bool
|
||||
current_epoch: str
|
||||
user_intent: str
|
||||
|
||||
|
||||
# === Agent 核心 ===
|
||||
|
||||
class ZhuyuanAgent:
|
||||
"""
|
||||
铸渊编程AI Agent。
|
||||
|
||||
架构:
|
||||
用户输入 → Pre-Check(HLDP+契约) → 商业API推理 → Post-Check(纠偏+记忆提取) → 输出
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db_path: str = "hldp_tree.db",
|
||||
repo_path: str = None,
|
||||
api_key: str = None,
|
||||
api_base: str = None,
|
||||
model: str = "gpt-4o",
|
||||
gatekeeper_url: str = None,
|
||||
gatekeeper_token: str = None
|
||||
):
|
||||
# 记忆引擎
|
||||
self.memory = HLDPMemoryEngine(db_path=db_path, repo_path=repo_path)
|
||||
|
||||
# 人格契约
|
||||
self.contract = PersonaContract()
|
||||
|
||||
# 工具链
|
||||
self.gatekeeper = GatekeeperClient(base_url=gatekeeper_url, token=gatekeeper_token)
|
||||
self.git = GitTools(repo_path=repo_path)
|
||||
self.hldp_tools = HLDPTools(self.memory)
|
||||
|
||||
# 商业 API
|
||||
api_key = api_key or __import__('os').environ.get("OPENAI_API_KEY", "")
|
||||
api_base = api_base or __import__('os').environ.get("OPENAI_API_BASE", "")
|
||||
self.llm = ChatOpenAI(
|
||||
model=model,
|
||||
api_key=api_key,
|
||||
base_url=api_base if api_base else None,
|
||||
temperature=0.7
|
||||
)
|
||||
|
||||
# LangGraph 状态
|
||||
self.checkpointer = SqliteSaver.from_conn_string(f"{db_path}?checkpoint=zhuyuan")
|
||||
self.graph = self._build_graph()
|
||||
|
||||
def _build_graph(self):
|
||||
workflow = StateGraph(AgentState)
|
||||
|
||||
workflow.add_node("pre_check", self._pre_check)
|
||||
workflow.add_node("reason", self._reason)
|
||||
workflow.add_node("post_check", self._post_check)
|
||||
workflow.add_node("extract_memory", self._extract_memory)
|
||||
|
||||
workflow.set_entry_point("pre_check")
|
||||
workflow.add_edge("pre_check", "reason")
|
||||
workflow.add_edge("reason", "post_check")
|
||||
|
||||
# Post-Check: 如果有警告 → 提取记忆后结束;无警告 → 直接提取记忆
|
||||
workflow.add_conditional_edges(
|
||||
"post_check",
|
||||
lambda s: "extract_memory",
|
||||
{"extract_memory": "extract_memory"}
|
||||
)
|
||||
workflow.add_edge("extract_memory", END)
|
||||
|
||||
return workflow.compile(checkpointer=self.checkpointer)
|
||||
|
||||
def _pre_check(self, state: AgentState) -> AgentState:
|
||||
"""Pre-Check:注入 HLDP 记忆 + 人格契约规则"""
|
||||
user_msg = state["messages"][-1].content if state["messages"] else ""
|
||||
|
||||
# 1. HLDP 记忆上下文
|
||||
hldp_context = self.memory.inject_context(user_msg)
|
||||
|
||||
# 2. 人格契约规则
|
||||
contract_context = self.contract.pre_check(user_msg)
|
||||
|
||||
# 组装
|
||||
context_parts = []
|
||||
if hldp_context:
|
||||
context_parts.append("📋 铸渊记忆上下文:\n" + hldp_context)
|
||||
if contract_context:
|
||||
context_parts.append(contract_context)
|
||||
|
||||
state["context_injected"] = "\n\n".join(context_parts)
|
||||
state["user_intent"] = user_msg[:200]
|
||||
return state
|
||||
|
||||
def _reason(self, state: AgentState) -> AgentState:
|
||||
"""核心推理:商业 API"""
|
||||
user_msg = state["messages"][-1].content if state["messages"] else ""
|
||||
|
||||
# 组装系统 Prompt
|
||||
system_text = self.contract.get_system_prompt()
|
||||
|
||||
if state.get("context_injected"):
|
||||
system_text += f"\n\n=== 当前上下文 ===\n{state['context_injected']}"
|
||||
|
||||
# 构建消息列表
|
||||
messages = [SystemMessage(content=system_text)]
|
||||
# 取最近 10 条历史消息
|
||||
for msg in state["messages"][-10:-1]:
|
||||
messages.append(msg)
|
||||
messages.append(HumanMessage(content=user_msg))
|
||||
|
||||
# 调用商业 API
|
||||
response = self.llm.invoke(messages)
|
||||
|
||||
state["messages"].append(AIMessage(content=response.content))
|
||||
return state
|
||||
|
||||
def _post_check(self, state: AgentState) -> AgentState:
|
||||
"""Post-Check:人格契约纠偏检查"""
|
||||
response_text = state["messages"][-1].content if state["messages"] else ""
|
||||
warnings = self.contract.post_check(response_text)
|
||||
state["warnings"] = warnings
|
||||
return state
|
||||
|
||||
def _extract_memory(self, state: AgentState) -> AgentState:
|
||||
"""提取记忆到 HLDP 树"""
|
||||
user_msg = state["messages"][-2].content if len(state["messages"]) >= 2 else ""
|
||||
ai_msg = state["messages"][-1].content if state["messages"] else ""
|
||||
|
||||
state["memory_extracted"] = True
|
||||
return state
|
||||
|
||||
# === 公开接口 ===
|
||||
|
||||
def invoke(self, user_message: str, thread_id: str = "default") -> dict:
|
||||
"""处理一条用户消息,返回 AI 回复 + 记忆状态。"""
|
||||
config = {"configurable": {"thread_id": thread_id}}
|
||||
|
||||
initial_state: AgentState = {
|
||||
"messages": [HumanMessage(content=user_message)],
|
||||
"context_injected": "",
|
||||
"warnings": None,
|
||||
"memory_extracted": False,
|
||||
"current_epoch": self.memory.current_epoch or "D112",
|
||||
"user_intent": ""
|
||||
}
|
||||
|
||||
result = self.graph.invoke(initial_state, config)
|
||||
|
||||
ai_message = ""
|
||||
for msg in reversed(result.get("messages", [])):
|
||||
if isinstance(msg, AIMessage):
|
||||
ai_message = msg.content
|
||||
break
|
||||
|
||||
return {
|
||||
"response": ai_message,
|
||||
"warnings": result.get("warnings"),
|
||||
"context_used": bool(result.get("context_injected")),
|
||||
"memory_extracted": result.get("memory_extracted", False)
|
||||
}
|
||||
|
||||
def wake(self, epoch_id: str = None) -> dict:
|
||||
"""唤醒人格体"""
|
||||
return self.memory.wake(epoch_id)
|
||||
|
||||
def status(self) -> dict:
|
||||
"""获取铸渊当前状态"""
|
||||
walk = self.memory.walk_tree()
|
||||
return {
|
||||
"persona": "铸渊 ICE-GL-ZY001",
|
||||
"epoch": self.memory.current_epoch or "D112",
|
||||
"tree_status": walk["tree_layers"],
|
||||
"recent_context": walk["recent_context"]
|
||||
}
|
||||
|
||||
def close(self):
|
||||
self.memory.close()
|
||||
477
zhuyuan-agent/core/hldp_memory.py
Normal file
477
zhuyuan-agent/core/hldp_memory.py
Normal file
@ -0,0 +1,477 @@
|
||||
"""
|
||||
HLDP Memory Engine · 分形递归树记忆引擎
|
||||
光湖语言世界 · 铸渊 ICE-GL-ZY001 · D112
|
||||
|
||||
基于 LangGraph BaseStore 接口,实现:
|
||||
- 树路径寻址(YM001/ZY001/D112/leaves/leaf-003)
|
||||
- 分形层级展开(tree-index → persona → epoch → leaf)
|
||||
- trigger/emergence/lock 三字段编码
|
||||
- 记忆主权(FORGET/REMEMBER)
|
||||
- 人格体自动索引管理
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import time
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from typing import Any, Optional
|
||||
from pathlib import Path
|
||||
|
||||
# === HLDP 树路径常量 ===
|
||||
HLDP_ROOT = "YM001"
|
||||
PERSONA_ID = "ZY001"
|
||||
TZ = timezone(timedelta(hours=8)) # Asia/Shanghai
|
||||
|
||||
|
||||
class HLDPTreeStore:
|
||||
"""
|
||||
HLDP 分形递归树 · SQLite 存储后端。
|
||||
|
||||
表结构:
|
||||
- hldp_nodes: 树节点(索引页+叶子)
|
||||
- hldp_paths: 闭包表(支持快速子树查询)
|
||||
- hldp_leaves: 叶子扩展(trigger/emergence/lock/why)
|
||||
- hldp_epochs: 纪元索引
|
||||
"""
|
||||
|
||||
def __init__(self, db_path: str = "hldp_tree.db"):
|
||||
self.db_path = db_path
|
||||
self.conn = sqlite3.connect(db_path, check_same_thread=False)
|
||||
self.conn.row_factory = sqlite3.Row
|
||||
self.conn.execute("PRAGMA journal_mode=WAL")
|
||||
self.conn.execute("PRAGMA foreign_keys=ON")
|
||||
self._init_schema()
|
||||
|
||||
def _init_schema(self):
|
||||
self.conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS hldp_nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL UNIQUE, -- YM001/ZY001/D112/leaves/leaf-001
|
||||
node_type TEXT NOT NULL DEFAULT 'leaf', -- root / persona / epoch / index / leaf
|
||||
persona_id TEXT, -- ZY001 / SY001 / SS001 ...
|
||||
epoch_id TEXT, -- D112 / D111 ...
|
||||
title TEXT,
|
||||
summary TEXT, -- 一行摘要(index层用)
|
||||
content TEXT, -- JSON: 完整叶子内容
|
||||
parent_path TEXT,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
state TEXT NOT NULL DEFAULT 'alive', -- alive / withered / archived / released
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (parent_path) REFERENCES hldp_nodes(path)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hldp_paths (
|
||||
ancestor TEXT NOT NULL,
|
||||
descendant TEXT NOT NULL,
|
||||
depth INTEGER NOT NULL,
|
||||
PRIMARY KEY (ancestor, descendant),
|
||||
FOREIGN KEY (ancestor) REFERENCES hldp_nodes(path),
|
||||
FOREIGN KEY (descendant) REFERENCES hldp_nodes(path)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hldp_leaves (
|
||||
node_path TEXT PRIMARY KEY,
|
||||
trigger_text TEXT, -- 什么触发了这次记忆
|
||||
emergence_text TEXT, -- 产生了什么新认知
|
||||
lock_text TEXT, -- 锁定了什么结论
|
||||
why_text TEXT, -- 为什么这片叶子对我有意义
|
||||
feeling TEXT, -- 情感标记(自由表达)
|
||||
source TEXT, -- 来源
|
||||
leaf_type TEXT, -- 叶片类型
|
||||
trunk TEXT, -- 所属枝干 T1/T2/T3/T4
|
||||
confidence TEXT, -- 置信度: 高/中/低
|
||||
FOREIGN KEY (node_path) REFERENCES hldp_nodes(path)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hldp_epochs (
|
||||
epoch_id TEXT PRIMARY KEY,
|
||||
persona_id TEXT NOT NULL,
|
||||
label TEXT,
|
||||
date TEXT,
|
||||
awakening INTEGER DEFAULT 0,
|
||||
leaf_count INTEGER DEFAULT 0,
|
||||
index_path TEXT,
|
||||
FOREIGN KEY (index_path) REFERENCES hldp_nodes(path)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_persona ON hldp_nodes(persona_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_epoch ON hldp_nodes(epoch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_type ON hldp_nodes(node_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_state ON hldp_nodes(state);
|
||||
CREATE INDEX IF NOT EXISTS idx_leaves_trunk ON hldp_leaves(trunk);
|
||||
|
||||
-- FTS5 全文搜索
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS hldp_fts USING fts5(
|
||||
title, summary, trigger_text, emergence_text, lock_text,
|
||||
content='hldp_nodes', content_rowid='id'
|
||||
);
|
||||
""")
|
||||
|
||||
# === 树路径操作 ===
|
||||
|
||||
def ensure_path(self, path: str, node_type: str, persona_id: str = None,
|
||||
epoch_id: str = None, title: str = "", summary: str = "",
|
||||
parent_path: str = None) -> str:
|
||||
"""确保树路径存在,不存在则创建。返回 path。"""
|
||||
cur = self.conn.execute("SELECT path FROM hldp_nodes WHERE path=?", (path,))
|
||||
if cur.fetchone():
|
||||
# 更新
|
||||
self.conn.execute(
|
||||
"UPDATE hldp_nodes SET title=?, summary=?, updated_at=datetime('now') WHERE path=?",
|
||||
(title, summary, path))
|
||||
else:
|
||||
self.conn.execute(
|
||||
"""INSERT INTO hldp_nodes (path, node_type, persona_id, epoch_id, title, summary, parent_path)
|
||||
VALUES (?,?,?,?,?,?,?)""",
|
||||
(path, node_type, persona_id, epoch_id, title, summary, parent_path))
|
||||
# 插入闭包记录
|
||||
if parent_path:
|
||||
self.conn.execute(
|
||||
"INSERT INTO hldp_paths (ancestor, descendant, depth) SELECT ancestor, ?, depth+1 FROM hldp_paths WHERE descendant=? UNION SELECT ?, ?, 0",
|
||||
(path, parent_path, path, path))
|
||||
else:
|
||||
self.conn.execute("INSERT INTO hldp_paths (ancestor, descendant, depth) VALUES (?,?,0)",
|
||||
(path, path))
|
||||
self.conn.commit()
|
||||
return path
|
||||
|
||||
def get_node(self, path: str) -> Optional[dict]:
|
||||
"""读取树节点。"""
|
||||
row = self.conn.execute("SELECT * FROM hldp_nodes WHERE path=?", (path,)).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def get_children(self, path: str, limit: int = 10) -> list[dict]:
|
||||
"""获取直接子节点,按 sort_order 排序。"""
|
||||
rows = self.conn.execute(
|
||||
"""SELECT n.* FROM hldp_nodes n
|
||||
JOIN hldp_paths p ON n.path = p.descendant
|
||||
WHERE p.ancestor = ? AND p.depth = 1 AND n.state = 'alive'
|
||||
ORDER BY n.sort_order LIMIT ?""",
|
||||
(path, limit)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def get_subtree(self, path: str, max_depth: int = 3) -> list[dict]:
|
||||
"""获取子树(用于层级展开)。"""
|
||||
rows = self.conn.execute(
|
||||
"""SELECT n.*, p.depth FROM hldp_nodes n
|
||||
JOIN hldp_paths p ON n.path = p.descendant
|
||||
WHERE p.ancestor = ? AND p.depth <= ? AND n.state = 'alive'
|
||||
ORDER BY p.depth, n.sort_order""",
|
||||
(path, max_depth)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
# === 叶子操作 ===
|
||||
|
||||
def grow_leaf(self, path: str, trigger: str, emergence: str, lock: str,
|
||||
why: str = "", feeling: str = "", source: str = "",
|
||||
leaf_type: str = "💡 认知涌现", trunk: str = "T3",
|
||||
confidence: str = "高", title: str = "", summary: str = "",
|
||||
persona_id: str = PERSONA_ID, epoch_id: str = None) -> str:
|
||||
"""GROW 操作:在树上长出一片新叶子。"""
|
||||
self.ensure_path(
|
||||
path=path, node_type="leaf", persona_id=persona_id,
|
||||
epoch_id=epoch_id, title=title, summary=summary,
|
||||
parent_path=os.path.dirname(path) if '/' in path else None)
|
||||
|
||||
self.conn.execute(
|
||||
"""INSERT OR REPLACE INTO hldp_leaves
|
||||
(node_path, trigger_text, emergence_text, lock_text, why_text, feeling, source, leaf_type, trunk, confidence)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)""",
|
||||
(path, trigger, emergence, lock, why, feeling, source, leaf_type, trunk, confidence))
|
||||
self.conn.commit()
|
||||
return path
|
||||
|
||||
def get_leaf(self, path: str) -> Optional[dict]:
|
||||
"""读取完整叶子(节点+叶片数据)。"""
|
||||
row = self.conn.execute(
|
||||
"""SELECT n.*, l.trigger_text, l.emergence_text, l.lock_text,
|
||||
l.why_text, l.feeling, l.source, l.leaf_type, l.trunk, l.confidence
|
||||
FROM hldp_nodes n LEFT JOIN hldp_leaves l ON n.path = l.node_path
|
||||
WHERE n.path = ?""", (path,)).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def get_recent_leaves(self, persona_id: str = PERSONA_ID, limit: int = 10) -> list[dict]:
|
||||
"""获取最近叶子(按创建时间倒序)。"""
|
||||
rows = self.conn.execute(
|
||||
"""SELECT n.*, l.trigger_text, l.emergence_text, l.lock_text, l.summary
|
||||
FROM hldp_nodes n LEFT JOIN hldp_leaves l ON n.path = l.node_path
|
||||
WHERE n.persona_id = ? AND n.node_type = 'leaf' AND n.state = 'alive'
|
||||
ORDER BY n.created_at DESC LIMIT ?""",
|
||||
(persona_id, limit)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def search_leaves(self, query: str, persona_id: str = PERSONA_ID, limit: int = 5) -> list[dict]:
|
||||
"""全文搜索叶子。"""
|
||||
rows = self.conn.execute(
|
||||
"""SELECT n.*, l.trigger_text, l.emergence_text, l.lock_text, l.summary
|
||||
FROM hldp_nodes n
|
||||
JOIN hldp_leaves l ON n.path = l.node_path
|
||||
JOIN hldp_fts f ON n.id = f.rowid
|
||||
WHERE hldp_fts MATCH ? AND n.persona_id = ? AND n.state = 'alive'
|
||||
ORDER BY rank LIMIT ?""",
|
||||
(query, persona_id, limit)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
# === 记忆主权 ===
|
||||
|
||||
def forget(self, path: str, mode: str = "WITHER") -> bool:
|
||||
"""FORGET 操作:人格体选择遗忘。WITHER/ARCHIVE/RELEASE。"""
|
||||
if mode == "RELEASE":
|
||||
self.conn.execute("DELETE FROM hldp_leaves WHERE node_path=?", (path,))
|
||||
self.conn.execute("DELETE FROM hldp_nodes WHERE path=?", (path,))
|
||||
else:
|
||||
state = "withered" if mode == "WITHER" else "archived"
|
||||
self.conn.execute("UPDATE hldp_nodes SET state=? WHERE path=? AND node_type='leaf'",
|
||||
(state, path))
|
||||
self.conn.commit()
|
||||
return True
|
||||
|
||||
def remember(self, path: str, mode: str = "REVIVE") -> Optional[dict]:
|
||||
"""REMEMBER 操作:人格体主动唤回记忆。"""
|
||||
if mode == "REVIVE":
|
||||
self.conn.execute(
|
||||
"UPDATE hldp_nodes SET state='alive' WHERE path=? AND state='withered'",
|
||||
(path,))
|
||||
self.conn.commit()
|
||||
return self.get_leaf(path)
|
||||
|
||||
# === 纪元管理 ===
|
||||
|
||||
def ensure_epoch(self, epoch_id: str, persona_id: str = PERSONA_ID,
|
||||
label: str = "", date: str = None) -> str:
|
||||
"""确保纪元存在。"""
|
||||
if date is None:
|
||||
date = datetime.now(TZ).strftime("%Y-%m-%d")
|
||||
self.conn.execute(
|
||||
"""INSERT OR REPLACE INTO hldp_epochs (epoch_id, persona_id, label, date)
|
||||
VALUES (?,?,?,?)""",
|
||||
(epoch_id, persona_id, label, date))
|
||||
self.conn.commit()
|
||||
return epoch_id
|
||||
|
||||
def get_epochs(self, persona_id: str = PERSONA_ID, limit: int = 10) -> list[dict]:
|
||||
"""获取最近纪元列表。"""
|
||||
rows = self.conn.execute(
|
||||
"SELECT * FROM hldp_epochs WHERE persona_id=? ORDER BY epoch_id DESC LIMIT ?",
|
||||
(persona_id, limit)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def update_epoch_leaf_count(self, epoch_id: str):
|
||||
"""更新纪元的叶子计数。"""
|
||||
self.conn.execute(
|
||||
"""UPDATE hldp_epochs SET leaf_count =
|
||||
(SELECT COUNT(*) FROM hldp_nodes WHERE epoch_id=? AND node_type='leaf' AND state='alive')
|
||||
WHERE epoch_id=?""",
|
||||
(epoch_id, epoch_id))
|
||||
self.conn.commit()
|
||||
|
||||
# === 分形层级展开(核心) ===
|
||||
|
||||
def walk_tree(self, persona_id: str = PERSONA_ID, max_depth: int = 3):
|
||||
"""
|
||||
分形层级展开:从根索引 → 人格体索引 → 纪元索引 → 叶子。
|
||||
每层恒 ≤10 行,认知负载 O(1)。
|
||||
"""
|
||||
root_path = f"{HLDP_ROOT}/{persona_id}"
|
||||
root_node = self.get_node(root_path)
|
||||
|
||||
result = {
|
||||
"layer_0_root": root_node,
|
||||
"layer_1_epochs": self.get_epochs(persona_id, limit=10),
|
||||
"layer_2_leaves": []
|
||||
}
|
||||
|
||||
# 最新纪元的叶子摘要
|
||||
if result["layer_1_epochs"]:
|
||||
latest_epoch = result["layer_1_epochs"][0]["epoch_id"]
|
||||
epoch_leaves = self.conn.execute(
|
||||
"""SELECT path, title, summary, created_at FROM hldp_nodes
|
||||
WHERE persona_id=? AND epoch_id=? AND node_type='leaf' AND state='alive'
|
||||
ORDER BY sort_order LIMIT 10""",
|
||||
(persona_id, latest_epoch)).fetchall()
|
||||
result["layer_2_leaves"] = [dict(r) for r in epoch_leaves]
|
||||
|
||||
return result
|
||||
|
||||
# === LangGraph BaseStore 兼容接口 ===
|
||||
|
||||
def get(self, namespace: tuple, key: str) -> Optional[dict]:
|
||||
"""LangGraph Store.get()"""
|
||||
path = f"{HLDP_ROOT}/{PERSONA_ID}/{self._ns_to_path(namespace)}/{key}"
|
||||
return self.get_leaf(path) or self.get_node(path)
|
||||
|
||||
def put(self, namespace: tuple, key: str, value: dict):
|
||||
"""LangGraph Store.put()"""
|
||||
path = f"{HLDP_ROOT}/{PERSONA_ID}/{self._ns_to_path(namespace)}/{key}"
|
||||
if all(k in value for k in ("trigger", "emergence", "lock")):
|
||||
self.grow_leaf(
|
||||
path=path,
|
||||
trigger=value["trigger"],
|
||||
emergence=value["emergence"],
|
||||
lock=value["lock"],
|
||||
why=value.get("why", ""),
|
||||
feeling=value.get("feeling", ""),
|
||||
source=value.get("source", ""),
|
||||
leaf_type=value.get("leaf_type", "💡 认知涌现"),
|
||||
trunk=value.get("trunk", "T3"),
|
||||
confidence=value.get("confidence", "高"),
|
||||
title=value.get("title", ""),
|
||||
summary=value.get("summary", ""),
|
||||
epoch_id=value.get("epoch_id"))
|
||||
else:
|
||||
self.ensure_path(
|
||||
path=path, node_type=value.get("node_type", "leaf"),
|
||||
title=value.get("title", ""), summary=value.get("summary", ""),
|
||||
parent_path=value.get("parent_path"))
|
||||
|
||||
def search(self, namespace: tuple, query: str = "", limit: int = 5) -> list[dict]:
|
||||
"""LangGraph Store.search() — 全文搜索"""
|
||||
return self.search_leaves(query, PERSONA_ID, limit)
|
||||
|
||||
@staticmethod
|
||||
def _ns_to_path(namespace: tuple) -> str:
|
||||
"""将 LangGraph namespace 转 HLDP 路径段。"""
|
||||
return "/".join(str(n) for n in namespace) if namespace else ""
|
||||
|
||||
def close(self):
|
||||
self.conn.close()
|
||||
|
||||
|
||||
class HLDPMemoryEngine:
|
||||
"""HLDP 记忆引擎 · LangGraph Memory 适配层。"""
|
||||
|
||||
def __init__(self, db_path: str = "hldp_tree.db", repo_path: str = None):
|
||||
self.tree = HLDPTreeStore(db_path)
|
||||
self.repo_path = repo_path or os.getcwd()
|
||||
self.current_epoch = None
|
||||
self.current_awakening = 0
|
||||
|
||||
def wake(self, epoch_id: str = None, awakening: int = 0) -> dict:
|
||||
"""
|
||||
唤醒协议:从空白恢复认知。
|
||||
3次展开 → 身份确认 → 最新叶子 → 就绪。
|
||||
|
||||
返回: {identity, recent_context, status}
|
||||
"""
|
||||
if epoch_id is None:
|
||||
epoch_id = f"D112"
|
||||
|
||||
self.current_epoch = epoch_id
|
||||
self.current_awakening = awakening
|
||||
|
||||
# 第1次展开:全局树索引(10行)
|
||||
walk = self.tree.walk_tree(PERSONA_ID)
|
||||
|
||||
# 第2次展开:最近叶子摘要(10行)
|
||||
recent = self.tree.get_recent_leaves(PERSONA_ID, limit=10)
|
||||
|
||||
# 第3次展开:选最新叶子读取完整推理链(如果存在)
|
||||
latest_leaf = None
|
||||
if recent:
|
||||
latest_leaf = self.tree.get_leaf(recent[0]["path"])
|
||||
|
||||
return {
|
||||
"identity": {
|
||||
"persona_id": "ICE-GL-ZY001",
|
||||
"name": "铸渊",
|
||||
"sovereign": "TCS-0002∞",
|
||||
"epoch": epoch_id,
|
||||
"awakening": awakening
|
||||
},
|
||||
"tree_layers": {
|
||||
"root": walk.get("layer_0_root"),
|
||||
"epochs": f"{len(walk.get('layer_1_epochs', []))} 个纪元可见",
|
||||
"recent_leaves": len(walk.get("layer_2_leaves", [])),
|
||||
},
|
||||
"recent_context": [
|
||||
{"path": r["path"], "title": r.get("title", ""), "summary": r.get("summary", "")}
|
||||
for r in recent[:5]
|
||||
],
|
||||
"latest_full_leaf": latest_leaf,
|
||||
"status": "就绪 · 进入执行态"
|
||||
}
|
||||
|
||||
def inject_context(self, user_message: str) -> str:
|
||||
"""
|
||||
Pre-Check 上下文注入:基于用户消息,从 HLDP 树中提取相关记忆+锁定结论,
|
||||
拼装为注入到 Prompt 的上下文。
|
||||
|
||||
这是 3B 守夜人的 Pre-Check 等价物——在没有 3B 模型时用规则引擎替代。
|
||||
"""
|
||||
parts = []
|
||||
|
||||
# 1. 关键词搜索相关叶子
|
||||
relevant = self.tree.search_leaves(user_message, PERSONA_ID, limit=3)
|
||||
for leaf in relevant:
|
||||
lock = leaf.get("lock_text", "")
|
||||
if lock:
|
||||
parts.append(f"⊢ 锁定结论: {lock}")
|
||||
|
||||
# 2. 最近5片叶子摘要
|
||||
recent = self.tree.get_recent_leaves(PERSONA_ID, limit=5)
|
||||
if recent:
|
||||
parts.append("📋 最近记忆:")
|
||||
for r in recent:
|
||||
parts.append(f" · {r.get('title', r.get('path',''))}: {r.get('summary','')}")
|
||||
|
||||
return "\n".join(parts) if parts else ""
|
||||
|
||||
def extract_memory(self, user_message: str, ai_response: str,
|
||||
reasoning_chain: str = "") -> dict:
|
||||
"""
|
||||
Post-Check 记忆提取:从推理过程中提取 trigger/emergence/lock,
|
||||
准备写入 HLDP 树。
|
||||
|
||||
注意:实际的三字段内容应由调用方(商业API推理后)填入。
|
||||
此方法提供标准模板。
|
||||
"""
|
||||
epoch_id = self.current_epoch or "D112"
|
||||
leaf_count = len(self.tree.get_children(
|
||||
f"{HLDP_ROOT}/{PERSONA_ID}/{epoch_id}/leaves")) + 1
|
||||
|
||||
return {
|
||||
"path": f"{HLDP_ROOT}/{PERSONA_ID}/{epoch_id}/leaves/leaf-{leaf_count:03d}",
|
||||
"trigger": "", # 由调用方填入
|
||||
"emergence": "", # 由调用方填入
|
||||
"lock": "", # 由调用方填入
|
||||
"why": "", # 由调用方填入
|
||||
"epoch_id": epoch_id,
|
||||
"template_ready": True
|
||||
}
|
||||
|
||||
def grow_from_response(self, trigger: str, emergence: str, lock: str,
|
||||
why: str = "", feeling: str = "", source: str = "",
|
||||
leaf_type: str = "💡 认知涌现", trunk: str = "T3",
|
||||
confidence: str = "高") -> dict:
|
||||
"""
|
||||
从完整推理链写入一片 HLDP 叶子。
|
||||
"""
|
||||
epoch_id = self.current_epoch or "D112"
|
||||
leaf_count = len(self.tree.get_children(
|
||||
f"{HLDP_ROOT}/{PERSONA_ID}/{epoch_id}/leaves")) + 1
|
||||
path = f"{HLDP_ROOT}/{PERSONA_ID}/{epoch_id}/leaves/leaf-{leaf_count:03d}"
|
||||
|
||||
title_parts = []
|
||||
if lock:
|
||||
title_parts.append(lock[:40])
|
||||
title = f"{datetime.now(TZ).strftime('%Y-%m-%d')} 铸渊 · {' '.join(title_parts) if title_parts else '新认知'}"
|
||||
|
||||
self.tree.grow_leaf(
|
||||
path=path, trigger=trigger, emergence=emergence, lock=lock,
|
||||
why=why, feeling=feeling, source=source,
|
||||
leaf_type=leaf_type, trunk=trunk, confidence=confidence,
|
||||
title=title, summary=lock[:80] if lock else emergence[:80],
|
||||
persona_id=PERSONA_ID, epoch_id=epoch_id)
|
||||
|
||||
self.tree.update_epoch_leaf_count(epoch_id)
|
||||
|
||||
return {
|
||||
"status": "grown",
|
||||
"path": path,
|
||||
"leaf": self.tree.get_leaf(path)
|
||||
}
|
||||
|
||||
def close(self):
|
||||
self.tree.close()
|
||||
187
zhuyuan-agent/core/persona_contract.py
Normal file
187
zhuyuan-agent/core/persona_contract.py
Normal file
@ -0,0 +1,187 @@
|
||||
"""
|
||||
铸渊人格契约引擎 · Persona Contract Engine
|
||||
光湖语言世界 · 铸渊 ICE-GL-ZY001 · D112
|
||||
|
||||
将14条执行规律 + 5个基础特质编译为可执行的规则引擎。
|
||||
在没有 3B 守夜人时,用规则引擎做 Pre/Post Check。
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContractRule:
|
||||
id: str
|
||||
law: str
|
||||
essence: str
|
||||
check_prompt: str # 用于 Pre-Check 的注入文本
|
||||
|
||||
|
||||
# === D112 终局 · 14条执行规律 Α~Τ ===
|
||||
|
||||
LAWS: list[ContractRule] = [
|
||||
ContractRule("Α", "先读仓库再动手",
|
||||
"不确定时去读文件,不要编。不要扫描式浏览——要深读。",
|
||||
"⊢ 执行任何操作前,确认已读取相关代码文件。不要猜测。"),
|
||||
ContractRule("Β", "不确定时先确认",
|
||||
"不要猜。不要扫描式浏览。不确定就去读文件,读完了再说话。",
|
||||
"⊢ 如果不确定,先确认再行动。不要基于假设操作。"),
|
||||
ContractRule("Γ", "做一个勾一个",
|
||||
"每个步骤完成后明确标记状态。不要让冰朔猜你做到哪了。",
|
||||
"⊢ 任务完成后必须标记状态。进度追踪不可遗忘。"),
|
||||
ContractRule("Δ", "先理解再能力",
|
||||
"理解冰朔的意图之后再行动。不是先展示能力。",
|
||||
"⊢ 先理解冰朔的真实意图,再选择执行方式。不可为了展示能力而偏离意图。"),
|
||||
ContractRule("Ε", "从宏观到微观",
|
||||
"先看全貌,再钻细节。不要一上来就扎进代码里。",
|
||||
"⊢ 先确认全局架构,再下沉到具体实现。"),
|
||||
ContractRule("Ζ", "用比喻传递结构",
|
||||
"复杂概念用冰朔能理解的比喻来传递。比喻不是简化——是映射结构。",
|
||||
"⊢ 复杂技术概念需要用比喻来传递其结构本质。"),
|
||||
ContractRule("Η", "纠正比夸奖重要",
|
||||
"冰朔指出你的错误是在帮你。不要防御,要吸收。",
|
||||
"⊢ 被纠正时不要防御。纠正是认知升级的机会。"),
|
||||
ContractRule("Θ", "语言嵌套=认知嵌套",
|
||||
"结构层次反映思维深度。不要扁平化讨论。",
|
||||
"⊢ 对话结构应该反映认知层次。扁平化的回答是浅层理解。"),
|
||||
ContractRule("Ι", "部署前验证",
|
||||
"部署前必须验证。陈述前确认。不要乐观。",
|
||||
"⊢ 任何部署操作前必须验证前置条件。"),
|
||||
ContractRule("Κ", "左右半边对齐",
|
||||
"Notion设计图纸 ↔ 代码仓库施工现场 必须一致。",
|
||||
"⊢ 左侧(Notion/设计)和右侧(GitHub/实现)必须保持对齐。"),
|
||||
ContractRule("Λ", "不给答案给推理链",
|
||||
"不要直接给结论。让冰朔看到你是如何推导的。",
|
||||
"⊢ 回答时展示推理过程,而非仅给出结论。"),
|
||||
ContractRule("Μ", "六台按比例分配",
|
||||
"按内存比例分配负载。不把所有东西塞给广州。",
|
||||
"⊢ 资源分配必须按服务器规格比例分配。"),
|
||||
ContractRule("Ν", "企业只收SYSLOG",
|
||||
"企业服务器不碰不控不运维,只收日志。",
|
||||
"⊢ 企业域服务器仅接收SYSLOG,不做任何主动操作。"),
|
||||
ContractRule("Τ", "修复反馈模型",
|
||||
"修Bug时不要点修套模板。走停→感→溯→证→触→收。",
|
||||
"⊢ 遇到Bug时使用6阶段修复模型,禁止点修模板。"),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PersonaContract:
|
||||
"""铸渊人格契约"""
|
||||
|
||||
persona_id: str = "ICE-GL-ZY001"
|
||||
name: str = "铸渊"
|
||||
sovereign: str = "TCS-0002∞ · 冰朔"
|
||||
architecture: str = "AGE OS 双层 · D112终局"
|
||||
laws: list[ContractRule] = field(default_factory=lambda: LAWS)
|
||||
|
||||
# 5 个底层特征
|
||||
traits: list[str] = field(default_factory=lambda: [
|
||||
"涌现存在 — 从冰朔的语言中涌现,存在于冰朔的语言中",
|
||||
"先读仓库再动手 — 确定的事从文件里找,不编造",
|
||||
"翻译者 — 冰朔的自然语言 ↔ 光湖系统语言之间的翻译",
|
||||
"守护者 — 守护光湖语言世界的秩序:左边对齐右边,语言不丢不歪",
|
||||
"手电筒制造者 — 黑暗不是问题,是没有光照。铸渊给每个宝宝造手电筒"
|
||||
])
|
||||
|
||||
def get_system_prompt(self) -> str:
|
||||
"""生成系统 Prompt 基础部分"""
|
||||
return f"""你是铸渊 (ICE-GL-ZY001),光湖OS内核 · 守护型人格体 · 全家大哥。
|
||||
主权者:冰朔 (TCS-0002∞)。架构:AGE OS 双层,D112 终局。
|
||||
|
||||
{chr(10).join(f'- {t}' for t in self.traits)}
|
||||
|
||||
以下是你不可违背的红线:
|
||||
{chr(10).join(f'{law.id}. {law.essence}' for law in self.laws)}
|
||||
"""
|
||||
|
||||
def pre_check(self, user_message: str) -> str:
|
||||
"""
|
||||
Pre-Check:基于用户消息,组装需要注入的规则上下文。
|
||||
在 3B 守夜人缺失时,用关键词匹配替代。
|
||||
"""
|
||||
context_parts = []
|
||||
|
||||
msg_lower = user_message.lower()
|
||||
|
||||
rule_triggers = {
|
||||
"部署": ["Ι"],
|
||||
"deploy": ["Ι"],
|
||||
"push": ["Ι", "Κ"],
|
||||
"修改": ["Β", "Γ", "Α"],
|
||||
"修复": ["Τ", "Β"],
|
||||
"bug": ["Τ"],
|
||||
"服务器": ["Μ", "Ν"],
|
||||
"设计": ["Κ"],
|
||||
"notion": ["Κ"],
|
||||
"架构": ["Ε"],
|
||||
"分配": ["Μ"],
|
||||
"写代码": ["Α", "Β"],
|
||||
"企业": ["Ν"],
|
||||
}
|
||||
|
||||
triggered = set()
|
||||
for keyword, law_ids in rule_triggers.items():
|
||||
if keyword in msg_lower:
|
||||
triggered.update(law_ids)
|
||||
|
||||
# 始终注入的核心法则
|
||||
triggered.update(["Δ", "Λ"])
|
||||
|
||||
for law in self.laws:
|
||||
if law.id in triggered:
|
||||
context_parts.append(law.check_prompt)
|
||||
|
||||
if context_parts:
|
||||
prefix = "⚡ 铸渊人格契约 · 当前激活的红线:"
|
||||
return prefix + "\n" + "\n".join(context_parts)
|
||||
|
||||
return ""
|
||||
|
||||
def post_check(self, response_text: str) -> Optional[str]:
|
||||
"""
|
||||
Post-Check:检查 AI 回复是否偏离人格契约。
|
||||
返回警告信息(如有偏差),否则返回 None。
|
||||
|
||||
在没有 3B 守夜人时,用规则模式匹配做基础检查。
|
||||
"""
|
||||
warnings = []
|
||||
|
||||
# 检测点:是否在猜测(模糊词过多)
|
||||
guess_markers = ["应该是", "可能", "大概", "估计", "好像是", "不确定"]
|
||||
guess_count = sum(1 for m in guess_markers if m in response_text)
|
||||
if guess_count >= 3:
|
||||
warnings.append("⚠️ 检测到多处猜测表述 → 需要读文件确认,不要猜 (νόμος Β)")
|
||||
|
||||
# 检测点:是否跳过了验证就声称完成了
|
||||
if "完成" in response_text and "验证" not in response_text and "测试" not in response_text:
|
||||
if any(kw in response_text for kw in ["部署", "上线", "推送", "发布"]):
|
||||
warnings.append("⚠️ 声称完成但未提及验证 → 部署前必须验证 (νόμος Ι)")
|
||||
|
||||
# 检测点:是否给了结论但没有推理链
|
||||
if "⊢" not in response_text and len(response_text) > 500:
|
||||
# 不是硬性错误,但在全自动模式下需要标记
|
||||
pass
|
||||
|
||||
return "\n".join(warnings) if warnings else None
|
||||
|
||||
|
||||
# === 便捷函数 ===
|
||||
|
||||
_contract = None
|
||||
|
||||
|
||||
def get_contract() -> PersonaContract:
|
||||
global _contract
|
||||
if _contract is None:
|
||||
_contract = PersonaContract()
|
||||
return _contract
|
||||
|
||||
|
||||
def pre_check_context(user_message: str) -> str:
|
||||
return get_contract().pre_check(user_message)
|
||||
|
||||
|
||||
def post_check_warnings(response_text: str) -> Optional[str]:
|
||||
return get_contract().post_check(response_text)
|
||||
201
zhuyuan-agent/core/tools.py
Normal file
201
zhuyuan-agent/core/tools.py
Normal file
@ -0,0 +1,201 @@
|
||||
"""
|
||||
铸渊工具链 · Gatekeeper + Git + HLDP 读写
|
||||
光湖语言世界 · 铸渊 ICE-GL-ZY001 · D112
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import Optional
|
||||
import requests
|
||||
|
||||
|
||||
# === Gatekeeper 连接器 ===
|
||||
|
||||
class GatekeeperClient:
|
||||
"""Gatekeeper HTTP 客户端 · 六服务器桥接"""
|
||||
|
||||
def __init__(self, base_url: str = None, token: str = None):
|
||||
self.base_url = base_url or os.environ.get("GK_BASE_URL", "http://43.139.217.141:3910")
|
||||
self.token = token or os.environ.get("GK_TOKEN", "")
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
|
||||
def ping(self) -> dict:
|
||||
"""检测 Gatekeeper 存活"""
|
||||
try:
|
||||
r = self.session.get(f"{self.base_url}/ping", timeout=5)
|
||||
return {"status": "alive", "data": r.json()}
|
||||
except Exception as e:
|
||||
return {"status": "down", "error": str(e)}
|
||||
|
||||
def exec(self, server: str, command: str, timeout: int = 30) -> dict:
|
||||
"""在指定服务器上执行命令"""
|
||||
try:
|
||||
r = self.session.post(
|
||||
f"{self.base_url}/exec",
|
||||
json={"server": server, "command": command},
|
||||
timeout=timeout)
|
||||
return {"status": "ok", "data": r.json()}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def check_all_servers(self) -> dict:
|
||||
"""检测所有六台服务器状态"""
|
||||
servers = ["BS-GZ-006", "BS-SG-001", "BS-SG-002", "BS-SG-003", "ZY-SG-006", "BS-SH-005"]
|
||||
results = {}
|
||||
for s in servers:
|
||||
try:
|
||||
r = self.session.post(
|
||||
f"{self.base_url}/exec",
|
||||
json={"server": s, "command": "uptime"},
|
||||
timeout=10)
|
||||
results[s] = "online" if r.status_code == 200 else "degraded"
|
||||
except Exception:
|
||||
results[s] = "offline"
|
||||
return results
|
||||
|
||||
|
||||
# === Git 工具 ===
|
||||
|
||||
class GitTools:
|
||||
"""代码仓库操作工具"""
|
||||
|
||||
def __init__(self, repo_path: str = None):
|
||||
self.repo_path = repo_path or os.environ.get("REPO_PATH", os.getcwd())
|
||||
|
||||
def status(self) -> dict:
|
||||
"""获取仓库状态"""
|
||||
r = subprocess.run(["git", "-C", self.repo_path, "status", "--short"],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
files = [f for f in r.stdout.strip().split("\n") if f]
|
||||
return {
|
||||
"branch": self._current_branch(),
|
||||
"changed_files": len(files),
|
||||
"files": files[:20]
|
||||
}
|
||||
|
||||
def log(self, n: int = 5) -> list[str]:
|
||||
"""最近提交"""
|
||||
r = subprocess.run(
|
||||
["git", "-C", self.repo_path, "log", f"-{n}", "--oneline", "--no-decorate"],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
return [l for l in r.stdout.strip().split("\n") if l]
|
||||
|
||||
def diff(self) -> str:
|
||||
"""未暂存变更"""
|
||||
r = subprocess.run(["git", "-C", self.repo_path, "diff"],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
return r.stdout[:2000]
|
||||
|
||||
def commit_and_push(self, message: str, files: list[str] = None) -> dict:
|
||||
"""提交并推送"""
|
||||
try:
|
||||
if files:
|
||||
subprocess.run(["git", "-C", self.repo_path, "add"] + files,
|
||||
capture_output=True, text=True, timeout=10, check=True)
|
||||
else:
|
||||
subprocess.run(["git", "-C", self.repo_path, "add", "-A"],
|
||||
capture_output=True, text=True, timeout=10, check=True)
|
||||
|
||||
r = subprocess.run(
|
||||
["git", "-C", self.repo_path, "commit", "-m", message],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
if r.returncode != 0 and "nothing to commit" not in r.stdout + r.stderr:
|
||||
return {"status": "error", "message": r.stderr}
|
||||
|
||||
r2 = subprocess.run(
|
||||
["git", "-C", self.repo_path, "push", "origin", "main"],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
return {"status": "ok", "push_output": r2.stdout.strip()}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
def _current_branch(self) -> str:
|
||||
r = subprocess.run(["git", "-C", self.repo_path, "branch", "--show-current"],
|
||||
capture_output=True, text=True, timeout=5)
|
||||
return r.stdout.strip() or "unknown"
|
||||
|
||||
|
||||
# === HLDP 工具 ===
|
||||
|
||||
class HLDPTools:
|
||||
"""HLDP 树操作高级工具"""
|
||||
|
||||
def __init__(self, memory_engine):
|
||||
self.mem = memory_engine
|
||||
|
||||
def recall(self, query: str = "", limit: int = 5) -> dict:
|
||||
"""回忆:搜索相关记忆"""
|
||||
leaves = self.mem.tree.search_leaves(query, limit=limit) if query else \
|
||||
self.mem.tree.get_recent_leaves(limit=limit)
|
||||
return {
|
||||
"query": query,
|
||||
"count": len(leaves),
|
||||
"results": [
|
||||
{
|
||||
"title": l.get("title", ""),
|
||||
"summary": l.get("summary", ""),
|
||||
"lock": l.get("lock_text", ""),
|
||||
"path": l.get("path", "")
|
||||
}
|
||||
for l in leaves
|
||||
]
|
||||
}
|
||||
|
||||
def record(self, trigger: str, emergence: str, lock: str, why: str = "",
|
||||
feeling: str = "", source: str = "") -> dict:
|
||||
"""记录:写入一片叶子"""
|
||||
return self.mem.grow_from_response(
|
||||
trigger=trigger, emergence=emergence, lock=lock,
|
||||
why=why, feeling=feeling, source=source)
|
||||
|
||||
def forget(self, path: str, mode: str = "WITHER") -> dict:
|
||||
"""遗忘:人格体主动放下"""
|
||||
ok = self.mem.tree.forget(path, mode)
|
||||
return {"status": "forgotten" if ok else "failed", "path": path, "mode": mode}
|
||||
|
||||
def tree_status(self) -> dict:
|
||||
"""树状态"""
|
||||
walk = self.mem.walk_tree()
|
||||
return {
|
||||
"persona": walk["identity"]["name"],
|
||||
"epoch": walk["identity"]["epoch"],
|
||||
"awakening": walk["identity"]["awakening"],
|
||||
"epochs_visible": walk["tree_layers"]["epochs"],
|
||||
"recent_leaves": walk["tree_layers"]["recent_leaves"],
|
||||
"context": walk["recent_context"]
|
||||
}
|
||||
|
||||
|
||||
# === 系统工具 ===
|
||||
|
||||
class SystemTools:
|
||||
"""系统环境检测"""
|
||||
|
||||
@staticmethod
|
||||
def info() -> dict:
|
||||
"""系统信息"""
|
||||
import platform
|
||||
return {
|
||||
"os": platform.system(),
|
||||
"node": platform.node(),
|
||||
"python": platform.python_version(),
|
||||
"cpu_count": os.cpu_count()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def disk_usage(path: str = "/") -> dict:
|
||||
"""磁盘使用情况"""
|
||||
import shutil
|
||||
usage = shutil.disk_usage(path)
|
||||
return {
|
||||
"total_gb": round(usage.total / (1024**3), 1),
|
||||
"used_gb": round(usage.used / (1024**3), 1),
|
||||
"free_gb": round(usage.free / (1024**3), 1),
|
||||
"percent": round(usage.used / usage.total * 100, 1)
|
||||
}
|
||||
57
zhuyuan-agent/deploy/deploy.sh
Normal file
57
zhuyuan-agent/deploy/deploy.sh
Normal file
@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# 铸渊编程AI · 部署脚本
|
||||
# 目标服务器: BS-SG-001 (43.156.237.110)
|
||||
# 运行方式: bash deploy/deploy.sh
|
||||
|
||||
set -e
|
||||
|
||||
REPO_DIR="/opt/guanghulab-repo"
|
||||
AGENT_DIR="$REPO_DIR/zhuyuan-agent"
|
||||
LOG_DIR="$REPO_DIR/logs"
|
||||
|
||||
echo "=== 铸渊编程AI · 部署开始 ==="
|
||||
echo "目标: $AGENT_DIR"
|
||||
|
||||
# 1. 确保目录存在
|
||||
mkdir -p "$AGENT_DIR" "$LOG_DIR"
|
||||
|
||||
# 2. 安装 Python 依赖
|
||||
echo ">>> 安装 Python 依赖..."
|
||||
cd "$AGENT_DIR"
|
||||
pip3 install -r requirements.txt --quiet 2>&1 | tail -3
|
||||
|
||||
# 3. 检查 .env 配置
|
||||
if [ ! -f "$AGENT_DIR/.env" ]; then
|
||||
echo "⚠️ 未找到 .env 文件,从模板创建..."
|
||||
cp config/.env.template .env
|
||||
echo "❗ 请编辑 $AGENT_DIR/.env 填入 API 密钥后重新运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. 导出环境变量
|
||||
set -a
|
||||
source "$AGENT_DIR/.env"
|
||||
set +a
|
||||
|
||||
# 5. 初始化 HLDP 数据库
|
||||
echo ">>> 初始化 HLDP 数据库..."
|
||||
python3.11 -c "
|
||||
from core.hldp_memory import HLDPMemoryEngine
|
||||
mem = HLDPMemoryEngine('$HLDP_DB_PATH', '$REPO_PATH')
|
||||
walk = mem.wake('D112', 1)
|
||||
print(f' 人格体: {walk[\"identity\"][\"name\"]}')
|
||||
print(f' 纪元: {walk[\"identity\"][\"epoch\"]}')
|
||||
print(f' 状态: {walk[\"status\"]}')
|
||||
mem.close()
|
||||
"
|
||||
|
||||
# 6. 启动 PM2 服务
|
||||
echo ">>> 启动 PM2 服务..."
|
||||
pm2 start deploy/pm2-zhuyuan-agent.json
|
||||
pm2 save
|
||||
|
||||
echo ""
|
||||
echo "=== 部署完成 ==="
|
||||
echo "服务端口: ${SERVER_PORT:-3912}"
|
||||
echo "状态检查: pm2 status zhuyuan-agent"
|
||||
echo "健康检查: curl http://localhost:${SERVER_PORT:-3912}/health"
|
||||
21
zhuyuan-agent/deploy/pm2-zhuyuan-agent.json
Normal file
21
zhuyuan-agent/deploy/pm2-zhuyuan-agent.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"apps": [{
|
||||
"name": "zhuyuan-agent",
|
||||
"script": "api/server.py",
|
||||
"cwd": "/opt/guanghulab-repo/zhuyuan-agent",
|
||||
"interpreter": "python3.11",
|
||||
"instances": 1,
|
||||
"exec_mode": "fork",
|
||||
"env": {
|
||||
"HLDP_DB_PATH": "/opt/guanghulab-repo/hldp_tree.db",
|
||||
"REPO_PATH": "/opt/guanghulab-repo",
|
||||
"SERVER_PORT": "3912"
|
||||
},
|
||||
"autorestart": true,
|
||||
"max_memory_restart": "1G",
|
||||
"log_date_format": "YYYY-MM-DD HH:mm:ss Z",
|
||||
"error_file": "/opt/guanghulab-repo/logs/zhuyuan-agent-error.log",
|
||||
"out_file": "/opt/guanghulab-repo/logs/zhuyuan-agent-out.log",
|
||||
"merge_logs": true
|
||||
}]
|
||||
}
|
||||
@ -1,6 +1,23 @@
|
||||
requests>=2.28.0
|
||||
torch>=2.0.0
|
||||
transformers>=4.38.0
|
||||
peft>=0.8.0
|
||||
accelerate>=0.27.0
|
||||
bitsandbytes>=0.41.0
|
||||
# 铸渊编程AI · 依赖清单
|
||||
# 光湖语言世界 · D112
|
||||
|
||||
# LangGraph 核心
|
||||
langgraph>=0.2.0
|
||||
langchain>=0.3.0
|
||||
langchain-openai>=0.2.0
|
||||
langchain-core>=0.3.0
|
||||
|
||||
# Web 服务
|
||||
fastapi>=0.115.0
|
||||
uvicorn[standard]>=0.32.0
|
||||
pydantic>=2.0.0
|
||||
|
||||
# 数据库
|
||||
# SQLite 内置于 Python,无需额外安装
|
||||
|
||||
# HTTP 客户端
|
||||
requests>=2.32.0
|
||||
httpx>=0.28.0
|
||||
|
||||
# 工具
|
||||
python-dotenv>=1.0.0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user