@opensaas/stack-rag 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +10 -0
  3. package/CLAUDE.md +565 -0
  4. package/LICENSE +21 -0
  5. package/README.md +406 -0
  6. package/dist/config/index.d.ts +63 -0
  7. package/dist/config/index.d.ts.map +1 -0
  8. package/dist/config/index.js +94 -0
  9. package/dist/config/index.js.map +1 -0
  10. package/dist/config/plugin.d.ts +38 -0
  11. package/dist/config/plugin.d.ts.map +1 -0
  12. package/dist/config/plugin.js +215 -0
  13. package/dist/config/plugin.js.map +1 -0
  14. package/dist/config/plugin.test.d.ts +2 -0
  15. package/dist/config/plugin.test.d.ts.map +1 -0
  16. package/dist/config/plugin.test.js +554 -0
  17. package/dist/config/plugin.test.js.map +1 -0
  18. package/dist/config/types.d.ts +249 -0
  19. package/dist/config/types.d.ts.map +1 -0
  20. package/dist/config/types.js +5 -0
  21. package/dist/config/types.js.map +1 -0
  22. package/dist/fields/embedding.d.ts +85 -0
  23. package/dist/fields/embedding.d.ts.map +1 -0
  24. package/dist/fields/embedding.js +81 -0
  25. package/dist/fields/embedding.js.map +1 -0
  26. package/dist/fields/embedding.test.d.ts +2 -0
  27. package/dist/fields/embedding.test.d.ts.map +1 -0
  28. package/dist/fields/embedding.test.js +323 -0
  29. package/dist/fields/embedding.test.js.map +1 -0
  30. package/dist/fields/index.d.ts +6 -0
  31. package/dist/fields/index.d.ts.map +1 -0
  32. package/dist/fields/index.js +5 -0
  33. package/dist/fields/index.js.map +1 -0
  34. package/dist/index.d.ts +8 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +9 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/mcp/index.d.ts +19 -0
  39. package/dist/mcp/index.d.ts.map +1 -0
  40. package/dist/mcp/index.js +18 -0
  41. package/dist/mcp/index.js.map +1 -0
  42. package/dist/providers/index.d.ts +38 -0
  43. package/dist/providers/index.d.ts.map +1 -0
  44. package/dist/providers/index.js +68 -0
  45. package/dist/providers/index.js.map +1 -0
  46. package/dist/providers/ollama.d.ts +49 -0
  47. package/dist/providers/ollama.d.ts.map +1 -0
  48. package/dist/providers/ollama.js +151 -0
  49. package/dist/providers/ollama.js.map +1 -0
  50. package/dist/providers/openai.d.ts +41 -0
  51. package/dist/providers/openai.d.ts.map +1 -0
  52. package/dist/providers/openai.js +126 -0
  53. package/dist/providers/openai.js.map +1 -0
  54. package/dist/providers/providers.test.d.ts +2 -0
  55. package/dist/providers/providers.test.d.ts.map +1 -0
  56. package/dist/providers/providers.test.js +224 -0
  57. package/dist/providers/providers.test.js.map +1 -0
  58. package/dist/providers/types.d.ts +88 -0
  59. package/dist/providers/types.d.ts.map +1 -0
  60. package/dist/providers/types.js +2 -0
  61. package/dist/providers/types.js.map +1 -0
  62. package/dist/runtime/batch.d.ts +183 -0
  63. package/dist/runtime/batch.d.ts.map +1 -0
  64. package/dist/runtime/batch.js +240 -0
  65. package/dist/runtime/batch.js.map +1 -0
  66. package/dist/runtime/batch.test.d.ts +2 -0
  67. package/dist/runtime/batch.test.d.ts.map +1 -0
  68. package/dist/runtime/batch.test.js +251 -0
  69. package/dist/runtime/batch.test.js.map +1 -0
  70. package/dist/runtime/chunking.d.ts +42 -0
  71. package/dist/runtime/chunking.d.ts.map +1 -0
  72. package/dist/runtime/chunking.js +264 -0
  73. package/dist/runtime/chunking.js.map +1 -0
  74. package/dist/runtime/chunking.test.d.ts +2 -0
  75. package/dist/runtime/chunking.test.d.ts.map +1 -0
  76. package/dist/runtime/chunking.test.js +212 -0
  77. package/dist/runtime/chunking.test.js.map +1 -0
  78. package/dist/runtime/embeddings.d.ts +147 -0
  79. package/dist/runtime/embeddings.d.ts.map +1 -0
  80. package/dist/runtime/embeddings.js +201 -0
  81. package/dist/runtime/embeddings.js.map +1 -0
  82. package/dist/runtime/embeddings.test.d.ts +2 -0
  83. package/dist/runtime/embeddings.test.d.ts.map +1 -0
  84. package/dist/runtime/embeddings.test.js +366 -0
  85. package/dist/runtime/embeddings.test.js.map +1 -0
  86. package/dist/runtime/index.d.ts +14 -0
  87. package/dist/runtime/index.d.ts.map +1 -0
  88. package/dist/runtime/index.js +18 -0
  89. package/dist/runtime/index.js.map +1 -0
  90. package/dist/runtime/search.d.ts +135 -0
  91. package/dist/runtime/search.d.ts.map +1 -0
  92. package/dist/runtime/search.js +101 -0
  93. package/dist/runtime/search.js.map +1 -0
  94. package/dist/storage/index.d.ts +41 -0
  95. package/dist/storage/index.d.ts.map +1 -0
  96. package/dist/storage/index.js +73 -0
  97. package/dist/storage/index.js.map +1 -0
  98. package/dist/storage/json.d.ts +34 -0
  99. package/dist/storage/json.d.ts.map +1 -0
  100. package/dist/storage/json.js +82 -0
  101. package/dist/storage/json.js.map +1 -0
  102. package/dist/storage/pgvector.d.ts +53 -0
  103. package/dist/storage/pgvector.d.ts.map +1 -0
  104. package/dist/storage/pgvector.js +168 -0
  105. package/dist/storage/pgvector.js.map +1 -0
  106. package/dist/storage/sqlite-vss.d.ts +49 -0
  107. package/dist/storage/sqlite-vss.d.ts.map +1 -0
  108. package/dist/storage/sqlite-vss.js +148 -0
  109. package/dist/storage/sqlite-vss.js.map +1 -0
  110. package/dist/storage/storage.test.d.ts +2 -0
  111. package/dist/storage/storage.test.d.ts.map +1 -0
  112. package/dist/storage/storage.test.js +440 -0
  113. package/dist/storage/storage.test.js.map +1 -0
  114. package/dist/storage/types.d.ts +79 -0
  115. package/dist/storage/types.d.ts.map +1 -0
  116. package/dist/storage/types.js +49 -0
  117. package/dist/storage/types.js.map +1 -0
  118. package/package.json +82 -0
  119. package/src/config/index.ts +116 -0
  120. package/src/config/plugin.test.ts +664 -0
  121. package/src/config/plugin.ts +257 -0
  122. package/src/config/types.ts +283 -0
  123. package/src/fields/embedding.test.ts +408 -0
  124. package/src/fields/embedding.ts +150 -0
  125. package/src/fields/index.ts +6 -0
  126. package/src/index.ts +33 -0
  127. package/src/mcp/index.ts +21 -0
  128. package/src/providers/index.ts +81 -0
  129. package/src/providers/ollama.ts +186 -0
  130. package/src/providers/openai.ts +161 -0
  131. package/src/providers/providers.test.ts +275 -0
  132. package/src/providers/types.ts +100 -0
  133. package/src/runtime/batch.test.ts +332 -0
  134. package/src/runtime/batch.ts +424 -0
  135. package/src/runtime/chunking.test.ts +258 -0
  136. package/src/runtime/chunking.ts +334 -0
  137. package/src/runtime/embeddings.test.ts +441 -0
  138. package/src/runtime/embeddings.ts +380 -0
  139. package/src/runtime/index.ts +51 -0
  140. package/src/runtime/search.ts +243 -0
  141. package/src/storage/index.ts +86 -0
  142. package/src/storage/json.ts +106 -0
  143. package/src/storage/pgvector.ts +206 -0
  144. package/src/storage/sqlite-vss.ts +193 -0
  145. package/src/storage/storage.test.ts +521 -0
  146. package/src/storage/types.ts +126 -0
  147. package/tsconfig.json +13 -0
  148. package/tsconfig.tsbuildinfo +1 -0
  149. package/vitest.config.ts +18 -0
