重构agent通讯层

This commit is contained in:
ACT丶流星雨 2026-03-27 21:35:49 +08:00
parent c21f85b7e6
commit c6a7591d4b
11 changed files with 745 additions and 106 deletions

View File

@ -11,8 +11,10 @@ export interface AgentContext {
socket: Socket;
isolationKey: string;
text: string;
userMessageTime?: number;
abortSignal?: AbortSignal;
resTool: ResTool;
msg: ReturnType<ResTool["newMessage"]>;
}
function buildSystemPrompt(skillPrompt: string, mem: Awaited<ReturnType<Memory["get"]>>): string {
@ -35,19 +37,16 @@ function buildSystemPrompt(skillPrompt: string, mem: Awaited<ReturnType<Memory["
const subAgentList = ["executionAI", "supervisionAI"] as const;
export async function decisionAI(ctx: AgentContext) {
const { isolationKey, text, abortSignal, resTool } = ctx;
resTool.systemMessage("决策层AI 接管聊天");
const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx;
const memory = new Memory("scriptAgent", isolationKey);
await memory.add("user", text);
await memory.add("user", text, { createTime: userMessageTime });
const [skill, mem] = await Promise.all([useSkill("script_agent_decision.md"), memory.get(text)]);
const systemPrompt = buildSystemPrompt(skill.prompt, mem);
const projectData = await u.db("o_project").where("id", resTool.data.projectId).first();
const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index");
console.log("%c Line:50 🥒 novelData", "background:#2eafb0", novelData);
const projectInfo = [
"## 项目信息",
@ -70,10 +69,6 @@ export async function decisionAI(ctx: AgentContext) {
run_sub_agent: runSubAgent(ctx),
...useTools(ctx.resTool),
},
onFinish: async (completion) => {
console.log("%c Line:73 🍧 completion", "background:#93c0a4", completion);
await memory.add("assistant:decision", completion.text);
},
});
return textStream;
@ -83,9 +78,6 @@ export async function decisionAI(ctx: AgentContext) {
export async function executionAI(ctx: AgentContext) {
const { isolationKey, text, abortSignal, resTool } = ctx;
resTool.systemMessage("执行层AI 接管聊天");
const memory = new Memory("scriptAgent", isolationKey);
const [skill, mem] = await Promise.all([useSkill("script_agent_execution.md"), memory.get(text)]);
@ -100,10 +92,6 @@ export async function executionAI(ctx: AgentContext) {
...memory.getTools(),
...useTools(ctx.resTool),
},
onFinish: async (completion) => {
console.log("%c Line:102 🍻 completion", "background:#fca650", completion);
await memory.add("assistant:execution", completion.text);
},
});
return textStream;
@ -112,8 +100,6 @@ export async function executionAI(ctx: AgentContext) {
export async function supervisionAI(ctx: AgentContext) {
const { isolationKey, text, abortSignal, resTool } = ctx;
resTool.systemMessage("监督层AI 接管聊天");
const memory = new Memory("scriptAgent", isolationKey);
const [skill, mem] = await Promise.all([useSkill("script_agent_supervision.md"), memory.get(text)]);
@ -127,10 +113,6 @@ export async function supervisionAI(ctx: AgentContext) {
...skill.tools,
...useTools(ctx.resTool),
},
onFinish: async (completion) => {
console.log("%c Line:129 🍣 completion", "background:#3f7cff", completion);
await memory.add("assistant:supervision", completion.text);
},
});
return textStream;
@ -138,6 +120,7 @@ export async function supervisionAI(ctx: AgentContext) {
//工具函数
function runSubAgent(parentCtx: AgentContext) {
const memory = new Memory("scriptAgent", parentCtx.isolationKey);
return tool({
description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI",
inputSchema: z.object({
@ -146,17 +129,30 @@ function runSubAgent(parentCtx: AgentContext) {
}),
execute: async ({ agent, prompt }) => {
const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)];
//运行子Agent
const subMsg = parentCtx.resTool.newMessage("assistant", agent == "executionAI" ? "编剧" : "编辑");
// 先完成主Agent当前的消息
parentCtx.msg.complete();
// 子Agent用新消息回复
const subTextStream = await fn({ ...parentCtx, text: prompt });
let msg = parentCtx.resTool.textMessage();
let text = subMsg.text();
let fullResponse = "";
for await (const chunk of subTextStream) {
msg.send(chunk);
text.append(chunk);
fullResponse += chunk;
}
msg!.end();
text.complete();
subMsg.complete();
if (fullResponse.trim()) {
await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, {
name: agent === "executionAI" ? "编剧" : "编辑",
createTime: new Date(subMsg.datetime).getTime(),
});
}
// 为主Agent后续输出创建新消息
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹");
return fullResponse;
},

View File

@ -38,7 +38,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => {
ids: z.array(z.number()).describe("章节id注意区分"),
}),
execute: async ({ ids }) => {
resTool.systemMessage(`正在阅读 章节事件 数据...`);
resTool.newMessage('system').text(`正在获取章节事件章节ID${ids.join(",")}`).complete();
console.log("[tools] get_novel_events", ids);
const data = await u
.db("o_novel")
@ -55,7 +55,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => {
key: keySchema.describe("数据key"),
}),
execute: async ({ key }) => {
resTool.systemMessage(`正在阅读 ${planDataKeyLabels[key]} 数据...`);
resTool.newMessage('system').text(`正在阅读 ${planDataKeyLabels[key]} 数据...`).complete();
console.log("[tools] get_planData", key);
const planData: planData = await new Promise((resolve) => socket.emit("getPlanData", { key }, (res: any) => resolve(res)));
return planData[key];
@ -76,7 +76,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => {
inputSchema: z.object({ value: planData.shape.storySkeleton }),
execute: async ({ value }) => {
console.log("[tools] set_planData storySkeleton", value);
resTool.systemMessage("正在保存 故事骨架 数据");
resTool.newMessage('system').text("正在保存 故事骨架 数据").complete();
socket.emit("setPlanData", { key: "storySkeleton", value });
return true;
},
@ -86,7 +86,7 @@ export default (resTool: ResTool, toolsNames?: string[]) => {
inputSchema: z.object({ value: planData.shape.adaptationStrategy }),
execute: async ({ value }) => {
console.log("[tools] set_planData adaptationStrategy", value);
resTool.systemMessage("正在保存 改编策略 数据");
resTool.newMessage('system').text("正在保存 改编策略 数据").complete();
socket.emit("setPlanData", { key: "adaptationStrategy", value });
return true;
},

View File

@ -27,4 +27,5 @@ export default async (knex: Knex): Promise<void> => {
// memories 表新增字段
await addColumn("memories", "episodesId", "text");
await addColumn("memories", "agentType", "text");
await addColumn("memories", "name", "text");
};

View File

@ -789,6 +789,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.text("isolationKey").notNullable(); // 记忆隔离键
table.text("type").notNullable(); // 'message' | 'summary'
table.text("role"); // 'user' | 'assistant'
table.text("name");
table.text("content").notNullable();
table.text("embedding"); // 向量嵌入 JSON
table.text("relatedMessageIds"); // summary关联的message id列表 JSON

View File

@ -9,11 +9,6 @@ function normalizeRole(role?: string | null): "user" | "assistant" {
return role?.startsWith("assistant") ? "assistant" : "user";
}
function getAssistantName(role?: string | null): string | undefined {
if (!role?.startsWith("assistant:")) return undefined;
return role.split(":")[1] || "assistant";
}
export default router.post(
"/",
validateFields({
@ -29,12 +24,14 @@ export default router.post(
.db("memories")
.where({ isolationKey, type: "message" })
.orderBy("createTime", "asc")
.select("id", "role", "content", "createTime");
.select("id", "role", "name", "content", "createTime");
const history = rows.map((row) => ({
id: row.id,
role: normalizeRole(row.role),
name: getAssistantName(row.role),
name: row.name ?? undefined,
status: "complete",
datetime: new Date(row.createTime).toISOString(),
content: [{ type: "markdown", status: "complete", data: row.content }],
createTime: row.createTime,
}));

58
src/socket/chatMessagesData.d.ts vendored Normal file
View File

@ -0,0 +1,58 @@
import type { ToolCallEventType } from './adapters/agui/types/events';
export type ChatMessageStatus = 'pending' | 'streaming' | 'complete' | 'stop' | 'error';
export type AttachmentType = 'image' | 'video' | 'audio' | 'pdf' | 'doc' | 'ppt' | 'txt';
export type ChatComment = 'good' | 'bad' | '';
// 基础内容接口
export interface ChatBaseContent<T extends string, D> {
type: T;
data: D;
status?: ChatMessageStatus;
id?: string;
strategy?: 'merge' | 'append';
ext?: Record<string, any>;
}
// 内容类型定义
export type TextContent = ChatBaseContent<'text', string>;
export type MarkdownContent = ChatBaseContent<'markdown', string>;
export type ImageContent = ChatBaseContent<'image', { name?: string; url?: string; width?: number; height?: number }>;
export type ThinkingContent = ChatBaseContent<'thinking', { text?: string; title?: string }>;
export type SearchContent = ChatBaseContent<'search', { title?: string; references?: { title: string; icon?: string; type?: string; url?: string; content?: string; site?: string; date?: string }[] }>;
export type SuggestionContent = ChatBaseContent<'suggestion', { title: string; prompt?: string }[]>;
export type AttachmentContent = ChatBaseContent<'attachment', { fileType: AttachmentType; size?: number; name?: string; url?: string; isReference?: boolean; width?: number; height?: number; extension?: string; metadata?: Record<string, any> }[]>;
export type ToolCallContent = ChatBaseContent<'toolcall', { toolCallId: string; toolCallName: string; eventType?: ToolCallEventType; parentMessageId?: string; args?: string; chunk?: string; result?: string }>;
export type ActivityContent<T = Record<string, any>> = ChatBaseContent<'activity', { activityType: string; messageId?: string; content: T; deltaInfo?: { fromIndex: number; toIndex: number } }>;
// 聚合内容类型
export type AIMessageContent = TextContent | MarkdownContent | ImageContent | ThinkingContent | SearchContent | SuggestionContent | ReasoningContent | ToolCallContent | ActivityContent;
export type ReasoningContent = ChatBaseContent<'reasoning', AIMessageContent[]>;
export type UserMessageContent = TextContent | AttachmentContent;
// 消息类型定义
export interface ChatBaseMessage {
id: string;
status?: ChatMessageStatus;
datetime?: string;
ext?: any;
}
export interface UserMessage extends ChatBaseMessage {
role: 'user';
content: UserMessageContent[];
}
export interface AIMessage extends ChatBaseMessage {
role: 'assistant';
content?: AIMessageContent[];
history?: AIMessageContent[][];
comment?: ChatComment;
}
export interface SystemMessage extends ChatBaseMessage {
role: 'system';
content: TextContent[];
}
export type ChatMessagesData = UserMessage | AIMessage | SystemMessage;

View File

@ -0,0 +1,79 @@
import u from "@/utils";
import { Socket } from "socket.io";
class ResTool {
public socket: Socket;
public data: Record<string, any>;
constructor(socket: Socket, data: Record<string, any> = {}) {
this.socket = socket;
this.data = data;
}
textMessage(name: string = "AI") {
const messageId = u.uuid();
this.socket.emit("textMessage", {
type: "start",
messageId,
delta: null,
role: "assistant",
name,
});
const handle = {
send: (delta: string) => {
this.socket.emit("textMessage", {
type: "content",
messageId,
delta,
role: "assistant",
name,
});
return handle;
},
end: () => {
this.socket.emit("textMessage", {
type: "end",
messageId,
delta: null,
role: "assistant",
name,
});
},
};
return handle;
}
thinkMessage() {
const messageId = u.uuid();
this.socket.emit("thinkMessage", {
type: "start",
messageId,
delta: null,
role: "assistant",
});
const handle = {
send: (delta: string) => {
this.socket.emit("thinkMessage", {
type: "content",
messageId,
delta,
role: "assistant",
});
return handle;
},
end: () => {
this.socket.emit("thinkMessage", {
type: "end",
messageId,
delta: null,
role: "assistant",
});
},
};
return handle;
}
systemMessage(content: string) {
const messageId = u.uuid();
this.socket.emit("systemMessage", { messageId, content });
}
}
export default ResTool;

View File

@ -1,79 +1,544 @@
import u from "@/utils";
import { Socket } from "socket.io";
import type {
ChatMessageStatus,
AIMessageContent,
UserMessageContent,
TextContent,
MarkdownContent,
ImageContent,
ThinkingContent,
SearchContent,
SuggestionContent,
ToolCallContent,
ActivityContent,
ReasoningContent,
AttachmentContent,
} from "./ChatMessagesData";
type ContentType = AIMessageContent["type"];
class ResTool {
public socket: Socket;
public data: Record<string, any>;
constructor(socket: Socket, data: Record<string, any> = {}) {
this.socket = socket;
this.data = data;
}
textMessage(name: string = "AI") {
// 创建新消息
newMessage(role: "assistant" | "user" | "system" = "assistant", name?: string) {
const messageId = u.uuid();
this.socket.emit("textMessage", {
type: "start",
messageId,
delta: null,
role: "assistant",
const datetime = new Date().toISOString();
this.socket.emit("message", {
id: messageId,
role,
name,
status: "pending" as ChatMessageStatus,
datetime,
content: [],
});
const handle = {
send: (delta: string) => {
this.socket.emit("textMessage", {
type: "content",
messageId,
delta,
role: "assistant",
name,
});
return handle;
},
end: () => {
this.socket.emit("textMessage", {
type: "end",
messageId,
delta: null,
role: "assistant",
name,
});
},
};
return handle;
return new MessageBuilder(this.socket, messageId, role, name, datetime);
}
thinkMessage() {
const messageId = u.uuid();
this.socket.emit("thinkMessage", {
type: "start",
messageId,
delta: null,
role: "assistant",
// 发送错误消息
sendError(messageId: string, error: string) {
this.socket.emit("message:update", {
id: messageId,
status: "error" as ChatMessageStatus,
ext: { error },
});
const handle = {
send: (delta: string) => {
this.socket.emit("thinkMessage", {
type: "content",
messageId,
delta,
role: "assistant",
});
return handle;
},
end: () => {
this.socket.emit("thinkMessage", {
type: "end",
messageId,
delta: null,
role: "assistant",
});
},
};
return handle;
}
systemMessage(content: string) {
const messageId = u.uuid();
this.socket.emit("systemMessage", { messageId, content });
// 发送完成状态
sendComplete(messageId: string) {
this.socket.emit("message:update", {
id: messageId,
status: "complete" as ChatMessageStatus,
});
}
}
// 消息构建器
class MessageBuilder {
private socket: Socket;
private messageId: string;
private messageRole: "assistant" | "user" | "system";
private messageName?: string;
private messageDatetime: string;
constructor(socket: Socket, messageId: string, role: "assistant" | "user" | "system", name?: string, datetime?: string) {
this.socket = socket;
this.messageId = messageId;
this.messageRole = role;
this.messageName = name;
this.messageDatetime = datetime ?? new Date().toISOString();
}
get id() {
return this.messageId;
}
get role() {
return this.messageRole;
}
get name() {
return this.messageName;
}
get datetime() {
return this.messageDatetime;
}
// 更新消息状态
updateStatus(status: ChatMessageStatus) {
this.socket.emit("message:update", {
id: this.messageId,
status,
});
return this;
}
// 添加文本内容
text(initialText = "") {
const contentId = u.uuid();
const content: TextContent = {
type: "text",
id: contentId,
data: initialText,
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ContentStream<string>(this.socket, this.messageId, contentId, "text");
}
// 添加 Markdown 内容
markdown(initialText = "") {
const contentId = u.uuid();
const content: MarkdownContent = {
type: "markdown",
id: contentId,
data: initialText,
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ContentStream<string>(this.socket, this.messageId, contentId, "markdown");
}
// 添加思考内容
thinking(title = "思考中...") {
const contentId = u.uuid();
const content: ThinkingContent = {
type: "thinking",
id: contentId,
data: { title, text: "" },
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ThinkingStream(this.socket, this.messageId, contentId);
}
// 添加搜索内容
search(title = "搜索中...") {
const contentId = u.uuid();
const content: SearchContent = {
type: "search",
id: contentId,
data: { title, references: [] },
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new SearchStream(this.socket, this.messageId, contentId);
}
// 添加图片内容
image(data: ImageContent["data"]) {
const contentId = u.uuid();
const content: ImageContent = {
type: "image",
id: contentId,
data,
status: "complete",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return this;
}
// 添加建议内容
suggestion(suggestions: SuggestionContent["data"]) {
const contentId = u.uuid();
const content: SuggestionContent = {
type: "suggestion",
id: contentId,
data: suggestions,
status: "complete",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return this;
}
// 添加工具调用内容
toolCall(data: ToolCallContent["data"]) {
const contentId = u.uuid();
const content: ToolCallContent = {
type: "toolcall",
id: contentId,
data: { ...data, parentMessageId: this.messageId },
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ToolCallStream(this.socket, this.messageId, contentId, data.toolCallId);
}
// 添加活动内容
activity<T = Record<string, any>>(activityType: string, content: T) {
const contentId = u.uuid();
const activityContent: ActivityContent<T> = {
type: "activity",
id: contentId,
data: {
activityType,
messageId: this.messageId,
content,
},
status: "complete",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content: activityContent,
});
return this;
}
// 添加推理内容
reasoning() {
const contentId = u.uuid();
const content: ReasoningContent = {
type: "reasoning",
id: contentId,
data: [],
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ReasoningBuilder(this.socket, this.messageId, contentId);
}
// 完成消息
complete() {
this.socket.emit("message:update", {
id: this.messageId,
status: "complete" as ChatMessageStatus,
});
}
// 停止消息
stop() {
this.socket.emit("message:update", {
id: this.messageId,
status: "stop" as ChatMessageStatus,
});
}
// 错误
error(errorMsg?: string) {
this.socket.emit("message:update", {
id: this.messageId,
status: "error" as ChatMessageStatus,
ext: errorMsg ? { error: errorMsg } : undefined,
});
}
}
// 内容流基类
class ContentStream<T> {
protected socket: Socket;
protected messageId: string;
protected contentId: string;
protected contentType: ContentType;
constructor(socket: Socket, messageId: string, contentId: string, contentType: ContentType) {
this.socket = socket;
this.messageId = messageId;
this.contentId = contentId;
this.contentType = contentType;
}
get id() {
return this.contentId;
}
// 流式追加数据
append(chunk: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: this.contentType,
data: chunk,
strategy: "append",
status: "streaming",
});
return this;
}
// 合并/替换数据
merge(data: T) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: this.contentType,
data,
strategy: "merge",
status: "streaming",
});
return this;
}
// 完成内容
complete(finalData?: T) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: this.contentType,
data: finalData,
status: "complete",
});
return this;
}
// 错误
error() {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
status: "error",
});
return this;
}
}
// 思考内容流
class ThinkingStream extends ContentStream<ThinkingContent["data"]> {
constructor(socket: Socket, messageId: string, contentId: string) {
super(socket, messageId, contentId, "thinking");
}
// 追加思考文本
appendText(chunk: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "thinking",
data: { text: chunk },
strategy: "append",
status: "streaming",
});
return this;
}
// 更新标题
updateTitle(title: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "thinking",
data: { title },
strategy: "merge",
status: "streaming",
});
return this;
}
}
// 搜索内容流
class SearchStream extends ContentStream<SearchContent["data"]> {
constructor(socket: Socket, messageId: string, contentId: string) {
super(socket, messageId, contentId, "search");
}
// 添加引用
addReference(ref: Exclude<SearchContent["data"]["references"], undefined>[0]) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "search",
data: { references: [ref] },
strategy: "append",
status: "streaming",
});
return this;
}
// 批量添加引用
addReferences(refs: SearchContent["data"]["references"]) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "search",
data: { references: refs },
strategy: "append",
status: "streaming",
});
return this;
}
// 更新标题
updateTitle(title: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "search",
data: { title },
strategy: "merge",
status: "streaming",
});
return this;
}
}
// 工具调用流
class ToolCallStream extends ContentStream<ToolCallContent["data"]> {
private toolCallId: string;
constructor(socket: Socket, messageId: string, contentId: string, toolCallId: string) {
super(socket, messageId, contentId, "toolcall");
this.toolCallId = toolCallId;
}
// 追加参数块
appendArgs(chunk: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "toolcall",
data: { toolCallId: this.toolCallId, args: chunk },
strategy: "append",
status: "streaming",
});
return this;
}
// 追加结果块
appendResult(chunk: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "toolcall",
data: { toolCallId: this.toolCallId, chunk },
strategy: "append",
status: "streaming",
});
return this;
}
// 设置完整结果
setResult(result: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "toolcall",
data: { toolCallId: this.toolCallId, result },
strategy: "merge",
status: "complete",
});
return this;
}
// 更新事件类型
updateEventType(eventType: ToolCallContent["data"]["eventType"]) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "toolcall",
data: { toolCallId: this.toolCallId, eventType },
strategy: "merge",
status: "streaming",
});
return this;
}
}
// 推理构建器
class ReasoningBuilder {
private socket: Socket;
private messageId: string;
private contentId: string;
constructor(socket: Socket, messageId: string, contentId: string) {
this.socket = socket;
this.messageId = messageId;
this.contentId = contentId;
}
// 添加子内容
addContent(content: AIMessageContent) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "reasoning",
data: [content],
strategy: "append",
status: "streaming",
});
return this;
}
// 完成推理
complete() {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "reasoning",
status: "complete",
});
return this;
}
}
export default ResTool;
export { MessageBuilder, ContentStream, ThinkingStream, SearchStream, ToolCallStream, ReasoningBuilder };

