Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/@n8n/agents/src/__tests__/title-generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ describe('generateTitleFromMessage', () => {
expect(mockGenerateText).not.toHaveBeenCalled();
});

it('returns the message itself for trivial greetings without calling the LLM', async () => {
it('returns null for trivial greetings without calling the LLM', async () => {
const result = await generateTitleFromMessage(fakeModel, 'hey');
expect(result).toBe('hey');
expect(result).toBeNull();
expect(mockGenerateText).not.toHaveBeenCalled();
});

it('skips the LLM for short multi-word messages', async () => {
it('returns null for short multi-word messages without calling the LLM', async () => {
const result = await generateTitleFromMessage(fakeModel, 'hi there');
expect(result).toBe('hi there');
expect(result).toBeNull();
expect(mockGenerateText).not.toHaveBeenCalled();
});

Expand Down
10 changes: 5 additions & 5 deletions packages/@n8n/agents/src/runtime/title-generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ const TRIVIAL_MESSAGE_MAX_WORDS = 3;
const MAX_TITLE_LENGTH = 80;

/**
* Whether a user message is too trivial to bother sending to an LLM for
* title generation (e.g. "hey", "hello"). For these, the LLM tends to
* hallucinate an assistant-voice reply as the title instead of echoing
* the user intent — it's better to just use the message itself.
* Whether a user message has too little substance to title a conversation
* (e.g. "hey", "hello"). For these, the LLM tends to hallucinate an
* assistant-voice reply as the title — better to signal "defer, not enough
* signal yet" so the caller can retry once more context accumulates.
*/
function isTrivialMessage(message: string): boolean {
const normalized = message.trim();
Expand Down Expand Up @@ -69,7 +69,7 @@ export async function generateTitleFromMessage(
if (!trimmed) return null;

if (isTrivialMessage(trimmed)) {
return sanitizeTitle(trimmed) || null;
return null;
}

const result = await generateText({
Expand Down
14 changes: 7 additions & 7 deletions packages/cli/src/modules/instance-ai/instance-ai.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2457,14 +2457,14 @@ export class InstanceAiService {
// Skip if thread already has an LLM-refined title
if (thread.metadata?.titleRefined) return;

// Get first user message
// Concat all recalled user messages so retries after a trivial first message
// (e.g. "hey") have enough signal to produce a good title.
const result = await memory.recall({ threadId, resourceId: userId, perPage: 5 });
const firstUserMsg = result.messages.find((m) => m.role === 'user');
if (!firstUserMsg) return;
const userText =
typeof firstUserMsg.content === 'string'
? firstUserMsg.content
: JSON.stringify(firstUserMsg.content);
const userTexts = result.messages
.filter((m) => m.role === 'user')
.map((m) => (typeof m.content === 'string' ? m.content : JSON.stringify(m.content)));
if (userTexts.length === 0) return;
const userText = userTexts.join('\n');

const llmTitle = await generateTitleForRun(modelId, userText);
if (!llmTitle) return;
Expand Down
Loading