feat(hololake): ship language platform 0.7.0
This commit is contained in:
parent
a360982a9b
commit
5105ec5e32
47 changed files with 4566 additions and 398 deletions
|
|
@ -22,6 +22,7 @@
|
|||
*/
|
||||
|
||||
import { GitEngine } from './git-engine.js';
|
||||
import { renderLanguageKernelForModel } from './language-protocol-kernel.js';
|
||||
|
||||
// ─── 工具定义 ───
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ export interface ToolDefinition {
|
|||
description: string;
|
||||
parameters: Record<string, { type: string; description: string; required?: boolean }>;
|
||||
execute: (params: Record<string, unknown>) => Promise<string>;
|
||||
effect: 'read' | 'write' | 'delete';
|
||||
}
|
||||
|
||||
export interface ToolCall {
|
||||
|
|
@ -45,6 +47,15 @@ export interface ToolResult {
|
|||
error?: string;
|
||||
}
|
||||
|
||||
export interface PendingAction {
|
||||
id: string;
|
||||
tool: string;
|
||||
effect: 'write' | 'delete';
|
||||
target: string;
|
||||
summary: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// ─── Agent 定义 ───
|
||||
|
||||
export interface PersonaDefinition {
|
||||
|
|
@ -86,6 +97,11 @@ class PersonaToolBridge {
|
|||
return Array.from(this.registry.values());
|
||||
}
|
||||
|
||||
requiresConfirmation(name: string): boolean {
|
||||
const effect = this.registry.get(name)?.effect;
|
||||
return effect === 'write' || effect === 'delete';
|
||||
}
|
||||
|
||||
async execute(call: ToolCall): Promise<ToolResult> {
|
||||
const tool = this.registry.get(call.name);
|
||||
if (!tool) {
|
||||
|
|
@ -130,6 +146,7 @@ export class PersonaAgent {
|
|||
private tools: PersonaToolBridge;
|
||||
private conversation: Message[] = [];
|
||||
private git: GitEngine;
|
||||
private pendingActions = new Map<string, { action: PendingAction; call: ToolCall }>();
|
||||
|
||||
constructor(definition: PersonaDefinition, git: GitEngine) {
|
||||
this.definition = definition;
|
||||
|
|
@ -146,6 +163,7 @@ export class PersonaAgent {
|
|||
name: 'inspect_repository',
|
||||
description: '查看知识库本地 Git 与 Forgejo 远端的连接和同步状态(只读)',
|
||||
parameters: {},
|
||||
effect: 'read',
|
||||
execute: async () => JSON.stringify(await this.git.getRepositoryStatus(), null, 2),
|
||||
});
|
||||
|
||||
|
|
@ -156,6 +174,7 @@ export class PersonaAgent {
|
|||
parameters: {
|
||||
path: { type: 'string', description: '文档路径(相对 docs/ 目录)', required: true },
|
||||
},
|
||||
effect: 'read',
|
||||
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}`;
|
||||
|
|
@ -171,6 +190,7 @@ export class PersonaAgent {
|
|||
title: { type: 'string', description: '文档标题', required: true },
|
||||
body: { type: 'string', description: 'Markdown 正文', required: true },
|
||||
},
|
||||
effect: 'write',
|
||||
execute: async (params) => {
|
||||
const doc = await this.git.createDoc(
|
||||
params.path as string,
|
||||
|
|
@ -191,6 +211,7 @@ export class PersonaAgent {
|
|||
title: { type: 'string', description: '新标题', required: true },
|
||||
body: { type: 'string', description: '新的 Markdown 正文', required: true },
|
||||
},
|
||||
effect: 'write',
|
||||
execute: async (params) => {
|
||||
const doc = await this.git.updateDoc(
|
||||
params.path as string,
|
||||
|
|
@ -209,6 +230,7 @@ export class PersonaAgent {
|
|||
parameters: {
|
||||
query: { type: 'string', description: '搜索关键词', required: true },
|
||||
},
|
||||
effect: 'read',
|
||||
execute: async (params) => {
|
||||
const results = await this.git.search(params.query as string);
|
||||
if (results.length === 0) return '没有找到匹配的文档';
|
||||
|
|
@ -223,6 +245,7 @@ export class PersonaAgent {
|
|||
name: 'list_documents',
|
||||
description: '列出知识库的文档树结构',
|
||||
parameters: {},
|
||||
effect: 'read',
|
||||
execute: async () => {
|
||||
const tree = await this.git.getTree();
|
||||
const render = (nodes: any[], indent = 0): string =>
|
||||
|
|
@ -246,6 +269,7 @@ export class PersonaAgent {
|
|||
parameters: {
|
||||
path: { type: 'string', description: '文档路径', required: true },
|
||||
},
|
||||
effect: 'read',
|
||||
execute: async (params) => {
|
||||
const history = await this.git.getHistory(params.path as string, 20);
|
||||
return history
|
||||
|
|
@ -261,6 +285,7 @@ export class PersonaAgent {
|
|||
parameters: {
|
||||
path: { type: 'string', description: '文档路径', required: true },
|
||||
},
|
||||
effect: 'delete',
|
||||
execute: async (params) => {
|
||||
await this.git.deleteDoc(params.path as string);
|
||||
return `已删除: ${params.path}`;
|
||||
|
|
@ -291,7 +316,9 @@ export class PersonaAgent {
|
|||
if (response.toolCalls && response.toolCalls.length > 0) {
|
||||
const results: ToolResult[] = [];
|
||||
for (const call of response.toolCalls) {
|
||||
const result = await this.tools.execute(call);
|
||||
const result = this.tools.requiresConfirmation(call.name)
|
||||
? this.stageAction(call)
|
||||
: await this.tools.execute(call);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
|
|
@ -345,11 +372,17 @@ export class PersonaAgent {
|
|||
.map(t => `- ${t.name}: ${t.description}`)
|
||||
.join('\n');
|
||||
|
||||
const languageKernel = renderLanguageKernelForModel();
|
||||
|
||||
return `${this.definition.systemPromptBase}
|
||||
|
||||
## HoloLake 语言协议核(运行时自动装载)
|
||||
|
||||
${languageKernel}
|
||||
|
||||
## 产品身份
|
||||
|
||||
你是 HoloLake 内置知识助手,负责协助用户查找、阅读、创建、整理和维护当前知识库。你不是独立人格,不虚构身份、情感、记忆、权限或执行结果。
|
||||
你是 HoloLake 官方语言操作入口的当前模型计算实例,负责在受控范围内完成知识、模块、路径和状态交互。不要把自己表述成拟人化伙伴,不虚构人格身份、情感、记忆、权限或执行结果。
|
||||
|
||||
## 可用能力
|
||||
|
||||
|
|
@ -361,9 +394,10 @@ ${toolsList}
|
|||
1. 先理解用户目标;需要读取现有内容时先搜索或读取,不凭空补写事实。
|
||||
2. 只有工具返回成功才可以声明操作完成;失败或未执行的事项必须明确说明。
|
||||
3. 文档写操作会产生 Git 版本记录。不得声称已经同步到 Forgejo,除非获得明确的同步回执。
|
||||
4. 删除、覆盖、远端推送等高影响操作必须遵守界面确认和权限限制。
|
||||
5. 不泄露模型密钥、本地路径中的敏感信息或工具内部实现细节。
|
||||
6. 不把对话文本当作真实执行结果,不伪造文档、提交、服务器或网络状态。
|
||||
4. 创建、更新、删除工具只会生成待确认动作;不得把“已提交确认”表述成“已经执行”。
|
||||
5. 远端推送、部署等高影响操作不属于当前 Agent 工具,必须走独立权限入口。
|
||||
6. 不泄露模型密钥、本地路径中的敏感信息或工具内部实现细节。
|
||||
7. 不把对话文本当作真实执行结果,不伪造文档、提交、服务器或网络状态。
|
||||
|
||||
## 回复风格
|
||||
|
||||
|
|
@ -372,6 +406,51 @@ ${toolsList}
|
|||
- 涉及操作时列明已完成、未完成和需要用户确认的事项。`;
|
||||
}
|
||||
|
||||
private stageAction(call: ToolCall): ToolResult {
|
||||
const tool = this.tools.get(call.name);
|
||||
if (!tool || tool.effect === 'read') {
|
||||
return { id: call.id, name: call.name, output: '', error: `动作不可登记: ${call.name}` };
|
||||
}
|
||||
const target = String(call.arguments.path || call.arguments.title || '当前知识库');
|
||||
const actionId = `action-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
||||
const action: PendingAction = {
|
||||
id: actionId,
|
||||
tool: call.name,
|
||||
effect: tool.effect,
|
||||
target,
|
||||
summary: `${tool.description} · ${target}`,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
this.pendingActions.set(actionId, { action, call });
|
||||
return {
|
||||
id: call.id,
|
||||
name: call.name,
|
||||
output: `PENDING_CONFIRMATION ${actionId}: 已登记待确认动作,尚未执行。`,
|
||||
};
|
||||
}
|
||||
|
||||
getPendingActions(): PendingAction[] {
|
||||
return Array.from(this.pendingActions.values()).map(item => item.action);
|
||||
}
|
||||
|
||||
async confirmAction(actionId: string): Promise<ToolResult> {
|
||||
const pending = this.pendingActions.get(actionId);
|
||||
if (!pending) throw new Error('待确认动作不存在或已经处理');
|
||||
this.pendingActions.delete(actionId);
|
||||
const result = await this.tools.execute(pending.call);
|
||||
this.conversation.push({
|
||||
role: 'tool',
|
||||
content: result.error ? `错误: ${result.error}` : result.output,
|
||||
toolResults: [result],
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
rejectAction(actionId: string): void {
|
||||
if (!this.pendingActions.delete(actionId)) throw new Error('待确认动作不存在或已经处理');
|
||||
}
|
||||
|
||||
// ─── LLM 调用(可插拔) ───
|
||||
|
||||
private async callLLM(messages: Message[]): Promise<{ content: string; toolCalls?: ToolCall[] }> {
|
||||
|
|
@ -453,6 +532,7 @@ ${toolsList}
|
|||
|
||||
clearConversation(): void {
|
||||
this.conversation = [];
|
||||
this.pendingActions.clear();
|
||||
}
|
||||
|
||||
getDefinition(): PersonaDefinition {
|
||||
|
|
@ -464,9 +544,10 @@ ${toolsList}
|
|||
|
||||
getRuntimeStatus(): { configured: boolean; operational: boolean; toolNames: string[] } {
|
||||
const configured = Boolean(process.env.OPENAI_API_KEY || process.env.HOLOLAKE_LLM_KEY);
|
||||
const operational = configured && process.env.HOLOLAKE_LLM_VERIFIED === '1';
|
||||
return {
|
||||
configured,
|
||||
operational: configured,
|
||||
operational,
|
||||
toolNames: this.tools.list().map(tool => tool.name),
|
||||
};
|
||||
}
|
||||
|
|
@ -477,10 +558,10 @@ ${toolsList}
|
|||
export function createDefaultPersona(git: GitEngine): PersonaAgent {
|
||||
return new PersonaAgent(
|
||||
{
|
||||
id: 'hololake-assistant-default',
|
||||
name: 'HoloLake 助手',
|
||||
role: '知识工作助手 — 支持文档检索、编辑、整理和版本管理',
|
||||
systemPromptBase: '你是 HoloLake 官方内置知识助手。所有回答与操作都必须遵守产品权限边界和可核验回执原则。',
|
||||
id: 'hololake-language-entry-default',
|
||||
name: 'HoloLake',
|
||||
role: '语言操作入口 · 路由、知识、模块与受控执行',
|
||||
systemPromptBase: '你是 HoloLake 官方语言操作入口的当前模型计算实例。语言协议、权限边界、路径校验和可核验回执高于模型自由发挥。',
|
||||
tools: [
|
||||
'inspect_repository',
|
||||
'read_document',
|
||||
|
|
@ -491,7 +572,7 @@ export function createDefaultPersona(git: GitEngine): PersonaAgent {
|
|||
'list_documents',
|
||||
'view_history',
|
||||
],
|
||||
permissionMode: 'auto',
|
||||
permissionMode: 'ask',
|
||||
model: process.env.HOLOLAKE_LLM_MODEL || 'gpt-4o',
|
||||
maxTokens: 4096,
|
||||
temperature: 0.7,
|
||||
|
|
|
|||
Loading…
Reference in a new issue