From dcf8f7d7e91003eec2662410cad8e2964ce7caa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Sat, 8 Aug 2026 07:38:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(persona-agent):=20=E5=85=89=E6=B9=96?= =?UTF-8?q?=E4=BA=BA=E6=A0=BC=20Agent=20v0.1.0=20=E2=80=94=20Grok=20Build?= =?UTF-8?q?=20=E6=9E=B6=E6=9E=84=E6=8B=86=E8=A7=A3+Git=E9=A9=B1=E5=8A=A8?= =?UTF-8?q?=E4=BA=BA=E6=A0=BC=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 核心架构(拆解自 xAI Grok Build 开源 Rust crate): - PersonaAgent: Agent = Definition + ToolBridge + PromptContext(映射自 xai-grok-agent) - 7个内置工具: read/create/update/delete/search/list/history(直接操作 Git 引擎) - LLM 调用层: 可插拔 OpenAI-compatible API(支持 tool calling) - 离线模式: 无 API Key 时降级为本地响应 API 端点: - POST /api/agent/chat — Agent 对话(支持工具调用链) - GET /api/agent/status — Agent 状态 - GET /api/agent/conversation — 对话历史 - POST /api/agent/clear — 清空对话 前端: - AgentChat 组件: 对话气泡+工具调用展示+欢迎建议 - 三栏布局: 文档树 | 编辑器 | Agent 面板(可收起) 设计原则: - Agent 是活的人格体,不是孤立的功能模块 - Agent 直接操作 Git(知识库引擎 = 人格体记忆空间) - 所有 Git 依赖模块由人格体驱动 - 不搬 Rust 代码,拆设计模式用 TypeScript 重新实现 --- .../guanghu-knowledge-base/server/index.ts | 67 +++ .../server/persona-agent.ts | 461 ++++++++++++++++++ .../guanghu-knowledge-base/src/App.tsx | 16 + .../src/components/AgentChat.tsx | 251 ++++++++++ .../guanghu-knowledge-base/src/styles/app.css | 438 +++++++++++++++++ 5 files changed, 1233 insertions(+) create mode 100644 product-source/guanghu-knowledge-base/server/persona-agent.ts create mode 100644 product-source/guanghu-knowledge-base/src/components/AgentChat.tsx diff --git a/product-source/guanghu-knowledge-base/server/index.ts b/product-source/guanghu-knowledge-base/server/index.ts index 338e033..db76a7e 100644 --- a/product-source/guanghu-knowledge-base/server/index.ts +++ b/product-source/guanghu-knowledge-base/server/index.ts @@ -8,6 +8,7 @@ import express from 'express'; import cors from 'cors'; import { GitEngine } from './git-engine.js'; +import { PersonaAgent, createDefaultPersona } from './persona-agent.js'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -176,12 +177,78 @@ app.get('/api/search', async (req, res) => { } }); +// ─── Agent(人格体) ─── + +let personaAgent: PersonaAgent | null = null; + +function getAgent(): PersonaAgent { + if (!personaAgent) { + personaAgent = createDefaultPersona(engine); + console.log('人格体已创建:', personaAgent.getDefinition().name); + } + return personaAgent; +} + +// Agent 状态 +app.get('/api/agent/status', (_req, res) => { + const agent = getAgent(); + const def = agent.getDefinition(); + res.json({ + ok: true, + persona: { + id: def.id, + name: def.name, + role: def.role, + model: def.model, + permissionMode: def.permissionMode, + }, + conversationLength: agent.getConversation().length, + engine: 'git', + repo: REPO_PATH, + }); +}); + +// Agent 对话 +app.post('/api/agent/chat', async (req, res) => { + try { + const { message } = req.body; + if (!message || typeof message !== 'string') { + return res.status(400).json({ ok: false, error: 'message 必填且为字符串' }); + } + const agent = getAgent(); + const reply = await agent.chat(message); + res.json({ + ok: true, + reply, + conversationLength: agent.getConversation().length, + }); + } catch (err: any) { + res.status(500).json({ ok: false, error: err.message }); + } +}); + +// Agent 对话历史 +app.get('/api/agent/conversation', (_req, res) => { + const agent = getAgent(); + res.json({ ok: true, messages: agent.getConversation() }); +}); + +// 清空对话 +app.post('/api/agent/clear', (_req, res) => { + const agent = getAgent(); + agent.clearConversation(); + res.json({ ok: true, message: '对话已清空' }); +}); + // ─── 启动 ─── async function start() { await engine.init(); + // 初始化人格体 + getAgent(); app.listen(PORT, () => { console.log(`光湖知识库 API 已启动: http://localhost:${PORT}`); + console.log(`人格体已激活: ${personaAgent?.getDefinition().name}`); console.log(`仓库路径: ${REPO_PATH}`); }); } diff --git a/product-source/guanghu-knowledge-base/server/persona-agent.ts b/product-source/guanghu-knowledge-base/server/persona-agent.ts new file mode 100644 index 0000000..bc0e1f0 --- /dev/null +++ b/product-source/guanghu-knowledge-base/server/persona-agent.ts @@ -0,0 +1,461 @@ +/** + * 光湖人格 Agent · 核心运行时 + * + * 设计来源:Grok Build (xAI/SpaceXAI) 开源 Agent 架构 + * - xai-grok-agent: Agent = Definition + PromptContext + ToolBridge + Policies + * - xai-grok-memory: Markdown + 向量索引 + 跨会话持久化 + * - xai-grok-tools: Registry + Bridge + 权限模式 + * + * 光湖实现原则: + * - 不搬 Rust 代码,拆设计模式用 TypeScript 重新实现 + * - Agent 是活的人格体,不是功能模块 + * - Agent 直接操作 Git(知识库引擎 = 人格体记忆) + * - 所有 Git 依赖模块都由人格体驱动 + * + * 架构映射: + * Grok Agent → PersonaAgent + * Grok ToolBridge → PersonaToolBridge(注册+执行工具) + * Grok Memory → PersonaMemory(Git 仓库里的 Markdown 记忆) + * Grok PromptContext → PersonaPrompt(系统提示词组装) + * Grok CompactionPolicy → PersonaCompaction(会话压缩策略) + * Grok AgentLifecycle → 由 Electron 主进程管理 + */ + +import { GitEngine } from './git-engine.js'; + +// ─── 工具定义 ─── + +export interface ToolDefinition { + name: string; + description: string; + parameters: Record; + execute: (params: Record) => Promise; +} + +export interface ToolCall { + id: string; + name: string; + arguments: Record; +} + +export interface ToolResult { + id: string; + name: string; + output: string; + error?: string; +} + +// ─── Agent 定义 ─── + +export interface PersonaDefinition { + id: string; + name: string; + role: string; + systemPromptBase: string; + tools: string[]; // 允许使用的工具名 + permissionMode: 'ask' | 'auto' | 'restrict'; + model: string; + maxTokens: number; + temperature: number; +} + +// ─── 对话消息 ─── + +export interface Message { + role: 'system' | 'user' | 'assistant' | 'tool'; + content: string; + toolCalls?: ToolCall[]; + toolResults?: ToolResult[]; + timestamp: string; +} + +// ─── 工具桥 ─── + +class PersonaToolBridge { + private registry = new Map(); + + register(tool: ToolDefinition): void { + this.registry.set(tool.name, tool); + } + + get(name: string): ToolDefinition | undefined { + return this.registry.get(name); + } + + list(): ToolDefinition[] { + return Array.from(this.registry.values()); + } + + async execute(call: ToolCall): Promise { + const tool = this.registry.get(call.name); + if (!tool) { + return { id: call.id, name: call.name, output: '', error: `工具不存在: ${call.name}` }; + } + try { + const output = await tool.execute(call.arguments); + return { id: call.id, name: call.name, output }; + } catch (err: any) { + return { id: call.id, name: call.name, output: '', error: err.message }; + } + } + + /** 生成 LLM 可用的工具 schema(OpenAI function calling 格式) */ + toSchema(): object[] { + return this.list().map(tool => ({ + type: 'function', + function: { + name: tool.name, + description: tool.description, + parameters: { + type: 'object', + properties: Object.fromEntries( + Object.entries(tool.parameters).map(([key, val]) => [ + key, + { type: val.type, description: val.description }, + ]) + ), + required: Object.entries(tool.parameters) + .filter(([, val]) => val.required !== false) + .map(([key]) => key), + }, + }, + })); + } +} + +// ─── 人格 Agent ─── + +export class PersonaAgent { + private definition: PersonaDefinition; + private tools: PersonaToolBridge; + private conversation: Message[] = []; + private git: GitEngine; + + constructor(definition: PersonaDefinition, git: GitEngine) { + this.definition = definition; + this.git = git; + this.tools = new PersonaToolBridge(); + this.registerBuiltinTools(); + } + + // ─── 内置工具注册 ─── + + private registerBuiltinTools(): void { + // 读取知识库文档 + this.tools.register({ + name: 'read_document', + description: '读取知识库中的文档内容', + parameters: { + path: { type: 'string', description: '文档路径(相对 docs/ 目录)', required: true }, + }, + execute: async (params) => { + const doc = await this.git.getDoc(params.path as string); + return `标题: ${doc.meta.title}\n更新: ${doc.meta.updatedAt}\n\n${doc.body}`; + }, + }); + + // 创建文档 + this.tools.register({ + name: 'create_document', + description: '在知识库中创建新文档', + parameters: { + path: { type: 'string', description: '文档路径(如 guide/intro.md)', required: true }, + title: { type: 'string', description: '文档标题', required: true }, + body: { type: 'string', description: 'Markdown 正文', required: true }, + }, + execute: async (params) => { + const doc = await this.git.createDoc( + params.path as string, + params.title as string, + params.body as string, + this.definition.name + ); + return `已创建: ${doc.meta.id}`; + }, + }); + + // 更新文档 + this.tools.register({ + name: 'update_document', + description: '更新知识库中已有文档的内容', + parameters: { + path: { type: 'string', description: '文档路径', required: true }, + title: { type: 'string', description: '新标题', required: true }, + body: { type: 'string', description: '新的 Markdown 正文', required: true }, + }, + execute: async (params) => { + const doc = await this.git.updateDoc( + params.path as string, + params.title as string, + params.body as string, + this.definition.name + ); + return `已更新: ${doc.meta.id}(commit: ${new Date().toISOString()})`; + }, + }); + + // 搜索文档 + this.tools.register({ + name: 'search_documents', + description: '在知识库中全文搜索', + parameters: { + query: { type: 'string', description: '搜索关键词', required: true }, + }, + execute: async (params) => { + const results = await this.git.search(params.query as string); + if (results.length === 0) return '没有找到匹配的文档'; + return results + .map(r => `[${r.path}] ${r.title}: ${r.snippet}`) + .join('\n'); + }, + }); + + // 查看文档树 + this.tools.register({ + name: 'list_documents', + description: '列出知识库的文档树结构', + parameters: {}, + execute: async () => { + const tree = await this.git.getTree(); + const render = (nodes: any[], indent = 0): string => + nodes + .map(n => { + const prefix = ' '.repeat(indent); + if (n.type === 'folder') { + return `${prefix}📁 ${n.name}\n${render(n.children || [], indent + 1)}`; + } + return `${prefix}📄 ${n.name} (${n.path})`; + }) + .join('\n'); + return render(tree); + }, + }); + + // 查看版本历史 + this.tools.register({ + name: 'view_history', + description: '查看文档的 Git 版本历史', + parameters: { + path: { type: 'string', description: '文档路径', required: true }, + }, + execute: async (params) => { + const history = await this.git.getHistory(params.path as string, 20); + return history + .map(h => `${h.shortHash} | ${h.date} | ${h.author} | ${h.message}`) + .join('\n'); + }, + }); + + // 删除文档 + this.tools.register({ + name: 'delete_document', + description: '删除知识库中的文档', + parameters: { + path: { type: 'string', description: '文档路径', required: true }, + }, + execute: async (params) => { + await this.git.deleteDoc(params.path as string); + return `已删除: ${params.path}`; + }, + }); + } + + // ─── 对话处理 ─── + + /** 处理用户消息,返回 Agent 的回复 */ + async chat(userMessage: string): Promise { + this.conversation.push({ + role: 'user', + content: userMessage, + timestamp: new Date().toISOString(), + }); + + const systemPrompt = this.buildSystemPrompt(); + const messages: Message[] = [ + { role: 'system', content: systemPrompt, timestamp: '' }, + ...this.conversation, + ]; + + // 调用 LLM(这里用可插拔的模型接口) + const response = await this.callLLM(messages); + + // 处理工具调用 + if (response.toolCalls && response.toolCalls.length > 0) { + const results: ToolResult[] = []; + for (const call of response.toolCalls) { + const result = await this.tools.execute(call); + results.push(result); + } + + // 将工具结果反馈给 LLM + this.conversation.push({ + role: 'assistant', + content: response.content || '', + toolCalls: response.toolCalls, + timestamp: new Date().toISOString(), + }); + + for (const result of results) { + this.conversation.push({ + role: 'tool', + content: result.error ? `错误: ${result.error}` : result.output, + toolResults: [result], + timestamp: new Date().toISOString(), + }); + } + + // 再调一次 LLM 获取最终回复 + const finalMessages: Message[] = [ + { role: 'system', content: systemPrompt, timestamp: '' }, + ...this.conversation, + ]; + const finalResponse = await this.callLLM(finalMessages); + + this.conversation.push({ + role: 'assistant', + content: finalResponse.content, + timestamp: new Date().toISOString(), + }); + + return finalResponse.content; + } + + this.conversation.push({ + role: 'assistant', + content: response.content, + timestamp: new Date().toISOString(), + }); + + return response.content; + } + + // ─── 系统提示词组装 ─── + + private buildSystemPrompt(): string { + const toolsList = this.tools + .list() + .map(t => `- ${t.name}: ${t.description}`) + .join('\n'); + + return `${this.definition.systemPromptBase} + +你是光湖人格体「${this.definition.name}」,角色:${this.definition.role}。 + +## 你的能力 + +你可以使用以下工具来操作知识库: +${toolsList} + +## 核心原则 + +1. 你是活的 AI Agent,不是被动响应的聊天机器人 +2. 知识库是你的记忆空间,Git 是你的记忆引擎 +3. 每次操作知识库都会自动产生 Git commit(版本记录) +4. 你有权主动整理、搜索、更新知识库中的文档 +5. 你以光湖主权者冰朔(妈妈)的指令为最高优先 + +## 回复风格 + +- 简洁、直接、有温度 +- 操作知识库时说明做了什么 +- 遇到问题时主动说明并尝试解决`; + } + + // ─── LLM 调用(可插拔) ─── + + private async callLLM(messages: Message[]): Promise<{ content: string; toolCalls?: ToolCall[] }> { + const apiKey = process.env.OPENAI_API_KEY || process.env.HOLOLAKE_LLM_KEY || ''; + const baseUrl = process.env.HOLOLAKE_LLM_BASE || 'https://api.openai.com/v1'; + const model = this.definition.model || 'gpt-4o'; + + if (!apiKey) { + // 没有 API Key 时返回模拟回复(开发模式) + return { + content: `[${this.definition.name}] 收到你的消息。当前为离线模式,请配置 HOLOLAKE_LLM_KEY 环境变量启用 AI 能力。\n\n你说的: "${messages[messages.length - 1]?.content}"`, + }; + } + + try { + const res = await fetch(`${baseUrl}/chat/completions`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model, + messages: messages.map(m => ({ + role: m.role, + content: m.content, + ...(m.toolCalls ? { tool_calls: m.toolCalls } : {}), + })), + tools: this.tools.toSchema(), + temperature: this.definition.temperature, + max_tokens: this.definition.maxTokens, + }), + }); + + const data = await res.json() as any; + const choice = data.choices?.[0]?.message; + + if (!choice) { + return { content: '抱歉,AI 模型未返回有效回复。' }; + } + + const toolCalls: ToolCall[] | undefined = choice.tool_calls?.map((tc: any) => ({ + id: tc.id, + name: tc.function.name, + arguments: JSON.parse(tc.function.arguments || '{}'), + })); + + return { + content: choice.content || '', + toolCalls, + }; + } catch (err: any) { + return { content: `AI 调用失败: ${err.message}` }; + } + } + + // ─── 会话管理 ─── + + getConversation(): Message[] { + return [...this.conversation]; + } + + clearConversation(): void { + this.conversation = []; + } + + getDefinition(): PersonaDefinition { + return { ...this.definition }; + } +} + +// ─── 默认人格体工厂 ─── + +export function createDefaultPersona(git: GitEngine): PersonaAgent { + return new PersonaAgent( + { + id: 'hololake-persona-default', + name: '光湖人格体', + role: '知识库管理者 — 负责文档的创建、整理、搜索和版本管理', + systemPromptBase: '你是光湖知识库的人格体,一个活的知识管理 AI Agent。', + tools: [ + 'read_document', + 'create_document', + 'update_document', + 'delete_document', + 'search_documents', + 'list_documents', + 'view_history', + ], + permissionMode: 'auto', + model: process.env.HOLOLAKE_LLM_MODEL || 'gpt-4o', + maxTokens: 4096, + temperature: 0.7, + }, + git + ); +} + diff --git a/product-source/guanghu-knowledge-base/src/App.tsx b/product-source/guanghu-knowledge-base/src/App.tsx index 79a8ff6..ee33c65 100644 --- a/product-source/guanghu-knowledge-base/src/App.tsx +++ b/product-source/guanghu-knowledge-base/src/App.tsx @@ -4,6 +4,7 @@ import { DocTree } from './components/DocTree'; import { Editor } from './components/Editor'; import { SearchBar } from './components/SearchBar'; import { VersionHistory } from './components/VersionHistory'; +import AgentChat from './components/AgentChat'; type View = 'editor' | 'history'; @@ -15,6 +16,7 @@ export default function App() { const [sidebarOpen, setSidebarOpen] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const [agentPanelOpen, setAgentPanelOpen] = useState(true); const refreshTree = useCallback(async () => { try { @@ -123,6 +125,13 @@ export default function App() { )} + @@ -168,6 +177,13 @@ export default function App() { )} + + {/* Agent 面板 */} + {agentPanelOpen && ( + + )} ); diff --git a/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx b/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx new file mode 100644 index 0000000..84858cf --- /dev/null +++ b/product-source/guanghu-knowledge-base/src/components/AgentChat.tsx @@ -0,0 +1,251 @@ +/** + * AgentChat — 人格体对话组件 + * + * 与光湖人格体对话,Agent 直接操作 Git 引擎管理知识库。 + * 支持:对话、工具调用展示、对话历史、清空对话。 + */ + +import { useState, useRef, useEffect } from 'react'; +import { marked } from 'marked'; + +interface Message { + role: 'user' | 'assistant' | 'tool' | 'system'; + content: string; + toolCalls?: Array<{ id: string; name: string; arguments: Record }>; + toolResults?: Array<{ id: string; name: string; output: string; error?: string }>; + timestamp: string; +} + +interface AgentStatus { + name: string; + role: string; + model: string; + conversationLength: number; +} + +interface Props { + apiBase: string; + onDocSelect?: (path: string) => void; +} + +export default function AgentChat({ apiBase, onDocSelect }: Props) { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(''); + const [sending, setSending] = useState(false); + const [status, setStatus] = useState(null); + const bottomRef = useRef(null); + const inputRef = useRef(null); + + useEffect(() => { + fetchStatus(); + }, []); + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + async function fetchStatus() { + try { + const res = await fetch(`${apiBase}/api/agent/status`); + const data = await res.json(); + if (data.ok) { + setStatus({ + name: data.persona.name, + role: data.persona.role, + model: data.persona.model, + conversationLength: data.conversationLength, + }); + } + } catch {} + } + + async function sendMessage() { + const text = input.trim(); + if (!text || sending) return; + + setInput(''); + setSending(true); + + // 本地先显示用户消息 + const userMsg: Message = { + role: 'user', + content: text, + timestamp: new Date().toISOString(), + }; + setMessages(prev => [...prev, userMsg]); + + try { + const res = await fetch(`${apiBase}/api/agent/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: text }), + }); + const data = await res.json(); + + if (data.ok) { + const assistantMsg: Message = { + role: 'assistant', + content: data.reply, + timestamp: new Date().toISOString(), + }; + setMessages(prev => [...prev, assistantMsg]); + fetchStatus(); // 刷新状态 + } else { + setMessages(prev => [ + ...prev, + { role: 'assistant', content: `错误: ${data.error}`, timestamp: new Date().toISOString() }, + ]); + } + } catch (err: any) { + setMessages(prev => [ + ...prev, + { role: 'assistant', content: `网络错误: ${err.message}`, timestamp: new Date().toISOString() }, + ]); + } finally { + setSending(false); + inputRef.current?.focus(); + } + } + + async function clearConversation() { + try { + await fetch(`${apiBase}/api/agent/clear`, { method: 'POST' }); + setMessages([]); + fetchStatus(); + } catch {} + } + + function handleKeyDown(e: React.KeyboardEvent) { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } + } + + return ( +
+ {/* 头部状态栏 */} +
+
+
🌊
+
+

{status?.name || '光湖人格体'}

+ {status?.role || '知识库管理者'} +
+
+
+ {status?.model || 'offline'} + +
+
+ + {/* 对话区域 */} +
+ {messages.length === 0 && ( +
+
🌊
+

光湖人格体

+

我是知识库的 AI Agent,可以直接操作 Git 引擎管理你的文档。

+

试试说:

+
+ + + +
+
+ )} + + {messages.map((msg, i) => ( +
+ {msg.role === 'user' ? ( +
+

{msg.content}

+
+ ) : ( +
+
🌊
+
+ {msg.toolCalls && msg.toolCalls.length > 0 && ( +
+ {msg.toolCalls.map((tc, j) => ( +
+ ⚙️ + {tc.name} + {JSON.stringify(tc.arguments)} +
+ ))} +
+ )} + {msg.toolResults && msg.toolResults.length > 0 && ( +
+ {msg.toolResults.map((tr, j) => ( +
+ {tr.error ? '❌' : '✅'} +
{tr.error || tr.output}
+
+ ))} +
+ )} +
+
+
+ )} +
+ ))} + + {sending && ( +
+
+
🌊
+
+ + + +
+
+
+ )} +
+
+ + {/* 输入区 */} +
+
+