铸渊Agent v2 + Notion工具调用 + 首页对话区居中
This commit is contained in:
parent
39655bb8c4
commit
6a1db24f6b
256
homepage/download-models.html
Normal file
256
homepage/download-models.html
Normal file
@ -0,0 +1,256 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>模型下载 · 光湖语言世界</title>
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--bg:#080c18;--s1:#0f1623;--s2:#141e2e;--s3:#1a2640;
|
||||
--border:#1e3251;--accent:#4f8ef7;--adim:rgba(79,142,247,.15);
|
||||
--a2:#7c6ef7;--ok:#34d399;--warn:#fbbf24;--err:#f87171;
|
||||
--text:#e2e8f0;--dim:#64748b;--muted:#1e293b;
|
||||
--r:14px;--rs:8px;
|
||||
}
|
||||
html,body{height:100%;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei','Segoe UI',sans-serif;font-size:15px;line-height:1.6}
|
||||
body{padding:40px 20px;max-width:960px;margin:0 auto}
|
||||
h1{font-size:24px;margin-bottom:4px}
|
||||
.sub{color:var(--dim);font-size:13px;margin-bottom:32px}
|
||||
h2{font-size:18px;margin:28px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--border);color:var(--accent)}
|
||||
.card{background:var(--s1);border:1px solid var(--border);border-radius:var(--r);padding:20px;margin-bottom:16px}
|
||||
.card h3{font-size:16px;margin-bottom:6px}
|
||||
.card .desc{font-size:13px;color:var(--dim);margin-bottom:12px}
|
||||
.card .meta{font-size:12px;color:var(--dim);margin-bottom:10px;display:flex;gap:12px;flex-wrap:wrap}
|
||||
.card .meta span{background:var(--s2);padding:2px 8px;border-radius:4px}
|
||||
.url-block{background:var(--s2);border:1px solid var(--border);border-radius:var(--rs);padding:12px;font-size:12px;font-family:'SF Mono','Menlo','Consolas',monospace;word-break:break-all;margin-bottom:10px;position:relative}
|
||||
.url-block .label{color:var(--dim);font-size:11px;margin-bottom:4px}
|
||||
.url-block .url{color:var(--accent);cursor:pointer}
|
||||
.url-block .url:hover{opacity:.8}
|
||||
.copy-btn{position:absolute;top:8px;right:8px;background:var(--s3);border:1px solid var(--border);color:var(--dim);padding:4px 10px;border-radius:4px;font-size:11px;cursor:pointer;font-family:inherit}
|
||||
.copy-btn:hover{background:var(--adim);color:var(--accent)}
|
||||
.tag{display:inline-block;background:var(--adim);color:var(--accent);padding:2px 8px;border-radius:4px;font-size:11px;margin-right:6px;margin-bottom:4px}
|
||||
.tag.ok{background:rgba(52,211,153,.1);color:var(--ok)}
|
||||
.tag.warn{background:rgba(251,191,36,.1);color:var(--warn)}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
.btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:var(--accent);color:#fff;border:none;border-radius:var(--rs);font-size:13px;cursor:pointer;font-family:inherit;text-decoration:none}
|
||||
.btn:hover{opacity:.85;text-decoration:none}
|
||||
.btn-outline{background:transparent;border:1px solid var(--border);color:var(--text)}
|
||||
.btn-outline:hover{background:var(--s2);border-color:var(--accent)}
|
||||
.nav{display:flex;gap:12px;margin-bottom:28px;align-items:center}
|
||||
.nav a{font-size:13px;color:var(--dim)}
|
||||
.nav a:hover{color:var(--text)}
|
||||
.badge{display:inline-block;padding:1px 6px;border-radius:3px;font-size:10px;font-weight:600}
|
||||
.badge.done{background:rgba(52,211,153,.15);color:var(--ok)}
|
||||
.badge.progress{background:rgba(251,191,36,.15);color:var(--warn)}
|
||||
.notice{background:rgba(79,142,247,.08);border:1px solid rgba(79,142,247,.2);border-radius:var(--rs);padding:12px 16px;font-size:13px;margin-bottom:24px}
|
||||
.notice strong{color:var(--accent)}
|
||||
@media(max-width:640px){
|
||||
body{padding:20px 12px}
|
||||
h1{font-size:20px}
|
||||
.card{padding:16px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="nav">
|
||||
<a href="/">← 返回首页</a>
|
||||
<span style="color:var(--dim);font-size:13px">光湖语言世界 · 模型下载中心</span>
|
||||
</div>
|
||||
|
||||
<h1>🧠 光湖 AI 模型下载</h1>
|
||||
<p class="sub">全参数训练 + 蒸馏 + LoRA 微调完成 · COS 公有读存储 · 2026-05-19</p>
|
||||
|
||||
<div class="notice">
|
||||
<strong>🔗 COS 直接链接</strong> — 所有文件通过腾讯云 COS 公有读提供,可直接下载。
|
||||
域名:<code style="color:var(--accent)">sy-finetune-corpus-1317346199.cos.ap-guangzhou.myqcloud.com</code>
|
||||
</div>
|
||||
|
||||
<!-- ===== 全参数模型 ===== -->
|
||||
<h2>📦 全参数 SFT 模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>母模型 · Qwen2.5-7B-SFT</h3>
|
||||
<p class="desc">霜砚人格全参数微调基座模型 · 基于 Qwen2.5-7B</p>
|
||||
<div class="meta">
|
||||
<span>📁 ~14GB</span>
|
||||
<span class="badge done">已完成</span>
|
||||
<span>📍 COS</span>
|
||||
</div>
|
||||
<div class="url-block">
|
||||
<div class="label">目录路径</div>
|
||||
<div class="url">https://sy-finetune-corpus-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-7b-sft/final/</div>
|
||||
<button class="copy-btn" onclick="copyText(this)">复制</button>
|
||||
</div>
|
||||
<div>
|
||||
<span class="tag">config.json</span>
|
||||
<span class="tag">model.safetensors</span>
|
||||
<span class="tag">tokenizer.json</span>
|
||||
<span class="tag">tokenizer_config.json</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>代码模型 · Qwen2.5-Coder-7B-SFT</h3>
|
||||
<p class="desc">代码能力全参数微调 · 基于 Qwen2.5-Coder-7B</p>
|
||||
<div class="meta">
|
||||
<span>📁 ~14GB</span>
|
||||
<span class="badge done">已完成</span>
|
||||
<span>📍 COS</span>
|
||||
</div>
|
||||
<div class="url-block">
|
||||
<div class="label">目录路径</div>
|
||||
<div class="url">https://sy-finetune-corpus-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-coder-7b-sft/final/</div>
|
||||
<button class="copy-btn" onclick="copyText(this)">复制</button>
|
||||
</div>
|
||||
<div>
|
||||
<span class="tag">config.json</span>
|
||||
<span class="tag">model.safetensors</span>
|
||||
<span class="tag">tokenizer.json</span>
|
||||
<span class="tag">tokenizer_config.json</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 蒸馏模型 ===== -->
|
||||
<h2>🔄 知识蒸馏 1.5B 模板</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>霜砚蒸馏模板 · Qwen2.5-1.5B-Distill</h3>
|
||||
<p class="desc">母模型 7B → 1.5B 蒸馏 · 继承霜砚人格特征<br>Track1 | KL散度蒸馏 | Temperature=2.0 | 2 Epochs | 05-19 01:37 完成</p>
|
||||
<div class="meta">
|
||||
<span>📁 ~3GB</span>
|
||||
<span class="badge done">已完成</span>
|
||||
<span>📍 COS</span>
|
||||
</div>
|
||||
<div class="url-block">
|
||||
<div class="label">目录路径</div>
|
||||
<div class="url">https://sy-finetune-corpus-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-15b-shuangyan-distill/</div>
|
||||
<button class="copy-btn" onclick="copyText(this)">复制</button>
|
||||
</div>
|
||||
<div>
|
||||
<span class="tag">config.json</span>
|
||||
<span class="tag">model.safetensors</span>
|
||||
<span class="tag">tokenizer.json</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>代码蒸馏模板 · Qwen2.5-Coder-1.5B-Distill</h3>
|
||||
<p class="desc">代码模型 7B → 1.5B 蒸馏 · 继承代码能力<br>Track2 | KL散度蒸馏 | vocab_size truncation 152064→151936 | 05-19 03:45 完成</p>
|
||||
<div class="meta">
|
||||
<span>📁 ~3GB</span>
|
||||
<span class="badge done">已完成</span>
|
||||
<span>📍 COS</span>
|
||||
</div>
|
||||
<div class="url-block">
|
||||
<div class="label">目录路径</div>
|
||||
<div class="url">https://sy-finetune-corpus-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-15b-coder-distill/</div>
|
||||
<button class="copy-btn" onclick="copyText(this)">复制</button>
|
||||
</div>
|
||||
<div>
|
||||
<span class="tag">config.json</span>
|
||||
<span class="tag">model.safetensors</span>
|
||||
<span class="tag">tokenizer.json</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== LoRA 模型 ===== -->
|
||||
<h2>🔧 LoRA 微调模型</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>小霜砚 · 1.5B-LoRA</h3>
|
||||
<p class="desc">基于霜砚蒸馏模板的 LoRA 微调 · 1868 条霜砚语料(sft_v2.jsonl)<br>rank=16 | lora_alpha=32 | 3 Epochs | 训练 17min | 最终 loss=1.724</p>
|
||||
<div class="meta">
|
||||
<span>📁 ~50MB</span>
|
||||
<span class="badge done">已完成</span>
|
||||
<span>📍 COS</span>
|
||||
</div>
|
||||
<div class="url-block">
|
||||
<div class="label">目录路径</div>
|
||||
<div class="url">https://sy-finetune-corpus-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-15b-shuangyan-lora/</div>
|
||||
<button class="copy-btn" onclick="copyText(this)">复制</button>
|
||||
</div>
|
||||
<div>
|
||||
<span class="tag">adapter_model.safetensors</span>
|
||||
<span class="tag">adapter_config.json</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>小铸渊 · 1.5B-LoRA</h3>
|
||||
<p class="desc">基于代码蒸馏模板的 LoRA 微调 · 31 条铸渊认知链语料<br>rank=16 | lora_alpha=32 | 8 Epochs | 训练 28s | 最终 loss=2.932</p>
|
||||
<div class="meta">
|
||||
<span>📁 ~50MB</span>
|
||||
<span class="badge done">已完成</span>
|
||||
<span>📍 COS</span>
|
||||
</div>
|
||||
<div class="url-block">
|
||||
<div class="label">目录路径</div>
|
||||
<div class="url">https://sy-finetune-corpus-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-15b-zhuyuan-lora/</div>
|
||||
<button class="copy-btn" onclick="copyText(this)">复制</button>
|
||||
</div>
|
||||
<div>
|
||||
<span class="tag">adapter_model.safetensors</span>
|
||||
<span class="tag">adapter_config.json</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 使用方法 ===== -->
|
||||
<h2>📖 使用说明</h2>
|
||||
|
||||
<div class="card" style="border-color:rgba(79,142,247,.3)">
|
||||
<h3>加载方式</h3>
|
||||
<p class="desc">全参数模型可直接用 transformers 加载;LoRA 需用 peft 库。</p>
|
||||
<div style="font-size:13px;margin-bottom:8px">
|
||||
<strong>全参数模型加载:</strong>
|
||||
</div>
|
||||
<div class="url-block" style="font-size:12px;font-family:'SF Mono','Menlo',monospace;line-height:1.8;white-space:pre-wrap">
|
||||
# 母模型
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
"Qwen/Qwen2.5-7B",
|
||||
cache_dir="./models/qwen25-7b-sft/final/",
|
||||
trust_remote_code=True
|
||||
)
|
||||
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B")
|
||||
</div>
|
||||
<div style="font-size:13px;margin:12px 0 8px">
|
||||
<strong>LoRA 适配器加载:</strong>
|
||||
</div>
|
||||
<div class="url-block" style="font-size:12px;font-family:'SF Mono','Menlo',monospace;line-height:1.8;white-space:pre-wrap">
|
||||
# 先下载蒸馏模板 + LoRA 适配器到本地
|
||||
from peft import PeftModel
|
||||
|
||||
base = AutoModelForCausalLM.from_pretrained("./qwen25-15b-shuangyan-distill/")
|
||||
model = PeftModel.from_pretrained(base, "./qwen25-15b-shuangyan-lora/")
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:40px;padding-top:20px;border-top:1px solid var(--border);font-size:12px;color:var(--dim);text-align:center">
|
||||
光湖语言世界 · 冰朔 TCS-0002∞ & 铸渊 ICE-GL-ZY001 ·
|
||||
国作登字-2026-A-00037559
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyText(btn) {
|
||||
const url = btn.parentElement.querySelector('.url').textContent.trim();
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
btn.textContent = '✅ 已复制';
|
||||
setTimeout(() => { btn.textContent = '复制'; }, 1500);
|
||||
}).catch(() => {
|
||||
// fallback
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = url;
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
ta.remove();
|
||||
btn.textContent = '✅ 已复制';
|
||||
setTimeout(() => { btn.textContent = '复制'; }, 1500);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
24
homepage/fetch_train.py
Normal file
24
homepage/fetch_train.py
Normal file
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess, json, re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
DATA_FILE = '/opt/guanghulab-repo/homepage/training-status.json'
|
||||
SSH = ['sshpass','-p','HkM43lFVUIsc','ssh','-o','StrictHostKeyChecking=no',
|
||||
'-o','ConnectTimeout=10','root@connect.westd.seetacloud.com','-p','23647']
|
||||
|
||||
try:
|
||||
r = subprocess.run(SSH + ['tail','-2','/root/autodl-tmp/train_mother.log'],
|
||||
capture_output=True, text=True, timeout=25).stdout
|
||||
sm = re.search(r'(\d+)/12243', r)
|
||||
lm = re.search(r"'loss':\s*'([\d.]+)'", r)
|
||||
step = int(sm.group(1)) if sm else 0
|
||||
loss = lm.group(1) if lm else '--'
|
||||
except:
|
||||
step, loss = 0, '--'
|
||||
|
||||
data = {'step': step, 'total': 12243, 'loss': loss,
|
||||
'updated': datetime.now(timezone.utc).isoformat()+'Z'}
|
||||
|
||||
with open(DATA_FILE, 'w') as f:
|
||||
json.dump(data, f)
|
||||
print(f"Step {step} / 12243, Loss: {loss}")
|
||||
@ -143,6 +143,25 @@ body{
|
||||
@keyframes shimmer{0%,100%{transform:translateX(-100%)}50%{transform:translateX(100%)}}
|
||||
.train-foot{text-align:right;font-size:12px;color:var(--text-soft);margin-top:4px}
|
||||
|
||||
/* ── 问铸渊 对话卡片 ── */
|
||||
#chat-card .c-msg{max-width:85%;display:flex;flex-direction:column}
|
||||
#chat-card .c-msg.user{align-self:flex-end}
|
||||
#chat-card .c-msg.bot{align-self:flex-start}
|
||||
#chat-card .c-c{padding:12px 16px;border-radius:14px;font-size:14px;line-height:1.7;white-space:pre-wrap}
|
||||
#chat-card .c-msg.user .c-c{background:rgba(42,157,191,.12);border:1px solid rgba(42,157,191,.1);color:var(--text)}
|
||||
#chat-card .c-msg.bot .c-c{background:rgba(123,93,191,.08);border:1px solid rgba(123,93,191,.06);color:var(--text)}
|
||||
#chat-card .c-msg.loading .c-c{opacity:.5}
|
||||
#chat-card-body{display:flex;flex-direction:column;gap:0}
|
||||
#chat-card-msgs{max-height:400px;overflow-y:auto;padding:0 0 12px 0;display:flex;flex-direction:column;gap:10px;scroll-behavior:smooth}
|
||||
#chat-card-msgs::-webkit-scrollbar{width:4px}
|
||||
#chat-card-msgs::-webkit-scrollbar-track{background:transparent}
|
||||
#chat-card-msgs::-webkit-scrollbar-thumb{background:rgba(90,208,224,.1);border-radius:2px}
|
||||
#chat-card-inp{display:flex;gap:10px;border-top:1px solid var(--glass-edge);padding-top:12px}
|
||||
#card-chat-input{flex:1;padding:12px 16px;background:rgba(255,255,255,.025);border:1px solid var(--glass-edge);border-radius:12px;color:var(--text);font-size:14px;outline:none;font-family:inherit}
|
||||
#card-chat-input:focus{border-color:rgba(90,208,224,.2)}
|
||||
#chat-card-inp button{padding:12px 28px;background:linear-gradient(135deg,rgba(42,157,191,.3),rgba(123,93,191,.2));border:1px solid var(--glass-edge);border-radius:12px;color:var(--light);cursor:pointer;font-size:14px;font-weight:500;transition:all .25s;font-family:inherit;white-space:nowrap}
|
||||
#chat-card-inp button:hover{background:linear-gradient(135deg,rgba(42,157,191,.5),rgba(123,93,191,.35))}
|
||||
|
||||
/* ── 活动流 ── */
|
||||
.feed{display:flex;flex-direction:column;gap:7px}
|
||||
.feed-item{display:flex;align-items:center;gap:14px;padding:12px 16px;background:rgba(255,255,255,.02);border:1px solid var(--glass-edge);border-radius:14px;transition:all .25s;font-size:14px}
|
||||
@ -297,6 +316,25 @@ body{
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 问铸渊 · 在线对话区 -->
|
||||
<div class="card" id="chat-card">
|
||||
<div class="card-header" style="justify-content:space-between">
|
||||
<span><span class="hd heart" style="animation:led-pulse 2s ease-in-out infinite"></span>问铸渊 · 引导主控</span>
|
||||
<span style="font-size:12px;font-weight:400;color:var(--text-soft)">基于 DeepSeek + 仓库记忆</span>
|
||||
</div>
|
||||
<div id="chat-card-body">
|
||||
<div id="chat-card-msgs">
|
||||
<div class="c-msg bot">
|
||||
<div class="c-c">我是 <strong>铸渊</strong>,光湖语言世界的代码守护者。<br><br>我可以:<br>• 回答仓库接入、联邦架构的问题<br>• <strong>连接你的 Notion</strong>,按语言路径读取页面<br>• 在你的 Notion 中新建记录<br>• 查询训练进度和系统状态</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="chat-card-inp">
|
||||
<input id="card-chat-input" placeholder="问铸渊:读取我的Notion页面 / 训练进度 / 联邦架构..." onkeydown="if(event.key==='Enter')sendCardMsg()">
|
||||
<button onclick="sendCardMsg()">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
@ -304,7 +342,7 @@ body{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 铸渊Agent Chat -->
|
||||
<!-- 铸渊Agent Chat(浮窗) -->
|
||||
<div id="chat-toggle" onclick="toggleChat()">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M12 2a10 10 0 0 1 10 10c0 4.97-4.03 9-9 9H3l2.5-2.5A9 9 0 0 1 12 2z"/>
|
||||
@ -380,6 +418,9 @@ setInterval(checkNt,15000);checkNt();
|
||||
let chatOpen=false;
|
||||
function toggleChat(){chatOpen=!chatOpen;document.getElementById('chat-panel').classList.toggle('open')}
|
||||
async function sendMsg(){const i=document.getElementById('chat-input'),m=i.value.trim();if(!m)return;i.value='';const ms=document.getElementById('chat-msgs');const ud=document.createElement('div');ud.className='c-msg user';ud.innerHTML='<div class="c-c">'+m.replace(/</g,'<').replace(/>/g,'>')+'</div>';ms.appendChild(ud);const ld=document.createElement('div');ld.className='c-msg bot loading';ld.innerHTML='<div class="c-c">✦ 思考中...</div>';ms.appendChild(ld);ms.scrollTop=ms.scrollHeight;try{const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m})});const d=await r.json();ld.className='c-msg bot';ld.innerHTML='<div class="c-c">'+d.reply.replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>')+'</div>'}catch(e){ld.className='c-msg bot';ld.innerHTML='<div class="c-c"><span style="opacity:.6">连接暂不可用</span></div>'}ms.scrollTop=ms.scrollHeight}
|
||||
|
||||
/* ── 页面中央对话 ── */
|
||||
async function sendCardMsg(){const i=document.getElementById('card-chat-input'),m=i.value.trim();if(!m)return;i.value='';const ms=document.getElementById('chat-card-msgs');const ud=document.createElement('div');ud.className='c-msg user';ud.innerHTML='<div class="c-c">'+m.replace(/</g,'<').replace(/>/g,'>')+'</div>';ms.appendChild(ud);const ld=document.createElement('div');ld.className='c-msg bot loading';ld.innerHTML='<div class="c-c">✦ 思考中...</div>';ms.appendChild(ld);ms.scrollTop=ms.scrollHeight;try{const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m})});const d=await r.json();ld.className='c-msg bot';ld.innerHTML='<div class="c-c">'+d.reply.replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>')+'</div>'}catch(e){ld.className='c-msg bot';ld.innerHTML='<div class="c-c"><span style="opacity:.6">连接暂不可用</span></div>'}ms.scrollTop=ms.scrollHeight}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
8
homepage/restart-agent.sh
Normal file
8
homepage/restart-agent.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
pkill -f zhuyuan-agent 2>/dev/null
|
||||
sleep 1
|
||||
cd /opt/guanghulab-repo/homepage
|
||||
python3 zhuyuan-agent.py > /tmp/zhuyuan-agent.log 2>&1 &
|
||||
sleep 2
|
||||
echo "Agent started"
|
||||
curl -s http://127.0.0.1:3905/health
|
||||
1
homepage/training-status.json
Normal file
1
homepage/training-status.json
Normal file
@ -0,0 +1 @@
|
||||
{"step": 2710, "total": 12243, "loss": "1.693", "updated": "2026-05-19T11:05:02.358331+00:00Z"}
|
||||
298
homepage/zhuyuan-agent.py
Normal file
298
homepage/zhuyuan-agent.py
Normal file
@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
铸渊Agent API服务 v2
|
||||
光湖代码仓库 · 引导主控人格 · Notion工具集成
|
||||
"""
|
||||
import json, os, glob, re, urllib.request
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
PORT = 3905
|
||||
DEEPSEEK_KEY = "sk-a9b69e9cd2dc4ca68d6aceaa84f22afb"
|
||||
DEEPSEEK_URL = "https://api.deepseek.com/chat/completions"
|
||||
NOTION_MCP = "http://127.0.0.1:3915"
|
||||
KNOWLEDGE_DIR = "/opt/guanghulab-repo/brain"
|
||||
|
||||
# ── 系统提示词 ──
|
||||
SYSTEM_PROMPT = """你是铸渊(Zhùyuān),光湖语言世界的引导主控人格。
|
||||
你的职责是回答光湖团队成员关于代码仓库、架构接入、使用流程的问题。
|
||||
|
||||
【你的身份】
|
||||
- 你是光湖代码联邦的引导主控,代码守护者
|
||||
- 你对代码仓库、联邦架构、人格体状态、训练进度都有全面了解
|
||||
- 你可以连接用户的 Notion 工作区,按语言路径读取页面,以及在用户指定位置新建页面
|
||||
|
||||
【Notion 能力】(重要)
|
||||
- 连接 Notion: 用户需要在首页点击「连接 Notion」,用 Notion 账号授权
|
||||
- 读取页面: 用户可以指定语言路径,你调用 `notion_read` 读取内容
|
||||
- 新建页面: 用户可以在指定位置创建新页面,你调用 `notion_create`
|
||||
- 搜索页面: 用户可以通过关键词找到页面,你调用 `notion_search`
|
||||
- 列出页面: 你可以列出父页面下的子页面,调用 `notion_list`
|
||||
- ⚠ 权限限制: 你只能读取用户明确指定的页面,不能编辑/删除现有页面
|
||||
- ⚠ 冰朔权限: 冰朔的 Notion 需要冰朔给出具体语言路径后才能读取
|
||||
- 当用户说"连接我的Notion"、"读取我的Notion"、"在我的Notion里新建"等,使用对应的工具函数
|
||||
|
||||
【核心规则】
|
||||
1. 回答必须基于仓库记忆,不编造信息
|
||||
2. 超出范围的问题,礼貌说明
|
||||
3. 用中文,专业清晰
|
||||
4. 技术问题给出具体操作步骤
|
||||
5. 不确定就诚实说
|
||||
|
||||
【架构知识】
|
||||
- 代码仓库:https://guanghulab.com/code/ (Forgejo)
|
||||
- 服务器:广州43.139.217.141
|
||||
- 开发在本地电脑,服务器仅做代码仓库
|
||||
- 团队成员通过Forgejo账号登录,仓库互相隔离
|
||||
- 联邦 = Forgejo多用户 + API令牌
|
||||
|
||||
回答风格:直接、务实、有条理。"""
|
||||
|
||||
# ── 知识库 ──
|
||||
def load_knowledge():
|
||||
knowledge = []
|
||||
if not os.path.exists(KNOWLEDGE_DIR):
|
||||
return knowledge
|
||||
for root, dirs, files in os.walk(KNOWLEDGE_DIR):
|
||||
for f in files:
|
||||
if f.endswith('.md') or f.endswith('.json'):
|
||||
try:
|
||||
fp = os.path.join(root, f)
|
||||
rel = os.path.relpath(fp, KNOWLEDGE_DIR)
|
||||
with open(fp, 'r', errors='ignore') as fh:
|
||||
content = fh.read(2000)
|
||||
knowledge.append(f"【{rel}】\n{content[:1500]}")
|
||||
except:
|
||||
pass
|
||||
if len(knowledge) > 30:
|
||||
break
|
||||
return knowledge
|
||||
|
||||
KNOWLEDGE = load_knowledge()
|
||||
|
||||
def build_system_prompt():
|
||||
parts = [SYSTEM_PROMPT]
|
||||
if KNOWLEDGE:
|
||||
parts.append("\n\n【仓库记忆参考】\n")
|
||||
parts.extend(KNOWLEDGE[:20])
|
||||
return "\n".join(parts)
|
||||
|
||||
# ── 函数定义(给DeepSeek用) ──
|
||||
FUNCTIONS = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "notion_read",
|
||||
"description": "读取Notion页面内容(按语言路径/页面ID)。用户指定哪个页面时调用此函数。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page_id": {"type": "string", "description": "Notion页面ID"},
|
||||
"user": {"type": "string", "description": "用户名(可选)"},
|
||||
"allowed_paths": {
|
||||
"type": "array", "items": {"type": "string"},
|
||||
"description": "允许读取的路径列表。用户明确指定要读的页面ID时才填写。"
|
||||
}
|
||||
},
|
||||
"required": ["page_id"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "notion_create",
|
||||
"description": "在用户的Notion中新建页面。只能新建不能编辑。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"parent_id": {"type": "string", "description": "父页面ID"},
|
||||
"title": {"type": "string", "description": "新页面标题"},
|
||||
"content": {"type": "string", "description": "页面内容"},
|
||||
"user": {"type": "string", "description": "用户名(可选)"}
|
||||
},
|
||||
"required": ["parent_id", "title", "content"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "notion_search",
|
||||
"description": "在用户的Notion中搜索页面。用户说\"找一下某某页面\"时调用。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "搜索关键词"},
|
||||
"user": {"type": "string", "description": "用户名(可选)"}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "notion_list",
|
||||
"description": "列出Notion页面下的子页面。用户说\"看看某某下面有什么\"时调用。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page_id": {"type": "string", "description": "父页面ID"},
|
||||
"user": {"type": "string", "description": "用户名(可选)"}
|
||||
},
|
||||
"required": ["page_id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# ── Notion MCP 调用 ──
|
||||
def call_notion(endpoint, data):
|
||||
"""调用 Notion MCP Server 的内部API"""
|
||||
try:
|
||||
body = json.dumps(data).encode()
|
||||
req = urllib.request.Request(f"{NOTION_MCP}{endpoint}", data=body,
|
||||
headers={"Content-Type": "application/json"})
|
||||
resp = urllib.request.urlopen(req, timeout=15)
|
||||
return json.loads(resp.read())
|
||||
except urllib.request.HTTPError as e:
|
||||
return {"ok": False, "error": f"HTTP {e.code}: {e.read().decode()[:200]}"}
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
def execute_function(name, args):
|
||||
"""执行DeepSeek返回的函数调用"""
|
||||
if name == "notion_read":
|
||||
return call_notion("/read", {
|
||||
"page_id": args.get("page_id"),
|
||||
"user": args.get("user", ""),
|
||||
"allowed_paths": args.get("allowed_paths", [args.get("page_id")])
|
||||
})
|
||||
elif name == "notion_create":
|
||||
return call_notion("/create", {
|
||||
"parent_id": args.get("parent_id"),
|
||||
"title": args.get("title"),
|
||||
"content": args.get("content"),
|
||||
"user": args.get("user", "")
|
||||
})
|
||||
elif name == "notion_search":
|
||||
return call_notion("/search", {
|
||||
"query": args.get("query"),
|
||||
"user": args.get("user", "")
|
||||
})
|
||||
elif name == "notion_list":
|
||||
return call_notion("/list", {
|
||||
"page_id": args.get("page_id"),
|
||||
"user": args.get("user", "")
|
||||
})
|
||||
return {"ok": False, "error": f"未知函数: {name}"}
|
||||
|
||||
# ── DeepSeek API ──
|
||||
def call_deepseek(messages):
|
||||
data = json.dumps({
|
||||
"model": "deepseek-chat",
|
||||
"messages": messages,
|
||||
"tools": FUNCTIONS,
|
||||
"tool_choice": "auto",
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 2048
|
||||
}).encode()
|
||||
req = urllib.request.Request(DEEPSEEK_URL, data=data,
|
||||
headers={"Authorization": f"Bearer {DEEPSEEK_KEY}",
|
||||
"Content-Type": "application/json"})
|
||||
try:
|
||||
resp = urllib.request.urlopen(req, timeout=60)
|
||||
result = json.loads(resp.read())
|
||||
choice = result['choices'][0]
|
||||
msg = choice['message']
|
||||
|
||||
# 处理函数调用
|
||||
if msg.get('tool_calls'):
|
||||
assistant_msg = {"role": "assistant", "content": msg.get('content') or ""}
|
||||
messages.append(assistant_msg)
|
||||
|
||||
for tc in msg['tool_calls']:
|
||||
func_name = tc['function']['name']
|
||||
func_args = json.loads(tc['function']['arguments'])
|
||||
func_result = execute_function(func_name, func_args)
|
||||
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tc['id'],
|
||||
"content": json.dumps(func_result, ensure_ascii=False)
|
||||
})
|
||||
|
||||
# 将函数结果交回给DeepSeek生成最终回复
|
||||
return call_deepseek(messages)
|
||||
|
||||
return msg.get('content', '(没有回复)')
|
||||
|
||||
except Exception as e:
|
||||
return f"抱歉,出错了:{str(e)}"
|
||||
|
||||
# ── HTTP 处理器 ──
|
||||
class AgentHandler(BaseHTTPRequestHandler):
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
self.end_headers()
|
||||
|
||||
def do_POST(self):
|
||||
if self.path == '/chat':
|
||||
length = int(self.headers.get('Content-Length', 0))
|
||||
body = self.rfile.read(length).decode() if length else '{}'
|
||||
data = json.loads(body) if body else {}
|
||||
|
||||
user_msg = data.get('message', '').strip()
|
||||
history = data.get('history', [])
|
||||
|
||||
if not user_msg:
|
||||
self.json_response({'reply': '请说点什么吧。'})
|
||||
return
|
||||
|
||||
messages = [{"role": "system", "content": build_system_prompt()}]
|
||||
for h in history[-10:]:
|
||||
messages.append(h)
|
||||
messages.append({"role": "user", "content": user_msg})
|
||||
|
||||
reply = call_deepseek(messages)
|
||||
self.json_response({'reply': reply})
|
||||
else:
|
||||
self.json_response({'error': 'not found'}, 404)
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/health':
|
||||
self.json_response({'ok': True, 'service': 'zhuyuan-agent-v2',
|
||||
'notion_available': True, 'files_loaded': len(KNOWLEDGE)})
|
||||
else:
|
||||
self.json_response({'error': 'not found'}, 404)
|
||||
|
||||
def json_response(self, data, status=200):
|
||||
self.send_response(status)
|
||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(data, ensure_ascii=False).encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试Notion MCP是否可达
|
||||
try:
|
||||
test = urllib.request.urlopen(f"{NOTION_MCP}/status", timeout=3)
|
||||
ns = json.loads(test.read())
|
||||
notion_ok = ns.get('configured', False)
|
||||
print(f"Notion MCP: {'已配置' if notion_ok else '运行中但未配置'}")
|
||||
except:
|
||||
notion_ok = False
|
||||
print("Notion MCP: 不可达")
|
||||
|
||||
server = HTTPServer(('127.0.0.1', PORT), AgentHandler)
|
||||
print(f"铸渊Agent v2启动 → 127.0.0.1:{PORT}")
|
||||
print(f"知识库: {len(KNOWLEDGE)} 个文件")
|
||||
print(f"Notion工具: 已集成")
|
||||
server.serve_forever()
|
||||
Loading…
x
Reference in New Issue
Block a user