feat: LLM intent router, direct chat execution, and Memory V2 light intervention
Wire chat intent routing with direct_chat on regular sessions, skill-selected short-circuit to Agent, memory light/heavy intervention tiers, and fix direct chat UI stuck streaming after completion. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -152,14 +152,16 @@ test('saveAndAnalyze stores messages and fallback memories', async () => {
|
||||
else process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED = previous;
|
||||
});
|
||||
|
||||
test('saveAndAnalyze leaves messages unanalyzed when extraction fails and no memory is stored', async () => {
|
||||
test('saveAndAnalyze marks messages analyzed when llm extraction fails and no memory is stored', async () => {
|
||||
const previous = process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED;
|
||||
process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED = '1';
|
||||
const pool = createPool();
|
||||
const service = createConversationMemoryService(pool, {
|
||||
now: () => 2000,
|
||||
fetch: async () => {
|
||||
throw new Error('upstream unavailable');
|
||||
llmProviderService: {
|
||||
async createChatCompletion() {
|
||||
throw new Error('upstream unavailable');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -174,7 +176,83 @@ test('saveAndAnalyze leaves messages unanalyzed when extraction fails and no mem
|
||||
|
||||
assert.equal(result.saved, 1);
|
||||
assert.equal(result.memories, 0);
|
||||
assert.equal(pool.state.messages.find((item) => item.message_key === 'm3')?.analyzed_at, null);
|
||||
assert.equal(pool.state.messages.find((item) => item.message_key === 'm3')?.analyzed_at, 2000);
|
||||
|
||||
if (previous == null) delete process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED;
|
||||
else process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED = previous;
|
||||
});
|
||||
|
||||
test('saveAndAnalyze uses admin effective env for memory extraction model', async () => {
|
||||
const previous = process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED;
|
||||
process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED = '1';
|
||||
const pool = createPool();
|
||||
const llmCalls = [];
|
||||
const service = createConversationMemoryService(pool, {
|
||||
now: () => 2600,
|
||||
getEffectiveEnv: async () => ({
|
||||
MEMIND_CHAT_ROUTER_MODEL_PROVIDER_KEY_ID: 'router-key',
|
||||
MEMIND_CHAT_ROUTER_MODEL: 'deepseek-chat',
|
||||
}),
|
||||
llmProviderService: {
|
||||
async createChatCompletion({ providerKeyId, model, messages }) {
|
||||
llmCalls.push({ providerKeyId, model, messages });
|
||||
return {
|
||||
ok: true,
|
||||
reply: JSON.stringify({
|
||||
memories: [{ label: 'fact', text: '用户叫 John', confidence: 0.9 }],
|
||||
}),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await service.saveAndAnalyze('session-admin', 'user-admin', [
|
||||
{
|
||||
id: 'm-admin',
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: '我是 John。' }],
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(llmCalls.length, 1);
|
||||
assert.equal(llmCalls[0].providerKeyId, 'router-key');
|
||||
assert.equal(llmCalls[0].model, 'deepseek-chat');
|
||||
|
||||
if (previous == null) delete process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED;
|
||||
else process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED = previous;
|
||||
});
|
||||
|
||||
test('saveAndAnalyze extracts memories through llmProviderService', async () => {
|
||||
const previous = process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED;
|
||||
process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED = '1';
|
||||
const pool = createPool();
|
||||
const service = createConversationMemoryService(pool, {
|
||||
now: () => 2500,
|
||||
llmProviderService: {
|
||||
async createChatCompletion({ messages }) {
|
||||
assert.match(String(messages?.[0]?.content ?? ''), /长期记忆/);
|
||||
return {
|
||||
ok: true,
|
||||
reply: JSON.stringify({
|
||||
memories: [{ label: 'goal', text: '用户计划去日本旅游', confidence: 0.82 }],
|
||||
}),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.saveAndAnalyze('session-llm', 'user-llm', [
|
||||
{
|
||||
id: 'm-japan',
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: '我打算带家人去日本玩 5 天。' }],
|
||||
metadata: { userVisible: true },
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(result.memories, 1);
|
||||
assert.equal(pool.state.memories[0].label, 'goal');
|
||||
assert.match(pool.state.memories[0].memory_text, /日本/);
|
||||
|
||||
if (previous == null) delete process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED;
|
||||
else process.env.USER_CONVERSATION_MEMORY_LLM_ENABLED = previous;
|
||||
|
||||
Reference in New Issue
Block a user