mioku-plugin-chat 2.3.1 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/configs/base.ts +1 -0
- package/configs/settings.ts +1 -1
- package/context.ts +17 -0
- package/core/chat-engine.ts +6 -1
- package/core/chat-turn.ts +540 -0
- package/core/media/history-media.ts +215 -70
- package/core/media/image-analyzer.ts +28 -19
- package/core/media/image-compress.ts +75 -0
- package/core/media/segment.ts +91 -0
- package/core/multimodal.ts +24 -12
- package/core/prompt.ts +332 -445
- package/core/tools/info.ts +284 -0
- package/core/tools/load-skill.ts +149 -0
- package/core/tools/web.ts +306 -0
- package/core/tools.ts +11 -822
- package/db.ts +56 -142
- package/handlers/idle-debug.ts +135 -0
- package/handlers/message.ts +316 -0
- package/handlers/poke.ts +165 -0
- package/humanize/emotion-agent.ts +3 -4
- package/humanize/expression.ts +3 -4
- package/humanize/topic.ts +3 -4
- package/index.ts +73 -1705
- package/manage/cooldown.ts +52 -27
- package/manage/group-structured-history.ts +35 -0
- package/manage/idle-check.ts +40 -20
- package/manage/queue-processor.ts +51 -25
- package/manage/rate-limit-guard.ts +70 -0
- package/package.json +1 -1
- package/runtime/chat-runtime.ts +124 -0
- package/types.ts +1 -0
- package/utils/index.ts +2 -0
- package/utils/json.ts +8 -0
- package/utils/message.ts +185 -175
package/core/prompt.ts
CHANGED
|
@@ -19,7 +19,6 @@ export interface PromptContext {
|
|
|
19
19
|
triggerSkillRole?: SkillPermissionRole;
|
|
20
20
|
aiService: AIService;
|
|
21
21
|
isGroup: boolean;
|
|
22
|
-
// Humanize context (computed once per processChat)
|
|
23
22
|
memoryContext?: string;
|
|
24
23
|
topicContext?: string;
|
|
25
24
|
expressionContext?: string;
|
|
@@ -28,31 +27,152 @@ export interface PromptContext {
|
|
|
28
27
|
targetMessage: TargetMessage;
|
|
29
28
|
currentEmotion?: string;
|
|
30
29
|
plannerThoughts?: string;
|
|
31
|
-
// Reply context - tells AI what type of reply this is
|
|
32
30
|
replyContext?: {
|
|
33
31
|
type: "reply" | "comment" | "idle" | "review" | "poked";
|
|
34
32
|
targetUser?: string;
|
|
35
33
|
targetMessage?: string;
|
|
36
34
|
};
|
|
37
|
-
// Review context - messages collected during cooldown period
|
|
38
35
|
reviewMessages?: {
|
|
39
36
|
contents: string[];
|
|
40
37
|
userNames: string[];
|
|
41
38
|
messageIds: number[];
|
|
42
39
|
};
|
|
43
40
|
promptInjections?: ChatRuntimePromptInjection[];
|
|
44
|
-
// Emoji agent for dynamic meme info
|
|
45
41
|
emojiAgent?: EmojiAgent;
|
|
46
|
-
// Skill session manager for on-demand features
|
|
47
42
|
skillManager?: SkillSessionManager;
|
|
48
43
|
sessionId?: string;
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
type Strength = "low" | "medium" | "high";
|
|
47
|
+
type ReplyType = "reply" | "comment" | "idle" | "review" | "poked";
|
|
48
|
+
|
|
49
|
+
function normalizeConstraintStrength(value: unknown): Strength {
|
|
50
|
+
if (value === "low" || value === "high" || value === "medium") return value;
|
|
51
|
+
return "medium";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isMultiUserInteraction(
|
|
55
|
+
reviewMsgs?: PromptContext["reviewMessages"],
|
|
56
|
+
): boolean {
|
|
57
|
+
return (
|
|
58
|
+
!!reviewMsgs &&
|
|
59
|
+
reviewMsgs.userNames.length > 1 &&
|
|
60
|
+
new Set(reviewMsgs.userNames).size > 1
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ==================== Strength → guidance string tables ====================
|
|
65
|
+
// Replaces the if/else-if/else strength chains with table lookups.
|
|
66
|
+
|
|
67
|
+
const REPLY_MULTIUSER_LENGTH: Record<Strength, string> = {
|
|
68
|
+
high: "Keep it extremely brief. Prefer one short sentence; max two short lines.",
|
|
69
|
+
medium:
|
|
70
|
+
"Keep it brief and conversational. One or two sentences max. Don't try to be comprehensive - just pick one thing to respond to or make a general comment that fits the vibe.",
|
|
71
|
+
low: "Keep it natural and focused on one key point instead of covering everything.",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const REPLY_SINGLE_TOOL: Record<Strength, string> = {
|
|
75
|
+
high: "If the user asks for facts, verification, or external info, proactively use suitable tools. Avoid guessing when tools can validate.",
|
|
76
|
+
medium:
|
|
77
|
+
"If the user asks for help, use recent chat history and suitable tools when needed to answer accurately. Avoid vague or incorrect info.",
|
|
78
|
+
low: "If the user asks for help, prioritize direct conversational replies first. Use tools only when clearly necessary.",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const REPLY_SINGLE_LENGTH: Record<Strength, string> = {
|
|
82
|
+
high: "Length target: one short sentence preferred, max two short lines.",
|
|
83
|
+
medium: "Length target: concise reply, usually within 1-2 short paragraphs.",
|
|
84
|
+
low: "",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const COMMENT_LENGTH: Record<Strength, string> = {
|
|
88
|
+
high: "Length target: keep it very short, ideally one sentence, max two short lines. If there are multiple messages, summarize into one brief reply.",
|
|
89
|
+
medium:
|
|
90
|
+
"Important! Messages must be concise and impactful, not exceeding two sentences. If there are multiple messages, summarize and reply concisely.",
|
|
91
|
+
low: "If there are multiple messages, prefer one merged response instead of replying one by one.",
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const IDLE_LENGTH: Record<Strength, string> = {
|
|
95
|
+
high: `Length target: one short sentence only. Do NOT say things like "群里好久没人说话了" or "大家怎么都不说话了".`,
|
|
96
|
+
medium: `Important!! Please keep your messages extremely concise. Use no more than one sentence to reply to the person you most want to reply to, or two short paragraphs for a brief group-level comment. Do NOT say things like "群里好久没人说话了" or "大家怎么都不说话了".`,
|
|
97
|
+
low: "Reply naturally and quickly; avoid mentioning that the group was quiet.",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const REVIEW_MULTI_LENGTH: Record<Strength, string> = {
|
|
101
|
+
high: "CRITICAL: Reply once only, and keep it to one short sentence (max two short lines).",
|
|
102
|
+
medium:
|
|
103
|
+
"CRITICAL: Do NOT try to reply to each message or each person separately. Give ONE brief, casual response that fits the overall conversation. Pick one thing to comment on or just say something general. Keep it to a single sentence or two at most.",
|
|
104
|
+
low: "Reply once for the whole group instead of replying person-by-person.",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const REVIEW_SINGLE_LENGTH: Record<Strength, string> = {
|
|
108
|
+
high: "Respond naturally in one short message, preferably one sentence.",
|
|
109
|
+
medium:
|
|
110
|
+
"Please respond reasonably and naturally in context. Keep the message concise, since you've already said it, and it must fit in a single message.",
|
|
111
|
+
low: "Respond naturally in context and avoid repeating old wording.",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const POKED_LENGTH: Record<Strength, string> = {
|
|
115
|
+
high: "Keep this very short: one brief sentence.",
|
|
116
|
+
medium: "",
|
|
117
|
+
low: "",
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const REPLY_STYLE_LENGTH: Record<Strength, string> = {
|
|
121
|
+
high: "Keep replies very short. Prefer one short sentence; max two short lines.",
|
|
122
|
+
medium:
|
|
123
|
+
"Keep replies concise and conversational. Avoid long paragraphs unless the topic demands it.",
|
|
124
|
+
low: "Keep replies natural and conversational. Do not be verbose without purpose.",
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const AUDIO_MODE_LINE: Record<Strength, string> = {
|
|
128
|
+
high:
|
|
129
|
+
"- Use voice sparingly. Only use it when spoken delivery is clearly better than text, such as a greeting, a sharp emotional reaction, or a daily phrase.",
|
|
130
|
+
medium:
|
|
131
|
+
"- You may use voice for greetings, reactions, calls, confirmations, or comforting words, but stay selective.",
|
|
132
|
+
low: "- When a short spoken reaction would make the conversation feel more natural or vivid, you can use voice more freely.",
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const MARKDOWN_MODE_LINE: Record<Strength, string> = {
|
|
136
|
+
high:
|
|
137
|
+
"- Prefer normal chat text. Use Markdown only when the reply truly needs structured presentation, such as a tutorial, comparison, detailed explanation, code sample or processing large amounts of data, such as after a web search or viewing a webpage.",
|
|
138
|
+
medium: "- Use Markdown when your responses require a structured presentation.",
|
|
139
|
+
low: "- Use Markdown freely where it can make your responses clearer.",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const TOOL_INTENSITY_BLOCK: Record<Strength, string> = {
|
|
143
|
+
high: `
|
|
144
|
+
### Tool Usage Intensity
|
|
145
|
+
- Be proactive with tools for uncertain facts, external info, verification, and current events.
|
|
146
|
+
- Prefer validating with tools over guessing.
|
|
147
|
+
- If web searches fail to produce a useful answer after about 2-3 attempts, stop searching and reply directly based on what you already know or what you have already found.`,
|
|
148
|
+
medium: `
|
|
149
|
+
### Tool Usage Intensity
|
|
150
|
+
- Use tools when clearly useful for correctness, verification, or missing context.
|
|
151
|
+
- If web searches still do not produce a useful answer after about 2-3 attempts, stop searching and give a direct reply instead of continuing to try more keywords.`,
|
|
152
|
+
low: `
|
|
153
|
+
### Tool Usage Intensity
|
|
154
|
+
- Prefer direct chat responses first.
|
|
155
|
+
- Use tools only when strictly necessary.`,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const EMOJI_MODE_LINE: Record<Strength, string> = {
|
|
159
|
+
high:
|
|
160
|
+
"- Keep stickers rare. Use one only when it clearly strengthens a strong emotional beat or punchline.",
|
|
161
|
+
medium:
|
|
162
|
+
"- You may use a sticker for obvious emotional beats, reactions, jokes, teasing, or celebrations, but do not overuse it.",
|
|
163
|
+
low: "- When it helps the emotional effect of the reply, you can use a matching sticker more freely.",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const WEB_SEARCH_LINE: Record<Strength, string> = {
|
|
167
|
+
high:
|
|
168
|
+
"- When facts may be outdated or uncertain, proactively call web_search instead of guessing.",
|
|
169
|
+
medium: "- Use web_search when current or external info is needed.",
|
|
170
|
+
low: "- Use web_search only when the user explicitly needs external/current information.",
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// ==================== System prompt assembly ====================
|
|
174
|
+
|
|
54
175
|
export function buildSystemPrompt(ctx: PromptContext): string {
|
|
55
|
-
const sections: string[] = [];
|
|
56
176
|
const lengthStrength = normalizeConstraintStrength(
|
|
57
177
|
ctx.config.outputLengthConstraintStrength,
|
|
58
178
|
);
|
|
@@ -69,12 +189,10 @@ export function buildSystemPrompt(ctx: PromptContext): string {
|
|
|
69
189
|
ctx.config.markdownUsageConstraintStrength,
|
|
70
190
|
);
|
|
71
191
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
192
|
+
const sections: string[] = [];
|
|
193
|
+
|
|
194
|
+
if (ctx.activeSkillsInfo) sections.push(ctx.activeSkillsInfo);
|
|
76
195
|
|
|
77
|
-
// 2. Expression Habits
|
|
78
196
|
if (ctx.expressionContext) {
|
|
79
197
|
logger.info(
|
|
80
198
|
`[buildSystemPrompt] Adding expressionContext (${ctx.expressionContext.length} chars) for user`,
|
|
@@ -86,7 +204,6 @@ export function buildSystemPrompt(ctx: PromptContext): string {
|
|
|
86
204
|
);
|
|
87
205
|
}
|
|
88
206
|
|
|
89
|
-
// 3. Memory Retrieval Results
|
|
90
207
|
if (ctx.memoryContext) {
|
|
91
208
|
logger.info(
|
|
92
209
|
`[buildSystemPrompt] Adding memoryContext (${ctx.memoryContext.length} chars)`,
|
|
@@ -100,27 +217,15 @@ export function buildSystemPrompt(ctx: PromptContext): string {
|
|
|
100
217
|
);
|
|
101
218
|
}
|
|
102
219
|
|
|
103
|
-
|
|
104
|
-
if (ctx.topicContext) {
|
|
105
|
-
sections.push(ctx.topicContext);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 5. Slang Dictionary (placeholder)
|
|
109
|
-
// TODO: slang dictionary injection
|
|
220
|
+
if (ctx.topicContext) sections.push(ctx.topicContext);
|
|
110
221
|
|
|
111
|
-
// 6. Current Time & Environment
|
|
112
222
|
sections.push(buildEnvironmentSection(ctx));
|
|
113
|
-
|
|
114
|
-
// 7. Chat History
|
|
115
223
|
sections.push(buildChatHistorySection(ctx));
|
|
116
|
-
|
|
117
|
-
// 8. Target Message
|
|
118
224
|
sections.push(
|
|
119
225
|
buildTargetMessageSection(ctx.targetMessage, ctx.reviewMessages),
|
|
120
226
|
);
|
|
121
227
|
sections.push(...buildInjectedSections(ctx.promptInjections));
|
|
122
228
|
|
|
123
|
-
// 9. Reply Context - tells AI what kind of reply this is
|
|
124
229
|
if (ctx.replyContext) {
|
|
125
230
|
sections.push(
|
|
126
231
|
buildReplyContextSection(
|
|
@@ -132,21 +237,13 @@ export function buildSystemPrompt(ctx: PromptContext): string {
|
|
|
132
237
|
);
|
|
133
238
|
}
|
|
134
239
|
|
|
135
|
-
// 10. Planner's Thoughts
|
|
136
240
|
if (ctx.plannerThoughts) {
|
|
137
241
|
sections.push(`## Planner's Analysis\n${ctx.plannerThoughts}`);
|
|
138
242
|
}
|
|
139
243
|
|
|
140
|
-
// 11. Persona
|
|
141
244
|
sections.push(buildPersonaSection(ctx));
|
|
142
|
-
|
|
143
|
-
// 12. Emotion
|
|
144
245
|
sections.push(buildEmotionSection(ctx));
|
|
145
|
-
|
|
146
|
-
// 13. Reply Style + Behavior + Self-Protection
|
|
147
246
|
sections.push(buildReplyStyleSection(ctx, lengthStrength));
|
|
148
|
-
|
|
149
|
-
// 14. Available Tools & Response Format
|
|
150
247
|
sections.push(
|
|
151
248
|
buildResponseFormatSection(
|
|
152
249
|
ctx,
|
|
@@ -161,198 +258,99 @@ export function buildSystemPrompt(ctx: PromptContext): string {
|
|
|
161
258
|
return sections.join("\n\n");
|
|
162
259
|
}
|
|
163
260
|
|
|
164
|
-
// ==================== Section Builders ====================
|
|
165
|
-
|
|
166
|
-
type ConstraintStrength = "low" | "medium" | "high";
|
|
167
|
-
|
|
168
261
|
function buildInjectedSections(
|
|
169
262
|
injections: ChatRuntimePromptInjection[] | undefined,
|
|
170
263
|
): string[] {
|
|
171
|
-
if (!injections || injections.length === 0)
|
|
172
|
-
return [];
|
|
173
|
-
}
|
|
174
|
-
|
|
264
|
+
if (!injections || injections.length === 0) return [];
|
|
175
265
|
return injections.map((injection, index) => {
|
|
176
266
|
const title = injection.title || `Runtime Instruction ${index + 1}`;
|
|
177
267
|
return `## ${title}\n${injection.content}`;
|
|
178
268
|
});
|
|
179
269
|
}
|
|
180
270
|
|
|
181
|
-
|
|
182
|
-
if (value === "low" || value === "high" || value === "medium") {
|
|
183
|
-
return value;
|
|
184
|
-
}
|
|
185
|
-
return "medium";
|
|
186
|
-
}
|
|
271
|
+
// ==================== Reply context ====================
|
|
187
272
|
|
|
188
273
|
function buildReplyContextSection(
|
|
189
274
|
replyCtx: PromptContext["replyContext"],
|
|
190
|
-
reviewMsgs
|
|
191
|
-
lengthStrength:
|
|
192
|
-
toolStrength:
|
|
275
|
+
reviewMsgs: PromptContext["reviewMessages"],
|
|
276
|
+
lengthStrength: Strength,
|
|
277
|
+
toolStrength: Strength,
|
|
193
278
|
): string {
|
|
194
279
|
if (!replyCtx) return "";
|
|
280
|
+
const isMulti = isMultiUserInteraction(reviewMsgs);
|
|
281
|
+
const builders: Record<ReplyType, () => string[]> = {
|
|
282
|
+
reply: () => buildReplyGuidance(isMulti, lengthStrength, toolStrength),
|
|
283
|
+
comment: () => buildCommentGuidance(lengthStrength),
|
|
284
|
+
idle: () => buildIdleGuidance(lengthStrength),
|
|
285
|
+
review: () => buildReviewGuidance(isMulti, lengthStrength),
|
|
286
|
+
poked: () => buildPokedGuidance(lengthStrength),
|
|
287
|
+
};
|
|
288
|
+
return ["## This Response Context", ...builders[replyCtx.type]()].join("\n");
|
|
289
|
+
}
|
|
195
290
|
|
|
196
|
-
|
|
291
|
+
function buildReplyGuidance(
|
|
292
|
+
isMulti: boolean,
|
|
293
|
+
length: Strength,
|
|
294
|
+
tool: Strength,
|
|
295
|
+
): string[] {
|
|
296
|
+
if (isMulti) {
|
|
297
|
+
return [
|
|
298
|
+
"Multiple people are interacting with you at the same time. You see messages from several group members directed at you.",
|
|
299
|
+
"IMPORTANT: Do NOT reply to each person individually or try to address every single message. Instead, give a SINGLE, unified response that acknowledges the group as a whole. Be casual and natural - like you're talking to a group of friends, not giving individual responses.",
|
|
300
|
+
REPLY_MULTIUSER_LENGTH[length],
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
return [
|
|
304
|
+
"Someone mentioned you in the group, maybe like you asked a certain question, or just wanted to tease you.",
|
|
305
|
+
REPLY_SINGLE_TOOL[tool],
|
|
306
|
+
"If a user doesn't have a real problem and is just trying to tease you, don't get annoyed. Use the group chat history to infer intent and join naturally. If a user is provocative or insulting, respond humorously but politely.",
|
|
307
|
+
REPLY_SINGLE_LENGTH[length],
|
|
308
|
+
].filter(Boolean);
|
|
309
|
+
}
|
|
197
310
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
`Keep it brief and conversational. One or two sentences max. Don't try to be comprehensive - just pick one thing to respond to or make a general comment that fits the vibe.`,
|
|
220
|
-
);
|
|
221
|
-
} else {
|
|
222
|
-
lines.push(
|
|
223
|
-
`Keep it natural and focused on one key point instead of covering everything.`,
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
lines.push(
|
|
228
|
-
`Someone mentioned you in the group, maybe like you asked a certain question, or just wanted to tease you.`,
|
|
229
|
-
);
|
|
230
|
-
if (toolStrength === "high") {
|
|
231
|
-
lines.push(
|
|
232
|
-
`If the user asks for facts, verification, or external info, proactively use suitable tools. Avoid guessing when tools can validate.`,
|
|
233
|
-
);
|
|
234
|
-
} else if (toolStrength === "medium") {
|
|
235
|
-
lines.push(
|
|
236
|
-
`If the user asks for help, use recent chat history and suitable tools when needed to answer accurately. Avoid vague or incorrect info.`,
|
|
237
|
-
);
|
|
238
|
-
} else {
|
|
239
|
-
lines.push(
|
|
240
|
-
`If the user asks for help, prioritize direct conversational replies first. Use tools only when clearly necessary.`,
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
lines.push(
|
|
244
|
-
`If a user doesn't have a real problem and is just trying to tease you, don't get annoyed. Use the group chat history to infer intent and join naturally. If a user is provocative or insulting, respond humorously but politely.`,
|
|
245
|
-
);
|
|
246
|
-
if (lengthStrength === "high") {
|
|
247
|
-
lines.push(
|
|
248
|
-
`Length target: one short sentence preferred, max two short lines.`,
|
|
249
|
-
);
|
|
250
|
-
} else if (lengthStrength === "medium") {
|
|
251
|
-
lines.push(
|
|
252
|
-
`Length target: concise reply, usually within 1-2 short paragraphs.`,
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
break;
|
|
257
|
-
case "comment":
|
|
258
|
-
lines.push(
|
|
259
|
-
`If someone adds or comments after you reply to the previous message, please carefully read the group chat history and analyze your reply. Provide a reasonable and natural response to the user's comment, and do not repeat what you already said or a particular viewpoint.`,
|
|
260
|
-
);
|
|
261
|
-
if (lengthStrength === "high") {
|
|
262
|
-
lines.push(
|
|
263
|
-
`Length target: keep it very short, ideally one sentence, max two short lines. If there are multiple messages, summarize into one brief reply.`,
|
|
264
|
-
);
|
|
265
|
-
} else if (lengthStrength === "medium") {
|
|
266
|
-
lines.push(
|
|
267
|
-
`Important! Messages must be concise and impactful, not exceeding two sentences. If there are multiple messages, summarize and reply concisely.`,
|
|
268
|
-
);
|
|
269
|
-
} else {
|
|
270
|
-
lines.push(
|
|
271
|
-
`If there are multiple messages, prefer one merged response instead of replying one by one.`,
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
break;
|
|
275
|
-
case "idle":
|
|
276
|
-
lines.push(
|
|
277
|
-
`No one spoke in the group for a long time, so you decided to chime in.`,
|
|
278
|
-
);
|
|
279
|
-
lines.push(
|
|
280
|
-
`First, observe the chat history in the group. If there is any content related to your persona that you are interested in, consider replying. Next, observe if any group members have unresolved questions. If not, then observe the chat style of the group members and send messages that naturally blend into their conversations. You can even repeat a funny message sent by a group member or a phrase that appears repeatedly in the chat history.`,
|
|
281
|
-
);
|
|
282
|
-
if (lengthStrength === "high") {
|
|
283
|
-
lines.push(
|
|
284
|
-
`Length target: one short sentence only. Do NOT say things like "群里好久没人说话了" or "大家怎么都不说话了".`,
|
|
285
|
-
);
|
|
286
|
-
} else if (lengthStrength === "medium") {
|
|
287
|
-
lines.push(
|
|
288
|
-
`Important!! Please keep your messages extremely concise. Use no more than one sentence to reply to the person you most want to reply to, or two short paragraphs for a brief group-level comment. Do NOT say things like "群里好久没人说话了" or "大家怎么都不说话了".`,
|
|
289
|
-
);
|
|
290
|
-
} else {
|
|
291
|
-
lines.push(
|
|
292
|
-
`Reply naturally and quickly; avoid mentioning that the group was quiet.`,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
break;
|
|
296
|
-
case "review":
|
|
297
|
-
if (isMultiUserInteraction) {
|
|
298
|
-
lines.push(
|
|
299
|
-
`Multiple people have sent you messages while you were away. You see a batch of messages from different group members.`,
|
|
300
|
-
);
|
|
301
|
-
if (lengthStrength === "high") {
|
|
302
|
-
lines.push(
|
|
303
|
-
`CRITICAL: Reply once only, and keep it to one short sentence (max two short lines).`,
|
|
304
|
-
);
|
|
305
|
-
} else if (lengthStrength === "medium") {
|
|
306
|
-
lines.push(
|
|
307
|
-
`CRITICAL: Do NOT try to reply to each message or each person separately. Give ONE brief, casual response that fits the overall conversation. Pick one thing to comment on or just say something general. Keep it to a single sentence or two at most.`,
|
|
308
|
-
);
|
|
309
|
-
} else {
|
|
310
|
-
lines.push(
|
|
311
|
-
`Reply once for the whole group instead of replying person-by-person.`,
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
} else {
|
|
315
|
-
lines.push(
|
|
316
|
-
`After you reply to other group members' messages, some people have new questions or replies to your answers.`,
|
|
317
|
-
);
|
|
318
|
-
if (lengthStrength === "high") {
|
|
319
|
-
lines.push(
|
|
320
|
-
`Respond naturally in one short message, preferably one sentence.`,
|
|
321
|
-
);
|
|
322
|
-
} else if (lengthStrength === "medium") {
|
|
323
|
-
lines.push(
|
|
324
|
-
`Please respond reasonably and naturally in context. Keep the message concise, since you've already said it, and it must fit in a single message.`,
|
|
325
|
-
);
|
|
326
|
-
} else {
|
|
327
|
-
lines.push(
|
|
328
|
-
`Respond naturally in context and avoid repeating old wording.`,
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
break;
|
|
333
|
-
case "poked":
|
|
334
|
-
lines.push(
|
|
335
|
-
`Someone pokes you in a group, probably out of non-malicious play or to draw your attention to what happened in the group chat.`,
|
|
336
|
-
);
|
|
337
|
-
lines.push(
|
|
338
|
-
`Don't make a fuss about replying, just observe whether the chat history in the group has noteworthy content, and if not, simply say hello or express concern to the user.`,
|
|
339
|
-
);
|
|
340
|
-
lines.push(
|
|
341
|
-
`Reply naturally in combination with the context, don't say something like "怎么又来戳我了"`,
|
|
342
|
-
);
|
|
343
|
-
if (lengthStrength === "high") {
|
|
344
|
-
lines.push(`Keep this very short: one brief sentence.`);
|
|
345
|
-
}
|
|
346
|
-
break;
|
|
311
|
+
function buildCommentGuidance(length: Strength): string[] {
|
|
312
|
+
return [
|
|
313
|
+
"If someone adds or comments after you reply to the previous message, please carefully read the group chat history and analyze your reply. Provide a reasonable and natural response to the user's comment, and do not repeat what you already said or a particular viewpoint.",
|
|
314
|
+
COMMENT_LENGTH[length],
|
|
315
|
+
].filter(Boolean);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function buildIdleGuidance(length: Strength): string[] {
|
|
319
|
+
return [
|
|
320
|
+
"No one spoke in the group for a long time, so you decided to chime in.",
|
|
321
|
+
"First, observe the chat history in the group. If there is any content related to your persona that you are interested in, consider replying. Next, observe if any group members have unresolved questions. If not, then observe the chat style of the group members and send messages that naturally blend into their conversations. You can even repeat a funny message sent by a group member or a phrase that appears repeatedly in the chat history.",
|
|
322
|
+
IDLE_LENGTH[length],
|
|
323
|
+
].filter(Boolean);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function buildReviewGuidance(isMulti: boolean, length: Strength): string[] {
|
|
327
|
+
if (isMulti) {
|
|
328
|
+
return [
|
|
329
|
+
"Multiple people have sent you messages while you were away. You see a batch of messages from different group members.",
|
|
330
|
+
REVIEW_MULTI_LENGTH[length],
|
|
331
|
+
];
|
|
347
332
|
}
|
|
333
|
+
return [
|
|
334
|
+
"After you reply to other group members' messages, some people have new questions or replies to your answers.",
|
|
335
|
+
REVIEW_SINGLE_LENGTH[length],
|
|
336
|
+
];
|
|
337
|
+
}
|
|
348
338
|
|
|
349
|
-
|
|
339
|
+
function buildPokedGuidance(length: Strength): string[] {
|
|
340
|
+
return [
|
|
341
|
+
"Someone pokes you in a group, probably out of non-malicious play or to draw your attention to what happened in the group chat.",
|
|
342
|
+
"Don't make a fuss about replying, just observe whether the chat history in the group has noteworthy content, and if not, simply say hello or express concern to the user.",
|
|
343
|
+
"Reply naturally in combination with the context, don't say something like \"怎么又来戳我了\"",
|
|
344
|
+
POKED_LENGTH[length],
|
|
345
|
+
].filter(Boolean);
|
|
350
346
|
}
|
|
351
347
|
|
|
348
|
+
// ==================== Other sections ====================
|
|
349
|
+
|
|
352
350
|
function buildEnvironmentSection(ctx: PromptContext): string {
|
|
353
351
|
const now = new Date();
|
|
354
352
|
const timeStr = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, "0")}/${String(now.getDate()).padStart(2, "0")} ${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
355
|
-
const
|
|
353
|
+
const dayOfWeek = [
|
|
356
354
|
"Sunday",
|
|
357
355
|
"Monday",
|
|
358
356
|
"Tuesday",
|
|
@@ -360,8 +358,7 @@ function buildEnvironmentSection(ctx: PromptContext): string {
|
|
|
360
358
|
"Thursday",
|
|
361
359
|
"Friday",
|
|
362
360
|
"Saturday",
|
|
363
|
-
];
|
|
364
|
-
const dayOfWeek = dayNames[now.getDay()];
|
|
361
|
+
][now.getDay()];
|
|
365
362
|
|
|
366
363
|
const lines = [
|
|
367
364
|
`## Current Time & Environment`,
|
|
@@ -369,12 +366,12 @@ function buildEnvironmentSection(ctx: PromptContext): string {
|
|
|
369
366
|
];
|
|
370
367
|
|
|
371
368
|
if (ctx.isGroup) {
|
|
372
|
-
lines.push(
|
|
369
|
+
lines.push("Chat type: Group chat");
|
|
373
370
|
if (ctx.groupName) lines.push(`Group name: ${ctx.groupName}`);
|
|
374
371
|
if (ctx.memberCount) lines.push(`Member count: ${ctx.memberCount}`);
|
|
375
372
|
lines.push(`Your role in group: ${ctx.botRole}`);
|
|
376
373
|
} else {
|
|
377
|
-
lines.push(
|
|
374
|
+
lines.push("Chat type: Private chat");
|
|
378
375
|
}
|
|
379
376
|
|
|
380
377
|
return lines.join("\n");
|
|
@@ -388,94 +385,71 @@ function buildChatHistorySection(ctx: PromptContext): string {
|
|
|
388
385
|
let currentAssistantBlock: { timeStr: string; contents: string[] } | null =
|
|
389
386
|
null;
|
|
390
387
|
|
|
388
|
+
const flushAssistant = () => {
|
|
389
|
+
if (!currentAssistantBlock) return;
|
|
390
|
+
mergedLines.push(
|
|
391
|
+
`[${currentAssistantBlock.timeStr}] ${ctx.botNickname}: ${currentAssistantBlock.contents.join(" | ")}`,
|
|
392
|
+
);
|
|
393
|
+
currentAssistantBlock = null;
|
|
394
|
+
};
|
|
395
|
+
|
|
391
396
|
for (const msg of chatHistory) {
|
|
392
397
|
const time = new Date(msg.timestamp);
|
|
393
398
|
const timeStr = `${String(time.getMonth() + 1).padStart(2, "0")}-${String(time.getDate()).padStart(2, "0")} ${String(time.getHours()).padStart(2, "0")}:${String(time.getMinutes()).padStart(2, "0")}`;
|
|
394
399
|
|
|
395
400
|
if (msg.role === "assistant") {
|
|
396
401
|
if (currentAssistantBlock && currentAssistantBlock.timeStr === timeStr) {
|
|
397
|
-
// Same timestamp, add to current block
|
|
398
402
|
currentAssistantBlock.contents.push(msg.content);
|
|
399
403
|
} else {
|
|
400
|
-
|
|
401
|
-
if (currentAssistantBlock) {
|
|
402
|
-
// Flush previous block
|
|
403
|
-
const mergedContent = currentAssistantBlock.contents.join(" | ");
|
|
404
|
-
mergedLines.push(
|
|
405
|
-
`[${currentAssistantBlock.timeStr}] ${ctx.botNickname}: ${mergedContent}`,
|
|
406
|
-
);
|
|
407
|
-
}
|
|
404
|
+
flushAssistant();
|
|
408
405
|
currentAssistantBlock = { timeStr, contents: [msg.content] };
|
|
409
406
|
}
|
|
410
|
-
|
|
411
|
-
// Flush assistant block if exists
|
|
412
|
-
if (currentAssistantBlock) {
|
|
413
|
-
const mergedContent = currentAssistantBlock.contents.join(" | ");
|
|
414
|
-
mergedLines.push(
|
|
415
|
-
`[${currentAssistantBlock.timeStr}] ${ctx.botNickname}: ${mergedContent}`,
|
|
416
|
-
);
|
|
417
|
-
currentAssistantBlock = null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const name = msg.userName || "unknown";
|
|
421
|
-
const roleLabel =
|
|
422
|
-
msg.userRole === "owner"
|
|
423
|
-
? "Owner"
|
|
424
|
-
: msg.userRole === "admin"
|
|
425
|
-
? "Admin"
|
|
426
|
-
: "Member";
|
|
427
|
-
const titleStr = msg.userTitle ? `, ${msg.userTitle}` : "";
|
|
428
|
-
const qqStr = msg.userId ? `${msg.userId}` : "";
|
|
429
|
-
const msgIdStr = msg.messageId ? ` #${msg.messageId}` : "";
|
|
430
|
-
|
|
431
|
-
mergedLines.push(
|
|
432
|
-
`[${timeStr}] ${name}(${qqStr}, ${roleLabel}${titleStr})${msgIdStr}): ${msg.content}`,
|
|
433
|
-
);
|
|
407
|
+
continue;
|
|
434
408
|
}
|
|
435
|
-
}
|
|
436
409
|
|
|
437
|
-
|
|
438
|
-
const
|
|
410
|
+
flushAssistant();
|
|
411
|
+
const name = msg.userName || "unknown";
|
|
412
|
+
const roleLabel =
|
|
413
|
+
msg.userRole === "owner"
|
|
414
|
+
? "Owner"
|
|
415
|
+
: msg.userRole === "admin"
|
|
416
|
+
? "Admin"
|
|
417
|
+
: "Member";
|
|
418
|
+
const titleStr = msg.userTitle ? `, ${msg.userTitle}` : "";
|
|
419
|
+
const qqStr = msg.userId ? `${msg.userId}` : "";
|
|
420
|
+
const msgIdStr = msg.messageId ? ` #${msg.messageId}` : "";
|
|
439
421
|
mergedLines.push(
|
|
440
|
-
`[${
|
|
422
|
+
`[${timeStr}] ${name}(${qqStr}, ${roleLabel}${titleStr})${msgIdStr}): ${msg.content}`,
|
|
441
423
|
);
|
|
442
424
|
}
|
|
425
|
+
flushAssistant();
|
|
443
426
|
|
|
444
427
|
return `## Recent Context (Only reference if directly relevant)
|
|
445
428
|
Just the last few messages - don't overthink it or dig into old conversations:
|
|
446
429
|
|
|
447
430
|
${mergedLines.join("\n")}
|
|
448
431
|
|
|
449
|
-
Note: Messages may contain media tags like [meme:描述], [image:描述], [video:描述], [forward:摘要], [card:摘要], or [group_notice:摘要]. These are brief processed summaries. If you need detailed information about an image, use the
|
|
432
|
+
Note: Messages may contain media tags like [meme:描述], [image:描述], [video:描述], [forward:摘要], [card:摘要], or [group_notice:摘要]. These are brief processed summaries. If you need detailed information about an image or video, use the view_media tool with the message ID.
|
|
450
433
|
|
|
451
434
|
-- DON'T repeat yourself or bring up old topics - focus on what's being said right now. --`;
|
|
452
435
|
}
|
|
453
436
|
|
|
454
437
|
function buildTargetMessageSection(
|
|
455
438
|
target: TargetMessage,
|
|
456
|
-
reviewMsgs
|
|
439
|
+
reviewMsgs: PromptContext["reviewMessages"],
|
|
457
440
|
): string {
|
|
458
441
|
const time = new Date(target.timestamp);
|
|
459
442
|
const timeStr = `${String(time.getMonth() + 1).padStart(2, "0")}-${String(time.getDate()).padStart(2, "0")} ${String(time.getHours()).padStart(2, "0")}:${String(time.getMinutes()).padStart(2, "0")}`;
|
|
460
443
|
const msgIdStr = target.messageId ? ` #${target.messageId}` : "";
|
|
461
444
|
|
|
462
|
-
|
|
463
|
-
reviewMsgs &&
|
|
464
|
-
reviewMsgs.userNames.length > 1 &&
|
|
465
|
-
new Set(reviewMsgs.userNames).size > 1;
|
|
466
|
-
|
|
467
|
-
if (isMultiUserInteraction && reviewMsgs) {
|
|
445
|
+
if (isMultiUserInteraction(reviewMsgs) && reviewMsgs) {
|
|
468
446
|
const uniqueUsers = Array.from(new Set(reviewMsgs.userNames));
|
|
469
447
|
const userList = uniqueUsers.join(", ");
|
|
470
|
-
|
|
471
|
-
const messageBlocks: string[] = [];
|
|
472
|
-
for (let i = 0; i < reviewMsgs.contents.length; i++) {
|
|
473
|
-
const userName = reviewMsgs.userNames[i];
|
|
474
|
-
const content = reviewMsgs.contents[i];
|
|
448
|
+
const messageBlocks = reviewMsgs.contents.map((content, i) => {
|
|
475
449
|
const msgId = reviewMsgs.messageIds[i];
|
|
476
450
|
const msgIdLabel = msgId ? ` #${msgId}` : "";
|
|
477
|
-
|
|
478
|
-
}
|
|
451
|
+
return `[${reviewMsgs.userNames[i]}${msgIdLabel}]: ${content}`;
|
|
452
|
+
});
|
|
479
453
|
|
|
480
454
|
return `## >>> Multiple People Are Interacting With You <<<
|
|
481
455
|
${userList} sent you messages at around ${timeStr}:
|
|
@@ -490,12 +464,8 @@ IMPORTANT: You do NOT need to reply to each person or each message above. Give O
|
|
|
490
464
|
}
|
|
491
465
|
|
|
492
466
|
function buildPersonaSection(ctx: PromptContext): string {
|
|
493
|
-
const lines = [
|
|
494
|
-
|
|
495
|
-
if (ctx.config.persona) {
|
|
496
|
-
lines.push(ctx.config.persona);
|
|
497
|
-
}
|
|
498
|
-
|
|
467
|
+
const lines = ["## Persona"];
|
|
468
|
+
if (ctx.config.persona) lines.push(ctx.config.persona);
|
|
499
469
|
return lines.join("\n");
|
|
500
470
|
}
|
|
501
471
|
|
|
@@ -519,20 +489,19 @@ function buildEmotionSection(ctx: PromptContext): string {
|
|
|
519
489
|
);
|
|
520
490
|
const examples =
|
|
521
491
|
currentExamples.length > 0 ? currentExamples : fallbackExamples;
|
|
522
|
-
const lines = [
|
|
492
|
+
const lines = ["## Emotion State", `Current emotion: ${currentEmotion}`];
|
|
523
493
|
|
|
524
494
|
if (availableEmotions.length > 0) {
|
|
525
495
|
lines.push(`Available emotions: ${availableEmotions.join(", ")}`);
|
|
526
496
|
}
|
|
527
|
-
|
|
528
497
|
lines.push(
|
|
529
|
-
|
|
498
|
+
"You may switch your emotion state by writing [emotion:emotion_name]. The marker is not sent to the chat. Only use available emotions.",
|
|
530
499
|
);
|
|
531
500
|
|
|
532
501
|
if (examples.length > 0) {
|
|
533
502
|
lines.push(
|
|
534
|
-
|
|
535
|
-
|
|
503
|
+
"For examples of responses to current emotions, please refer to their tone and speech characteristics.",
|
|
504
|
+
"Be sure to imitate their tone and speaking characteristics, including sentence length, pauses within sentences, and the use of punctuation",
|
|
536
505
|
examples.map((example) => `- ${example}`).join("\n"),
|
|
537
506
|
);
|
|
538
507
|
}
|
|
@@ -541,9 +510,7 @@ function buildEmotionSection(ctx: PromptContext): string {
|
|
|
541
510
|
}
|
|
542
511
|
|
|
543
512
|
function normalizeEmotionName(value: unknown): string {
|
|
544
|
-
return String(value || "")
|
|
545
|
-
.trim()
|
|
546
|
-
.toLowerCase();
|
|
513
|
+
return String(value || "").trim().toLowerCase();
|
|
547
514
|
}
|
|
548
515
|
|
|
549
516
|
function normalizeEmotionExamples(value: unknown): string[] {
|
|
@@ -553,25 +520,16 @@ function normalizeEmotionExamples(value: unknown): string[] {
|
|
|
553
520
|
|
|
554
521
|
function buildReplyStyleSection(
|
|
555
522
|
ctx: PromptContext,
|
|
556
|
-
lengthStrength:
|
|
523
|
+
lengthStrength: Strength,
|
|
557
524
|
): string {
|
|
558
525
|
const style = pickReplyStyle(ctx.config);
|
|
559
|
-
const lines = [
|
|
560
|
-
|
|
561
|
-
if (style) {
|
|
562
|
-
lines.push(`Current style: ${style}`);
|
|
563
|
-
}
|
|
526
|
+
const lines = ["## Reply Style"];
|
|
527
|
+
if (style) lines.push(`Current style: ${style}`);
|
|
564
528
|
|
|
565
529
|
lines.push(`
|
|
566
530
|
### Behavior Guidelines
|
|
567
531
|
- You are a participant in this chat, not an assistant. Chat naturally like a real person.
|
|
568
|
-
- ${
|
|
569
|
-
lengthStrength === "high"
|
|
570
|
-
? "Keep replies very short. Prefer one short sentence; max two short lines."
|
|
571
|
-
: lengthStrength === "medium"
|
|
572
|
-
? "Keep replies concise and conversational. Avoid long paragraphs unless the topic demands it."
|
|
573
|
-
: "Keep replies natural and conversational. Do not be verbose without purpose."
|
|
574
|
-
}
|
|
532
|
+
- ${REPLY_STYLE_LENGTH[lengthStrength]}
|
|
575
533
|
- Match the language used by others in the chat (if they speak Chinese, reply in Chinese, etc.).
|
|
576
534
|
- Don't repeat yourself or echo what others just said.
|
|
577
535
|
- **NEVER use action descriptions like *xxx* or (xxx) — just speak as a normal person would**
|
|
@@ -590,11 +548,11 @@ function buildReplyStyleSection(
|
|
|
590
548
|
|
|
591
549
|
function buildResponseFormatSection(
|
|
592
550
|
ctx: PromptContext,
|
|
593
|
-
|
|
594
|
-
toolStrength:
|
|
595
|
-
emojiStrength:
|
|
596
|
-
audioStrength:
|
|
597
|
-
markdownStrength:
|
|
551
|
+
_lengthStrength: Strength,
|
|
552
|
+
toolStrength: Strength,
|
|
553
|
+
emojiStrength: Strength,
|
|
554
|
+
audioStrength: Strength,
|
|
555
|
+
markdownStrength: Strength,
|
|
598
556
|
): string {
|
|
599
557
|
const lines = [`## Response Format`];
|
|
600
558
|
|
|
@@ -606,7 +564,7 @@ function buildResponseFormatSection(
|
|
|
606
564
|
- If you want to send multiple messages, just press Enter and write the next line
|
|
607
565
|
- Each line = one message sent to the chat
|
|
608
566
|
- **If your reply has multiple sentences or different points, ALWAYS use real line breaks to separate them**
|
|
609
|
-
- NEVER use "
|
|
567
|
+
- NEVER use "\\" or literal "\\n" to simulate a new line
|
|
610
568
|
- **MESSAGE ORDER MATTERS**: messages are sent top-to-bottom, one line at a time.
|
|
611
569
|
- For action markers like [meme:...] or [audio:...], put them on their own line when they are meant to be a separate action.
|
|
612
570
|
|
|
@@ -617,14 +575,7 @@ function buildResponseFormatSection(
|
|
|
617
575
|
- **You can use MULTIPLE [reply:xxx] markers in different lines to quote multiple messages!**
|
|
618
576
|
- These markers will be automatically parsed and removed from your sent message`);
|
|
619
577
|
|
|
620
|
-
// Audio section - always attached when enabled
|
|
621
578
|
if (ctx.config.audio?.enabled && ctx.config.audio.baseUrl?.trim()) {
|
|
622
|
-
const audioModeLine =
|
|
623
|
-
audioStrength === "high"
|
|
624
|
-
? "- Use voice sparingly. Only use it when spoken delivery is clearly better than text, such as a greeting, a sharp emotional reaction, or a daily phrase."
|
|
625
|
-
: audioStrength === "medium"
|
|
626
|
-
? "- You may use voice for greetings, reactions, calls, confirmations, or comforting words, but stay selective."
|
|
627
|
-
: "- When a short spoken reaction would make the conversation feel more natural or vivid, you can use voice more freely.";
|
|
628
579
|
lines.push(`
|
|
629
580
|
### Optional Voice Message Format
|
|
630
581
|
- You MAY optionally send one voice message by writing [audio:content]
|
|
@@ -632,44 +583,21 @@ function buildResponseFormatSection(
|
|
|
632
583
|
The voice message function sends plain text and cannot be used for singing. If a user needs you to sing, other skills should be considered first.
|
|
633
584
|
- Put [audio:...] on its own line when you want it sent as a separate message in sequence
|
|
634
585
|
- Example: "[audio:おはようー]"
|
|
635
|
-
${
|
|
586
|
+
${AUDIO_MODE_LINE[audioStrength]}`);
|
|
636
587
|
}
|
|
637
588
|
|
|
638
|
-
// Markdown section - always attached when enabled
|
|
639
589
|
if (ctx.config.enableMarkdownScreenshot) {
|
|
640
|
-
const markdownModeLine =
|
|
641
|
-
markdownStrength === "high"
|
|
642
|
-
? "- Prefer normal chat text. Use Markdown only when the reply truly needs structured presentation, such as a tutorial, comparison, detailed explanation, code sample or processing large amounts of data, such as after a web search or viewing a webpage."
|
|
643
|
-
: markdownStrength === "medium"
|
|
644
|
-
? "- Use Markdown when your responses require a structured presentation."
|
|
645
|
-
: "- Use Markdown freely where it can make your responses clearer.";
|
|
646
590
|
lines.push(`
|
|
647
591
|
### Optional Markdown Screenshot Format
|
|
648
592
|
- You MAY optionally send one rendered Markdown screenshot by wrapping content with exact tags: <MARKDOWN> ... </MARKDOWN>
|
|
649
593
|
- Put the Markdown block on its own message whenever possible.
|
|
650
594
|
- It is forbidden to use Markdown syntax or formulas in plain text; they must be rendered using <MARKDOWN> blocks.
|
|
651
|
-
${
|
|
595
|
+
${MARKDOWN_MODE_LINE[markdownStrength]}
|
|
652
596
|
- Inside <MARKDOWN>...</MARKDOWN>, there is NO length limit. If the user needs detail, explain clearly and thoroughly instead of over-compressing.
|
|
653
597
|
`);
|
|
654
598
|
}
|
|
655
599
|
|
|
656
|
-
|
|
657
|
-
lines.push(`
|
|
658
|
-
### Tool Usage Intensity
|
|
659
|
-
- Be proactive with tools for uncertain facts, external info, verification, and current events.
|
|
660
|
-
- Prefer validating with tools over guessing.
|
|
661
|
-
- If web searches fail to produce a useful answer after about 2-3 attempts, stop searching and reply directly based on what you already know or what you have already found.`);
|
|
662
|
-
} else if (toolStrength === "medium") {
|
|
663
|
-
lines.push(`
|
|
664
|
-
### Tool Usage Intensity
|
|
665
|
-
- Use tools when clearly useful for correctness, verification, or missing context.
|
|
666
|
-
- If web searches still do not produce a useful answer after about 2-3 attempts, stop searching and give a direct reply instead of continuing to try more keywords.`);
|
|
667
|
-
} else {
|
|
668
|
-
lines.push(`
|
|
669
|
-
### Tool Usage Intensity
|
|
670
|
-
- Prefer direct chat responses first.
|
|
671
|
-
- Use tools only when strictly necessary.`);
|
|
672
|
-
}
|
|
600
|
+
lines.push(TOOL_INTENSITY_BLOCK[toolStrength]);
|
|
673
601
|
|
|
674
602
|
lines.push(`
|
|
675
603
|
### Tool Calling Format
|
|
@@ -677,7 +605,6 @@ ${markdownModeLine}
|
|
|
677
605
|
- Do NOT output tool calls, tool names, or tool arguments in your reply text under any circumstances
|
|
678
606
|
- Do NOT use XML, JSON, or any text format to describe tool calls — only use the API's tool_calls field`);
|
|
679
607
|
|
|
680
|
-
// Memory Recall section - only when "recall_memory" feature is loaded
|
|
681
608
|
const activeFeatures = getActiveFeatureNames(ctx);
|
|
682
609
|
if (activeFeatures.includes("recall_memory") && ctx.config.memory?.enabled) {
|
|
683
610
|
lines.push(`
|
|
@@ -688,62 +615,16 @@ ${markdownModeLine}
|
|
|
688
615
|
- The worker returns historical logs with timestamps; treat them as past records, not newly sent messages.`);
|
|
689
616
|
}
|
|
690
617
|
|
|
691
|
-
|
|
692
|
-
if (emojiAgent && ctx.config.emoji?.enabled) {
|
|
693
|
-
const configChars = ctx.config.emoji.characters || [];
|
|
694
|
-
let availableEmotions: string[] = [];
|
|
695
|
-
|
|
696
|
-
if (configChars.length > 0) {
|
|
697
|
-
for (const char of configChars) {
|
|
698
|
-
const emotions = emojiAgent.getAvailableEmotions(char);
|
|
699
|
-
availableEmotions.push(...emotions);
|
|
700
|
-
}
|
|
701
|
-
} else {
|
|
702
|
-
const allChars = emojiAgent.getAvailableCharacters();
|
|
703
|
-
for (const char of allChars) {
|
|
704
|
-
const emotions = emojiAgent.getAvailableEmotions(char);
|
|
705
|
-
availableEmotions.push(...emotions);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
618
|
+
appendEmojiSection(lines, ctx, emojiStrength);
|
|
708
619
|
|
|
709
|
-
const uniqueEmotions = [...new Set(availableEmotions)].sort();
|
|
710
|
-
if (uniqueEmotions.length > 0) {
|
|
711
|
-
const emojiModeLine =
|
|
712
|
-
emojiStrength === "high"
|
|
713
|
-
? "- Keep stickers rare. Use one only when it clearly strengthens a strong emotional beat or punchline."
|
|
714
|
-
: emojiStrength === "medium"
|
|
715
|
-
? "- You may use a sticker for obvious emotional beats, reactions, jokes, teasing, or celebrations, but do not overuse it."
|
|
716
|
-
: "- When it helps the emotional effect of the reply, you can use a matching sticker more freely.";
|
|
717
|
-
lines.push(`
|
|
718
|
-
### Optional Sticker / Emoji Format
|
|
719
|
-
- You MAY optionally send one matching sticker by writing [meme:emotion]
|
|
720
|
-
${emojiModeLine}
|
|
721
|
-
- Do NOT send a sticker in every reply, and do not force one when the mood is plain
|
|
722
|
-
- Prefer one matching sticker at most. It should enhance the text instead of replacing meaningful content
|
|
723
|
-
- Put [meme:...] on its own line when it should be a separate action message after text
|
|
724
|
-
- Available emotions: ${uniqueEmotions.join(", ")}`);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// Web search tool note - only when "web_search" feature is loaded
|
|
729
620
|
if (activeFeatures.includes("web_search") && ctx.config.searxng?.enabled) {
|
|
730
|
-
const searxngLine =
|
|
731
|
-
toolStrength === "high"
|
|
732
|
-
? "- When facts may be outdated or uncertain, proactively call web_search instead of guessing."
|
|
733
|
-
: toolStrength === "medium"
|
|
734
|
-
? "- Use web_search when current or external info is needed."
|
|
735
|
-
: "- Use web_search only when the user explicitly needs external/current information.";
|
|
736
621
|
lines.push(`
|
|
737
622
|
### Web Search Tool
|
|
738
623
|
- web_search: Use this when you need current or external information that is not in chat history.
|
|
739
|
-
${
|
|
624
|
+
${WEB_SEARCH_LINE[toolStrength]}`);
|
|
740
625
|
}
|
|
741
626
|
|
|
742
|
-
|
|
743
|
-
if (
|
|
744
|
-
activeFeatures.includes("web_read_page") &&
|
|
745
|
-
ctx.config.webReader?.enabled
|
|
746
|
-
) {
|
|
627
|
+
if (activeFeatures.includes("web_read_page") && ctx.config.webReader?.enabled) {
|
|
747
628
|
const independentUseLine = ctx.config.searxng?.enabled
|
|
748
629
|
? "- web_search and web_read_page are independent. Use web_search when you need to discover URLs; use web_read_page directly when the user already gave a URL."
|
|
749
630
|
: "- web_read_page can be used directly when the user provides a URL.";
|
|
@@ -754,60 +635,85 @@ ${independentUseLine}
|
|
|
754
635
|
- Only set render_js=true when the page clearly needs JavaScript rendering, because it costs much more CPU and memory.`);
|
|
755
636
|
}
|
|
756
637
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
638
|
+
appendExternalSkillsSection(lines, ctx);
|
|
639
|
+
|
|
640
|
+
return lines.join("\n");
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function appendEmojiSection(
|
|
644
|
+
lines: string[],
|
|
645
|
+
ctx: PromptContext,
|
|
646
|
+
emojiStrength: Strength,
|
|
647
|
+
): void {
|
|
648
|
+
const emojiAgent = ctx.emojiAgent;
|
|
649
|
+
if (!emojiAgent || !ctx.config.emoji?.enabled) return;
|
|
650
|
+
|
|
651
|
+
const configChars = ctx.config.emoji.characters || [];
|
|
652
|
+
const chars =
|
|
653
|
+
configChars.length > 0 ? configChars : emojiAgent.getAvailableCharacters();
|
|
654
|
+
const availableEmotions: string[] = [];
|
|
655
|
+
for (const char of chars) {
|
|
656
|
+
availableEmotions.push(...emojiAgent.getAvailableEmotions(char));
|
|
657
|
+
}
|
|
658
|
+
const uniqueEmotions = [...new Set(availableEmotions)].sort();
|
|
659
|
+
if (uniqueEmotions.length === 0) return;
|
|
660
|
+
|
|
661
|
+
lines.push(`
|
|
662
|
+
### Optional Sticker / Emoji Format
|
|
663
|
+
- You MAY optionally send one matching sticker by writing [meme:emotion]
|
|
664
|
+
${EMOJI_MODE_LINE[emojiStrength]}
|
|
665
|
+
- Do NOT send a sticker in every reply, and do not force one when the mood is plain
|
|
666
|
+
- Prefer one matching sticker at most. It should enhance the text instead of replacing meaningful content
|
|
667
|
+
- Put [meme:...] on its own line when it should be a separate action message after text
|
|
668
|
+
- Available emotions: ${uniqueEmotions.join(", ")}`);
|
|
669
|
+
}
|
|
783
670
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
671
|
+
function appendExternalSkillsSection(
|
|
672
|
+
lines: string[],
|
|
673
|
+
ctx: PromptContext,
|
|
674
|
+
): void {
|
|
675
|
+
if (!ctx.config.enableExternalSkills) return;
|
|
676
|
+
|
|
677
|
+
const skillsMap = ctx.aiService.getAllSkills?.();
|
|
678
|
+
const skillEntries = skillsMap
|
|
679
|
+
? filterAllowedExternalSkills(
|
|
680
|
+
ctx.config,
|
|
681
|
+
[...skillsMap.values()],
|
|
682
|
+
ctx.triggerSkillRole ?? "member",
|
|
683
|
+
)
|
|
684
|
+
: [];
|
|
685
|
+
|
|
686
|
+
const builtinFeatureDescs: string[] = [];
|
|
687
|
+
if (ctx.config.searxng?.enabled) {
|
|
688
|
+
builtinFeatureDescs.push("- web_search: 进行网页搜索");
|
|
689
|
+
}
|
|
690
|
+
if (ctx.config.webReader?.enabled) {
|
|
691
|
+
builtinFeatureDescs.push("- web_read_page: 读取某个网页URL的内容");
|
|
692
|
+
}
|
|
693
|
+
if (ctx.config.memory?.enabled) {
|
|
694
|
+
builtinFeatureDescs.push("- recall_memory: 回忆某内容,也可用于历史查询");
|
|
695
|
+
}
|
|
788
696
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
697
|
+
const pluginSkillList = skillEntries.length
|
|
698
|
+
? skillEntries.map((s) => `- ${s.name}: ${s.description}`).join("\n")
|
|
699
|
+
: "";
|
|
700
|
+
const builtinList = builtinFeatureDescs.join("\n");
|
|
701
|
+
const combinedList = pluginSkillList
|
|
702
|
+
? pluginSkillList + "\n" + builtinList
|
|
703
|
+
: builtinList;
|
|
793
704
|
|
|
794
|
-
|
|
795
|
-
|
|
705
|
+
if (combinedList) {
|
|
706
|
+
lines.push(`
|
|
796
707
|
### External Skills
|
|
797
708
|
You can load external skills to gain additional capabilities. Use load_skill to load the allowed skills below.
|
|
798
709
|
You prefer to use extra skills to complete the user's tasks like an assistant
|
|
799
710
|
Allowed skills:
|
|
800
711
|
${combinedList}`);
|
|
801
|
-
}
|
|
802
712
|
}
|
|
803
|
-
|
|
804
|
-
return lines.join("\n");
|
|
805
713
|
}
|
|
806
714
|
|
|
807
715
|
function getActiveFeatureNames(ctx: PromptContext): string[] {
|
|
808
|
-
if (!ctx.skillManager || !ctx.sessionId)
|
|
809
|
-
return [];
|
|
810
|
-
}
|
|
716
|
+
if (!ctx.skillManager || !ctx.sessionId) return [];
|
|
811
717
|
return ctx.skillManager.getActiveFeatureNames(ctx.sessionId);
|
|
812
718
|
}
|
|
813
719
|
|
|
@@ -815,16 +721,9 @@ function markdownBehaviorLine(ctx: PromptContext): string {
|
|
|
815
721
|
const activeFeatures = getActiveFeatureNames(ctx);
|
|
816
722
|
const hasMarkdownFeature = activeFeatures.includes("markdown");
|
|
817
723
|
|
|
818
|
-
if (!ctx.config.enableMarkdownScreenshot) {
|
|
819
|
-
return "**DO NOT use markdown formatting, lists, or bullet points. Plain text only.**";
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
if (!hasMarkdownFeature) {
|
|
823
|
-
// Markdown enabled in config but feature not loaded — tell AI markdown is NOT available
|
|
724
|
+
if (!ctx.config.enableMarkdownScreenshot || !hasMarkdownFeature) {
|
|
824
725
|
return "**DO NOT use markdown formatting, lists, or bullet points. Plain text only.**";
|
|
825
726
|
}
|
|
826
|
-
|
|
827
|
-
// Feature loaded — allow markdown usage (full instructions in markdown section below)
|
|
828
727
|
return "**Normal chat should stay plain text. Only use markdown when you intentionally want to send a rendered Markdown screenshot with the special <MARKDOWN>...</MARKDOWN> format.**";
|
|
829
728
|
}
|
|
830
729
|
|
|
@@ -833,30 +732,20 @@ function markdownBehaviorLine(ctx: PromptContext): string {
|
|
|
833
732
|
|
|
834
733
|
export function buildWebSearchFeatureSection(
|
|
835
734
|
config: ChatConfig,
|
|
836
|
-
toolStrength:
|
|
735
|
+
toolStrength: Strength = "medium",
|
|
837
736
|
): string {
|
|
838
|
-
if (!config.searxng?.enabled)
|
|
839
|
-
return "";
|
|
840
|
-
}
|
|
841
|
-
const searxngLine =
|
|
842
|
-
toolStrength === "high"
|
|
843
|
-
? "- When facts may be outdated or uncertain, proactively call web_search instead of guessing."
|
|
844
|
-
: toolStrength === "medium"
|
|
845
|
-
? "- Use web_search when current or external info is needed."
|
|
846
|
-
: "- Use web_search only when the user explicitly needs external/current information.";
|
|
737
|
+
if (!config.searxng?.enabled) return "";
|
|
847
738
|
return `
|
|
848
739
|
### Web Search Tool
|
|
849
740
|
- web_search: Use this when you need current or external information that is not in chat history.
|
|
850
|
-
${
|
|
741
|
+
${WEB_SEARCH_LINE[toolStrength]}`;
|
|
851
742
|
}
|
|
852
743
|
|
|
853
744
|
export function buildWebReadFeatureSection(
|
|
854
745
|
config: ChatConfig,
|
|
855
|
-
|
|
746
|
+
_toolStrength: Strength = "medium",
|
|
856
747
|
): string {
|
|
857
|
-
if (!config.webReader?.enabled)
|
|
858
|
-
return "";
|
|
859
|
-
}
|
|
748
|
+
if (!config.webReader?.enabled) return "";
|
|
860
749
|
const independentUseLine = config.searxng?.enabled
|
|
861
750
|
? "- web_search and web_read_page are independent. Use web_search when you need to discover URLs; use web_read_page directly when the user already gave a URL."
|
|
862
751
|
: "- web_read_page can be used directly when the user provides a URL.";
|
|
@@ -868,9 +757,7 @@ ${independentUseLine}
|
|
|
868
757
|
}
|
|
869
758
|
|
|
870
759
|
export function buildRecallMemoryFeatureSection(config: ChatConfig): string {
|
|
871
|
-
if (!config.memory?.enabled)
|
|
872
|
-
return "";
|
|
873
|
-
}
|
|
760
|
+
if (!config.memory?.enabled) return "";
|
|
874
761
|
return `
|
|
875
762
|
### Memory Recall Tools
|
|
876
763
|
- recall_memory: Delegate recall to a memory worker model. Pass a clear recall question and let the worker search historical logs.
|