@@ -0,0 +1,257 @@
1
+ import type { Plugin } from '@opensaas/stack-core'
2
+ import type { RAGConfig, NormalizedRAGConfig } from './types.js'
3
+ import { normalizeRAGConfig } from './index.js'
4
+ import { createEmbeddingProvider } from '../providers/index.js'
5
+
6
+ /**
7
+ * RAG plugin for OpenSaas Stack
8
+ * Provides vector embeddings, semantic search, and automatic embedding generation
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { config, list } from '@opensaas/stack-core'
13
+ * import { text } from '@opensaas/stack-core/fields'
14
+ * import { ragPlugin, openaiEmbeddings, pgvectorStorage } from '@opensaas/stack-rag'
15
+ * import { embedding } from '@opensaas/stack-rag/fields'
16
+ *
17
+ * export default config({
18
+ * plugins: [
19
+ * ragPlugin({
20
+ * provider: openaiEmbeddings({ apiKey: process.env.OPENAI_API_KEY }),
21
+ * storage: pgvectorStorage()
22
+ * })
23
+ * ],
24
+ * db: { provider: 'postgresql', url: process.env.DATABASE_URL },
25
+ * lists: {
26
+ * Article: list({
27
+ * fields: {
28
+ * content: text(),
29
+ * contentEmbedding: embedding({
30
+ * sourceField: 'content',
31
+ * provider: 'openai',
32
+ * autoGenerate: true
33
+ * })
34
+ * }
35
+ * })
36
+ * }
37
+ * })
38
+ * ```
39
+ */
40
+ export function ragPlugin(config: RAGConfig): Plugin {
41
+ const normalized = normalizeRAGConfig(config)
42
+
43
+ return {
44
+ name: 'rag',
45
+ version: '0.1.0',
46
+
47
+ init: async (context) => {
48
+ // Find all embedding fields with autoGenerate enabled
49
+ for (const [listName, listConfig] of Object.entries(context.config.lists)) {
50
+ for (const [fieldName, fieldConfig] of Object.entries(listConfig.fields)) {
51
+ if (
52
+ fieldConfig.type === 'embedding' &&
53
+ (fieldConfig as { autoGenerate?: boolean }).autoGenerate
54
+ ) {
55
+ const embeddingConfig = fieldConfig as {
56
+ sourceField?: string
57
+ provider?: string
58
+ dimensions?: number
59
+ }
60
+
61
+ const sourceField = embeddingConfig.sourceField
62
+ if (!sourceField) {
63
+ throw new Error(
64
+ `RAG plugin: Field "${listName}.${fieldName}" has autoGenerate enabled but no sourceField specified`,
65
+ )
66
+ }
67
+
68
+ // Inject afterOperation hook to auto-generate embeddings
69
+ context.extendList(listName, {
70
+ hooks: {
71
+ resolveInput: async (args) => {
72
+ // Skip if item is missing
73
+ if (!args.resolvedData)
74
+ throw new Error('RAG plugin: Missing resolvedData in resolveInput hook')
75
+
76
+ const sourceText = args.resolvedData[sourceField] as string | undefined
77
+ const currentEmbedding = args.resolvedData[fieldName] as {
78
+ vector: number[]
79
+ metadata: { sourceHash?: string }
80
+ } | null
81
+
82
+ // Skip if source text is empty
83
+ if (!sourceText) return args.resolvedData
84
+
85
+ // Check if we need to regenerate (source text changed)
86
+ const sourceHash = await hashText(sourceText)
87
+ if (currentEmbedding && currentEmbedding.metadata.sourceHash === sourceHash) {
88
+ // Source text hasn't changed, skip regeneration
89
+ return args.resolvedData
90
+ }
91
+
92
+ // Get provider for this field
93
+ const providerName = embeddingConfig.provider || 'default'
94
+ const providerConfig =
95
+ providerName === 'default'
96
+ ? normalized.provider
97
+ : normalized.providers[providerName] || normalized.provider
98
+
99
+ if (!providerConfig) {
100
+ console.warn(
101
+ `RAG plugin: No provider configured for field "${listName}.${fieldName}"`,
102
+ )
103
+ return args.resolvedData
104
+ }
105
+
106
+ // Generate embedding
107
+ const provider = createEmbeddingProvider(providerConfig)
108
+ const vector = await provider.embed(sourceText)
109
+
110
+ return {
111
+ ...args.resolvedData,
112
+ [fieldName]: {
113
+ vector,
114
+ metadata: {
115
+ model: provider.model,
116
+ provider: provider.type,
117
+ dimensions: provider.dimensions,
118
+ generatedAt: new Date().toISOString(),
119
+ sourceHash,
120
+ },
121
+ },
122
+ }
123
+ },
124
+ },
125
+ })
126
+ }
127
+ }
128
+ }
129
+
130
+ // Register MCP tools if enabled
131
+ if (normalized.enableMcpTools && context.registerMcpTool) {
132
+ // Find all lists with embedding fields
133
+ for (const [listName, listConfig] of Object.entries(context.config.lists)) {
134
+ const embeddingFields = Object.entries(listConfig.fields).filter(
135
+ ([, fieldConfig]) => fieldConfig.type === 'embedding',
136
+ )
137
+
138
+ if (embeddingFields.length > 0) {
139
+ // Register semantic search tool for this list
140
+ const toolName = `semantic_search_${listName.toLowerCase()}`
141
+
142
+ context.registerMcpTool({
143
+ name: toolName,
144
+ description: `Search ${listName} using natural language (semantic search)`,
145
+ inputSchema: {
146
+ type: 'object',
147
+ properties: {
148
+ query: { type: 'string', description: 'Natural language search query' },
149
+ limit: { type: 'number', description: 'Maximum results', default: 10 },
150
+ minScore: {
151
+ type: 'number',
152
+ description: 'Minimum similarity score (0-1)',
153
+ default: 0.5,
154
+ },
155
+ field: {
156
+ type: 'string',
157
+ description: 'Embedding field to search',
158
+ default: embeddingFields[0][0],
159
+ enum: embeddingFields.map(([name]) => name),
160
+ },
161
+ },
162
+ required: ['query'],
163
+ },
164
+ handler: async ({ input, context }) => {
165
+ const { query, limit = 10, minScore = 0.5, field = embeddingFields[0][0] } = input
166
+
167
+ // Get provider
168
+ const providerConfig = normalized.provider
169
+ if (!providerConfig) {
170
+ throw new Error('RAG plugin: No default provider configured')
171
+ }
172
+
173
+ // Generate query embedding
174
+ const provider = createEmbeddingProvider(providerConfig)
175
+ const queryVector = await provider.embed(query)
176
+
177
+ // Search using configured storage backend
178
+ // Note: This is a simplified implementation
179
+ // Full implementation would use VectorStorage interface
180
+ const dbKey = listName.charAt(0).toLowerCase() + listName.slice(1)
181
+ const allItems = await context.db[dbKey].findMany()
182
+
183
+ // Calculate cosine similarity for each item
184
+ const results = allItems
185
+ .map((item: { [key: string]: { vector: number[] } | null }) => {
186
+ const embedding = item[field]
187
+ if (!embedding || !embedding.vector) return null
188
+
189
+ const score = cosineSimilarity(queryVector, embedding.vector)
190
+ return { item, score }
191
+ })
192
+ .filter((r: { score: number } | null) => r !== null && r.score >= minScore)
193
+ .sort((a: { score: number }, b: { score: number }) => b.score - a.score)
194
+ .slice(0, limit)
195
+
196
+ return {
197
+ results: results.map((r: { item: unknown; score: number }) => {
198
+ // Ensure item is an object before spreading
199
+ const item = r.item as Record<string, unknown>
200
+ return {
201
+ ...item,
202
+ _similarity: r.score,
203
+ }
204
+ }),
205
+ count: results.length,
206
+ }
207
+ },
208
+ })
209
+ }
210
+ }
211
+ }
212
+
213
+ // Store RAG config for runtime access
214
+ // Access at runtime via: config._pluginData.rag
215
+ context.setPluginData<NormalizedRAGConfig>('rag', normalized)
216
+ },
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Hash text using SHA-256 for change detection
222
+ */
223
+ async function hashText(text: string): Promise<string> {
224
+ // Simple hash implementation
225
+ // In production, could use crypto.subtle.digest for better performance
226
+ let hash = 0
227
+ for (let i = 0; i < text.length; i++) {
228
+ const char = text.charCodeAt(i)
229
+ hash = (hash << 5) - hash + char
230
+ hash = hash & hash // Convert to 32-bit integer
231
+ }
232
+ return hash.toString(36)
233
+ }
234
+
235
+ /**
236
+ * Calculate cosine similarity between two vectors
237
+ */
238
+ function cosineSimilarity(a: number[], b: number[]): number {
239
+ if (a.length !== b.length) {
240
+ throw new Error('Vectors must have same dimensions')
241
+ }
242
+
243
+ let dotProduct = 0
244
+ let normA = 0
245
+ let normB = 0
246
+
247
+ for (let i = 0; i < a.length; i++) {
248
+ dotProduct += a[i] * b[i]
249
+ normA += a[i] * a[i]
250
+ normB += b[i] * b[i]
251
+ }
252
+
253
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB)
254
+ if (denominator === 0) return 0
255
+
256
+ return dotProduct / denominator
257
+ }
@@ -0,0 +1,283 @@
1
+ /**
2
+ * RAG (Retrieval-Augmented Generation) configuration types
3
+ */
4
+
5
+ /**
6
+ * Text chunking strategies for splitting documents
7
+ */
8
+ export type ChunkingStrategy = 'none' | 'recursive' | 'sentence' | 'sliding-window'
9
+
10
+ /**
11
+ * Chunking configuration options
12
+ */
13
+ export type ChunkingConfig = {
14
+ /**
15
+ * Chunking strategy to use
16
+ * @default 'recursive'
17
+ */
18
+ strategy?: ChunkingStrategy
19
+ /**
20
+ * Maximum tokens per chunk
21
+ * @default 500
22
+ */
23
+ maxTokens?: number
24
+ /**
25
+ * Overlap between chunks (tokens)
26
+ * Only applies to 'recursive' and 'sliding-window' strategies
27
+ * @default 50
28
+ */
29
+ overlap?: number
30
+ }
31
+
32
+ /**
33
+ * Embedding provider names
34
+ */
35
+ export type EmbeddingProviderName = 'openai' | 'ollama' | string
36
+
37
+ /**
38
+ * OpenAI embedding model options
39
+ */
40
+ export type OpenAIEmbeddingModel =
41
+ | 'text-embedding-3-small'
42
+ | 'text-embedding-3-large'
43
+ | 'text-embedding-ada-002'
44
+
45
+ /**
46
+ * OpenAI embedding provider configuration
47
+ */
48
+ export type OpenAIEmbeddingConfig = {
49
+ type: 'openai'
50
+ /**
51
+ * OpenAI API key
52
+ */
53
+ apiKey: string
54
+ /**
55
+ * Model to use for embeddings
56
+ * @default 'text-embedding-3-small'
57
+ */
58
+ model?: OpenAIEmbeddingModel
59
+ /**
60
+ * Organization ID (optional)
61
+ */
62
+ organization?: string
63
+ /**
64
+ * Base URL for API requests (optional, for Azure or custom endpoints)
65
+ */
66
+ baseURL?: string
67
+ }
68
+
69
+ /**
70
+ * Ollama embedding provider configuration
71
+ */
72
+ export type OllamaEmbeddingConfig = {
73
+ type: 'ollama'
74
+ /**
75
+ * Ollama API endpoint
76
+ * @default 'http://localhost:11434'
77
+ */
78
+ baseURL?: string
79
+ /**
80
+ * Model to use for embeddings
81
+ * @default 'nomic-embed-text'
82
+ */
83
+ model?: string
84
+ }
85
+
86
+ /**
87
+ * Generic embedding provider configuration
88
+ * For custom or third-party providers
89
+ */
90
+ export type CustomEmbeddingConfig = {
91
+ type: string
92
+ [key: string]: unknown
93
+ }
94
+
95
+ /**
96
+ * Embedding provider configuration union
97
+ */
98
+ export type EmbeddingProviderConfig =
99
+ | OpenAIEmbeddingConfig
100
+ | OllamaEmbeddingConfig
101
+ | CustomEmbeddingConfig
102
+
103
+ /**
104
+ * Vector storage backend types
105
+ */
106
+ export type VectorStorageBackend = 'pgvector' | 'sqlite-vss' | 'json' | string
107
+
108
+ /**
109
+ * pgvector storage configuration
110
+ */
111
+ export type PgVectorStorageConfig = {
112
+ type: 'pgvector'
113
+ /**
114
+ * Distance function for similarity search
115
+ * @default 'cosine'
116
+ */
117
+ distanceFunction?: 'cosine' | 'l2' | 'inner_product'
118
+ }
119
+
120
+ /**
121
+ * SQLite VSS storage configuration
122
+ */
123
+ export type SqliteVssStorageConfig = {
124
+ type: 'sqlite-vss'
125
+ /**
126
+ * Distance function for similarity search
127
+ * @default 'cosine'
128
+ */
129
+ distanceFunction?: 'cosine' | 'l2'
130
+ }
131
+
132
+ /**
133
+ * JSON-based storage (in-memory search)
134
+ * Stores vectors as JSON and uses JavaScript for similarity search
135
+ * Good for development and small datasets
136
+ */
137
+ export type JsonStorageConfig = {
138
+ type: 'json'
139
+ }
140
+
141
+ /**
142
+ * Generic storage configuration for custom backends
143
+ */
144
+ export type CustomStorageConfig = {
145
+ type: string
146
+ [key: string]: unknown
147
+ }
148
+
149
+ /**
150
+ * Vector storage configuration union
151
+ */
152
+ export type VectorStorageConfig =
153
+ | PgVectorStorageConfig
154
+ | SqliteVssStorageConfig
155
+ | JsonStorageConfig
156
+ | CustomStorageConfig
157
+
158
+ /**
159
+ * Main RAG configuration
160
+ */
161
+ export type RAGConfig = {
162
+ /**
163
+ * Default embedding provider
164
+ * Can be overridden per field
165
+ */
166
+ provider?: EmbeddingProviderConfig
167
+
168
+ /**
169
+ * Named embedding providers
170
+ * Allows using different providers for different fields
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * providers: {
175
+ * openai: openaiEmbeddings({ apiKey: '...' }),
176
+ * ollama: ollamaEmbeddings({ model: 'nomic-embed-text' })
177
+ * }
178
+ * ```
179
+ */
180
+ providers?: Record<string, EmbeddingProviderConfig>
181
+
182
+ /**
183
+ * Vector storage backend
184
+ * @default { type: 'json' }
185
+ */
186
+ storage?: VectorStorageConfig
187
+
188
+ /**
189
+ * Default chunking configuration
190
+ * Can be overridden per field
191
+ */
192
+ chunking?: ChunkingConfig
193
+
194
+ /**
195
+ * Whether to enable MCP tools for semantic search
196
+ * Requires MCP to be enabled in main config
197
+ * @default true
198
+ */
199
+ enableMcpTools?: boolean
200
+
201
+ /**
202
+ * Batch size for embedding generation
203
+ * @default 10
204
+ */
205
+ batchSize?: number
206
+
207
+ /**
208
+ * Rate limit (requests per minute) for embedding API calls
209
+ * @default 100
210
+ */
211
+ rateLimit?: number
212
+ }
213
+
214
+ /**
215
+ * Normalized RAG configuration with defaults applied
216
+ */
217
+ export type NormalizedRAGConfig = {
218
+ provider: EmbeddingProviderConfig | null
219
+ providers: Record<string, EmbeddingProviderConfig>
220
+ storage: VectorStorageConfig
221
+ chunking: Required<ChunkingConfig>
222
+ enableMcpTools: boolean
223
+ batchSize: number
224
+ rateLimit: number
225
+ }
226
+
227
+ /**
228
+ * Embedding metadata stored alongside vectors
229
+ */
230
+ export type EmbeddingMetadata = {
231
+ /**
232
+ * Embedding model used
233
+ */
234
+ model: string
235
+ /**
236
+ * Provider type (openai, ollama, etc.)
237
+ */
238
+ provider: string
239
+ /**
240
+ * Vector dimensions
241
+ */
242
+ dimensions: number
243
+ /**
244
+ * When the embedding was generated
245
+ */
246
+ generatedAt: string
247
+ /**
248
+ * Hash of source text (for detecting changes)
249
+ */
250
+ sourceHash?: string
251
+ }
252
+
253
+ /**
254
+ * Stored embedding with vector and metadata
255
+ */
256
+ export type StoredEmbedding = {
257
+ /**
258
+ * The embedding vector
259
+ */
260
+ vector: number[]
261
+ /**
262
+ * Metadata about the embedding
263
+ */
264
+ metadata: EmbeddingMetadata
265
+ }
266
+
267
+ /**
268
+ * Semantic search result
269
+ */
270
+ export type SearchResult<T = unknown> = {
271
+ /**
272
+ * The matching item
273
+ */
274
+ item: T
275
+ /**
276
+ * Similarity score (0-1, higher is more similar)
277
+ */
278
+ score: number
279
+ /**
280
+ * Distance metric (depends on storage backend)
281
+ */
282
+ distance: number
283
+ }