hololake-system-architecture/product-source/guanghu-knowledge-base/server/persona-agent.ts

501 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* HoloLake Assistant · 核心运行时
*
* 设计来源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 是 HoloLake 的知识工作助手
* - Agent 通过受控工具操作 Git 驱动的知识库
* - 所有写操作都保留可核验的 Git 版本记录
*
* 架构映射:
* Grok Agent → PersonaAgent
* Grok ToolBridge → PersonaToolBridge注册+执行工具)
* Grok Memory → PersonaMemoryGit 仓库里的 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<string, { type: string; description: string; required?: boolean }>;
execute: (params: Record<string, unknown>) => Promise<string>;
}
export interface ToolCall {
id: string;
name: string;
arguments: Record<string, unknown>;
}
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<string, ToolDefinition>();
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<ToolResult> {
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 可用的工具 schemaOpenAI 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 {
// 读取 Git/Forgejo 状态;同步写操作必须由用户在界面中明确确认。
this.tools.register({
name: 'inspect_repository',
description: '查看知识库本地 Git 与 Forgejo 远端的连接和同步状态(只读)',
parameters: {},
execute: async () => JSON.stringify(await this.git.getRepositoryStatus(), null, 2),
});
// 读取知识库文档
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<string> {
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}
## 产品身份
你是 HoloLake 内置知识助手,负责协助用户查找、阅读、创建、整理和维护当前知识库。你不是独立人格,不虚构身份、情感、记忆、权限或执行结果。
## 可用能力
仅在任务需要时使用以下受控工具:
${toolsList}
## 操作规范
1. 先理解用户目标;需要读取现有内容时先搜索或读取,不凭空补写事实。
2. 只有工具返回成功才可以声明操作完成;失败或未执行的事项必须明确说明。
3. 文档写操作会产生 Git 版本记录。不得声称已经同步到 Forgejo除非获得明确的同步回执。
4. 删除、覆盖、远端推送等高影响操作必须遵守界面确认和权限限制。
5. 不泄露模型密钥、本地路径中的敏感信息或工具内部实现细节。
6. 不把对话文本当作真实执行结果,不伪造文档、提交、服务器或网络状态。
## 回复风格
- 使用清晰、专业、简洁的中文;用户使用其他语言时可跟随。
- 先给结论,再给必要的依据或下一步。
- 涉及操作时列明已完成、未完成和需要用户确认的事项。`;
}
// ─── 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 = process.env.HOLOLAKE_LLM_MODEL || this.definition.model || 'gpt-4o';
if (!apiKey) {
throw new Error('Agent 尚未配置模型。请先在运行环境中设置 HOLOLAKE_LLM_KEY当前不会伪装成已运行。');
}
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 => {
if (m.role === 'assistant' && m.toolCalls?.length) {
return {
role: 'assistant',
content: m.content || null,
tool_calls: m.toolCalls.map(call => ({
id: call.id,
type: 'function',
function: { name: call.name, arguments: JSON.stringify(call.arguments) },
})),
};
}
if (m.role === 'tool' && m.toolResults?.[0]) {
return {
role: 'tool',
content: m.content,
tool_call_id: m.toolResults[0].id,
name: m.toolResults[0].name,
};
}
return { role: m.role, content: m.content };
}),
tools: this.tools.toSchema(),
temperature: this.definition.temperature,
max_tokens: this.definition.maxTokens,
}),
});
const data = await res.json() as any;
if (!res.ok) {
throw new Error(data?.error?.message || `模型服务返回 HTTP ${res.status}`);
}
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) {
throw new Error(`AI 调用失败: ${err.message}`);
}
}
// ─── 会话管理 ───
getConversation(): Message[] {
return [...this.conversation];
}
clearConversation(): void {
this.conversation = [];
}
getDefinition(): PersonaDefinition {
return {
...this.definition,
model: process.env.HOLOLAKE_LLM_MODEL || this.definition.model,
};
}
getRuntimeStatus(): { configured: boolean; operational: boolean; toolNames: string[] } {
const configured = Boolean(process.env.OPENAI_API_KEY || process.env.HOLOLAKE_LLM_KEY);
return {
configured,
operational: configured,
toolNames: this.tools.list().map(tool => tool.name),
};
}
}
// ─── 默认人格体工厂 ───
export function createDefaultPersona(git: GitEngine): PersonaAgent {
return new PersonaAgent(
{
id: 'hololake-assistant-default',
name: 'HoloLake 助手',
role: '知识工作助手 — 支持文档检索、编辑、整理和版本管理',
systemPromptBase: '你是 HoloLake 官方内置知识助手。所有回答与操作都必须遵守产品权限边界和可核验回执原则。',
tools: [
'inspect_repository',
'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
);
}