diff --git a/example/convex/_generated/api.d.ts b/example/convex/_generated/api.d.ts index d562b3df..9bcb1812 100644 --- a/example/convex/_generated/api.d.ts +++ b/example/convex/_generated/api.d.ts @@ -18,6 +18,7 @@ import type * as chat_human from "../chat/human.js"; import type * as chat_streamAbort from "../chat/streamAbort.js"; import type * as chat_streaming from "../chat/streaming.js"; import type * as chat_streamingReasoning from "../chat/streamingReasoning.js"; +import type * as coreMemories_utils from "../coreMemories/utils.js"; import type * as crons from "../crons.js"; import type * as debugging_rawRequestResponseHandler from "../debugging/rawRequestResponseHandler.js"; import type * as etc_objects from "../etc/objects.js"; @@ -71,6 +72,7 @@ declare const fullApi: ApiFromModules<{ "chat/streamAbort": typeof chat_streamAbort; "chat/streaming": typeof chat_streaming; "chat/streamingReasoning": typeof chat_streamingReasoning; + "coreMemories/utils": typeof coreMemories_utils; crons: typeof crons; "debugging/rawRequestResponseHandler": typeof debugging_rawRequestResponseHandler; "etc/objects": typeof etc_objects; @@ -135,6 +137,61 @@ export declare const components: { boolean >; }; + coreMemories: { + append: FunctionReference< + "mutation", + "internal", + { field: "persona" | "human"; text: string; userId?: string }, + null + >; + get: FunctionReference< + "query", + "internal", + { userId?: string }, + null | { + _creationTime: number; + _id: string; + human: string; + persona: string; + userId?: string; + } + >; + getOrCreate: FunctionReference< + "mutation", + "internal", + { human: string; persona: string; userId?: string }, + null | { + _creationTime: number; + _id: string; + human: string; + persona: string; + userId?: string; + } + >; + remove: FunctionReference< + "mutation", + "internal", + { userId?: string }, + null + >; + replace: FunctionReference< + "mutation", + "internal", + { + field: "persona" | "human"; + newContent: string; + oldContent: string; + userId?: string; + }, + number + >; + update: FunctionReference< + "mutation", + "internal", + { human?: string; persona?: string; userId?: string }, + null + >; + }; files: { addFile: FunctionReference< "mutation", @@ -838,7 +895,7 @@ export declare const components: { "mutation", "internal", { messageIds: Array }, - Array + any >; deleteByOrder: FunctionReference< "mutation", diff --git a/example/convex/agents/config.ts b/example/convex/agents/config.ts index 11d0c19e..31198043 100644 --- a/example/convex/agents/config.ts +++ b/example/convex/agents/config.ts @@ -12,4 +12,6 @@ export const defaultConfig = { }, // If you want to use vector search, you need to set this. textEmbeddingModel, + // Enable built-in memory tools (append/replace core memory, message search) + memoryTools: true, } satisfies Config; diff --git a/example/convex/coreMemories/utils.ts b/example/convex/coreMemories/utils.ts new file mode 100644 index 00000000..d698bea8 --- /dev/null +++ b/example/convex/coreMemories/utils.ts @@ -0,0 +1,78 @@ +import { components } from "../_generated/api"; +import { mutation, query } from "../_generated/server"; +import { v } from "convex/values"; +import { getAuthUserId } from "../utils"; + +export const get = query({ + args: {}, + handler: async (ctx) => { + const userId = await getAuthUserId(ctx); + return await ctx.runQuery(components.agent.coreMemories.get, { userId }); + }, +}); + +export const getOrCreate = mutation({ + args: {}, + handler: async (ctx) => { + const userId = await getAuthUserId(ctx); + return await ctx.runMutation(components.agent.coreMemories.getOrCreate, { + userId, + persona: "", + human: "", + }); + }, +}); + +export const update = mutation({ + args: { + persona: v.optional(v.string()), + human: v.optional(v.string()), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + await ctx.runMutation(components.agent.coreMemories.update, { + userId, + ...args, + }); + }, +}); + +export const append = mutation({ + args: { + field: v.union(v.literal("persona"), v.literal("human")), + text: v.string(), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + await ctx.runMutation(components.agent.coreMemories.append, { + userId, + field: args.field, + text: args.text, + }); + }, +}); + +export const replace = mutation({ + args: { + field: v.union(v.literal("persona"), v.literal("human")), + oldContent: v.string(), + newContent: v.string(), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + return await ctx.runMutation(components.agent.coreMemories.replace, { + userId, + field: args.field, + oldContent: args.oldContent, + newContent: args.newContent, + }); + }, +}); + +export const remove = mutation({ + args: {}, + handler: async (ctx) => { + const userId = await getAuthUserId(ctx); + await ctx.runMutation(components.agent.coreMemories.remove, { userId }); + }, +}); diff --git a/example/convex/modelsForDemo.ts b/example/convex/modelsForDemo.ts index 982a7f03..4a8f6031 100644 --- a/example/convex/modelsForDemo.ts +++ b/example/convex/modelsForDemo.ts @@ -4,13 +4,13 @@ import type { LanguageModelV2 } from "@ai-sdk/provider"; import { openai } from "@ai-sdk/openai"; import { groq } from "@ai-sdk/groq"; import { mockModel } from "@convex-dev/agent"; +import { google } from "@ai-sdk/google"; let languageModel: LanguageModelV2; let textEmbeddingModel: EmbeddingModel; if (process.env.OPENAI_API_KEY) { languageModel = openai.chat("gpt-4o-mini"); - textEmbeddingModel = openai.textEmbeddingModel("text-embedding-3-small"); } else if (process.env.GROQ_API_KEY) { languageModel = groq.languageModel( "meta-llama/llama-4-scout-17b-16e-instruct", @@ -24,5 +24,11 @@ if (process.env.OPENAI_API_KEY) { ); } +if (process.env.OPENAI_API_KEY) { + textEmbeddingModel = openai.textEmbeddingModel("text-embedding-3-small"); +} else if (process.env.GOOGLE_GENERATIVE_AI_API_KEY) { + textEmbeddingModel = google.textEmbedding("gemini-embedding-001"); +} + // If you want to use different models for examples, you can change them here. export { languageModel, textEmbeddingModel }; diff --git a/example/package-lock.json b/example/package-lock.json index d5fbd30a..66c6aa26 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -8,6 +8,7 @@ "name": "agent-example", "version": "0.0.0", "dependencies": { + "@ai-sdk/google": "^2.0.14", "@ai-sdk/groq": "^2.0.0", "@ai-sdk/openai": "^2.0.0", "@ai-sdk/provider": "^2.0.0", @@ -141,6 +142,39 @@ "zod": "^3.25.76 || ^4" } }, + "node_modules/@ai-sdk/google": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.14.tgz", + "integrity": "sha512-OCBBkEUq1RNLkbJuD+ejqGsWDD0M5nRyuFWDchwylxy0J4HSsAiGNhutNYVTdnqmNw+r9LyZlkyZ1P4YfAfLdg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.9" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4" + } + }, + "node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.9.tgz", + "integrity": "sha512-Pm571x5efqaI4hf9yW4KsVlDBDme8++UepZRnq+kqVBWWjgvGhQlzU8glaFq0YJEB9kkxZHbRRyVeHoV2sRYaQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4" + } + }, "node_modules/@ai-sdk/groq": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@ai-sdk/groq/-/groq-2.0.11.tgz", diff --git a/example/package.json b/example/package.json index 4f8c5691..f934332d 100644 --- a/example/package.json +++ b/example/package.json @@ -14,6 +14,7 @@ "lint": "tsc -p convex && eslint convex" }, "dependencies": { + "@ai-sdk/google": "^2.0.14", "@ai-sdk/groq": "^2.0.0", "@ai-sdk/openai": "^2.0.0", "@ai-sdk/provider": "^2.0.0", diff --git a/example/ui/coreMemories/MemoryUI.tsx b/example/ui/coreMemories/MemoryUI.tsx new file mode 100644 index 00000000..54367a54 --- /dev/null +++ b/example/ui/coreMemories/MemoryUI.tsx @@ -0,0 +1,95 @@ +import { useMutation, useQuery } from "convex/react"; +import { api } from "../../convex/_generated/api"; +import { useEffect, useState } from "react"; + +export default function MemoryUI() { + const ensureCore = useMutation(api.coreMemories.utils.getOrCreate); + + useEffect(() => { + void ensureCore(); + }, [ensureCore]); + + return ( +
+
+

Core Memories

+
+
+
+ +
+
+
+ ); +} + +function MemoryEditor() { + const mem = useQuery(api.coreMemories.utils.get, {}); + const update = useMutation(api.coreMemories.utils.update); + const append = useMutation(api.coreMemories.utils.append); + const replace = useMutation(api.coreMemories.utils.replace); + const remove = useMutation(api.coreMemories.utils.remove); + + const [persona, setPersona] = useState(""); + const [human, setHuman] = useState(""); + const [appendField, setAppendField] = useState<"persona" | "human">("persona"); + const [appendText, setAppendText] = useState(""); + const [replaceField, setReplaceField] = useState<"persona" | "human">("persona"); + const [oldText, setOldText] = useState(""); + const [newText, setNewText] = useState(""); + + useEffect(() => { + if (mem) { + setPersona(mem.persona ?? ""); + setHuman(mem.human ?? ""); + } + }, [mem]); + + return ( +
+

Edit Memory

+
+ +