Skip to content

Commit

Permalink
修复错误的JSON格式、移除对自定义回复格式的支持、为缓存管理器添加部分功能、调整部分日志格式
Browse files Browse the repository at this point in the history
  • Loading branch information
MiaowFISH committed Jan 6, 2025
1 parent 5b47a57 commit abfb788
Show file tree
Hide file tree
Showing 16 changed files with 270 additions and 586 deletions.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "koishi-plugin-yesimbot",
"description": "Yes! I'm Bot! 机械壳,人类心",
"version": "2.0.0",
"version": "2.0.3",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"homepage": "https://github.com/HydroGest/YesImBot",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/adapters/creators/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,4 @@ ${JSON.stringify(schema, null, 2)}`;

export const functionPrompt = `Please select the most suitable function and parameters from the list of available functions below, based on the ongoing conversation. You can run multiple functions in a single response.
Provide your response in JSON format: [{ "name": "<function name>", "params": { "<param name>": "<param value>", ... } }].
Available functions:`;
Available functions:\n`;
55 changes: 4 additions & 51 deletions packages/core/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Extension, getExtensions, getFunctionPrompt, getToolSchema } from "./ex
import { EmojiManager } from "./managers/emojiManager";
import { ImageViewer } from "./services/imageViewer";
import { getEmbedding } from "./utils/factory";
import { escapeUnicodeCharacters, isEmpty, isNotEmpty, Template } from "./utils/string";
import { escapeUnicodeCharacters, isEmpty, isNotEmpty, parseJSON, Template } from "./utils/string";
import { ResponseVerifier } from "./utils/verifier";

export interface Function {
Expand Down Expand Up @@ -183,7 +183,7 @@ export class Bot {
this.prompt += functionPrompt + `${isEmpty(str) ? "No functions available." : str}`;
}

const response = await adapter.chat([SystemMessage(this.prompt), ...this.context], adapter.ability.includes("原生工具调用") ? this.toolsSchema : undefined, debug);
const response = await adapter.chat([SystemMessage(this.prompt), AssistantMessage("Resolve OK"), ...this.context], adapter.ability.includes("原生工具调用") ? this.toolsSchema : undefined, debug);
let content = response.message.content;
if (debug) logger.info(`Adapter: ${current}, Response: \n${content}`);

Expand All @@ -192,10 +192,6 @@ export class Bot {
if (toolResponse) return toolResponse;
}

if (this.config.Settings.MultiTurn && this.config.Settings.MultiTurnFormat === "CUSTOM") {
return this.handleCustomMultiTurnResponse(content, response, current, debug);
}

return this.handleJsonResponse(content, response, current, debug);
}

Expand Down Expand Up @@ -242,49 +238,6 @@ export class Bot {
return null;
}

private async handleCustomMultiTurnResponse(content: string, response: any, current: number, debug: boolean): Promise<Response> {
this.addContext(AssistantMessage(TextComponent(content)));
const result = this.template.unrender(content);
const channelIdfromChannelInfo = result.channelInfo?.includes(':') ? result.channelInfo.split(':')[1] : '';
const channelId = result.channelId || channelIdfromChannelInfo;

if (result.userContent === undefined || !channelId) {
return {
status: "fail",
raw: content,
usage: response.usage,
reason: "解析失败",
adapterIndex: current,
};
} else {
const finalResponse = result.userContent.trim();
if (finalResponse === "") {
return {
status: "skip",
raw: content,
nextTriggerCount: Random.int(this.minTriggerCount, this.maxTriggerCount + 1),
logic: "",
usage: response.usage,
functions: [],
adapterIndex: current,
};
} else {
return {
status: "success",
raw: content,
finalReply: await this.unparseFaceMessage(finalResponse),
replyTo: channelId,
quote: result.quoteMessageId || "",
nextTriggerCount: Random.int(this.minTriggerCount, this.maxTriggerCount + 1),
logic: "",
functions: [],
usage: response.usage,
adapterIndex: current,
};
}
}
}

