diff options
| author | Fuwn <[email protected]> | 2026-02-03 20:51:41 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-03 20:51:41 -0800 |
| commit | 0af9376ddabc50548a2d06710a071f40b864816f (patch) | |
| tree | b45363f2b411e656af461180dd1deff9965c8575 | |
| parent | feat(web): Initialise T3 Stack (diff) | |
| download | archived-imemio-0af9376ddabc50548a2d06710a071f40b864816f.tar.xz archived-imemio-0af9376ddabc50548a2d06710a071f40b864816f.zip | |
feat(sdk): Add semantic search and embedding support
| -rw-r--r-- | packages/sdk/package.json | 3 | ||||
| -rw-r--r-- | packages/sdk/src/embedding-service.ts | 38 | ||||
| -rw-r--r-- | packages/sdk/src/index.ts | 7 | ||||
| -rw-r--r-- | packages/sdk/src/supabase-store.ts | 73 | ||||
| -rw-r--r-- | packages/sdk/src/types.ts | 21 |
5 files changed, 133 insertions, 9 deletions
diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 2f830f9..21e7e26 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -27,6 +27,7 @@ "vitest": "^3.0.5" }, "dependencies": { - "@supabase/supabase-js": "^2.94.0" + "@supabase/supabase-js": "^2.94.0", + "openai": "^6.17.0" } } diff --git a/packages/sdk/src/embedding-service.ts b/packages/sdk/src/embedding-service.ts new file mode 100644 index 0000000..9d3367f --- /dev/null +++ b/packages/sdk/src/embedding-service.ts @@ -0,0 +1,38 @@ +import OpenAI from "openai"; + +export type EmbeddingModel = + | "text-embedding-3-small" + | "text-embedding-3-large"; + +export type EmbeddingServiceConfiguration = { + apiKey: string; + model?: EmbeddingModel; +}; + +export class EmbeddingService { + private client: OpenAI; + private model: EmbeddingModel; + + constructor(configuration: EmbeddingServiceConfiguration) { + this.client = new OpenAI({ apiKey: configuration.apiKey }); + this.model = configuration.model ?? "text-embedding-3-small"; + } + + async generate(text: string): Promise<number[]> { + const response = await this.client.embeddings.create({ + model: this.model, + input: text, + }); + + return response.data[0]?.embedding ?? []; + } + + async generateBatch(texts: string[]): Promise<number[][]> { + const response = await this.client.embeddings.create({ + model: this.model, + input: texts, + }); + + return response.data.map((item) => item.embedding); + } +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index b61164e..e02192b 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -9,6 +9,8 @@ export type { MemoryCreateInput, MemoryUpdateInput, MemoryFilter, + SearchOptions, + SearchResult, } from "./types.js"; export type { Result, Success, Failure } from "./result.js"; export type { @@ -36,3 +38,8 @@ export { } from "./supabase-client.js"; export { SupabaseStore } from "./supabase-store.js"; export { SupabaseProjectStore } from "./supabase-project-store.js"; +export { + EmbeddingService, + type EmbeddingModel, + type EmbeddingServiceConfiguration, +} from "./embedding-service.js"; diff --git a/packages/sdk/src/supabase-store.ts b/packages/sdk/src/supabase-store.ts index acb9364..c8465c4 100644 --- a/packages/sdk/src/supabase-store.ts +++ b/packages/sdk/src/supabase-store.ts @@ -6,6 +6,8 @@ import type { MemoryCreateInput, MemoryFilter, MemoryUpdateInput, + SearchOptions, + SearchResult, Tag, } from "./types.js"; @@ -22,6 +24,18 @@ type MemoryRow = { updated_at: string; }; +type SearchResultRow = { + id: string; + content: string; + project_id: string; + folder_id: string | null; + tags: Tag[]; + metadata: Record<string, unknown>; + similarity: number; + created_at: string; + updated_at: string; +}; + function memoryNotFoundError(memoryId: string): MemoryNotFoundError { return { type: "MEMORY_NOT_FOUND", memoryId }; } @@ -39,6 +53,20 @@ function rowToMemory(row: MemoryRow): Memory { }; } +function rowToSearchResult(row: SearchResultRow): SearchResult { + return { + id: row.id, + content: row.content, + projectId: row.project_id, + folderId: row.folder_id, + tags: row.tags ?? [], + metadata: row.metadata ?? {}, + similarity: row.similarity, + createdAt: new Date(row.created_at), + updatedAt: new Date(row.updated_at), + }; +} + export class SupabaseStore implements MemoryStore { private client: SupabaseClient; private userId: string; @@ -49,16 +77,22 @@ export class SupabaseStore implements MemoryStore { } async create(input: MemoryCreateInput): Promise<Memory> { + const insertData: Record<string, unknown> = { + user_id: this.userId, + project_id: input.projectId, + folder_id: input.folderId ?? null, + content: input.content, + tags: input.tags ?? [], + metadata: input.metadata ?? {}, + }; + + if (input.embedding) { + insertData.embedding = input.embedding; + } + const { data, error } = await this.client .from("memories") - .insert({ - user_id: this.userId, - project_id: input.projectId, - folder_id: input.folderId ?? null, - content: input.content, - tags: input.tags ?? [], - metadata: input.metadata ?? {}, - }) + .insert(insertData) .select() .single(); @@ -106,6 +140,10 @@ export class SupabaseStore implements MemoryStore { updates.metadata = input.metadata; } + if (input.embedding !== undefined) { + updates.embedding = input.embedding; + } + const { data, error } = await this.client .from("memories") .update(updates) @@ -164,4 +202,23 @@ export class SupabaseStore implements MemoryStore { return (data as MemoryRow[]).map(rowToMemory); } + + async search( + embedding: number[], + options?: SearchOptions, + ): Promise<SearchResult[]> { + const { data, error } = await this.client.rpc("search_memories", { + query_embedding: embedding, + match_threshold: options?.threshold ?? 0.7, + match_count: options?.limit ?? 10, + filter_project_id: options?.projectId ?? null, + filter_folder_id: options?.folderId ?? null, + }); + + if (error) { + throw new Error(`Failed to search memories: ${error.message}`); + } + + return (data as SearchResultRow[]).map(rowToSearchResult); + } } diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 306da64..76a63de 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -41,6 +41,7 @@ export type MemoryCreateInput = { folderId?: string; tags?: Tag[]; metadata?: MemoryMetadata; + embedding?: number[]; }; export type MemoryUpdateInput = { @@ -48,6 +49,7 @@ export type MemoryUpdateInput = { folderId?: string | null; tags?: Tag[]; metadata?: MemoryMetadata; + embedding?: number[]; }; export type MemoryFilter = { @@ -55,3 +57,22 @@ export type MemoryFilter = { folderId?: string; tags?: string[]; }; + +export type SearchOptions = { + threshold?: number; + limit?: number; + projectId?: string; + folderId?: string; +}; + +export type SearchResult = { + id: string; + content: string; + projectId: string; + folderId: string | null; + tags: Tag[]; + metadata: MemoryMetadata; + similarity: number; + createdAt: Date; + updatedAt: Date; +}; |