@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,4 @@
1
+
2
+ > @opensaas/stack-rag@0.1.6 build /home/runner/work/stack/stack/packages/rag
3
+ > tsc
4
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # @opensaas/stack-rag
2
+
3
+ ## 0.1.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 39996ca: Fix missing StoredEmbedding type import in generated types. Fields can now declare TypeScript imports needed for their types via the new `getTypeScriptImports()` method. This resolves the type error where `StoredEmbedding` was referenced but not imported in the generated `.opensaas/types.ts` file.
8
+ - Updated dependencies [39996ca]
9
+ - Updated dependencies [39996ca]
10
+ - @opensaas/stack-core@0.1.6
package/CLAUDE.md ADDED
@@ -0,0 +1,565 @@
1
+ # @opensaas/stack-rag
2
+
3
+ RAG (Retrieval-Augmented Generation) and AI embeddings integration for OpenSaas Stack. Turn your OpenSaas app into a knowledge base with semantic search capabilities.
4
+
5
+ ## Purpose
6
+
7
+ Adds vector embeddings and semantic search to OpenSaas Stack apps with minimal configuration. Supports multiple embedding providers (OpenAI, Ollama) and vector storage backends (pgvector, SQLite VSS, JSON).
8
+
9
+ ## Key Features
10
+
11
+ - **Multiple abstraction levels**: From automatic "magic" RAG to low-level primitives
12
+ - **Embedding providers**: OpenAI, Ollama (local), extensible for custom providers
13
+ - **Vector storage**: pgvector, sqlite-vss, JSON-based (for development)
14
+ - **MCP integration**: Automatic semantic search tools for AI assistants
15
+ - **Access control**: All searches respect existing access control rules
16
+ - **Automatic embedding generation**: Via hooks when source fields change
17
+
18
+ ## Package Structure
19
+
20
+ ```
21
+ packages/rag/
22
+ ├── src/
23
+ │ ├── config/ # withRAG(), ragConfig()
24
+ │ ├── fields/ # embedding() field type
25
+ │ ├── providers/ # OpenAI, Ollama embedding providers
26
+ │ ├── storage/ # pgvector, sqlite-vss, JSON backends
27
+ │ ├── runtime/ # generateEmbeddings(), semanticSearch()
28
+ │ └── mcp/ # MCP tool generators
29
+ ```
30
+
31
+ ## Package Exports
32
+
33
+ ### Main exports (`@opensaas/stack-rag`)
34
+
35
+ - `withRAG(config, ragConfig)` - Wrap OpenSaas config with RAG integration
36
+ - `ragConfig({ ... })` - RAG configuration builder
37
+ - `openaiEmbeddings({ ... })` - OpenAI provider config helper
38
+ - `ollamaEmbeddings({ ... })` - Ollama provider config helper
39
+ - `pgvectorStorage({ ... })` - pgvector backend config helper
40
+ - `sqliteVssStorage({ ... })` - SQLite VSS backend config helper
41
+ - `jsonStorage()` - JSON-based storage config helper
42
+
43
+ ### Fields (`@opensaas/stack-rag/fields`)
44
+
45
+ - `embedding({ ... })` - Vector embedding field type
46
+
47
+ ### Providers (`@opensaas/stack-rag/providers`)
48
+
49
+ - `createEmbeddingProvider(config)` - Factory for embedding providers
50
+ - `OpenAIEmbeddingProvider` - OpenAI implementation
51
+ - `OllamaEmbeddingProvider` - Ollama implementation
52
+ - `registerEmbeddingProvider(type, factory)` - Register custom providers
53
+
54
+ ### Storage (`@opensaas/stack-rag/storage`)
55
+
56
+ - `createVectorStorage(config)` - Factory for storage backends
57
+ - `JsonVectorStorage` - JSON-based storage (development)
58
+ - `PgVectorStorage` - PostgreSQL pgvector storage
59
+ - `SqliteVssStorage` - SQLite VSS storage
60
+ - `registerVectorStorage(type, factory)` - Register custom backends
61
+
62
+ ### Runtime (`@opensaas/stack-rag/runtime`)
63
+
64
+ - `generateEmbeddings(config, text, provider)` - Generate embeddings
65
+ - `semanticSearch(context, query, options)` - Search by natural language
66
+ - `findSimilar(context, itemId, options)` - Find similar items
67
+ - `chunkText(text, strategy)` - Text chunking utilities
68
+
69
+ ### MCP (`@opensaas/stack-rag/mcp`)
70
+
71
+ - Auto-generated semantic search tools for MCP server
72
+
73
+ ## Usage Patterns
74
+
75
+ ### Basic Setup with OpenAI
76
+
77
+ ```typescript
78
+ // opensaas.config.ts
79
+ import { config, list } from '@opensaas/stack-core'
80
+ import { text } from '@opensaas/stack-core/fields'
81
+ import { withRAG, ragConfig, openaiEmbeddings, pgvectorStorage } from '@opensaas/stack-rag'
82
+ import { embedding } from '@opensaas/stack-rag/fields'
83
+
84
+ export default withRAG(
85
+ config({
86
+ db: {
87
+ provider: 'postgresql',
88
+ url: process.env.DATABASE_URL!,
89
+ },
90
+ lists: {
91
+ Article: list({
92
+ fields: {
93
+ title: text({ validation: { isRequired: true } }),
94
+ content: text({ validation: { isRequired: true } }),
95
+ contentEmbedding: embedding({
96
+ sourceField: 'content',
97
+ provider: 'openai',
98
+ dimensions: 1536,
99
+ autoGenerate: true,
100
+ }),
101
+ },
102
+ }),
103
+ },
104
+ }),
105
+ ragConfig({
106
+ provider: openaiEmbeddings({
107
+ apiKey: process.env.OPENAI_API_KEY!,
108
+ model: 'text-embedding-3-small',
109
+ }),
110
+ storage: pgvectorStorage({ distanceFunction: 'cosine' }),
111
+ }),
112
+ )
113
+ ```
114
+
115
+ ### Local Development with Ollama
116
+
117
+ ```typescript
118
+ // opensaas.config.ts
119
+ import { withRAG, ragConfig, ollamaEmbeddings, jsonStorage } from '@opensaas/stack-rag'
120
+
121
+ export default withRAG(
122
+ config({
123
+ db: { provider: 'sqlite', url: 'file:./dev.db' },
124
+ lists: {
125
+ Document: list({
126
+ fields: {
127
+ text: text(),
128
+ embedding: embedding({
129
+ sourceField: 'text',
130
+ provider: 'ollama',
131
+ autoGenerate: true,
132
+ }),
133
+ },
134
+ }),
135
+ },
136
+ }),
137
+ ragConfig({
138
+ provider: ollamaEmbeddings({
139
+ baseURL: 'http://localhost:11434',
140
+ model: 'nomic-embed-text',
141
+ }),
142
+ storage: jsonStorage(), // Good for development, no DB extensions needed
143
+ }),
144
+ )
145
+ ```
146
+
147
+ ### Multiple Providers
148
+
149
+ ```typescript
150
+ ragConfig({
151
+ providers: {
152
+ openai: openaiEmbeddings({
153
+ apiKey: process.env.OPENAI_API_KEY!,
154
+ model: 'text-embedding-3-small',
155
+ }),
156
+ ollama: ollamaEmbeddings({
157
+ model: 'nomic-embed-text',
158
+ }),
159
+ },
160
+ storage: pgvectorStorage(),
161
+ })
162
+
163
+ // In fields
164
+ fields: {
165
+ titleEmbedding: embedding({
166
+ sourceField: 'title',
167
+ provider: 'ollama', // Use local Ollama for titles
168
+ }),
169
+ contentEmbedding: embedding({
170
+ sourceField: 'content',
171
+ provider: 'openai', // Use OpenAI for content
172
+ })
173
+ }
174
+ ```
175
+
176
+ ### Manual Embedding Storage (Low-Level)
177
+
178
+ ```typescript
179
+ import { createEmbeddingProvider } from '@opensaas/stack-rag/providers'
180
+ import { getContext } from '@/.opensaas/context'
181
+
182
+ // Generate embeddings manually
183
+ const provider = createEmbeddingProvider({
184
+ type: 'openai',
185
+ apiKey: process.env.OPENAI_API_KEY!,
186
+ })
187
+
188
+ const vector = await provider.embed('Hello world')
189
+
190
+ // Store manually
191
+ const context = await getContext()
192
+ await context.db.article.create({
193
+ data: {
194
+ title: 'Hello',
195
+ contentEmbedding: {
196
+ vector,
197
+ metadata: {
198
+ model: provider.model,
199
+ provider: provider.type,
200
+ dimensions: provider.dimensions,
201
+ generatedAt: new Date().toISOString(),
202
+ },
203
+ },
204
+ },
205
+ })
206
+ ```
207
+
208
+ ### Semantic Search (Runtime)
209
+
210
+ ```typescript
211
+ import { createVectorStorage } from '@opensaas/stack-rag/storage'
212
+ import { createEmbeddingProvider } from '@opensaas/stack-rag/providers'
213
+ import { getContext } from '@/.opensaas/context'
214
+ import config from '@/opensaas.config'
215
+
216
+ // Server action or API route
217
+ export async function searchArticles(query: string) {
218
+ const context = await getContext()
219
+
220
+ // Generate query embedding
221
+ const provider = createEmbeddingProvider({
222
+ type: 'openai',
223
+ apiKey: process.env.OPENAI_API_KEY!,
224
+ })
225
+ const queryVector = await provider.embed(query)
226
+
227
+ // Search
228
+ const storage = createVectorStorage({ type: 'pgvector' })
229
+ const results = await storage.search('Article', 'contentEmbedding', queryVector, {
230
+ limit: 10,
231
+ minScore: 0.7,
232
+ context,
233
+ })
234
+
235
+ return results.map((r) => ({
236
+ article: r.item,
237
+ similarity: r.score,
238
+ }))
239
+ }
240
+ ```
241
+
242
+ ### MCP Integration (Automatic)
243
+
244
+ When RAG is enabled with MCP, semantic search tools are automatically generated:
245
+
246
+ ```typescript
247
+ // opensaas.config.ts
248
+ import { withAuth, authConfig } from '@opensaas/stack-auth'
249
+ import { withRAG, ragConfig } from '@opensaas/stack-rag'
250
+
251
+ export default withRAG(
252
+ withAuth(
253
+ config({
254
+ db: { provider: 'postgresql', url: process.env.DATABASE_URL! },
255
+ mcp: {
256
+ enabled: true,
257
+ auth: { type: 'better-auth', loginPage: '/sign-in' },
258
+ },
259
+ lists: {
260
+ Article: list({
261
+ fields: {
262
+ content: text(),
263
+ contentEmbedding: embedding({ sourceField: 'content' }),
264
+ },
265
+ }),
266
+ },
267
+ }),
268
+ authConfig({ emailAndPassword: { enabled: true } }),
269
+ ),
270
+ ragConfig({
271
+ provider: openaiEmbeddings({ apiKey: process.env.OPENAI_API_KEY! }),
272
+ enableMcpTools: true, // Auto-generates semantic_search_article tool
273
+ }),
274
+ )
275
+ ```
276
+
277
+ AI assistants can then use:
278
+
279
+ ```json
280
+ {
281
+ "name": "semantic_search_article",
282
+ "arguments": {
283
+ "query": "articles about machine learning",
284
+ "limit": 5
285
+ }
286
+ }
287
+ ```
288
+
289
+ ## Architecture Patterns
290
+
291
+ ### Automatic Embedding Generation
292
+
293
+ The `withRAG()` wrapper injects hooks into fields with `sourceField` set:
294
+
295
+ ```typescript
296
+ // User config
297
+ contentEmbedding: embedding({
298
+ sourceField: 'content',
299
+ autoGenerate: true
300
+ })
301
+
302
+ // withRAG() automatically adds:
303
+ contentEmbedding: embedding({
304
+ hooks: {
305
+ afterOperation: async ({ operation, value, item, context }) => {
306
+ if (operation === 'create' || operation === 'update') {
307
+ // Check if source field changed
308
+ const sourceText = item.content
309
+ const currentEmbedding = item.contentEmbedding
310
+
311
+ // Generate embedding if needed
312
+ if (shouldRegenerate(sourceText, currentEmbedding)) {
313
+ const provider = getEmbeddingProvider(ragConfig)
314
+ const vector = await provider.embed(sourceText)
315
+
316
+ await context.db.article.update({
317
+ where: { id: item.id },
318
+ data: {
319
+ contentEmbedding: {
320
+ vector,
321
+ metadata: { ... }
322
+ }
323
+ }
324
+ })
325
+ }
326
+ }
327
+ }
328
+ }
329
+ })
330
+ ```
331
+
332
+ ### Access Control Integration
333
+
334
+ All searches use the access-controlled context:
335
+
336
+ ```typescript
337
+ // Search respects access control
338
+ const context = await getContext({ userId: 'user-123' })
339
+
340
+ const results = await storage.search('Article', 'contentEmbedding', queryVector, {
341
+ context, // Access control applied
342
+ where: { published: true }, // Additional filters
343
+ })
344
+
345
+ // Users only see articles they have access to
346
+ ```
347
+
348
+ ### Vector Storage Backends
349
+
350
+ Storage backends are pluggable:
351
+
352
+ ```typescript
353
+ // pgvector (best for production PostgreSQL)
354
+ storage: pgvectorStorage({ distanceFunction: 'cosine' })
355
+ // Uses pgvector extension, fast indexed search
356
+
357
+ // sqlite-vss (good for SQLite apps)
358
+ storage: sqliteVssStorage({ distanceFunction: 'cosine' })
359
+ // Uses sqlite-vss extension
360
+
361
+ // JSON (good for development)
362
+ storage: jsonStorage()
363
+ // No extensions needed, JS-based similarity search
364
+ ```
365
+
366
+ ### Embedding Providers
367
+
368
+ Providers are pluggable and extensible:
369
+
370
+ ```typescript
371
+ // Register custom provider
372
+ import { registerEmbeddingProvider } from '@opensaas/stack-rag/providers'
373
+
374
+ registerEmbeddingProvider('custom', (config) => {
375
+ return {
376
+ type: 'custom',
377
+ model: config.model,
378
+ dimensions: config.dimensions,
379
+ async embed(text) {
380
+ // Your implementation
381
+ return [
382
+ /* vector */
383
+ ]
384
+ },
385
+ async embedBatch(texts) {
386
+ // Batch implementation
387
+ return [
388
+ [
389
+ /* vectors */
390
+ ],
391
+ ]
392
+ },
393
+ }
394
+ })
395
+ ```
396
+
397
+ ## Database Setup
398
+
399
+ ### PostgreSQL with pgvector
400
+
401
+ ```sql
402
+ -- Enable pgvector extension
403
+ CREATE EXTENSION vector;
404
+
405
+ -- Prisma will generate schema with Json fields
406
+ -- For optimal performance, create indexes:
407
+ CREATE INDEX article_embedding_vector_idx
408
+ ON "Article" USING ivfflat (("contentEmbedding"->>'vector')::vector(1536))
409
+ WITH (lists = 100);
410
+ ```
411
+
412
+ ### SQLite with VSS
413
+
414
+ ```sql
415
+ -- Load VSS extension (depends on your SQLite setup)
416
+ -- Embeddings stored as JSON, VSS used for search
417
+ ```
418
+
419
+ ### Any Database with JSON Storage
420
+
421
+ No special setup needed. Embeddings stored as JSON, similarity computed in JavaScript.
422
+
423
+ ## Type Safety
424
+
425
+ All operations are fully typed:
426
+
427
+ ```typescript
428
+ import type { SearchResult, StoredEmbedding } from '@opensaas/stack-rag'
429
+
430
+ const results: SearchResult<Article>[] = await storage.search(...)
431
+
432
+ const embedding: StoredEmbedding = {
433
+ vector: [0.1, 0.2, 0.3],
434
+ metadata: {
435
+ model: 'text-embedding-3-small',
436
+ provider: 'openai',
437
+ dimensions: 1536,
438
+ generatedAt: new Date().toISOString()
439
+ }
440
+ }
441
+ ```
442
+
443
+ ## Performance Considerations
444
+
445
+ 1. **Batch embedding generation**: Use `embedBatch()` for multiple texts
446
+ 2. **Index embeddings**: Create vector indexes in production (pgvector)
447
+ 3. **Chunking strategy**: Configure chunking for long texts
448
+ 4. **Rate limiting**: Configure `rateLimit` in RAG config to avoid API limits
449
+ 5. **Caching**: Hash source text to avoid regenerating unchanged embeddings
450
+
451
+ ## Common Patterns
452
+
453
+ ### Text Chunking for Long Documents
454
+
455
+ ```typescript
456
+ import { chunkText } from '@opensaas/stack-rag/runtime'
457
+
458
+ const chunks = chunkText(longDocument, {
459
+ strategy: 'recursive',
460
+ maxTokens: 500,
461
+ overlap: 50,
462
+ })
463
+
464
+ // Generate embeddings for each chunk
465
+ const vectors = await provider.embedBatch(chunks)
466
+ ```
467
+
468
+ ### Hybrid Search (Keyword + Semantic)
469
+
470
+ ```typescript
471
+ // Combine traditional search with semantic search
472
+ const keywordResults = await context.db.article.findMany({
473
+ where: {
474
+ OR: [
475
+ { title: { contains: query } },
476
+ { content: { contains: query } },
477
+ ],
478
+ },
479
+ })
480
+
481
+ const semanticResults = await storage.search(...)
482
+
483
+ // Merge and deduplicate results
484
+ ```
485
+
486
+ ### Find Similar Items
487
+
488
+ ```typescript
489
+ // Find articles similar to a given article
490
+ const article = await context.db.article.findUnique({ where: { id } })
491
+ const queryVector = article.contentEmbedding.vector
492
+
493
+ const similar = await storage.search('Article', 'contentEmbedding', queryVector, {
494
+ limit: 5,
495
+ where: { id: { not: id } }, // Exclude the article itself
496
+ context,
497
+ })
498
+ ```
499
+
500
+ ## Testing
501
+
502
+ ```typescript
503
+ // packages/rag/__tests__/providers.test.ts
504
+ import { describe, it, expect } from 'vitest'
505
+ import { createOpenAIProvider } from '../src/providers/openai'
506
+
507
+ describe('OpenAIEmbeddingProvider', () => {
508
+ it('should generate embeddings', async () => {
509
+ const provider = createOpenAIProvider({
510
+ type: 'openai',
511
+ apiKey: process.env.OPENAI_API_KEY!,
512
+ })
513
+
514
+ const embedding = await provider.embed('Hello world')
515
+
516
+ expect(embedding).toHaveLength(1536)
517
+ expect(embedding[0]).toBeTypeOf('number')
518
+ })
519
+ })
520
+ ```
521
+
522
+ ## Migration Guide
523
+
524
+ ### Adding RAG to Existing App
525
+
526
+ 1. Install package: `pnpm add @opensaas/stack-rag`
527
+ 2. Install provider: `pnpm add openai` (for OpenAI)
528
+ 3. Wrap config with `withRAG()`
529
+ 4. Add `embedding()` fields to lists
530
+ 5. Run `pnpm generate` and `pnpm db:push`
531
+ 6. Embeddings will be generated automatically on create/update
532
+
533
+ ### Switching Storage Backends
534
+
535
+ Change storage config and regenerate schema:
536
+
537
+ ```typescript
538
+ // From JSON to pgvector
539
+ storage: pgvectorStorage() // instead of jsonStorage()
540
+ ```
541
+
542
+ Then:
543
+
544
+ ```bash
545
+ pnpm generate
546
+ pnpm db:push
547
+ ```
548
+
549
+ Existing embeddings in JSON format are compatible.
550
+
551
+ ## Limitations
552
+
553
+ - **Dimensions must match**: Provider and field dimensions must match
554
+ - **JSON storage performance**: JSON-based search doesn't scale to millions of vectors
555
+ - **No automatic re-embedding**: Changing provider/model requires manual re-embedding
556
+ - **Access control bypass**: Raw Prisma queries bypass access control (handled in implementation)
557
+
558
+ ## Future Enhancements
559
+
560
+ - Automatic re-embedding when provider changes
561
+ - Built-in chunking strategies for fields
562
+ - Hybrid search utilities (keyword + semantic)
563
+ - Pinecone, Qdrant, Weaviate integrations
564
+ - Embedding caching and deduplication
565
+ - Advanced distance metrics
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OpenSaas Stack Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.