Bygger digitala upplevelser med passion

”Enkelhet är effektivitetens själ.” – Austin Freeman

Komma i kontakt

GitHubGmail iconEmailLinkedIn

© 2026 Abel Sintaro. Alla rättigheter förbehållna

Tillbaka till tekniska

Systemarkitektur: Skalbara RAG-pipelines

Architecture
February 15, 2026

För min Technical Ledger-sektion ville jag att AI:n faktiskt skulle förstå innehållet — inte bara göra keyword-matchning. Målet med det här skriptet är enkelt: hämta mina ledger-poster från DatoCMS, omvandla dem till rena embeddings med OpenAI och lagra allt i Upstash Vector så att assistenten kan hämta rätt kontext senare.

Det här är inte tänkt att vara överdrivet smart. Det ska vara stabilt och enkelt att köra om varje gång nya ledger-poster publiceras.


The Retrieval Dilemma

Den svåra delen var inte att generera embeddings — det är ganska rakt på sak. Den verkliga utmaningen var att bestämma hur embedding-inputen ska se ut så att retrieval fortsätter vara träffsäker när ledgern växer.

Varje ledger-post i DatoCMS är modulär. I stället för att embeda en stor blob av markdown plattar jag ut de strukturerade promptNotes till ett förutsägbart format. På så sätt ser modellen tydliga semantiska gränser i stället för brusig fri text.


Scalability at the Edge

Själva skriptet körs som ett offline seed-steg, men jag optimerade ändå flödet för att bete sig bra vid större datamängder. Det viktigaste beslutet var att batcha embeddings i stället för att generera dem en och en.

När CMS-datan väl är hämtad går allt genom samma pipeline: normalize → embed → upsert. Även om ledgern växer rejält förändras inte processen — den hanterar bara fler rader.

TypeScript


Infrastructure Considerations

Seed-flödet är medvetet linjärt och lätt att resonera kring. Först hämtar jag alla ledger-poster från DatoCMS. Om inget kommer tillbaka avslutas skriptet tidigt så att CI inte råkar lyckas med tom data.

TypeScript

Därefter transformeras varje ledger till en ren embedding-payload. Hjälpfunktionen gör det mesta av det viktiga jobbet här — den plattar ut det modulära innehållet samtidigt som betydelsen bevaras.

TypeScript

Detta producerar konsekvent, retrieval-vänlig text i stället för rått CMS-brus.

När det är dags att generera embeddings skickas allt i en batch. Det håller processen snabb och undviker onödig API-overhead.

TypeScript

Till sist gör jag en upsert till Upstash Vector tillsammans med rik metadata. Metadatan är viktig eftersom retrieval-lagret senare använder saker som slug, kategori och titel för att bygga det slutliga svaret och länkar.

TypeScript

Vid det här laget har ledger-innehållet blivit sökbar kontext för AI:n.


Conclusion

Det här skriptet är den tysta arbetshästen bakom Technical Ledger-assistenten. Det tar strukturerad kunskap från DatoCMS, normaliserar den till embedding-vänlig text och pushar allt till Upstash Vector i ett rent flöde.

Inget fancy — bara en förutsägbar pipeline som jag kan köra om när nya ledger-poster släpps. Resultatet är att AI-svaren håller sig förankrade i verkligt innehåll i stället för att glida iväg i generiska svar.

typescript
// scripts/seed.ts
import { Index } from '@upstash/vector';
import OpenAI from 'openai';
import * as dotenv from 'dotenv';

dotenv.config({ path: '.env' });

const index = new Index({
  url: process.env.UPSTASH_VECTOR_REST_URL!,
  token: process.env.UPSTASH_VECTOR_REST_TOKEN!,
});

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
typescript
const {
  allTechnicalLedgers,
} = await datoCMS({
  query: getCombinedQuery([allTechnicalLedgersQuery]),
  variables: { locale: 'en' },
});
typescript
const flattenedContext = note.promptNotes
  .map((block) => `[${block.contextTitle}]\n${block.contextContent}`)
  .join('\n\n');
typescript
const embeddingsResponse = await openai.embeddings.create({
  model: 'text-embedding-3-small',
  input: seedData.map((item) => item.text),
});
typescript
await index.upsert(records);