diff --git a/.changeset/global-memory-title-generation.md b/.changeset/global-memory-title-generation.md new file mode 100644 index 000000000..972a55453 --- /dev/null +++ b/.changeset/global-memory-title-generation.md @@ -0,0 +1,5 @@ +--- +"@voltagent/core": patch +--- + +Fix conversation title generation when memory is supplied globally to VoltAgent. diff --git a/packages/core/src/agent/agent.ts b/packages/core/src/agent/agent.ts index afb3e5c06..c97e12c85 100644 --- a/packages/core/src/agent/agent.ts +++ b/packages/core/src/agent/agent.ts @@ -8199,7 +8199,7 @@ export class Agent { if (this.memoryConfigured || this.memory === false) { return; } - this.memoryManager.setMemory(memory); + this.memoryManager.setMemory(memory, this.createConversationTitleGenerator(memory)); } /** diff --git a/packages/core/src/memory/manager/memory-manager.ts b/packages/core/src/memory/manager/memory-manager.ts index 32ae40f91..df23eeb6b 100644 --- a/packages/core/src/memory/manager/memory-manager.ts +++ b/packages/core/src/memory/manager/memory-manager.ts @@ -141,13 +141,26 @@ export class MemoryManager { context.input ?? messageWithMetadata, "Conversation", ); - await this.conversationMemory?.createConversation({ - id: conversationId, - userId: userId, - resourceId: this.resourceId, - title, - metadata: {}, - }); + try { + await this.conversationMemory?.createConversation({ + id: conversationId, + userId: userId, + resourceId: this.resourceId, + title, + metadata: {}, + }); + } catch (createError) { + if (this.isConversationAlreadyExistsError(createError)) { + context.logger.debug( + "[Memory] Conversation already exists (race condition handled)", + { + conversationId, + }, + ); + } else { + throw createError; + } + } } // Add message to conversation using Memory V2's saveMessageWithContext @@ -775,14 +788,16 @@ export class MemoryManager { /** * Replace the Memory instance used for this manager. */ - setMemory(memory: Memory | false): void { + setMemory(memory: Memory | false, titleGenerator?: ConversationTitleGenerator): void { if (memory === false) { this.conversationMemory = undefined; + this.titleGenerator = undefined; return; } if (memory instanceof Memory) { this.conversationMemory = memory; + this.titleGenerator = titleGenerator; } } diff --git a/packages/core/src/voltagent.spec.ts b/packages/core/src/voltagent.spec.ts index cf2bc97aa..9d2619d71 100644 --- a/packages/core/src/voltagent.spec.ts +++ b/packages/core/src/voltagent.spec.ts @@ -1,3 +1,4 @@ +import { MockLanguageModelV3 } from "ai/test"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { z } from "zod"; import { Agent } from "./agent/agent"; @@ -59,6 +60,66 @@ describe("VoltAgent defaults", () => { expect(agent.getMemory()).toBe(agentMemory); }); + it("applies title generation when default memory is assigned to a preconstructed agent", async () => { + const agentMemory = new Memory({ + storage: new InMemoryStorageAdapter(), + generateTitle: true, + }); + const model = new MockLanguageModelV3({ + modelId: "title-model", + doGenerate: async () => ({ + content: [{ type: "text", text: "Global Memory Title" }], + finishReason: "stop", + usage: { + inputTokens: 1, + outputTokens: 1, + totalTokens: 2, + inputTokenDetails: { + noCacheTokens: 1, + cacheReadTokens: 0, + cacheWriteTokens: 0, + }, + outputTokenDetails: { + textTokens: 1, + reasoningTokens: 0, + }, + }, + warnings: [], + }), + }); + const agent = new Agent({ + name: "assistant", + instructions: "Be helpful.", + model: model as any, + }); + + const voltAgent = new VoltAgent({ + agents: { assistant: agent }, + memory: agentMemory, + checkDependencies: false, + }); + + const operationContext = (agent as any).createOperationContext("Plan a weekend trip to Rome.", { + userId: "user-1", + conversationId: "conversation-1", + }); + await (agent as any).memoryManager.saveMessage( + operationContext, + { + id: "message-1", + role: "assistant", + parts: [{ type: "text", text: "Sure, let's plan it." }], + }, + "user-1", + "conversation-1", + ); + + const conversation = await agentMemory.getConversation("conversation-1"); + expect(conversation?.title).toBe("Global Memory Title"); + + await voltAgent.shutdown(); + }); + it("applies workspace to preconstructed registered agents without explicit workspace", async () => { const workspace = new Workspace({ id: "global-workspace" }); const agent = new Agent({