@girardmedia/bootspring 1.2.0 → 2.0.3
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.
- package/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# Embeddings and Vector Search Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for text embeddings and semantic search.
|
|
4
|
-
|
|
5
|
-
## OpenAI Embeddings
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/embeddings.ts
|
|
9
|
-
import OpenAI from 'openai'
|
|
10
|
-
|
|
11
|
-
const openai = new OpenAI({
|
|
12
|
-
apiKey: process.env.OPENAI_API_KEY
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
export async function getEmbedding(text: string) {
|
|
16
|
-
const response = await openai.embeddings.create({
|
|
17
|
-
model: 'text-embedding-3-small',
|
|
18
|
-
input: text
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
return response.data[0].embedding
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function getEmbeddings(texts: string[]) {
|
|
25
|
-
const response = await openai.embeddings.create({
|
|
26
|
-
model: 'text-embedding-3-small',
|
|
27
|
-
input: texts
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
return response.data.map((d) => d.embedding)
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Store with Prisma + pgvector
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
// lib/embeddings.ts
|
|
38
|
-
import { prisma } from '@/lib/db'
|
|
39
|
-
|
|
40
|
-
export async function storeDocument(
|
|
41
|
-
content: string,
|
|
42
|
-
metadata: Record<string, any>
|
|
43
|
-
) {
|
|
44
|
-
const embedding = await getEmbedding(content)
|
|
45
|
-
|
|
46
|
-
await prisma.$executeRaw`
|
|
47
|
-
INSERT INTO documents (content, metadata, embedding)
|
|
48
|
-
VALUES (${content}, ${metadata}::jsonb, ${embedding}::vector)
|
|
49
|
-
`
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Semantic Search
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
// lib/embeddings.ts
|
|
57
|
-
export async function semanticSearch(
|
|
58
|
-
query: string,
|
|
59
|
-
limit = 5
|
|
60
|
-
) {
|
|
61
|
-
const queryEmbedding = await getEmbedding(query)
|
|
62
|
-
|
|
63
|
-
const results = await prisma.$queryRaw`
|
|
64
|
-
SELECT
|
|
65
|
-
id,
|
|
66
|
-
content,
|
|
67
|
-
metadata,
|
|
68
|
-
1 - (embedding <=> ${queryEmbedding}::vector) as similarity
|
|
69
|
-
FROM documents
|
|
70
|
-
ORDER BY embedding <=> ${queryEmbedding}::vector
|
|
71
|
-
LIMIT ${limit}
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
return results
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## RAG Implementation
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
// lib/rag.ts
|
|
82
|
-
import { anthropic } from '@/lib/anthropic'
|
|
83
|
-
import { semanticSearch } from '@/lib/embeddings'
|
|
84
|
-
|
|
85
|
-
export async function ragQuery(question: string) {
|
|
86
|
-
// 1. Find relevant documents
|
|
87
|
-
const docs = await semanticSearch(question, 5)
|
|
88
|
-
|
|
89
|
-
// 2. Build context
|
|
90
|
-
const context = docs
|
|
91
|
-
.map((d: any) => d.content)
|
|
92
|
-
.join('\n\n---\n\n')
|
|
93
|
-
|
|
94
|
-
// 3. Query with context
|
|
95
|
-
const response = await anthropic.messages.create({
|
|
96
|
-
model: 'claude-sonnet-4-20250514',
|
|
97
|
-
max_tokens: 1024,
|
|
98
|
-
system: `Answer questions based on the following context. If the answer isn't in the context, say so.
|
|
99
|
-
|
|
100
|
-
Context:
|
|
101
|
-
${context}`,
|
|
102
|
-
messages: [{ role: 'user', content: question }]
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
answer: response.content[0].type === 'text'
|
|
107
|
-
? response.content[0].text
|
|
108
|
-
: null,
|
|
109
|
-
sources: docs
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## Chunking Strategy
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
// lib/chunking.ts
|
|
118
|
-
export function chunkText(
|
|
119
|
-
text: string,
|
|
120
|
-
chunkSize = 1000,
|
|
121
|
-
overlap = 200
|
|
122
|
-
) {
|
|
123
|
-
const chunks: string[] = []
|
|
124
|
-
let start = 0
|
|
125
|
-
|
|
126
|
-
while (start < text.length) {
|
|
127
|
-
const end = Math.min(start + chunkSize, text.length)
|
|
128
|
-
chunks.push(text.slice(start, end))
|
|
129
|
-
start += chunkSize - overlap
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return chunks
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Usage: Index a document
|
|
136
|
-
export async function indexDocument(content: string) {
|
|
137
|
-
const chunks = chunkText(content)
|
|
138
|
-
|
|
139
|
-
for (const chunk of chunks) {
|
|
140
|
-
await storeDocument(chunk, { source: 'document' })
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## When to Use
|
|
146
|
-
|
|
147
|
-
- Semantic search
|
|
148
|
-
- RAG applications
|
|
149
|
-
- Document Q&A
|
|
150
|
-
- Recommendation systems
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
# RAG (Retrieval-Augmented Generation) Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for implementing RAG systems.
|
|
4
|
-
|
|
5
|
-
## Document Ingestion
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// lib/rag/ingest.ts
|
|
9
|
-
import { OpenAI } from 'openai'
|
|
10
|
-
import { prisma } from '@/lib/db'
|
|
11
|
-
|
|
12
|
-
const openai = new OpenAI()
|
|
13
|
-
|
|
14
|
-
interface Document {
|
|
15
|
-
id: string
|
|
16
|
-
content: string
|
|
17
|
-
metadata?: Record<string, any>
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function ingestDocument(doc: Document) {
|
|
21
|
-
// Split into chunks
|
|
22
|
-
const chunks = splitIntoChunks(doc.content, 500)
|
|
23
|
-
|
|
24
|
-
// Generate embeddings for each chunk
|
|
25
|
-
const embeddingsResponse = await openai.embeddings.create({
|
|
26
|
-
model: 'text-embedding-3-small',
|
|
27
|
-
input: chunks
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
// Store chunks with embeddings
|
|
31
|
-
await prisma.$transaction(
|
|
32
|
-
chunks.map((chunk, i) =>
|
|
33
|
-
prisma.documentChunk.create({
|
|
34
|
-
data: {
|
|
35
|
-
documentId: doc.id,
|
|
36
|
-
content: chunk,
|
|
37
|
-
embedding: embeddingsResponse.data[i].embedding,
|
|
38
|
-
metadata: doc.metadata
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
)
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function splitIntoChunks(text: string, maxChunkSize: number): string[] {
|
|
46
|
-
const sentences = text.split(/(?<=[.!?])\s+/)
|
|
47
|
-
const chunks: string[] = []
|
|
48
|
-
let currentChunk = ''
|
|
49
|
-
|
|
50
|
-
for (const sentence of sentences) {
|
|
51
|
-
if ((currentChunk + sentence).length > maxChunkSize && currentChunk) {
|
|
52
|
-
chunks.push(currentChunk.trim())
|
|
53
|
-
currentChunk = ''
|
|
54
|
-
}
|
|
55
|
-
currentChunk += sentence + ' '
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (currentChunk.trim()) {
|
|
59
|
-
chunks.push(currentChunk.trim())
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return chunks
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Vector Search
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
// lib/rag/search.ts
|
|
70
|
-
import { OpenAI } from 'openai'
|
|
71
|
-
import { prisma } from '@/lib/db'
|
|
72
|
-
|
|
73
|
-
const openai = new OpenAI()
|
|
74
|
-
|
|
75
|
-
export async function searchSimilar(query: string, limit = 5) {
|
|
76
|
-
// Generate query embedding
|
|
77
|
-
const embeddingResponse = await openai.embeddings.create({
|
|
78
|
-
model: 'text-embedding-3-small',
|
|
79
|
-
input: query
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
const queryEmbedding = embeddingResponse.data[0].embedding
|
|
83
|
-
|
|
84
|
-
// Search using pgvector
|
|
85
|
-
const results = await prisma.$queryRaw<Array<{
|
|
86
|
-
id: string
|
|
87
|
-
content: string
|
|
88
|
-
similarity: number
|
|
89
|
-
}>>`
|
|
90
|
-
SELECT
|
|
91
|
-
id,
|
|
92
|
-
content,
|
|
93
|
-
1 - (embedding <=> ${queryEmbedding}::vector) as similarity
|
|
94
|
-
FROM "DocumentChunk"
|
|
95
|
-
ORDER BY embedding <=> ${queryEmbedding}::vector
|
|
96
|
-
LIMIT ${limit}
|
|
97
|
-
`
|
|
98
|
-
|
|
99
|
-
return results
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## RAG Query
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
// lib/rag/query.ts
|
|
107
|
-
import Anthropic from '@anthropic-ai/sdk'
|
|
108
|
-
import { searchSimilar } from './search'
|
|
109
|
-
|
|
110
|
-
const client = new Anthropic()
|
|
111
|
-
|
|
112
|
-
export async function ragQuery(question: string) {
|
|
113
|
-
// Retrieve relevant chunks
|
|
114
|
-
const relevantChunks = await searchSimilar(question, 5)
|
|
115
|
-
|
|
116
|
-
// Build context
|
|
117
|
-
const context = relevantChunks
|
|
118
|
-
.map(chunk => chunk.content)
|
|
119
|
-
.join('\n\n---\n\n')
|
|
120
|
-
|
|
121
|
-
// Generate answer
|
|
122
|
-
const response = await client.messages.create({
|
|
123
|
-
model: 'claude-sonnet-4-20250514',
|
|
124
|
-
max_tokens: 1024,
|
|
125
|
-
system: `You are a helpful assistant that answers questions based on the provided context.
|
|
126
|
-
If the context doesn't contain relevant information, say so.
|
|
127
|
-
Always cite which parts of the context you're using.`,
|
|
128
|
-
messages: [
|
|
129
|
-
{
|
|
130
|
-
role: 'user',
|
|
131
|
-
content: `Context:
|
|
132
|
-
${context}
|
|
133
|
-
|
|
134
|
-
Question: ${question}
|
|
135
|
-
|
|
136
|
-
Answer based on the context above:`
|
|
137
|
-
}
|
|
138
|
-
]
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const textBlock = response.content.find(b => b.type === 'text')
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
answer: textBlock?.text ?? '',
|
|
145
|
-
sources: relevantChunks.map(c => ({
|
|
146
|
-
content: c.content.slice(0, 200),
|
|
147
|
-
similarity: c.similarity
|
|
148
|
-
}))
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Streaming RAG
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
// lib/rag/streaming.ts
|
|
157
|
-
import Anthropic from '@anthropic-ai/sdk'
|
|
158
|
-
import { searchSimilar } from './search'
|
|
159
|
-
|
|
160
|
-
const client = new Anthropic()
|
|
161
|
-
|
|
162
|
-
export async function* streamingRagQuery(question: string) {
|
|
163
|
-
// Yield search status
|
|
164
|
-
yield { type: 'status', message: 'Searching knowledge base...' }
|
|
165
|
-
|
|
166
|
-
const relevantChunks = await searchSimilar(question, 5)
|
|
167
|
-
|
|
168
|
-
yield {
|
|
169
|
-
type: 'sources',
|
|
170
|
-
sources: relevantChunks.map(c => ({
|
|
171
|
-
content: c.content.slice(0, 100),
|
|
172
|
-
similarity: c.similarity
|
|
173
|
-
}))
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const context = relevantChunks.map(c => c.content).join('\n\n---\n\n')
|
|
177
|
-
|
|
178
|
-
yield { type: 'status', message: 'Generating answer...' }
|
|
179
|
-
|
|
180
|
-
const stream = client.messages.stream({
|
|
181
|
-
model: 'claude-sonnet-4-20250514',
|
|
182
|
-
max_tokens: 1024,
|
|
183
|
-
system: `Answer questions based on the provided context.`,
|
|
184
|
-
messages: [
|
|
185
|
-
{
|
|
186
|
-
role: 'user',
|
|
187
|
-
content: `Context:\n${context}\n\nQuestion: ${question}`
|
|
188
|
-
}
|
|
189
|
-
]
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
for await (const event of stream) {
|
|
193
|
-
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
|
|
194
|
-
yield { type: 'text', content: event.delta.text }
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
yield { type: 'done' }
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## Hybrid Search
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
// lib/rag/hybrid-search.ts
|
|
206
|
-
import { searchSimilar } from './search'
|
|
207
|
-
import { prisma } from '@/lib/db'
|
|
208
|
-
|
|
209
|
-
export async function hybridSearch(query: string, limit = 5) {
|
|
210
|
-
// Vector search
|
|
211
|
-
const vectorResults = await searchSimilar(query, limit * 2)
|
|
212
|
-
|
|
213
|
-
// Keyword search
|
|
214
|
-
const keywordResults = await prisma.documentChunk.findMany({
|
|
215
|
-
where: {
|
|
216
|
-
content: {
|
|
217
|
-
contains: query,
|
|
218
|
-
mode: 'insensitive'
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
take: limit * 2
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
// Combine and deduplicate
|
|
225
|
-
const allResults = new Map()
|
|
226
|
-
|
|
227
|
-
vectorResults.forEach((r, i) => {
|
|
228
|
-
allResults.set(r.id, {
|
|
229
|
-
...r,
|
|
230
|
-
vectorRank: i,
|
|
231
|
-
keywordRank: Infinity
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
keywordResults.forEach((r, i) => {
|
|
236
|
-
if (allResults.has(r.id)) {
|
|
237
|
-
allResults.get(r.id).keywordRank = i
|
|
238
|
-
} else {
|
|
239
|
-
allResults.set(r.id, {
|
|
240
|
-
...r,
|
|
241
|
-
vectorRank: Infinity,
|
|
242
|
-
keywordRank: i,
|
|
243
|
-
similarity: 0
|
|
244
|
-
})
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// RRF (Reciprocal Rank Fusion) scoring
|
|
249
|
-
const k = 60
|
|
250
|
-
const scoredResults = Array.from(allResults.values()).map(r => ({
|
|
251
|
-
...r,
|
|
252
|
-
rrfScore: 1 / (k + r.vectorRank) + 1 / (k + r.keywordRank)
|
|
253
|
-
}))
|
|
254
|
-
|
|
255
|
-
return scoredResults
|
|
256
|
-
.sort((a, b) => b.rrfScore - a.rrfScore)
|
|
257
|
-
.slice(0, limit)
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
## When to Use
|
|
262
|
-
|
|
263
|
-
- Knowledge bases
|
|
264
|
-
- Document Q&A
|
|
265
|
-
- Support chatbots
|
|
266
|
-
- Search enhancement
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# AI Streaming Response Patterns
|
|
2
|
-
|
|
3
|
-
Patterns for streaming AI responses to the client.
|
|
4
|
-
|
|
5
|
-
## Anthropic Streaming
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// app/api/chat/route.ts
|
|
9
|
-
import { anthropic } from '@/lib/anthropic'
|
|
10
|
-
|
|
11
|
-
export async function POST(req: Request) {
|
|
12
|
-
const { messages } = await req.json()
|
|
13
|
-
|
|
14
|
-
const stream = await anthropic.messages.stream({
|
|
15
|
-
model: 'claude-sonnet-4-20250514',
|
|
16
|
-
max_tokens: 1024,
|
|
17
|
-
messages
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
return new Response(stream.toReadableStream(), {
|
|
21
|
-
headers: {
|
|
22
|
-
'Content-Type': 'text/event-stream',
|
|
23
|
-
'Cache-Control': 'no-cache',
|
|
24
|
-
Connection: 'keep-alive'
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Vercel AI SDK Streaming
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
// app/api/chat/route.ts
|
|
34
|
-
import { anthropic } from '@ai-sdk/anthropic'
|
|
35
|
-
import { streamText } from 'ai'
|
|
36
|
-
|
|
37
|
-
export async function POST(req: Request) {
|
|
38
|
-
const { messages } = await req.json()
|
|
39
|
-
|
|
40
|
-
const result = streamText({
|
|
41
|
-
model: anthropic('claude-sonnet-4-20250514'),
|
|
42
|
-
messages
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
return result.toDataStreamResponse()
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Client-Side Stream Consumer
|
|
50
|
-
|
|
51
|
-
```tsx
|
|
52
|
-
// components/chat.tsx
|
|
53
|
-
'use client'
|
|
54
|
-
import { useState } from 'react'
|
|
55
|
-
|
|
56
|
-
export function Chat() {
|
|
57
|
-
const [messages, setMessages] = useState<Message[]>([])
|
|
58
|
-
const [input, setInput] = useState('')
|
|
59
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
60
|
-
|
|
61
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
62
|
-
e.preventDefault()
|
|
63
|
-
if (!input.trim()) return
|
|
64
|
-
|
|
65
|
-
const userMessage = { role: 'user', content: input }
|
|
66
|
-
setMessages(prev => [...prev, userMessage])
|
|
67
|
-
setInput('')
|
|
68
|
-
setIsLoading(true)
|
|
69
|
-
|
|
70
|
-
const response = await fetch('/api/chat', {
|
|
71
|
-
method: 'POST',
|
|
72
|
-
body: JSON.stringify({
|
|
73
|
-
messages: [...messages, userMessage]
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
const reader = response.body?.getReader()
|
|
78
|
-
const decoder = new TextDecoder()
|
|
79
|
-
let assistantMessage = ''
|
|
80
|
-
|
|
81
|
-
// Add placeholder for assistant message
|
|
82
|
-
setMessages(prev => [...prev, { role: 'assistant', content: '' }])
|
|
83
|
-
|
|
84
|
-
while (reader) {
|
|
85
|
-
const { done, value } = await reader.read()
|
|
86
|
-
if (done) break
|
|
87
|
-
|
|
88
|
-
assistantMessage += decoder.decode(value)
|
|
89
|
-
setMessages(prev => [
|
|
90
|
-
...prev.slice(0, -1),
|
|
91
|
-
{ role: 'assistant', content: assistantMessage }
|
|
92
|
-
])
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
setIsLoading(false)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<div className="flex flex-col h-full">
|
|
100
|
-
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
101
|
-
{messages.map((m, i) => (
|
|
102
|
-
<div
|
|
103
|
-
key={i}
|
|
104
|
-
className={`p-4 rounded-lg ${
|
|
105
|
-
m.role === 'user' ? 'bg-blue-100 ml-12' : 'bg-gray-100 mr-12'
|
|
106
|
-
}`}
|
|
107
|
-
>
|
|
108
|
-
{m.content}
|
|
109
|
-
</div>
|
|
110
|
-
))}
|
|
111
|
-
</div>
|
|
112
|
-
<form onSubmit={handleSubmit} className="p-4 border-t">
|
|
113
|
-
<input
|
|
114
|
-
value={input}
|
|
115
|
-
onChange={(e) => setInput(e.target.value)}
|
|
116
|
-
placeholder="Type a message..."
|
|
117
|
-
className="w-full p-2 border rounded"
|
|
118
|
-
disabled={isLoading}
|
|
119
|
-
/>
|
|
120
|
-
</form>
|
|
121
|
-
</div>
|
|
122
|
-
)
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## AI SDK useChat Hook
|
|
127
|
-
|
|
128
|
-
```tsx
|
|
129
|
-
// components/chat.tsx
|
|
130
|
-
'use client'
|
|
131
|
-
import { useChat } from 'ai/react'
|
|
132
|
-
|
|
133
|
-
export function Chat() {
|
|
134
|
-
const { messages, input, handleInputChange, handleSubmit, isLoading } =
|
|
135
|
-
useChat()
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<div className="flex flex-col h-full">
|
|
139
|
-
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
140
|
-
{messages.map((m) => (
|
|
141
|
-
<div
|
|
142
|
-
key={m.id}
|
|
143
|
-
className={`p-4 rounded-lg ${
|
|
144
|
-
m.role === 'user' ? 'bg-blue-100 ml-12' : 'bg-gray-100 mr-12'
|
|
145
|
-
}`}
|
|
146
|
-
>
|
|
147
|
-
{m.content}
|
|
148
|
-
</div>
|
|
149
|
-
))}
|
|
150
|
-
</div>
|
|
151
|
-
<form onSubmit={handleSubmit} className="p-4 border-t">
|
|
152
|
-
<input
|
|
153
|
-
value={input}
|
|
154
|
-
onChange={handleInputChange}
|
|
155
|
-
placeholder="Type a message..."
|
|
156
|
-
className="w-full p-2 border rounded"
|
|
157
|
-
disabled={isLoading}
|
|
158
|
-
/>
|
|
159
|
-
</form>
|
|
160
|
-
</div>
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## When to Use
|
|
166
|
-
|
|
167
|
-
- Chat interfaces
|
|
168
|
-
- Long-form content generation
|
|
169
|
-
- Real-time response display
|
|
170
|
-
- Improved UX for AI interactions
|