View File

@ -3,6 +3,7 @@ import u from "@/utils";
import { Namespace, Socket } from "socket.io";
import * as agent from "@/agents/scriptAgent/index";
import ResTool from "@/socket/resTool";
import Memory from "@/utils/agent/memory";
async function verifyToken(rawToken: string): Promise<Boolean> {
const setting = await u.db("o_setting").where("key", "tokenKey").select("value").first();
@ -40,23 +41,61 @@ export default (nsp: Namespace) => {
});
let abortController: AbortController | null = null;
socket.on("message", async (text: string) => {
socket.on("chat", async (data: { content: string }) => {
const { content } = data;
abortController?.abort();
abortController = new AbortController();
const currentController = abortController;
const memory = new Memory("scriptAgent", isolationKey);
const textStream = await agent.decisionAI({ socket, isolationKey, text, abortSignal: currentController.signal, resTool });
const msg = resTool.newMessage("assistant", "统筹");
const ctx: agent.AgentContext = {
socket,
isolationKey,
text: content,
userMessageTime: new Date(msg.datetime).getTime() - 1,
abortSignal: currentController.signal,
resTool,
msg,
};
let msg = resTool.textMessage();
const textStream = await agent.decisionAI(ctx);
let currentMsg = ctx.msg;
let text = currentMsg.text();
let currentContent = "";
const persistCurrentMessage = async () => {
if (!currentContent.trim()) return;
await memory.add("assistant:decision", currentContent, {
name: "统筹",
createTime: new Date(currentMsg.datetime).getTime(),
});
currentContent = "";
};
const syncCurrentMessage = async () => {
if (ctx.msg === currentMsg) return;
text.complete();
currentMsg.complete();
await persistCurrentMessage();
currentMsg = ctx.msg;
text = currentMsg.text();
};
try {
for await (const chunk of textStream) {
msg.send(chunk);
await syncCurrentMessage();
text.append(chunk);
currentContent += chunk;
}
} catch (err: any) {
if (err.name !== "AbortError") throw err;
} finally {
msg.end();
await syncCurrentMessage();
text.complete();
currentMsg.complete();
await persistCurrentMessage();
if (abortController === currentController) {
abortController = null;
}

View File

@ -1,4 +1,4 @@
// @db-hash 0041ea9843a4bb46f03412c516ec323b
// @db-hash e933f0aef750ba14e50c72aab5b6eeb7
//该文件由脚本自动生成,请勿手动修改
export interface memories {
@ -7,6 +7,7 @@ export interface memories {
'embedding'?: string | null;
'id'?: string;
'isolationKey': string;
'name'?: string | null;
'relatedMessageIds'?: string | null;
'role'?: string | null;
'summarized'?: number | null;

View File

@ -82,7 +82,8 @@ class Memory {
}
return result;
}
async add(role: string = "user", content: string) {
async add(role: string = "user", content: string, options?: { name?: string; createTime?: number }) {
const { messagesPerSummary } = await this.getConfigData({ messagesPerSummary: DEFAULTS.messagesPerSummary });
const id = uuidv4();
const embedding = await getEmbedding(content);
@ -93,11 +94,12 @@ class Memory {
isolationKey,
type: "message",
role,
name: options?.name,
content,
embedding: JSON.stringify(embedding),
relatedMessageIds: null,
summarized: 0,
createTime: Date.now(),
createTime: options?.createTime ?? Date.now(),
} as any);
// 检查未总结消息数量
@ -154,7 +156,7 @@ class Memory {
const ragResults = vectorSearch(allMessages, queryEmbedding, Number(ragLimit));
return {
shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, content: m.content, createTime: m.createTime })),
shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, name: m.name, content: m.content, createTime: m.createTime })),
summaries: summaries.map((s) => ({
id: s.id,
content: s.content,