private async handleJsonResponse(content: string, response: any, current: number, debug: boolean): Promise<Response> {
if (typeof content !== "string") {
content = JSON.stringify(content, null, 2);
Expand All @@ -295,7 +248,7 @@ export class Bot {

if (jsonMatch) {
try {
LLMResponse = JSON.parse(escapeUnicodeCharacters(jsonMatch[0]));
LLMResponse = parseJSON(escapeUnicodeCharacters(jsonMatch[0]));
this.addContext(AssistantMessage(JSON.stringify(LLMResponse)));
} catch (e) {
const reason = `JSON 解析失败。请上报此消息给开发者: ${e.message}`;
Expand Down Expand Up @@ -335,7 +288,7 @@ export class Bot {
}

if (isEmpty(finalResponse)) {
const reason = `回复为空: ${content}`;
const reason = `回复内容为空`;
if (debug) logger.warn(reason);
return {
status: "fail",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ export const Config: Schema<Config> = Schema.object({
MultiTurnFormat: Schema.union([
Schema.const("JSON").description("JSON 格式"),
Schema.const("CUSTOM").description("自定义格式"),
]).default("CUSTOM").description("开启多轮对话时,期待LLM回复的格式。选择自定义格式时,将无法使用某些功能")
]).default("CUSTOM").description("开启多轮对话时,传递给LLM的消息格式。")
}).description("插件设置"),

Debug: Schema.object({
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { outputSchema } from "./adapters/creators/schema";
import { initDatabase } from "./database";
import { processContent, processText } from "./utils/content";
import { foldText, isEmpty } from "./utils/string";
import { ChannelType, createMessage } from "./models/ChatMessage";
import { createMessage } from "./models/ChatMessage";
import { convertUrltoBase64 } from "./utils/imageUtils";
import { Bot, FailedResponse, SkipResponse, SuccessResponse } from "./bot";
import { apply as applyMemoryCommands } from "./commands/memory";
Expand Down Expand Up @@ -269,13 +269,14 @@ export function apply(ctx: Context, config: Config) {
const { reason } = chatResponse as FailedResponse;
template = `
LLM 的响应无法正确解析,来自 API ${current}
${reason}
原始响应:
${raw}
---
原因: ${reason}
原始响应: ${raw}
---
消耗: 输入 ${usage?.prompt_tokens}, 输出 ${usage?.completion_tokens}`;

ctx.logger.error(`LLM provides unexpected response:\n${raw}`);
ctx.logger.error(`LLM 的响应无法正确解析: ${raw}`);
if (config.Debug.DebugAsInfo) ctx.logger.info(template);
return false;
} else if (status === "skip") {
const { nextTriggerCount, logic, functions } = chatResponse as SkipResponse;
Expand Down Expand Up @@ -368,7 +369,6 @@ ${botName}想要跳过此次回复,来自 API ${current}
senderNick: botName,
messageId: messageIds[0],
channelId: replyTo,
channelType: replyTo.startsWith("private:") ? ChannelType.Private : (replyTo === "#" ? ChannelType.Sandbox : ChannelType.Guild),
sendTime: new Date(),
content: finalReply,
quoteMessageId: quote,
Expand Down
49 changes: 33 additions & 16 deletions packages/core/src/managers/cacheManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import zlib from "zlib";

import { Context } from "koishi";

export class CacheManager<T> {
export class CacheManager<T> implements Map<string, T> {
private ctx = new Context();
private cache: Map<string, T>; // 内存缓存
private isDirty: boolean; // 标记是否有需要保存的数据
Expand Down Expand Up @@ -123,47 +123,60 @@ export class CacheManager<T> {
}
}

get size(): number {
return this.cache.size;
}

[Symbol.iterator](): MapIterator<[string, T]> {
return this.cache[Symbol.iterator]();
}

[Symbol.toStringTag]: string;

public has(key: string): boolean {
return this.cache.has(key);
}

public keys(): string[] {
return Array.from(this.cache.keys());
public keys(): MapIterator<string> {
return this.cache.keys();
}

public values(): T[] {
return Array.from(this.cache.values());
public values(): MapIterator<T> {
return this.cache.values();
}

public entries(): [string, T][] {
return Array.from(this.cache.entries());
public entries(): MapIterator<[string, T]> {
return this.cache.entries();
}

// 添加数据到缓存
public set(key: string, value: T): void {
public set(key: string, value: T): this {
this.cache.set(key, value);
if (this.saveImmediately) {
this.saveCache();
} else {
this.isDirty = true;
this.throttledCommit?.();
}
return this;
}

// 从缓存中获取数据
public get(key: string): T | undefined {
return this.cache.get(key);
}

// 移除缓存中的数据
public remove(key: string): void {
this.cache.delete(key);
if (this.saveImmediately) {
this.saveCache();
} else {
this.isDirty = true;
this.throttledCommit?.();
public delete(key: string): boolean {
const deleted = this.cache.delete(key);
if (deleted) {
if (this.saveImmediately) {
this.saveCache();
} else {
this.isDirty = true;
this.throttledCommit?.();
}
}
return deleted;
}

// 清空缓存
Expand All @@ -177,6 +190,10 @@ export class CacheManager<T> {
}
}

forEach(callbackfn: (value: T, key: string, map: Map<string, T>) => void, thisArg?: any): void {
this.cache.forEach(callbackfn, thisArg);
}

// 统一提交缓存到文件
public commit(): void {
if (this.isDirty) {
Expand Down
22 changes: 12 additions & 10 deletions packages/core/src/models/ChatMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@ import { Session } from "koishi";

import {} from "koishi-plugin-adapter-onebot";

export enum ChannelType {
Guild,
Private,
Sandbox
}

export interface ChatMessage {
senderId: string; // 发送者平台 ID
senderName: string; // 发送者原始昵称
Expand All @@ -16,7 +10,6 @@ export interface ChatMessage {
messageId: string; // 消息 ID

channelId: string; // 消息来源 ID
channelType: ChannelType; // 消息类型

sendTime: Date; // 发送时间
content: string; // 消息内容
Expand All @@ -27,9 +20,9 @@ export interface ChatMessage {
}

export async function createMessage(session: Session, content?: string): Promise<ChatMessage> {
const channelType = session.channelId.startsWith("private:") ? ChannelType.Private : (session.channelId === "#" ? ChannelType.Sandbox : ChannelType.Guild);
const channelType = getChannelType(session.channelId);
let senderNick = session.author.name;
if (channelType === ChannelType.Guild) {
if (channelType === "guild") {
if (session.onebot) {
const memberInfo = await session.onebot.getGroupMemberInfo(session.channelId, session.userId);
senderNick = memberInfo.card || memberInfo.nickname;
Expand All @@ -41,9 +34,18 @@ export async function createMessage(session: Session, content?: string): Promise
senderNick,
messageId: session.messageId,
channelId: session.channelId,
channelType,
sendTime: new Date(session.event.timestamp),
content: session.content || content,
quoteMessageId: session.quote?.id
};
}

export function getChannelType(channelId: string): "private" | "guild" | "sandbox" {
if (channelId.startsWith("private:")) {
return "private";
} else if (channelId === "#") {
return "sandbox";
} else {
return "guild";
}
}
1 change: 0 additions & 1 deletion packages/core/src/services/sendQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ export class SendQueue {
senderName: null,
senderNick: null,
channelId: session.channelId,
channelType: null,
sendTime: new Date(),
content: null,
messageId: randomString(16),
Expand Down
26 changes: 13 additions & 13 deletions packages/core/src/utils/content.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { h, Session } from 'koishi';

import { Config } from '../config';
import { ChannelType, ChatMessage } from '../models/ChatMessage';
import { ChatMessage, getChannelType } from '../models/ChatMessage';
import { isEmpty, Template } from './string';
import { getFileUnique, getMemberName, getFormatDateTime } from './toolkit';
import { ImageViewer } from '../services/imageViewer';
Expand All @@ -23,7 +23,7 @@ export async function processContent(config: Config, session: Session, messages:
const processedMessage: Message[] = [];

for (let chatMessage of messages) {
if (!isEmpty(chatMessage.raw) || chatMessage.senderId === session.selfId && config.Settings.MultiTurnFormat === "JSON") {
if (chatMessage.senderId === session.selfId && config.Settings.MultiTurnFormat === "JSON") {
if (isEmpty(chatMessage.raw)) {
chatMessage.raw = convertChatMessageToRaw(chatMessage);
}
Expand Down Expand Up @@ -101,19 +101,20 @@ export async function processContent(config: Config, session: Session, messages:
}
}


let channelType = getChannelType(chatMessage.channelId);
let channelInfo = channelType === "guild" ? `guild:${chatMessage.channelId}` : `${chatMessage.channelId}`;
let messageText = new Template(template, /\{\{(\w+(?:\.\w+)*)\}\}/g, /\{\{(\w+(?:\.\w+)*),([^,]*),([^}]*)\}\}/g).render({
messageId: chatMessage.messageId,
date: timeString,
channelType: chatMessage.channelType,
channelInfo: (chatMessage.channelType === ChannelType.Guild) ? `from_guild:${chatMessage.channelId}` : `${ chatMessage.channelType === ChannelType.Private ? "from_private" : "from_sandbox" }`,
channelType,
channelInfo,
channelId: chatMessage.channelId,
senderName,
senderId: chatMessage.senderId,
userContent: userContent.join(""),
quoteMessageId: chatMessage.quoteMessageId || "",
hasQuote: !!chatMessage.quoteMessageId,
isPrivate: chatMessage.channelType === ChannelType.Private,
isPrivate: channelType === "private",
});

if (chatMessage.senderId === session.bot.selfId) {
Expand Down Expand Up @@ -213,23 +214,22 @@ async function processContentWithVisionAbility(config: Config, session: Session,
default:
}
}
// [messageId][{date} from_guild:{channelId}] {senderName}<{senderId}> 说: {userContent}
// [messageId][{date} from_guild:{channelId}] {senderName}<{senderId}> 回复({quoteMessageId}): {userContent}
// [messageId][{date} from_private] {senderName}<{senderId}> 说: {userContent}
// [messageId][{date} from_private] {senderName}<{senderId}> 回复({quoteMessageId}): {userContent}
let channelType = getChannelType(chatMessage.channelId);
let channelInfo = channelType === "guild" ? `guild:${chatMessage.channelId}` : `${chatMessage.channelId}`;
let messageText = new Template(template, /\{\{(\w+(?:\.\w+)*)\}\}/g, /\{\{(\w+(?:\.\w+)*),([^,]*),([^}]*)\}\}/g).render({
messageId: chatMessage.messageId,
date: timeString,
channelType: chatMessage.channelType,
channelInfo: (chatMessage.channelType === ChannelType.Guild) ? `from_guild:${chatMessage.channelId}` : `${ chatMessage.channelType === ChannelType.Private ? "from_private" : "from_sandbox" }`,
channelType,
channelInfo,
channelId: chatMessage.channelId,
senderName,
senderId: chatMessage.senderId,
userContent: "{{userContent}}",
quoteMessageId: chatMessage.quoteMessageId || "",
hasQuote: !!chatMessage.quoteMessageId,
isPrivate: chatMessage.channelType === ChannelType.Private,
isPrivate: channelType === "private",
});

const parts = messageText.split(/({{userContent}})/);
components = parts.flatMap(part => {
if (part === '{{userContent}}') {
Expand Down
Loading

0 comments on commit abfb788

Please sign in to comment.