be464a5b8d
Memind CI / Test, build, and release guards (push) Has been cancelled
Introduce rain-service orchestration, browser-safe chat skill filtering, MeInput adapter helpers, and verify/deploy scripts so Rain mode can summarize recent input without Memory V2 pollution. Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
4.3 KiB
JavaScript
119 lines
4.3 KiB
JavaScript
function stripRainSkillPrefix(text) {
|
||
let next = String(text ?? '').trim();
|
||
next = next.replace(/^【Rain[^】]*】\s*/u, '');
|
||
next = next.replace(/^请使用\s+rain\s+技能[::]\s*/iu, '');
|
||
if (/^请描述要分析的时间区间/u.test(next)) {
|
||
const marker = '我的问题是:';
|
||
const idx = next.indexOf(marker);
|
||
if (idx >= 0) next = next.slice(idx + marker.length);
|
||
}
|
||
return next.trim();
|
||
}
|
||
|
||
function parseRainLlmJson(raw) {
|
||
const text = String(raw ?? '').trim();
|
||
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
||
const candidate = fenced?.[1]?.trim() || text;
|
||
try {
|
||
return JSON.parse(candidate);
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {{ llmProviderService: object, userQuery: string, meinputBlock: string, timeRangeLabel: string, recordCount: number }} input
|
||
*/
|
||
export async function runRainLlmAnalysis(input) {
|
||
const userQuery = stripRainSkillPrefix(input.userQuery);
|
||
const system = [
|
||
'你是 TKMind Rain 分析层。只能依据【MeInput 原始输入】块中的内容做归纳,禁止引用或编造长期记忆、聊天历史、日程等外部信息。',
|
||
'输出必须是单个 JSON 对象,不要 markdown,不要代码围栏,字段如下:',
|
||
'{"needs_clarification":boolean,"clarification_question":string|null,"user_goal":string,"meinput_analysis":string,"suggested_next_steps":string[],"user_reply":string}',
|
||
'- needs_clarification=true 时:clarification_question 必填,user_reply 用自然语言向用户追问;meinput_analysis 可为空。',
|
||
'- needs_clarification=false 时:meinput_analysis 按时间线归纳用户在各 App 的输入活动;user_reply 是可直接展示给用户的中文回复(含区间说明);suggested_next_steps 供下游 Agent 参考(如生成报告页、继续追问)。',
|
||
'- 不要把内部排序分数、source 字段名暴露给用户。',
|
||
].join('\n');
|
||
|
||
const user = [
|
||
`时间区间:${input.timeRangeLabel}`,
|
||
`记录条数:${input.recordCount}`,
|
||
'',
|
||
input.meinputBlock,
|
||
'',
|
||
`用户诉求:${userQuery || '请总结我在上述区间的输入活动'}`,
|
||
].join('\n');
|
||
|
||
const completion = await input.llmProviderService.createChatCompletion({
|
||
messages: [
|
||
{ role: 'system', content: system },
|
||
{ role: 'user', content: user },
|
||
],
|
||
});
|
||
|
||
if (!completion?.ok) {
|
||
const err = new Error(completion?.message ?? 'Rain LLM 分析失败');
|
||
err.code = 'RAIN_LLM_FAILED';
|
||
throw err;
|
||
}
|
||
|
||
const parsed = parseRainLlmJson(completion.reply);
|
||
if (!parsed || typeof parsed !== 'object') {
|
||
return {
|
||
needs_clarification: false,
|
||
clarification_question: null,
|
||
user_goal: userQuery || '回顾 MeInput 输入',
|
||
meinput_analysis: String(completion.reply ?? '').trim(),
|
||
suggested_next_steps: [],
|
||
user_reply: String(completion.reply ?? '').trim(),
|
||
raw: completion.reply,
|
||
};
|
||
}
|
||
|
||
return {
|
||
needs_clarification: Boolean(parsed.needs_clarification),
|
||
clarification_question: parsed.clarification_question ?? null,
|
||
user_goal: String(parsed.user_goal ?? userQuery ?? '').trim(),
|
||
meinput_analysis: String(parsed.meinput_analysis ?? '').trim(),
|
||
suggested_next_steps: Array.isArray(parsed.suggested_next_steps)
|
||
? parsed.suggested_next_steps.map((s) => String(s).trim()).filter(Boolean)
|
||
: [],
|
||
user_reply: String(parsed.user_reply ?? '').trim(),
|
||
raw: completion.reply,
|
||
};
|
||
}
|
||
|
||
export function buildRainGooseHandoffText({
|
||
userQuery,
|
||
timeRangeLabel,
|
||
recordCount,
|
||
analysis,
|
||
userGoal,
|
||
suggestedNextSteps = [],
|
||
}) {
|
||
const steps =
|
||
suggestedNextSteps.length > 0
|
||
? suggestedNextSteps.map((s) => `- ${s}`).join('\n')
|
||
: '- (无明确工具动作,先给用户文字总结)';
|
||
|
||
return [
|
||
'[Rain · MeInput 分析简报]',
|
||
'以下简报由 Rain 分析层基于 MeInput 全量原始输入生成。请据此决定如何回复用户、是否调用工具或 skill;不要重复询问时间区间。',
|
||
'',
|
||
`时间区间:${timeRangeLabel}`,
|
||
`原始记录条数:${recordCount}`,
|
||
`用户诉求:${userGoal || stripRainSkillPrefix(userQuery)}`,
|
||
'',
|
||
'【分析归纳】',
|
||
analysis || '(无)',
|
||
'',
|
||
'【建议下一步】',
|
||
steps,
|
||
'',
|
||
'【用户原始问题】',
|
||
stripRainSkillPrefix(userQuery),
|
||
].join('\n');
|
||
}
|
||
|
||
export { stripRainSkillPrefix };
|