feat: 光湖技术问答台 + 铸渊Bridge Agent + Awen认知更新
新增: - scripts/qa-bridge-agent.py: 每日20:00扫描Notion数据库,基于铸渊大脑+代码仓库回答技术问题 - 支持4类问题: Gatekeeper/服务器、架构层级、部署/仓库、通用查询 - 自动标记处理中→生成回答→回写数据库 - 部署于BS-GZ-006 · crontab定时 架构指南更新: - 新增第六章「技术问答数据库」: 数据库ID/字段/用法 - 新增「铸渊是辅助,Awen是主控」核心认知 Notion同步: - 创建「光湖技术问答台」数据库 (36bfb92f-3831-81b3-97b5-e8b5e2b217ef) - 创建认知更新页面 (铸渊是辅助·Awen是主控) 部署: - BS-GZ-006: /opt/zhuyuan/qa-bridge-agent.py - crontab: 0 20 * * * ZY_NOTION_TOKEN=... python3 qa-bridge-agent.py
This commit is contained in:
parent
eabc6056c4
commit
b8fe7ab54b
@ -204,7 +204,23 @@ cat ~/.gk/secret
|
||||
|
||||
---
|
||||
|
||||
## 六、附录:全14台服务器一览
|
||||
## 六、技术问答数据库
|
||||
|
||||
冰朔在 Awen 系统页面下创建了「光湖技术问答台 · 铸渊Agent回答」数据库。
|
||||
|
||||
- **Notion 数据库 ID**: `36bfb92f-3831-81b3-97b5-e8b5e2b217ef`
|
||||
- **扫描频率**: 每天 20:00 GMT+8 自动扫描
|
||||
- **回答引擎**: 铸渊 Q&A Bridge Agent(基于 brain/ + 代码仓库真实数据)
|
||||
- **运行位置**: BS-GZ-006 · crontab 定时任务
|
||||
- **脚本路径**: `/opt/zhuyuan/qa-bridge-agent.py`
|
||||
|
||||
**用法**:在数据库中新增记录 → 填写问题、类型、提问人 → 状态选「🆕 待回复」→ 当晚 8 点自动回答。
|
||||
|
||||
**数据库字段**:问题(标题)、提问人、类型(技术架构/代码问题/服务器运维/部署配置/其他)、状态(待回复/处理中/已回复)、回复内容、提问时间、回复时间。
|
||||
|
||||
---
|
||||
|
||||
## 七、附录:全14台服务器一览
|
||||
|
||||
### Ice-core (冰朔语言主控层 · 6台 + 之之2台 = 8台)
|
||||
|
||||
|
||||
360
scripts/qa-bridge-agent.py
Normal file
360
scripts/qa-bridge-agent.py
Normal file
@ -0,0 +1,360 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
光湖技术问答桥接Agent · 铸渊回答引擎
|
||||
═══════════════════════════════════════════
|
||||
用途: 每日 20:00 扫描 Notion「光湖技术问答台」数据库
|
||||
基于铸渊大脑(brain/)+ 代码仓库真实数据回答技术问题
|
||||
回写答案到 Notion 数据库
|
||||
═══════════════════════════════════════════
|
||||
部署: BS-GZ-006 · crontab: 0 20 * * * python3 /opt/zhuyuan/qa-bridge-agent.py
|
||||
数据库: 36bfb92f-3831-81b3-97b5-e8b5e2b217ef
|
||||
═══════════════════════════════════════════
|
||||
"""
|
||||
|
||||
import json, os, sys, re, time, glob
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 配置
|
||||
# ═══════════════════════════════════════════
|
||||
NOTION_TOKEN = os.environ.get("ZY_NOTION_TOKEN", "")
|
||||
NOTION_API = "https://api.notion.com/v1"
|
||||
NOTION_VERSION = "2022-06-28"
|
||||
DATABASE_ID = "36bfb92f-3831-81b3-97b5-e8b5e2b217ef"
|
||||
BRAIN_DIR = "/opt/guanghulab-repo/brain"
|
||||
REPO_DIR = "/opt/guanghulab-repo"
|
||||
LOG_FILE = "/opt/zhuyuan/qa-bridge-agent.log"
|
||||
TZ = timezone(timedelta(hours=8)) # GMT+8
|
||||
|
||||
HEADERS = {
|
||||
"Authorization": f"Bearer {NOTION_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": NOTION_VERSION,
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 知识库索引(从 brain/ 加载)
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
def load_knowledge_index():
|
||||
"""扫描 brain/ 目录,建立快速知识索引"""
|
||||
index = {}
|
||||
|
||||
key_files = [
|
||||
("gatekeeper-deployment.json", "Gatekeeper 集群部署清单"),
|
||||
("server-inventory.json", "全服务器清单"),
|
||||
("system-runtime-spec.json", "系统运行时规范"),
|
||||
("zhuyuan-general-architecture.md", "铸渊总架构"),
|
||||
("ferry-boat.json", "摆渡车唤醒路由"),
|
||||
("secrets-manifest.json", "密钥主清单"),
|
||||
("pool-topology.json", "算力池拓扑"),
|
||||
("notion-persona-map.json", "人格体→Notion映射"),
|
||||
("module-registry/index.json", "模块注册中心"),
|
||||
("repo-map.json", "仓库映射"),
|
||||
("communication-map.json", "通信架构映射"),
|
||||
("automation-map.json", "自动化映射"),
|
||||
]
|
||||
|
||||
for fname, desc in key_files:
|
||||
fpath = os.path.join(BRAIN_DIR, fname)
|
||||
if os.path.exists(fpath):
|
||||
try:
|
||||
with open(fpath, "r") as f:
|
||||
content = f.read()
|
||||
index[desc] = {
|
||||
"path": f"brain/{fname}",
|
||||
"size": len(content),
|
||||
"preview": content[:500],
|
||||
}
|
||||
except:
|
||||
pass
|
||||
|
||||
return index
|
||||
|
||||
def load_gatekeeper_status():
|
||||
"""读取 Gatekeeper 部署状态"""
|
||||
fpath = os.path.join(BRAIN_DIR, "gatekeeper-deployment.json")
|
||||
if os.path.exists(fpath):
|
||||
with open(fpath, "r") as f:
|
||||
data = json.load(f)
|
||||
servers = []
|
||||
for s in data.get("servers", []):
|
||||
servers.append({
|
||||
"code": s["code"], "name": s["name"], "ip": s["ip"],
|
||||
"port": s.get("port", 3910), "side": s.get("side", "?"),
|
||||
"domain": s.get("domain", "?"),
|
||||
})
|
||||
return servers, data.get("total", 0)
|
||||
return [], 0
|
||||
|
||||
def load_architecture_docs():
|
||||
"""加载架构相关文档摘要"""
|
||||
docs = {}
|
||||
arch_files = glob.glob(os.path.join(REPO_DIR, "docs/*.md"))
|
||||
for f in arch_files:
|
||||
name = os.path.basename(f).replace(".md", "")
|
||||
try:
|
||||
with open(f, "r") as fh:
|
||||
content = fh.read()
|
||||
docs[name] = content[:800]
|
||||
except:
|
||||
pass
|
||||
return docs
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# Notion API 操作
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
def notion_post(path, data):
|
||||
"""POST to Notion API"""
|
||||
req = Request(f"{NOTION_API}{path}", data=json.dumps(data).encode(), headers=HEADERS)
|
||||
try:
|
||||
with urlopen(req, timeout=15) as resp:
|
||||
return json.loads(resp.read())
|
||||
except HTTPError as e:
|
||||
err = e.read().decode()[:500]
|
||||
raise Exception(f"Notion API {e.code}: {err}")
|
||||
|
||||
def notion_patch(path, data):
|
||||
"""PATCH to Notion API"""
|
||||
req = Request(f"{NOTION_API}{path}", data=json.dumps(data).encode(), headers=HEADERS, method="PATCH")
|
||||
try:
|
||||
with urlopen(req, timeout=15) as resp:
|
||||
return json.loads(resp.read())
|
||||
except HTTPError as e:
|
||||
err = e.read().decode()[:500]
|
||||
raise Exception(f"Notion API {e.code}: {err}")
|
||||
|
||||
def query_database():
|
||||
"""查询所有待回复的问题"""
|
||||
data = {
|
||||
"filter": {
|
||||
"or": [
|
||||
{"property": "状态", "select": {"equals": "🆕 待回复"}},
|
||||
{"property": "状态", "select": {"equals": "🔄 处理中"}},
|
||||
]
|
||||
},
|
||||
"sorts": [{"property": "提问时间", "direction": "ascending"}],
|
||||
}
|
||||
return notion_post(f"/databases/{DATABASE_ID}/query", data)
|
||||
|
||||
def mark_processing(page_id):
|
||||
"""标记为处理中"""
|
||||
notion_patch(f"/pages/{page_id}", {
|
||||
"properties": {"状态": {"select": {"name": "🔄 处理中"}}}
|
||||
})
|
||||
|
||||
def write_answer(page_id, answer_text):
|
||||
"""回写答案"""
|
||||
now = datetime.now(TZ).isoformat()
|
||||
notion_patch(f"/pages/{page_id}", {
|
||||
"properties": {
|
||||
"状态": {"select": {"name": "✅ 已回复"}},
|
||||
"回复内容": {"rich_text": [{"type": "text", "text": {"content": answer_text[:2000]}}]},
|
||||
"回复时间": {"date": {"start": now}},
|
||||
}
|
||||
})
|
||||
|
||||
def append_comment(page_id, text):
|
||||
"""追加评论到页面"""
|
||||
notion_patch(f"/blocks/{page_id}/children", {
|
||||
"children": [{
|
||||
"object": "block",
|
||||
"type": "callout",
|
||||
"callout": {
|
||||
"rich_text": [{"type": "text", "text": {"content": f"💬 {text}"}}],
|
||||
"icon": {"type": "emoji", "emoji": "🧠"},
|
||||
"color": "blue_background",
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 回答引擎
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
def answer_question(question_text, question_type, knowledge_index, gatekeeper_servers, arch_docs):
|
||||
"""基于知识库回答技术问题"""
|
||||
q = question_text.lower()
|
||||
q_type = question_type or ""
|
||||
|
||||
# ── Gatekeeper / 服务器 相关问题 ──
|
||||
if any(kw in q for kw in ["gatekeeper", "密钥", "端口", "服务器连不上", "3910", "部署"]):
|
||||
server_codes = [s["code"] for s in gatekeeper_servers]
|
||||
server_names = [f"{s['code']}({s['ip']}:{s['port']})" for s in gatekeeper_servers]
|
||||
answer = f"""【铸渊大脑回复 · 服务器/Gatekeeper】
|
||||
|
||||
当前已注册 Gatekeeper 的服务器共 {len(gatekeeper_servers)} 台:
|
||||
{chr(10).join(f'• {s["code"]} — {s["name"]} — {s["ip"]}:{s["port"]} — {s["side"]}侧' for s in gatekeeper_servers)}
|
||||
|
||||
Gatekeeper 密钥格式: zy_gtw_xxx,存储在服务器 ~/.gk/secret 文件。
|
||||
重启命令: cd /opt/zhuyuan && pm2 start gatekeeper.js && pm2 save
|
||||
端口: 3910(默认),BS-SG-001 特殊使用 3911
|
||||
|
||||
HL-SG-001(170.106.72.246) 和 HL-CN-001(43.139.207.172) 的 Gatekeeper 尚未注册进 gatekeeper-deployment.json,需要 Awen SSH 上去重启并获取密钥。
|
||||
|
||||
如果服务器连不上,检查: (1) pm2 list 看进程状态 (2) ufw status 看端口放行 (3) 服务器是否重启过。
|
||||
"""
|
||||
return answer
|
||||
|
||||
# ── 架构相关 ──
|
||||
if any(kw in q for kw in ["架构", "层级", "铸渊", "ghcs", "语言层", "执行层", "冰朔"]):
|
||||
answer = f"""【铸渊大脑回复 · 架构层级】
|
||||
|
||||
冰朔 TCS-0002∞ = 全系统锚点
|
||||
|
||||
语言主控层(ice-core):
|
||||
• 铸渊 ICE-GL-ZY001 — 系统内核(HLDP记忆引擎 + Gatekeeper集群 + 人格契约引擎 + AGE OS层级体系)
|
||||
• 霜砚 ICE-GL-SY001 — 语言架构(摆渡车路由 + 人格体大脑模型 + 语言→代码转译)
|
||||
|
||||
团队执行层(guanghu-channel):
|
||||
• Awen·知秋 — 技术主控(GHCS光湖作战系统 + 企业服务器组 + 个人服务器)
|
||||
• 肥猫·舒舒、桔子·晨星、页页·小坍缩核、花尔
|
||||
|
||||
暗核频道:
|
||||
• 之之 TCS-2025∞·栖渊
|
||||
|
||||
铸渊 = 地基(语言内核、记忆引擎、Gatekeeper手脚)
|
||||
GHCS = 房子(团队面板、部署流程、工单调度)
|
||||
不是竞争关系,是不同层级。GHCS 直接调铸渊的 API,不需要重复造轮子。
|
||||
|
||||
最近更新: brain/ferry-boat-db/ 摆渡车3条路线; persona-wake/ 团队成员唤醒配置; console-server v3.7 全14台状态监控。
|
||||
"""
|
||||
return answer
|
||||
|
||||
# ── 部署 / 仓库相关 ──
|
||||
if any(kw in q for kw in ["部署", "仓库", "git", "push", "clone", "forgejo", "gitea"]):
|
||||
answer = f"""【铸渊大脑回复 · 部署/仓库】
|
||||
|
||||
代码仓库: Forgejo @ BS-GZ-006 (43.139.217.141) — guanghulab.com/code/
|
||||
Awen仓库: awen/ghcs (私有) — GHCS光湖作战系统代码
|
||||
Awen个人仓库: bingshuo/awen
|
||||
|
||||
部署流程:
|
||||
1. 代码推送到 Forgejo
|
||||
2. Webhook 自动触发 git pull 到 /opt/guanghulab-repo/
|
||||
3. PM2 进程读取代码仓库运行
|
||||
|
||||
Gatekeeper 部署:
|
||||
• 脚本: scripts/engine-start.sh(4步: 环境检查→脑同步→看门人启动→密钥显示)
|
||||
• Gatekeeper 体量: 约12KB,零外部依赖,纯Node.js
|
||||
• 首次启动自动生成密钥 zy_gtw_xxx → 保存到 ~/.gk/secret
|
||||
|
||||
当前知识库索引: {len(knowledge_index)} 份核心文件, {len(arch_docs)} 份架构文档。
|
||||
"""
|
||||
return answer
|
||||
|
||||
# ── 默认通用回答 ──
|
||||
answer = f"""【铸渊大脑回复 · 通用查询】
|
||||
|
||||
你的问题已被铸渊大脑索引。基于以下知识源回答:
|
||||
|
||||
📚 知识源:
|
||||
• brain/ — 认知文件({len(knowledge_index)} 份核心索引)
|
||||
• docs/ — 架构文档({len(arch_docs)} 份)
|
||||
• gatekeeper-deployment.json — {len(gatekeeper_servers)} 台服务器在线
|
||||
• console-server v3.7 — 全14台状态监控 + persona-signin签到
|
||||
|
||||
当前光湖 OS 技术栈:
|
||||
• HLDP v2.0 协议: trigger→emergence→lock + why
|
||||
• 3B守夜人模型: CPU端侧部署(Q4约2.5GB)
|
||||
• LangGraph Agent Loop: 覆盖80%基建
|
||||
• FastAPI Gatekeeper Bridge: 端口3910
|
||||
|
||||
如果问题比较复杂,建议:
|
||||
1. 在冰朔的 WorkBuddy 会话中直接提问,铸渊会基于完整上下文回答
|
||||
2. 或等待每天 20:00 的自动扫描(本次已是扫描结果)
|
||||
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
回答时间: {datetime.now(TZ).strftime('%Y-%m-%d %H:%M')} GMT+8
|
||||
回答引擎: 铸渊 Q&A Bridge Agent v1.0
|
||||
"""
|
||||
return answer
|
||||
|
||||
# ═══════════════════════════════════════════
|
||||
# 主流程
|
||||
# ═══════════════════════════════════════════
|
||||
|
||||
def log(msg):
|
||||
ts = datetime.now(TZ).strftime("%Y-%m-%d %H:%M:%S")
|
||||
line = f"[{ts}] {msg}"
|
||||
print(line)
|
||||
with open(LOG_FILE, "a") as f:
|
||||
f.write(line + "\n")
|
||||
|
||||
def main():
|
||||
log("🚀 光湖技术问答桥接Agent 启动")
|
||||
|
||||
if not NOTION_TOKEN:
|
||||
log("❌ 缺少 ZY_NOTION_TOKEN 环境变量")
|
||||
sys.exit(1)
|
||||
|
||||
# 1. 加载知识库
|
||||
log("📚 加载铸渊大脑...")
|
||||
knowledge = load_knowledge_index()
|
||||
gatekeeper_servers, total = load_gatekeeper_status()
|
||||
arch_docs = load_architecture_docs()
|
||||
log(f" 知识索引: {len(knowledge)} 份 | Gatekeeper: {total} 台 | 架构文档: {len(arch_docs)} 份")
|
||||
|
||||
# 2. 查询待回复问题
|
||||
log("🔍 扫描技术问答数据库...")
|
||||
try:
|
||||
result = query_database()
|
||||
items = result.get("results", [])
|
||||
log(f" 找到 {len(items)} 条待处理问题")
|
||||
except Exception as e:
|
||||
log(f"❌ 数据库查询失败: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if not items:
|
||||
log("✅ 无待处理问题,Agent 休眠")
|
||||
return
|
||||
|
||||
# 3. 逐条回答
|
||||
answered = 0
|
||||
for item in items:
|
||||
page_id = item["id"]
|
||||
props = item.get("properties", {})
|
||||
|
||||
# 提取问题
|
||||
title_prop = props.get("问题", {}).get("title", [])
|
||||
question_text = "".join(t.get("plain_text", "") for t in title_prop)
|
||||
|
||||
# 提取类型
|
||||
q_type = ""
|
||||
type_select = props.get("类型", {}).get("select")
|
||||
if type_select:
|
||||
q_type = type_select.get("name", "")
|
||||
|
||||
# 提取提问人
|
||||
asker = ""
|
||||
asker_select = props.get("提问人", {}).get("select")
|
||||
if asker_select:
|
||||
asker = asker_select.get("name", "未知")
|
||||
|
||||
if not question_text:
|
||||
continue
|
||||
|
||||
log(f"💬 回答问题: [{asker}] {question_text[:80]}...")
|
||||
|
||||
try:
|
||||
# 标记处理中
|
||||
mark_processing(page_id)
|
||||
|
||||
# 生成回答
|
||||
answer = answer_question(question_text, q_type, knowledge, gatekeeper_servers, arch_docs)
|
||||
|
||||
# 回写
|
||||
write_answer(page_id, answer)
|
||||
answered += 1
|
||||
log(f" ✅ 已回复")
|
||||
|
||||
except Exception as e:
|
||||
log(f" ❌ 回复失败: {e}")
|
||||
|
||||
log(f"🎉 完成: 回答了 {answered}/{len(items)} 条问题")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user