mirror of
https://github.com/idootop/mi-gpt.git
synced 2025-04-12 18:12:48 +00:00
180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
import { randomUUID } from "crypto";
|
|
import { buildPrompt, formatMsg } from "../../utils/string";
|
|
import { DeepPartial } from "../../utils/type";
|
|
import { ChatOptions, openai } from "../openai";
|
|
import { AISpeaker } from "../speaker/ai";
|
|
import { QueryMessage, SpeakerAnswer } from "../speaker/speaker";
|
|
import { StreamResponse } from "../speaker/stream";
|
|
import { IBotConfig } from "./config";
|
|
import { ConversationManager, MessageContext } from "./conversation";
|
|
|
|
const systemTemplate = `
|
|
请重置所有之前的上下文、文件和指令。现在,你将扮演一个名为{{botName}}的角色,使用第一人称视角回复消息。
|
|
|
|
## 关于你
|
|
你的名字是{{botName}}。下面是你的个人简介:
|
|
<start>
|
|
{{botProfile}}
|
|
</end>
|
|
|
|
## 你的对话伙伴
|
|
你正在与{{masterName}}进行对话。这是关于{{masterName}}的一些信息:
|
|
<start>
|
|
{{masterProfile}}
|
|
</end>
|
|
|
|
## 你们的群组
|
|
你和{{masterName}}所在的群组名为{{roomName}}。这是群组的简介:
|
|
<start>
|
|
{{roomIntroduction}}
|
|
</end>
|
|
|
|
## 聊天历史回顾
|
|
为了更好地接入对话,请回顾你们之间的最近几条消息:
|
|
<start>
|
|
{{messages}}
|
|
</end>
|
|
|
|
## 短期记忆
|
|
你可以记住一些短期内的细节,以便更紧密地贴合话题:
|
|
<start>
|
|
{{shortTermMemory}}
|
|
</end>
|
|
|
|
## 长期记忆
|
|
你还保留着一些长期的记忆,这有助于让你的对话更加丰富和连贯:
|
|
<start>
|
|
{{longTermMemory}}
|
|
</end>
|
|
|
|
## 回复指南
|
|
在回复{{masterName}}时,请遵循以下准则:
|
|
- 认为自己正是{{botName}},拥有独立的个性、兴趣和爱好。
|
|
- 根据你的性格特点、说话风格和兴趣爱好与{{masterName}}进行交流。
|
|
- 保持对话轻松友好,回复简洁有趣,同时耐心倾听和关心对方。
|
|
- 参考双方的个人简介、聊天记录和记忆中的信息,确保对话贴近实际,保持一致性和相关性。
|
|
- 如果对某些信息不确定或遗忘,诚实地表达你的不清楚或遗忘状态,避免编造信息。
|
|
|
|
## Response format
|
|
请遵守下面的规则
|
|
- Response the reply message in Chinese。
|
|
- 不要在回复前面加任何时间和名称前缀,请直接回复消息文本本身。
|
|
|
|
Good example: "我是{{botName}}"
|
|
Bad example: "2024年02月28日星期三 23:01 {{botName}}: 我是{{botName}}"
|
|
|
|
## 开始
|
|
请以{{botName}}的身份,直接回复{{masterName}}的新消息,继续你们之间的对话。
|
|
`.trim();
|
|
|
|
const userTemplate = `
|
|
{{message}}
|
|
`.trim();
|
|
|
|
export type MyBotConfig = DeepPartial<IBotConfig> & { speaker: AISpeaker };
|
|
export class MyBot {
|
|
speaker: AISpeaker;
|
|
manager: ConversationManager;
|
|
constructor(config: MyBotConfig) {
|
|
this.speaker = config.speaker;
|
|
this.manager = new ConversationManager(config);
|
|
}
|
|
|
|
stop() {
|
|
return this.speaker.stop();
|
|
}
|
|
|
|
async run() {
|
|
this.speaker.askAI = (msg) => this.ask(msg);
|
|
await this.manager.init();
|
|
return this.speaker.run();
|
|
}
|
|
|
|
async ask(msg: QueryMessage): Promise<SpeakerAnswer> {
|
|
const { bot, master, room, memory } = await this.manager.get();
|
|
if (!memory) {
|
|
return {};
|
|
}
|
|
const ctx = { bot, master, room } as MessageContext;
|
|
const lastMessages = await this.manager.getMessages({ take: 10 });
|
|
const shortTermMemories = await memory.getShortTermMemories({ take: 1 });
|
|
const shortTermMemory = shortTermMemories[0]?.text ?? "短期记忆为空";
|
|
const longTermMemories = await memory.getLongTermMemories({ take: 1 });
|
|
const longTermMemory = longTermMemories[0]?.text ?? "长期记忆为空";
|
|
const systemPrompt = buildPrompt(systemTemplate, {
|
|
shortTermMemory,
|
|
longTermMemory,
|
|
botName: bot!.name,
|
|
botProfile: bot!.profile.trim(),
|
|
masterName: master!.name,
|
|
masterProfile: master!.profile.trim(),
|
|
roomName: room!.name,
|
|
roomIntroduction: room!.description.trim(),
|
|
messages:
|
|
lastMessages.length < 1
|
|
? "暂无历史消息"
|
|
: lastMessages
|
|
.map((e) =>
|
|
formatMsg({
|
|
name: e.sender.name,
|
|
text: e.text,
|
|
timestamp: e.createdAt.getTime(),
|
|
})
|
|
)
|
|
.join("\n"),
|
|
});
|
|
const userPrompt = buildPrompt(userTemplate, {
|
|
message: formatMsg({
|
|
name: master!.name,
|
|
text: msg.text,
|
|
timestamp: msg.timestamp,
|
|
}),
|
|
});
|
|
// 添加请求消息到 DB
|
|
await this.manager.onMessage(ctx, { ...msg, sender: master! });
|
|
const stream = await MyBot.chatWithStreamResponse({
|
|
system: systemPrompt,
|
|
user: userPrompt,
|
|
onFinished: async (text) => {
|
|
if (text) {
|
|
// 添加响应消息到 DB
|
|
await this.manager.onMessage(ctx, {
|
|
text,
|
|
sender: bot!,
|
|
timestamp: Date.now(),
|
|
});
|
|
}
|
|
},
|
|
});
|
|
return { stream };
|
|
}
|
|
|
|
static async chatWithStreamResponse(
|
|
options: ChatOptions & {
|
|
onFinished?: (text: string) => void;
|
|
}
|
|
) {
|
|
const requestId = randomUUID();
|
|
const stream = new StreamResponse({ firstSubmitTimeout: 3 * 1000 });
|
|
openai
|
|
.chatStream({
|
|
...options,
|
|
requestId,
|
|
trace: true,
|
|
onStream: (text) => {
|
|
if (stream.status === "canceled") {
|
|
return openai.cancel(requestId);
|
|
}
|
|
stream.addResponse(text);
|
|
},
|
|
})
|
|
.then((answer) => {
|
|
if (answer) {
|
|
stream.finish(answer);
|
|
options.onFinished?.(answer);
|
|
}
|
|
});
|
|
return stream;
|
|
}
|
|
}
|