Generative AI CAP development
A Retrieval-Augmented Generation (RAG) application built with SAP Cloud Application Programming Model (CAP), SAP HANA Cloud Vector Engine, and SAP Generative AI Hub.
Overview
This application allows users to:
- Upload documents (PDF, TXT, CSV) up to 10 MB
- Automatically extract text, chunk it, and generate embeddings
- Store embeddings in SAP HANA Cloud Vector Engine
- Chat with uploaded documents using GPT-4o via SAP Generative AI Hub
- Maintain conversation history with document-linked sessions
- Delete documents with cascade deletion of all related data
- View and delete failed (ERROR) documents
Application Architecture
High-Level Component Overview
123456789101112131415161718192021222324252627282930┌─────────────────────────────────────────────────────────────────────────────────┐
│ SAP Business Technology Platform │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Cloud Foundry Environment │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Web App │ │ CAP Service │ │ DB Deployer │ │ │
│ │ │ (UI5/Fiori) │─────▶│ (Node.js) │ │ (HDI Tasks) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ staticfile_ │ │ nodejs_ │ │ nodejs_ │ │ │
│ │ │ buildpack │ │ buildpack │ │ buildpack │ │ │
│ │ └─────────────────┘ └────────┬────────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ └────────────────────────────────────│────────────────────────│─────────────┘ │
│ │ │ │
│ ┌────────────────────────────────────│────────────────────────│─────────────┐ │
│ │ Services │ │ │
│ │ │ │ │ │
│ │ ┌─────────────────┐ ┌───────▼─────────┐ ┌───────▼─────────┐ │ │
│ │ │ SAP AI Core │ │ HANA Cloud │ │ HDI Container │ │ │
│ │ │ (Shared) │◀────▶│ Vector Engine │◀─────│ (Per User) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • GPT-4o │ │ • Vector(3072) │ │ • Tables │ │ │
│ │ │ • text-embed-3 │ │ • COSINE_SIM │ │ • Views │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
RAG Processing Flow
12345678910111213141516171819202122232425262728293031323334┌──────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Document │ │ CAP Service Layer │
│ Upload │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ PDF/TXT/CSV │────▶│ │ file-parser │───▶│ chunker │───▶│ embedder │ │
│ │ │ │ │ │ │ │ │ │
└──────────────┘ │ │ Extract │ │ Split into │ │ Generate │ │
│ │ raw text │ │ ~1000 token │ │ 3072-dim │ │
│ │ │ │ chunks │ │ vectors │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
└───────────────────────────────────────────────│──────────────┘
│
▼
┌──────────────-┐ ┌──────────────────────────────────────────────────────────────┐
│ User │ │ Chat Flow │
│ Question │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ "What is │────▶│ │ embedder │───▶│vector-search│───▶│ rag-engine │ │
│ this about?"│ │ │ │ │ │ │ │ │
│ │ │ │ Embed │ │ Find top-10 │ │ Build prompt│ │
└──────────────-┘ │ │ question │ │ similar │ │ + call GPT │ │
│ │ │ │ chunks │ │ │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌───────────────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Response │──────▶ "This document... │
│ │ + Sources │ covers topic X..." │
│ └─────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
User Workflow
- Upload Document: Click [+] button in Documents panel, select PDF/TXT/CSV file
- Wait for Processing: Status shows “Processing…”, auto-refreshes when complete
- Select Document: Click on READY document in left panel
- Start Chat Session: Click [+New] to create a session for the selected document
- Ask Questions: Type questions in the input bar, responses use only that document’s content
- Delete Document: Click delete button in document header (shows confirmation with counts)
- Delete Failed Document: Select ERROR document, click delete in the error panel
Data Model
The application uses four CDS entities organized into two functional domains.
Simplified ERD (Crow’s Foot Notation)
123456789101112131415161718192021222324252627┌──────────────────┐ ┌──────────────────┐
│ Documents │ │ DocumentChunks │
├──────────────────┤ ├──────────────────┤
│ ID (PK) │───────┬▶│ ID (PK) │
│ fileName │ │ │ document_ID (FK) │
│ fileType │ │ │ content │
│ fileSize │ │ │ chunkIndex │
│ status │ │ │ tokenCount │
│ chunkCount │ │ │ embedding │
│ errorMsg │ │ └──────────────────┘
│ createdAt │ │
│ modifiedAt │ │
└──────────────────┘ │ ┌──────────────────┐
│ │ │ ChatMessages │
│ 1 │ ├──────────────────┤
│ │ ┌────▶│ ID (PK) │
▼ * │ │ │ session_ID (FK) │
┌──────────────────┐ │ │ │ role │
│ ChatSessions │ │ │ │ content │
├──────────────────┤ │ │ │ timestamp │
│ ID (PK) │───────┘ │ │ sources │
│ document_ID (FK) │───────────┘ └──────────────────┘
│ title │ 1 : *
│ createdAt │
│ modifiedAt │
└──────────────────┘
Entity Descriptions
| Entity | Purpose | Key Fields |
|---|---|---|
| Documents | Stores metadata for uploaded files | status tracks processing state (UPLOADED → PROCESSING → READY/ERROR) |
| DocumentChunks | Stores text chunks with vector embeddings | embedding is a 3072-dimension vector for similarity search |
| ChatSessions | Groups chat messages into conversations, linked to a document | document_ID links session to a specific document for scoped chat |
| ChatMessages | Individual messages in a chat session | role is “user” or “assistant”, sources contains JSON of retrieved chunks |
SAP HANA Cloud Table Names
CDS entities are deployed to HANA with uppercase table names:
| CDS Entity | HANA Table |
|---|---|
| genai.rag.Documents | GENAI_RAG_DOCUMENTS |
| genai.rag.DocumentChunks | GENAI_RAG_DOCUMENTCHUNKS |
| genai.rag.ChatSessions | GENAI_RAG_CHATSESSIONS |
| genai.rag.ChatMessages | GENAI_RAG_CHATMESSAGES |
File Descriptions
Database Layer (db/)
| File | Description |
|---|---|
| schema.cds | CDS data model defining four entities: Documents (uploaded files metadata), DocumentChunks (text chunks with Vector(3072) embeddings), ChatSessions (conversation sessions), ChatMessages (individual messages with sources) |
Service Layer (srv/)
| File | Description |
|---|---|
| document-service.cds | OData service definition exposing Documents entity and actions: upload, getStatus, deleteDocument, getDeletePreview |
| document-service.js | Handler implementing document CRUD, cascade delete (chunks → sessions → messages), and delete preview |
| chat-service.cds | OData service definition exposing ChatSessions/ChatMessages and actions: createSession, updateSession, sendMessage, getSessionMessages, getDocumentSessions |
| chat-service.js | Handler implementing document-scoped chat with RAG pipeline integration |
| server.js | Custom Express middleware for multipart file upload (multer) and CORS headers |
Library Files (srv/lib/)
| File | Description |
|---|---|
| file-parser.js | Extracts text from uploaded files: pdf-parse for PDFs, direct buffer conversion for TXT, csv-parse for CSV (converts to “Column: Value” format) |
| chunker.js | Splits text into chunks of ~1000 tokens with ~200 token overlap, breaking at sentence boundaries for better context preservation |
| embedder.js | Wraps SAP AI SDK’s AzureOpenAiEmbeddingClient for text-embedding-3-large model, processes in batches of 20 texts |
| vector-search.js | Executes HANA SQL with COSINE_SIMILARITY() function to find top-K similar chunks to a query embedding |
| rag-engine.js | Wraps SAP AI SDK’s AzureOpenAiChatClient for gpt-4o, constructs RAG prompts with document context and chat history |
| upload-processor.js | Orchestrates async document processing: parse → chunk → embed → store with vector, updates document status |
Configuration Files
| File | Description |
|---|---|
| package.json | Dependencies (@sap/cds, @sap-ai-sdk/foundation-models, multer, pdf-parse, csv-parse) and CDS configuration |
| mta.yaml | Base MTA descriptor defining modules (srv, db-deployer, app) and resources (hana-hdi-rag, aicore) |
| .cdsrc.json | CDS build settings for HANA hdbtable format |
| .npmrc | npm cache directory configuration |
Deployment Files
| File | Description |
|---|---|
| setup-deployment.sh | Automated deployment script with --config (generate files) and --deploy (full deployment) modes |
| user-config.json | User configuration: USERNAME, REGION, DATABASE_ID, AICORE_SERVICE_NAME |
| user-config.mtaext | MTA extension template with placeholders |
| my-deployment.mtaext | Auto-generated MTA extension with user-specific routes and config |
| DEPLOYMENT_GUIDE.md | Detailed multi-user deployment instructions |
Try it out!
This application supports multi-user deployment where each user gets isolated apps and HDI container in a shared SAP BTP Cloud Foundry space.
Configure and setup SAP HANA Cloud Intelligent Application project
Create a new SAP HANA cloud Intelligent Application project using the setup wizard.
- Select to open {placeholder|bas_buildcode}">Business Application Studio to get started.
-
This will bring you into the SAP Build lobby.

-
Select the Create button and select the Clone from Git option.

-
Select the SAP HANA cloud Intelligent Application application type.

-
Complete the following project attributes and name project (_RAG_CAP):
Copy and paste in the following Git repository URL.
Code Snippet1https://github.com/boefbrak/genai_hana_rag_custom_pub.git- Name: {placeholder|userid}_RAG_CAP
- Description: {placeholder|userid} RAG Application with SAP AI core and SAP HANA Cloud
- Dev Space: {placeholder|userid}_DEV

-
Review the project configuration, and create it.


Configure and deploy application to Cloud Foundry
Once the _RAG_CAP project was created successfully the next steps would be to configure user specific settings.
-
Login to the Cloud Foundry account.


-
The Cloud Foundry Sign In panel will appear. Overwrite the Cloud Foundry Endpoint with the following value, and then choose SSO as the authentication method:
Login Option Selection Cloud Foundry Endpoint Authentication method SSO Passcode 
-
Enter academy-platform as the identity provider and then select the Sign in with alternative provider option.

-
Copy the generated passcode to the clipboard.

-
Paste the copied passcode in the Enter your SSO passcode field.

-
Sign into the Cloud Foundry Target by clicking the Sign In button.

-
Proceed by selecting the desired Cloud Foundry Target and Space as seen below.
Cloud Foundry Target Selection Cloud Foundry Organization Space dev 
Bring the project to life
This guide walks you through the code changes needed to bring the project to life. Each section tells you exactly which file to open, what it does, and what code to paste in.
-
Database Schema definition
File to update: db/schema.cds
Defines the four core database tables used by the application. Documents stores metadata about uploaded files (name, type, size, processing status). DocumentChunks holds the individual text segments split from each document, including a 3072-dimension vector embedding used for semantic similarity search. ChatSessions groups conversations and links them to a specific document. ChatMessages stores the full message history (both user and AI replies) along with the source chunks that informed each answer.
Find db/schema.cds and replace its contents with the following code:

Copy the CDS schema definition blow:
Code Snippet1234567891011121314151617181920212223242526272829303132333435namespace genai.rag; using { cuid, managed } from '@sap/cds/common'; entity Documents : cuid, managed { fileName : String(255) @mandatory; fileType : String(10) @mandatory; fileSize : Integer; status : String(20) default 'UPLOADED'; chunkCount : Integer default 0; errorMsg : String(1000); chunks : Composition of many DocumentChunks on chunks.document = $self; } entity DocumentChunks : cuid { document : Association to Documents @mandatory; content : LargeString @mandatory; chunkIndex : Integer @mandatory; tokenCount : Integer; embedding : Vector(3072); } entity ChatSessions : cuid, managed { title : String(200); document : Association to Documents; // Link session to document messages : Composition of many ChatMessages on messages.session = $self; } entity ChatMessages : cuid { session : Association to ChatSessions @mandatory; role : String(20) @mandatory; content : LargeString @mandatory; timestamp : Timestamp @cds.on.insert: $now; sources : LargeString; }
-
Document Service Definition
File to update: srv/document-service.cds
Exposes the Documents entity as an OData service at the /api/documents path. It hides the raw chunks association from API consumers (they only interact with document-level metadata). It also declares three operations: deleteDocument to remove a document and all its related data; getStatus to poll the processing state of an uploaded document; and getDeletePreview to show a summary of what will be deleted (sessions, messages, chunks) before a destructive action is confirmed.
Find srv/document-service.cds and replace its contents with the following code:
Code Snippet1234567891011121314151617181920using { genai.rag as db } from '../db/schema'; service DocumentService @(path: '/api/documents') { entity Documents as projection on db.Documents excluding { chunks }; action deleteDocument(documentId: UUID) returns Boolean; function getStatus(documentId: UUID) returns { status: String; chunkCount: Integer; errorMsg: String; }; function getDeletePreview(documentId: UUID) returns { sessionCount: Integer; messageCount: Integer; chunkCount: Integer; }; }
-
Chat Service Definition
File to update: srv/chat-service.cds
Exposes the chat functionality as an OData service at the /api/chat path. It surfaces ChatSessions and ChatMessages entities for reading. The sendMessage action is the core of the RAG pipeline — it accepts a session ID and a user message, then returns the AI-generated reply alongside the source document chunks that were used to produce it (including similarity scores). The remaining actions and functions handle session lifecycle: creating a new chat tied to a document, updating session metadata, and fetching messages or sessions by ID.
Find srv/chat-service.cds and replace its contents with the following code:
Code Snippet123456789101112131415161718192021222324using { genai.rag as db } from '../db/schema'; service ChatService @(path: '/api/chat') { entity ChatSessions as projection on db.ChatSessions excluding { messages }; entity ChatMessages as projection on db.ChatMessages; action sendMessage(sessionId: UUID, message: String) returns { reply: String; messageId: UUID; sources: array of { chunkId: UUID; documentName: String; content: String; similarity: Double; }; }; action createSession(documentId: UUID, title: String) returns ChatSessions; action updateSession(sessionId: UUID, documentId: UUID, title: String) returns ChatSessions; function getSessionMessages(sessionId: UUID) returns array of ChatMessages; function getDocumentSessions(documentId: UUID) returns array of ChatSessions; }
-
Implement RAG Engine
File to update: srv/lib/rag-engine.js
Implements the AI response logic using the SAP AI SDK’s AzureOpenAiChatClient pointed at the gpt-4o model. The client is initialised once and reused (singleton pattern). The generateRAGResponse function assembles a structured prompt: it injects a system instruction that constrains the model to answer only from provided document context, appends the relevant retrieved chunks (with source attribution and similarity percentages), replays prior chat history so the model is aware of the conversation, and finally adds the user’s current question. Buffer-to-string conversion handles HANA NCLOB columns that may arrive as Node.js Buffer objects rather than plain strings. The model is called with a moderate temperature (0.3) for factual, consistent answers.
Find srv/lib/rag-engine.js and replace its contents with the following code:
Code Snippet12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273const { AzureOpenAiChatClient } = require('@sap-ai-sdk/foundation-models'); let chatClient = null; function getChatClient() { if (!chatClient) { chatClient = new AzureOpenAiChatClient('gpt-4o'); } return chatClient; } const SYSTEM_PROMPT = `You are a helpful AI assistant that answers questions based on the provided document context. Rules: 1. Answer ONLY based on the provided context. If the context doesn't contain enough information, say so clearly. 2. Cite which document(s) your answer is based on when possible. 3. Be concise but thorough. 4. If the user's question is a greeting or general conversation, respond naturally. 5. Maintain a professional and helpful tone.`; async function generateRAGResponse({ query, chunks, history }) { const client = getChatClient(); const contextParts = chunks.map((chunk, idx) => { const similarity = (chunk.similarity * 100).toFixed(1); // Handle NCLOB content - may be Buffer or string let contentStr = chunk.content; if (Buffer.isBuffer(contentStr)) { contentStr = contentStr.toString('utf8'); } else if (typeof contentStr !== 'string') { contentStr = String(contentStr || ''); } return `[Source ${idx + 1}: "${chunk.documentName}", relevance: ${similarity}%]\n${contentStr}`; }); const contextBlock = contextParts.length > 0 ? `\n\n--- DOCUMENT CONTEXT ---\n${contextParts.join('\n\n---\n\n')}\n--- END CONTEXT ---\n\n` : '\n\n[No relevant documents found in the knowledge base.]\n\n'; const messages = []; messages.push({ role: 'system', content: SYSTEM_PROMPT + contextBlock }); // Add chat history (excluding the current user message which is last) const historyWithoutCurrent = history.slice(0, -1); for (const msg of historyWithoutCurrent) { if (msg.role === 'user' || msg.role === 'assistant') { // Handle NCLOB content - may be Buffer or string let msgContent = msg.content; if (Buffer.isBuffer(msgContent)) { msgContent = msgContent.toString('utf8'); } else if (typeof msgContent !== 'string') { msgContent = String(msgContent || ''); } messages.push({ role: msg.role, content: msgContent }); } } messages.push({ role: 'user', content: query }); const response = await client.run({ messages, max_tokens: 2000, temperature: 0.3 }); return response.getContent(); } module.exports = { generateRAGResponse };
Summary of Changes
File Layer Purpose db/schema.cds Database Creates the four entities: Documents, DocumentChunks, ChatSessions, ChatMessages srv/document-service.cds Service API Exposes document upload/management endpoints via OData srv/chat-service.cds Service API Exposes chat and RAG pipeline endpoints via OData srv/lib/rag-engine.js Business Logic Calls GPT-4o via SAP AI SDK to generate context-aware answers After applying all four changes, the backend data model, OData APIs, and AI response engine will be fully wired up.
Quick Start (Automated Deployment)
This is an automated deployment process using the setup-deployment.sh. Automated deployment script with --config (generate files) and --deploy (full deployment) modes.
-
Select and open a new Terminal

-
Verify that cloud foundry login was succesfull once the terminal is open by inspecting all running applications in the dev space.
Code Snippet1cf apps
-
Update the user-config.json file with the custom configuration below by copying and replacing the user-config.json template.

Copy the custom configuration below and paste it in the user-config.json template:
Code Snippet123456789101112131415161718{ "// INSTRUCTIONS": "Edit values below, then run: ./setup-deployment.sh --deploy", "USERNAME": "{placeholder|userid}", "REGION": "{placeholder|REGION}", "DATABASE_ID": "{placeholder|DATABASE_ID}", "AICORE_SERVICE_NAME": "default_aicore", "// NAMING_CONVENTION": { "CF_APP_SERVICE": "{USERNAME}-genai-hana-rag-srv", "CF_APP_WEBAPP": "{USERNAME}-genai-hana-rag-app", "CF_APP_DEPLOYER": "{USERNAME}-genai-hana-rag-db-deployer", "HDI_CONTAINER": "{USERNAME}-hana-hdi-rag", "AI_CORE": "default_aicore (shared)", "SERVICE_URL": "https://{USERNAME}-genai-hana-rag-srv.cfapps.{REGION}.hana.ondemand.com", "APP_URL": "https://{USERNAME}-genai-hana-rag-app.cfapps.{REGION}.hana.ondemand.com" } }The user-config.json file should resemble something similar:

-
Run the setup-deployment.sh --config from the terminal, the script will automatically:
- Validate your configuration
- Generate the MTA extension file
- Update the frontend config
- Build the application
- Deploy with namespace isolation
- Bind the shared AI Core service
- Restage the service app
Code Snippet1./setup-deployment.sh --config
-
Once the user configuration is completed continue with the application deployment to the Cloud foundry dev space.
Code Snippet1./setup-deployment.sh --deploy


Run the CAP application
Download the following sample data set Product Catalog and save it you your local machine. The catalog contains extended product catalog information.
-
Upload the product catalog file once the file is saved.



-
Notice the PROCESSING status, this means the document is being processed (chunking, embedding).

-
Once the status changes to READY the document ready for chat.

-
Explore the product catalog by “chatting” to it. This is purely an example, you can play around with this RAG application by uploading sample documents and interacting with is.
Code Snippet1234I need to purchase a wireless mouse in the Berlin area. Provide me with a list of: - warehouses where I can collect the item from - quantity of available stock - item price.

API Reference
Document Service (/api/documents)
| Endpoint | Method | Description |
|---|---|---|
| /Documents | GET | List all documents |
| /Documents('{id}') | GET | Get document by ID |
| /upload | POST | Upload file (multipart/form-data) |
| /getStatus(documentId='{id}') | GET | Get document processing status |
| /deleteDocument | POST | Delete document with cascade (chunks, sessions, messages) |
| /getDeletePreview(documentId='{id}') | GET | Get counts of related data before delete |
Chat Service (/api/chat)
| Endpoint | Method | Description |
|---|---|---|
| /ChatSessions | GET | List all sessions |
| /createSession | POST | Create session linked to a document |
| /updateSession | POST | Update session (reassign document, change title) |
| /getDocumentSessions(documentId='{id}') | GET | Get all sessions for a document |
| /getSessionMessages(sessionId='{id}') | GET | Get messages for session |
| /sendMessage | POST | Send message and get RAG response (scoped to session’s document) |
Technical Details
Embedding Configuration
- Model: text-embedding-3-large
- Dimensions: 3072
- Batch Size: 20 texts per API call
Chunking Configuration
- Chunk Size: ~1000 tokens (~4000 characters)
- Overlap: ~200 tokens (~800 characters)
- Boundary: Sentence-aware splitting
RAG Configuration
- Model: gpt-4o
- Temperature: 0.3
- Max Tokens: 2000
- Context: Top 10 similar chunks
- History: Last 10 messages
Vector Search
- Algorithm: Cosine Similarity
- Function: COSINE_SIMILARITY() in HANA SQL
- Top-K: 10 chunks returned
A Retrieval-Augmented Generation (RAG) application built with SAP Cloud Application Programming Model (CAP), SAP HANA Cloud Vector Engine, and SAP Generative AI Hub.
Overview
This application allows users to:
- Upload documents (PDF, TXT, CSV) up to 10 MB
- Automatically extract text, chunk it, and generate embeddings
- Store embeddings in SAP HANA Cloud Vector Engine
- Chat with uploaded documents using GPT-4o via SAP Generative AI Hub
- Maintain conversation history with document-linked sessions
- Delete documents with cascade deletion of all related data
- View and delete failed (ERROR) documents
Application Architecture
High-Level Component Overview
123456789101112131415161718192021222324252627282930┌─────────────────────────────────────────────────────────────────────────────────┐
│ SAP Business Technology Platform │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Cloud Foundry Environment │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Web App │ │ CAP Service │ │ DB Deployer │ │ │
│ │ │ (UI5/Fiori) │─────▶│ (Node.js) │ │ (HDI Tasks) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ staticfile_ │ │ nodejs_ │ │ nodejs_ │ │ │
│ │ │ buildpack │ │ buildpack │ │ buildpack │ │ │
│ │ └─────────────────┘ └────────┬────────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ └────────────────────────────────────│────────────────────────│─────────────┘ │
│ │ │ │
│ ┌────────────────────────────────────│────────────────────────│─────────────┐ │
│ │ Services │ │ │
│ │ │ │ │ │
│ │ ┌─────────────────┐ ┌───────▼─────────┐ ┌───────▼─────────┐ │ │
│ │ │ SAP AI Core │ │ HANA Cloud │ │ HDI Container │ │ │
│ │ │ (Shared) │◀────▶│ Vector Engine │◀─────│ (Per User) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • GPT-4o │ │ • Vector(3072) │ │ • Tables │ │ │
│ │ │ • text-embed-3 │ │ • COSINE_SIM │ │ • Views │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
RAG Processing Flow
12345678910111213141516171819202122232425262728293031323334┌──────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Document │ │ CAP Service Layer │
│ Upload │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ PDF/TXT/CSV │────▶│ │ file-parser │───▶│ chunker │───▶│ embedder │ │
│ │ │ │ │ │ │ │ │ │
└──────────────┘ │ │ Extract │ │ Split into │ │ Generate │ │
│ │ raw text │ │ ~1000 token │ │ 3072-dim │ │
│ │ │ │ chunks │ │ vectors │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
└───────────────────────────────────────────────│──────────────┘
│
▼
┌──────────────-┐ ┌──────────────────────────────────────────────────────────────┐
│ User │ │ Chat Flow │
│ Question │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ "What is │────▶│ │ embedder │───▶│vector-search│───▶│ rag-engine │ │
│ this about?"│ │ │ │ │ │ │ │ │
│ │ │ │ Embed │ │ Find top-10 │ │ Build prompt│ │
└──────────────-┘ │ │ question │ │ similar │ │ + call GPT │ │
│ │ │ │ chunks │ │ │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌───────────────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Response │──────▶ "This document... │
│ │ + Sources │ covers topic X..." │
│ └─────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
User Workflow
- Upload Document: Click [+] button in Documents panel, select PDF/TXT/CSV file
- Wait for Processing: Status shows “Processing…”, auto-refreshes when complete
- Select Document: Click on READY document in left panel
- Start Chat Session: Click [+New] to create a session for the selected document
- Ask Questions: Type questions in the input bar, responses use only that document’s content
- Delete Document: Click delete button in document header (shows confirmation with counts)
- Delete Failed Document: Select ERROR document, click delete in the error panel
Data Model
The application uses four CDS entities organized into two functional domains.
Simplified ERD (Crow’s Foot Notation)
123456789101112131415161718192021222324252627┌──────────────────┐ ┌──────────────────┐
│ Documents │ │ DocumentChunks │
├──────────────────┤ ├──────────────────┤
│ ID (PK) │───────┬▶│ ID (PK) │
│ fileName │ │ │ document_ID (FK) │
│ fileType │ │ │ content │
│ fileSize │ │ │ chunkIndex │
│ status │ │ │ tokenCount │
│ chunkCount │ │ │ embedding │
│ errorMsg │ │ └──────────────────┘
│ createdAt │ │
│ modifiedAt │ │
└──────────────────┘ │ ┌──────────────────┐
│ │ │ ChatMessages │
│ 1 │ ├──────────────────┤
│ │ ┌────▶│ ID (PK) │
▼ * │ │ │ session_ID (FK) │
┌──────────────────┐ │ │ │ role │
│ ChatSessions │ │ │ │ content │
├──────────────────┤ │ │ │ timestamp │
│ ID (PK) │───────┘ │ │ sources │
│ document_ID (FK) │───────────┘ └──────────────────┘
│ title │ 1 : *
│ createdAt │
│ modifiedAt │
└──────────────────┘
Entity Descriptions
| Entity | Purpose | Key Fields |
|---|---|---|
| Documents | Stores metadata for uploaded files | status tracks processing state (UPLOADED → PROCESSING → READY/ERROR) |
| DocumentChunks | Stores text chunks with vector embeddings | embedding is a 3072-dimension vector for similarity search |
| ChatSessions | Groups chat messages into conversations, linked to a document | document_ID links session to a specific document for scoped chat |
| ChatMessages | Individual messages in a chat session | role is “user” or “assistant”, sources contains JSON of retrieved chunks |
SAP HANA Cloud Table Names
CDS entities are deployed to HANA with uppercase table names:
| CDS Entity | HANA Table |
|---|---|
| genai.rag.Documents | GENAI_RAG_DOCUMENTS |
| genai.rag.DocumentChunks | GENAI_RAG_DOCUMENTCHUNKS |
| genai.rag.ChatSessions | GENAI_RAG_CHATSESSIONS |
| genai.rag.ChatMessages | GENAI_RAG_CHATMESSAGES |
File Descriptions
Database Layer (db/)
| File | Description |
|---|---|
| schema.cds | CDS data model defining four entities: Documents (uploaded files metadata), DocumentChunks (text chunks with Vector(3072) embeddings), ChatSessions (conversation sessions), ChatMessages (individual messages with sources) |
Service Layer (srv/)
| File | Description |
|---|---|
| document-service.cds | OData service definition exposing Documents entity and actions: upload, getStatus, deleteDocument, getDeletePreview |
| document-service.js | Handler implementing document CRUD, cascade delete (chunks → sessions → messages), and delete preview |
| chat-service.cds | OData service definition exposing ChatSessions/ChatMessages and actions: createSession, updateSession, sendMessage, getSessionMessages, getDocumentSessions |
| chat-service.js | Handler implementing document-scoped chat with RAG pipeline integration |
| server.js | Custom Express middleware for multipart file upload (multer) and CORS headers |
Library Files (srv/lib/)
| File | Description |
|---|---|
| file-parser.js | Extracts text from uploaded files: pdf-parse for PDFs, direct buffer conversion for TXT, csv-parse for CSV (converts to “Column: Value” format) |
| chunker.js | Splits text into chunks of ~1000 tokens with ~200 token overlap, breaking at sentence boundaries for better context preservation |
| embedder.js | Wraps SAP AI SDK’s AzureOpenAiEmbeddingClient for text-embedding-3-large model, processes in batches of 20 texts |
| vector-search.js | Executes HANA SQL with COSINE_SIMILARITY() function to find top-K similar chunks to a query embedding |
| rag-engine.js | Wraps SAP AI SDK’s AzureOpenAiChatClient for gpt-4o, constructs RAG prompts with document context and chat history |
| upload-processor.js | Orchestrates async document processing: parse → chunk → embed → store with vector, updates document status |
Configuration Files
| File | Description |
|---|---|
| package.json | Dependencies (@sap/cds, @sap-ai-sdk/foundation-models, multer, pdf-parse, csv-parse) and CDS configuration |
| mta.yaml | Base MTA descriptor defining modules (srv, db-deployer, app) and resources (hana-hdi-rag, aicore) |
| .cdsrc.json | CDS build settings for HANA hdbtable format |
| .npmrc | npm cache directory configuration |
Deployment Files
| File | Description |
|---|---|
| setup-deployment.sh | Automated deployment script with --config (generate files) and --deploy (full deployment) modes |
| user-config.json | User configuration: USERNAME, REGION, DATABASE_ID, AICORE_SERVICE_NAME |
| user-config.mtaext | MTA extension template with placeholders |
| my-deployment.mtaext | Auto-generated MTA extension with user-specific routes and config |
| DEPLOYMENT_GUIDE.md | Detailed multi-user deployment instructions |
Try it out!
This application supports multi-user deployment where each user gets isolated apps and HDI container in a shared SAP BTP Cloud Foundry space.
Configure and setup SAP HANA Cloud Intelligent Application project
Create a new SAP HANA cloud Intelligent Application project using the setup wizard.
- Select to open {placeholder|bas_buildcode}">Business Application Studio to get started.
-
This will bring you into the SAP Build lobby.

-
Select the Create button and select the Clone from Git option.

-
Select the SAP HANA cloud Intelligent Application application type.

-
Complete the following project attributes and name project (_RAG_CAP):
Copy and paste in the following Git repository URL.
Code Snippet1https://github.com/boefbrak/genai_hana_rag_custom_pub.git- Name: {placeholder|userid}_RAG_CAP
- Description: {placeholder|userid} RAG Application with SAP AI core and SAP HANA Cloud
- Dev Space: {placeholder|userid}_DEV

-
Review the project configuration, and create it.


Configure and deploy application to Cloud Foundry
Once the _RAG_CAP project was created successfully the next steps would be to configure user specific settings.
-
Login to the Cloud Foundry account.


-
The Cloud Foundry Sign In panel will appear. Overwrite the Cloud Foundry Endpoint with the following value, and then choose SSO as the authentication method:
Login Option Selection Cloud Foundry Endpoint Authentication method SSO Passcode 
-
Enter academy-platform as the identity provider and then select the Sign in with alternative provider option.

-
Copy the generated passcode to the clipboard.

-
Paste the copied passcode in the Enter your SSO passcode field.

-
Sign into the Cloud Foundry Target by clicking the Sign In button.

-
Proceed by selecting the desired Cloud Foundry Target and Space as seen below.
Cloud Foundry Target Selection Cloud Foundry Organization Space dev 
Bring the project to life
This guide walks you through the code changes needed to bring the project to life. Each section tells you exactly which file to open, what it does, and what code to paste in.
-
Database Schema definition
File to update: db/schema.cds
Defines the four core database tables used by the application. Documents stores metadata about uploaded files (name, type, size, processing status). DocumentChunks holds the individual text segments split from each document, including a 3072-dimension vector embedding used for semantic similarity search. ChatSessions groups conversations and links them to a specific document. ChatMessages stores the full message history (both user and AI replies) along with the source chunks that informed each answer.
Find db/schema.cds and replace its contents with the following code:

Copy the CDS schema definition blow:
Code Snippet1234567891011121314151617181920212223242526272829303132333435namespace genai.rag; using { cuid, managed } from '@sap/cds/common'; entity Documents : cuid, managed { fileName : String(255) @mandatory; fileType : String(10) @mandatory; fileSize : Integer; status : String(20) default 'UPLOADED'; chunkCount : Integer default 0; errorMsg : String(1000); chunks : Composition of many DocumentChunks on chunks.document = $self; } entity DocumentChunks : cuid { document : Association to Documents @mandatory; content : LargeString @mandatory; chunkIndex : Integer @mandatory; tokenCount : Integer; embedding : Vector(3072); } entity ChatSessions : cuid, managed { title : String(200); document : Association to Documents; // Link session to document messages : Composition of many ChatMessages on messages.session = $self; } entity ChatMessages : cuid { session : Association to ChatSessions @mandatory; role : String(20) @mandatory; content : LargeString @mandatory; timestamp : Timestamp @cds.on.insert: $now; sources : LargeString; }
-
Document Service Definition
File to update: srv/document-service.cds
Exposes the Documents entity as an OData service at the /api/documents path. It hides the raw chunks association from API consumers (they only interact with document-level metadata). It also declares three operations: deleteDocument to remove a document and all its related data; getStatus to poll the processing state of an uploaded document; and getDeletePreview to show a summary of what will be deleted (sessions, messages, chunks) before a destructive action is confirmed.
Find srv/document-service.cds and replace its contents with the following code:
Code Snippet1234567891011121314151617181920using { genai.rag as db } from '../db/schema'; service DocumentService @(path: '/api/documents') { entity Documents as projection on db.Documents excluding { chunks }; action deleteDocument(documentId: UUID) returns Boolean; function getStatus(documentId: UUID) returns { status: String; chunkCount: Integer; errorMsg: String; }; function getDeletePreview(documentId: UUID) returns { sessionCount: Integer; messageCount: Integer; chunkCount: Integer; }; }
-
Chat Service Definition
File to update: srv/chat-service.cds
Exposes the chat functionality as an OData service at the /api/chat path. It surfaces ChatSessions and ChatMessages entities for reading. The sendMessage action is the core of the RAG pipeline — it accepts a session ID and a user message, then returns the AI-generated reply alongside the source document chunks that were used to produce it (including similarity scores). The remaining actions and functions handle session lifecycle: creating a new chat tied to a document, updating session metadata, and fetching messages or sessions by ID.
Find srv/chat-service.cds and replace its contents with the following code:
Code Snippet123456789101112131415161718192021222324using { genai.rag as db } from '../db/schema'; service ChatService @(path: '/api/chat') { entity ChatSessions as projection on db.ChatSessions excluding { messages }; entity ChatMessages as projection on db.ChatMessages; action sendMessage(sessionId: UUID, message: String) returns { reply: String; messageId: UUID; sources: array of { chunkId: UUID; documentName: String; content: String; similarity: Double; }; }; action createSession(documentId: UUID, title: String) returns ChatSessions; action updateSession(sessionId: UUID, documentId: UUID, title: String) returns ChatSessions; function getSessionMessages(sessionId: UUID) returns array of ChatMessages; function getDocumentSessions(documentId: UUID) returns array of ChatSessions; }
-
Implement RAG Engine
File to update: srv/lib/rag-engine.js
Implements the AI response logic using the SAP AI SDK’s AzureOpenAiChatClient pointed at the gpt-4o model. The client is initialised once and reused (singleton pattern). The generateRAGResponse function assembles a structured prompt: it injects a system instruction that constrains the model to answer only from provided document context, appends the relevant retrieved chunks (with source attribution and similarity percentages), replays prior chat history so the model is aware of the conversation, and finally adds the user’s current question. Buffer-to-string conversion handles HANA NCLOB columns that may arrive as Node.js Buffer objects rather than plain strings. The model is called with a moderate temperature (0.3) for factual, consistent answers.
Find srv/lib/rag-engine.js and replace its contents with the following code:
Code Snippet12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273const { AzureOpenAiChatClient } = require('@sap-ai-sdk/foundation-models'); let chatClient = null; function getChatClient() { if (!chatClient) { chatClient = new AzureOpenAiChatClient('gpt-4o'); } return chatClient; } const SYSTEM_PROMPT = `You are a helpful AI assistant that answers questions based on the provided document context. Rules: 1. Answer ONLY based on the provided context. If the context doesn't contain enough information, say so clearly. 2. Cite which document(s) your answer is based on when possible. 3. Be concise but thorough. 4. If the user's question is a greeting or general conversation, respond naturally. 5. Maintain a professional and helpful tone.`; async function generateRAGResponse({ query, chunks, history }) { const client = getChatClient(); const contextParts = chunks.map((chunk, idx) => { const similarity = (chunk.similarity * 100).toFixed(1); // Handle NCLOB content - may be Buffer or string let contentStr = chunk.content; if (Buffer.isBuffer(contentStr)) { contentStr = contentStr.toString('utf8'); } else if (typeof contentStr !== 'string') { contentStr = String(contentStr || ''); } return `[Source ${idx + 1}: "${chunk.documentName}", relevance: ${similarity}%]\n${contentStr}`; }); const contextBlock = contextParts.length > 0 ? `\n\n--- DOCUMENT CONTEXT ---\n${contextParts.join('\n\n---\n\n')}\n--- END CONTEXT ---\n\n` : '\n\n[No relevant documents found in the knowledge base.]\n\n'; const messages = []; messages.push({ role: 'system', content: SYSTEM_PROMPT + contextBlock }); // Add chat history (excluding the current user message which is last) const historyWithoutCurrent = history.slice(0, -1); for (const msg of historyWithoutCurrent) { if (msg.role === 'user' || msg.role === 'assistant') { // Handle NCLOB content - may be Buffer or string let msgContent = msg.content; if (Buffer.isBuffer(msgContent)) { msgContent = msgContent.toString('utf8'); } else if (typeof msgContent !== 'string') { msgContent = String(msgContent || ''); } messages.push({ role: msg.role, content: msgContent }); } } messages.push({ role: 'user', content: query }); const response = await client.run({ messages, max_tokens: 2000, temperature: 0.3 }); return response.getContent(); } module.exports = { generateRAGResponse };
Summary of Changes
File Layer Purpose db/schema.cds Database Creates the four entities: Documents, DocumentChunks, ChatSessions, ChatMessages srv/document-service.cds Service API Exposes document upload/management endpoints via OData srv/chat-service.cds Service API Exposes chat and RAG pipeline endpoints via OData srv/lib/rag-engine.js Business Logic Calls GPT-4o via SAP AI SDK to generate context-aware answers After applying all four changes, the backend data model, OData APIs, and AI response engine will be fully wired up.
Quick Start (Automated Deployment)
This is an automated deployment process using the setup-deployment.sh. Automated deployment script with --config (generate files) and --deploy (full deployment) modes.
-
Select and open a new Terminal

-
Verify that cloud foundry login was succesfull once the terminal is open by inspecting all running applications in the dev space.
Code Snippet1cf apps
-
Update the user-config.json file with the custom configuration below by copying and replacing the user-config.json template.

Copy the custom configuration below and paste it in the user-config.json template:
Code Snippet123456789101112131415161718{ "// INSTRUCTIONS": "Edit values below, then run: ./setup-deployment.sh --deploy", "USERNAME": "{placeholder|userid}", "REGION": "{placeholder|REGION}", "DATABASE_ID": "{placeholder|DATABASE_ID}", "AICORE_SERVICE_NAME": "default_aicore", "// NAMING_CONVENTION": { "CF_APP_SERVICE": "{USERNAME}-genai-hana-rag-srv", "CF_APP_WEBAPP": "{USERNAME}-genai-hana-rag-app", "CF_APP_DEPLOYER": "{USERNAME}-genai-hana-rag-db-deployer", "HDI_CONTAINER": "{USERNAME}-hana-hdi-rag", "AI_CORE": "default_aicore (shared)", "SERVICE_URL": "https://{USERNAME}-genai-hana-rag-srv.cfapps.{REGION}.hana.ondemand.com", "APP_URL": "https://{USERNAME}-genai-hana-rag-app.cfapps.{REGION}.hana.ondemand.com" } }The user-config.json file should resemble something similar:

-
Run the setup-deployment.sh --config from the terminal, the script will automatically:
- Validate your configuration
- Generate the MTA extension file
- Update the frontend config
- Build the application
- Deploy with namespace isolation
- Bind the shared AI Core service
- Restage the service app
Code Snippet1./setup-deployment.sh --config
-
Once the user configuration is completed continue with the application deployment to the Cloud foundry dev space.
Code Snippet1./setup-deployment.sh --deploy


Run the CAP application
Download the following sample data set Product Catalog and save it you your local machine. The catalog contains extended product catalog information.
-
Upload the product catalog file once the file is saved.



-
Notice the PROCESSING status, this means the document is being processed (chunking, embedding).

-
Once the status changes to READY the document ready for chat.

-
Explore the product catalog by “chatting” to it. This is purely an example, you can play around with this RAG application by uploading sample documents and interacting with is.
Code Snippet1234I need to purchase a wireless mouse in the Berlin area. Provide me with a list of: - warehouses where I can collect the item from - quantity of available stock - item price.

API Reference
Document Service (/api/documents)
| Endpoint | Method | Description |
|---|---|---|
| /Documents | GET | List all documents |
| /Documents('{id}') | GET | Get document by ID |
| /upload | POST | Upload file (multipart/form-data) |
| /getStatus(documentId='{id}') | GET | Get document processing status |
| /deleteDocument | POST | Delete document with cascade (chunks, sessions, messages) |
| /getDeletePreview(documentId='{id}') | GET | Get counts of related data before delete |
Chat Service (/api/chat)
| Endpoint | Method | Description |
|---|---|---|
| /ChatSessions | GET | List all sessions |
| /createSession | POST | Create session linked to a document |
| /updateSession | POST | Update session (reassign document, change title) |
| /getDocumentSessions(documentId='{id}') | GET | Get all sessions for a document |
| /getSessionMessages(sessionId='{id}') | GET | Get messages for session |
| /sendMessage | POST | Send message and get RAG response (scoped to session’s document) |
Technical Details
Embedding Configuration
- Model: text-embedding-3-large
- Dimensions: 3072
- Batch Size: 20 texts per API call
Chunking Configuration
- Chunk Size: ~1000 tokens (~4000 characters)
- Overlap: ~200 tokens (~800 characters)
- Boundary: Sentence-aware splitting
RAG Configuration
- Model: gpt-4o
- Temperature: 0.3
- Max Tokens: 2000
- Context: Top 10 similar chunks
- History: Last 10 messages
Vector Search
- Algorithm: Cosine Similarity
- Function: COSINE_SIMILARITY() in HANA SQL
- Top-K: 10 chunks returned
A Retrieval-Augmented Generation (RAG) application built with SAP Cloud Application Programming Model (CAP), SAP HANA Cloud Vector Engine, and SAP Generative AI Hub.
Overview
This application allows users to:
- Upload documents (PDF, TXT, CSV) up to 10 MB
- Automatically extract text, chunk it, and generate embeddings
- Store embeddings in SAP HANA Cloud Vector Engine
- Chat with uploaded documents using GPT-4o via SAP Generative AI Hub
- Maintain conversation history with document-linked sessions
- Delete documents with cascade deletion of all related data
- View and delete failed (ERROR) documents
Application Architecture
High-Level Component Overview
123456789101112131415161718192021222324252627282930┌─────────────────────────────────────────────────────────────────────────────────┐
│ SAP Business Technology Platform │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Cloud Foundry Environment │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Web App │ │ CAP Service │ │ DB Deployer │ │ │
│ │ │ (UI5/Fiori) │─────▶│ (Node.js) │ │ (HDI Tasks) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ staticfile_ │ │ nodejs_ │ │ nodejs_ │ │ │
│ │ │ buildpack │ │ buildpack │ │ buildpack │ │ │
│ │ └─────────────────┘ └────────┬────────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ └────────────────────────────────────│────────────────────────│─────────────┘ │
│ │ │ │
│ ┌────────────────────────────────────│────────────────────────│─────────────┐ │
│ │ Services │ │ │
│ │ │ │ │ │
│ │ ┌─────────────────┐ ┌───────▼─────────┐ ┌───────▼─────────┐ │ │
│ │ │ SAP AI Core │ │ HANA Cloud │ │ HDI Container │ │ │
│ │ │ (Shared) │◀────▶│ Vector Engine │◀─────│ (Per User) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • GPT-4o │ │ • Vector(3072) │ │ • Tables │ │ │
│ │ │ • text-embed-3 │ │ • COSINE_SIM │ │ • Views │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
RAG Processing Flow
12345678910111213141516171819202122232425262728293031323334┌──────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Document │ │ CAP Service Layer │
│ Upload │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ PDF/TXT/CSV │────▶│ │ file-parser │───▶│ chunker │───▶│ embedder │ │
│ │ │ │ │ │ │ │ │ │
└──────────────┘ │ │ Extract │ │ Split into │ │ Generate │ │
│ │ raw text │ │ ~1000 token │ │ 3072-dim │ │
│ │ │ │ chunks │ │ vectors │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
└───────────────────────────────────────────────│──────────────┘
│
▼
┌──────────────-┐ ┌──────────────────────────────────────────────────────────────┐
│ User │ │ Chat Flow │
│ Question │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ "What is │────▶│ │ embedder │───▶│vector-search│───▶│ rag-engine │ │
│ this about?"│ │ │ │ │ │ │ │ │
│ │ │ │ Embed │ │ Find top-10 │ │ Build prompt│ │
└──────────────-┘ │ │ question │ │ similar │ │ + call GPT │ │
│ │ │ │ chunks │ │ │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌───────────────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Response │──────▶ "This document... │
│ │ + Sources │ covers topic X..." │
│ └─────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
User Workflow
- Upload Document: Click [+] button in Documents panel, select PDF/TXT/CSV file
- Wait for Processing: Status shows “Processing…”, auto-refreshes when complete
- Select Document: Click on READY document in left panel
- Start Chat Session: Click [+New] to create a session for the selected document
- Ask Questions: Type questions in the input bar, responses use only that document’s content
- Delete Document: Click delete button in document header (shows confirmation with counts)
- Delete Failed Document: Select ERROR document, click delete in the error panel
Data Model
The application uses four CDS entities organized into two functional domains.
Simplified ERD (Crow’s Foot Notation)
123456789101112131415161718192021222324252627┌──────────────────┐ ┌──────────────────┐
│ Documents │ │ DocumentChunks │
├──────────────────┤ ├──────────────────┤
│ ID (PK) │───────┬▶│ ID (PK) │
│ fileName │ │ │ document_ID (FK) │
│ fileType │ │ │ content │
│ fileSize │ │ │ chunkIndex │
│ status │ │ │ tokenCount │
│ chunkCount │ │ │ embedding │
│ errorMsg │ │ └──────────────────┘
│ createdAt │ │
│ modifiedAt │ │
└──────────────────┘ │ ┌──────────────────┐
│ │ │ ChatMessages │
│ 1 │ ├──────────────────┤
│ │ ┌────▶│ ID (PK) │
▼ * │ │ │ session_ID (FK) │
┌──────────────────┐ │ │ │ role │
│ ChatSessions │ │ │ │ content │
├──────────────────┤ │ │ │ timestamp │
│ ID (PK) │───────┘ │ │ sources │
│ document_ID (FK) │───────────┘ └──────────────────┘
│ title │ 1 : *
│ createdAt │
│ modifiedAt │
└──────────────────┘
Entity Descriptions
| Entity | Purpose | Key Fields |
|---|---|---|
| Documents | Stores metadata for uploaded files | status tracks processing state (UPLOADED → PROCESSING → READY/ERROR) |
| DocumentChunks | Stores text chunks with vector embeddings | embedding is a 3072-dimension vector for similarity search |
| ChatSessions | Groups chat messages into conversations, linked to a document | document_ID links session to a specific document for scoped chat |
| ChatMessages | Individual messages in a chat session | role is “user” or “assistant”, sources contains JSON of retrieved chunks |
SAP HANA Cloud Table Names
CDS entities are deployed to HANA with uppercase table names:
| CDS Entity | HANA Table |
|---|---|
| genai.rag.Documents | GENAI_RAG_DOCUMENTS |
| genai.rag.DocumentChunks | GENAI_RAG_DOCUMENTCHUNKS |
| genai.rag.ChatSessions | GENAI_RAG_CHATSESSIONS |
| genai.rag.ChatMessages | GENAI_RAG_CHATMESSAGES |
File Descriptions
Database Layer (db/)
| File | Description |
|---|---|
| schema.cds | CDS data model defining four entities: Documents (uploaded files metadata), DocumentChunks (text chunks with Vector(3072) embeddings), ChatSessions (conversation sessions), ChatMessages (individual messages with sources) |
Service Layer (srv/)
| File | Description |
|---|---|
| document-service.cds | OData service definition exposing Documents entity and actions: upload, getStatus, deleteDocument, getDeletePreview |
| document-service.js | Handler implementing document CRUD, cascade delete (chunks → sessions → messages), and delete preview |
| chat-service.cds | OData service definition exposing ChatSessions/ChatMessages and actions: createSession, updateSession, sendMessage, getSessionMessages, getDocumentSessions |
| chat-service.js | Handler implementing document-scoped chat with RAG pipeline integration |
| server.js | Custom Express middleware for multipart file upload (multer) and CORS headers |
Library Files (srv/lib/)
| File | Description |
|---|---|
| file-parser.js | Extracts text from uploaded files: pdf-parse for PDFs, direct buffer conversion for TXT, csv-parse for CSV (converts to “Column: Value” format) |
| chunker.js | Splits text into chunks of ~1000 tokens with ~200 token overlap, breaking at sentence boundaries for better context preservation |
| embedder.js | Wraps SAP AI SDK’s AzureOpenAiEmbeddingClient for text-embedding-3-large model, processes in batches of 20 texts |
| vector-search.js | Executes HANA SQL with COSINE_SIMILARITY() function to find top-K similar chunks to a query embedding |
| rag-engine.js | Wraps SAP AI SDK’s AzureOpenAiChatClient for gpt-4o, constructs RAG prompts with document context and chat history |
| upload-processor.js | Orchestrates async document processing: parse → chunk → embed → store with vector, updates document status |
Configuration Files
| File | Description |
|---|---|
| package.json | Dependencies (@sap/cds, @sap-ai-sdk/foundation-models, multer, pdf-parse, csv-parse) and CDS configuration |
| mta.yaml | Base MTA descriptor defining modules (srv, db-deployer, app) and resources (hana-hdi-rag, aicore) |
| .cdsrc.json | CDS build settings for HANA hdbtable format |
| .npmrc | npm cache directory configuration |
Deployment Files
| File | Description |
|---|---|
| setup-deployment.sh | Automated deployment script with --config (generate files) and --deploy (full deployment) modes |
| user-config.json | User configuration: USERNAME, REGION, DATABASE_ID, AICORE_SERVICE_NAME |
| user-config.mtaext | MTA extension template with placeholders |
| my-deployment.mtaext | Auto-generated MTA extension with user-specific routes and config |
| DEPLOYMENT_GUIDE.md | Detailed multi-user deployment instructions |
Try it out!
This application supports multi-user deployment where each user gets isolated apps and HDI container in a shared SAP BTP Cloud Foundry space.
Configure and setup SAP HANA Cloud Intelligent Application project
Create a new SAP HANA cloud Intelligent Application project using the setup wizard.
- Select to open {placeholder|bas_buildcode}">Business Application Studio to get started.
-
This will bring you into the SAP Build lobby.

-
Select the Create button and select the Clone from Git option.

-
Select the SAP HANA cloud Intelligent Application application type.

-
Complete the following project attributes and name project (_RAG_CAP):
Copy and paste in the following Git repository URL.
Code Snippet1https://github.com/boefbrak/genai_hana_rag_custom_pub.git- Name: {placeholder|userid}_RAG_CAP
- Description: {placeholder|userid} RAG Application with SAP AI core and SAP HANA Cloud
- Dev Space: {placeholder|userid}_DEV

-
Review the project configuration, and create it.


Configure and deploy application to Cloud Foundry
Once the _RAG_CAP project was created successfully the next steps would be to configure user specific settings.
-
Login to the Cloud Foundry account.


-
The Cloud Foundry Sign In panel will appear. Overwrite the Cloud Foundry Endpoint with the following value, and then choose SSO as the authentication method:
Login Option Selection Cloud Foundry Endpoint Authentication method SSO Passcode 
-
Enter academy-platform as the identity provider and then select the Sign in with alternative provider option.

-
Copy the generated passcode to the clipboard.

-
Paste the copied passcode in the Enter your SSO passcode field.

-
Sign into the Cloud Foundry Target by clicking the Sign In button.

-
Proceed by selecting the desired Cloud Foundry Target and Space as seen below.
Cloud Foundry Target Selection Cloud Foundry Organization Space dev 
Bring the project to life
This guide walks you through the code changes needed to bring the project to life. Each section tells you exactly which file to open, what it does, and what code to paste in.
-
Database Schema definition
File to update: db/schema.cds
Defines the four core database tables used by the application. Documents stores metadata about uploaded files (name, type, size, processing status). DocumentChunks holds the individual text segments split from each document, including a 3072-dimension vector embedding used for semantic similarity search. ChatSessions groups conversations and links them to a specific document. ChatMessages stores the full message history (both user and AI replies) along with the source chunks that informed each answer.
Find db/schema.cds and replace its contents with the following code:

Copy the CDS schema definition blow:
Code Snippet1234567891011121314151617181920212223242526272829303132333435namespace genai.rag; using { cuid, managed } from '@sap/cds/common'; entity Documents : cuid, managed { fileName : String(255) @mandatory; fileType : String(10) @mandatory; fileSize : Integer; status : String(20) default 'UPLOADED'; chunkCount : Integer default 0; errorMsg : String(1000); chunks : Composition of many DocumentChunks on chunks.document = $self; } entity DocumentChunks : cuid { document : Association to Documents @mandatory; content : LargeString @mandatory; chunkIndex : Integer @mandatory; tokenCount : Integer; embedding : Vector(3072); } entity ChatSessions : cuid, managed { title : String(200); document : Association to Documents; // Link session to document messages : Composition of many ChatMessages on messages.session = $self; } entity ChatMessages : cuid { session : Association to ChatSessions @mandatory; role : String(20) @mandatory; content : LargeString @mandatory; timestamp : Timestamp @cds.on.insert: $now; sources : LargeString; }
-
Document Service Definition
File to update: srv/document-service.cds
Exposes the Documents entity as an OData service at the /api/documents path. It hides the raw chunks association from API consumers (they only interact with document-level metadata). It also declares three operations: deleteDocument to remove a document and all its related data; getStatus to poll the processing state of an uploaded document; and getDeletePreview to show a summary of what will be deleted (sessions, messages, chunks) before a destructive action is confirmed.
Find srv/document-service.cds and replace its contents with the following code:
Code Snippet1234567891011121314151617181920using { genai.rag as db } from '../db/schema'; service DocumentService @(path: '/api/documents') { entity Documents as projection on db.Documents excluding { chunks }; action deleteDocument(documentId: UUID) returns Boolean; function getStatus(documentId: UUID) returns { status: String; chunkCount: Integer; errorMsg: String; }; function getDeletePreview(documentId: UUID) returns { sessionCount: Integer; messageCount: Integer; chunkCount: Integer; }; }
-
Chat Service Definition
File to update: srv/chat-service.cds
Exposes the chat functionality as an OData service at the /api/chat path. It surfaces ChatSessions and ChatMessages entities for reading. The sendMessage action is the core of the RAG pipeline — it accepts a session ID and a user message, then returns the AI-generated reply alongside the source document chunks that were used to produce it (including similarity scores). The remaining actions and functions handle session lifecycle: creating a new chat tied to a document, updating session metadata, and fetching messages or sessions by ID.
Find srv/chat-service.cds and replace its contents with the following code:
Code Snippet123456789101112131415161718192021222324using { genai.rag as db } from '../db/schema'; service ChatService @(path: '/api/chat') { entity ChatSessions as projection on db.ChatSessions excluding { messages }; entity ChatMessages as projection on db.ChatMessages; action sendMessage(sessionId: UUID, message: String) returns { reply: String; messageId: UUID; sources: array of { chunkId: UUID; documentName: String; content: String; similarity: Double; }; }; action createSession(documentId: UUID, title: String) returns ChatSessions; action updateSession(sessionId: UUID, documentId: UUID, title: String) returns ChatSessions; function getSessionMessages(sessionId: UUID) returns array of ChatMessages; function getDocumentSessions(documentId: UUID) returns array of ChatSessions; }
-
Implement RAG Engine
File to update: srv/lib/rag-engine.js
Implements the AI response logic using the SAP AI SDK’s AzureOpenAiChatClient pointed at the gpt-4o model. The client is initialised once and reused (singleton pattern). The generateRAGResponse function assembles a structured prompt: it injects a system instruction that constrains the model to answer only from provided document context, appends the relevant retrieved chunks (with source attribution and similarity percentages), replays prior chat history so the model is aware of the conversation, and finally adds the user’s current question. Buffer-to-string conversion handles HANA NCLOB columns that may arrive as Node.js Buffer objects rather than plain strings. The model is called with a moderate temperature (0.3) for factual, consistent answers.
Find srv/lib/rag-engine.js and replace its contents with the following code:
Code Snippet12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273const { AzureOpenAiChatClient } = require('@sap-ai-sdk/foundation-models'); let chatClient = null; function getChatClient() { if (!chatClient) { chatClient = new AzureOpenAiChatClient('gpt-4o'); } return chatClient; } const SYSTEM_PROMPT = `You are a helpful AI assistant that answers questions based on the provided document context. Rules: 1. Answer ONLY based on the provided context. If the context doesn't contain enough information, say so clearly. 2. Cite which document(s) your answer is based on when possible. 3. Be concise but thorough. 4. If the user's question is a greeting or general conversation, respond naturally. 5. Maintain a professional and helpful tone.`; async function generateRAGResponse({ query, chunks, history }) { const client = getChatClient(); const contextParts = chunks.map((chunk, idx) => { const similarity = (chunk.similarity * 100).toFixed(1); // Handle NCLOB content - may be Buffer or string let contentStr = chunk.content; if (Buffer.isBuffer(contentStr)) { contentStr = contentStr.toString('utf8'); } else if (typeof contentStr !== 'string') { contentStr = String(contentStr || ''); } return `[Source ${idx + 1}: "${chunk.documentName}", relevance: ${similarity}%]\n${contentStr}`; }); const contextBlock = contextParts.length > 0 ? `\n\n--- DOCUMENT CONTEXT ---\n${contextParts.join('\n\n---\n\n')}\n--- END CONTEXT ---\n\n` : '\n\n[No relevant documents found in the knowledge base.]\n\n'; const messages = []; messages.push({ role: 'system', content: SYSTEM_PROMPT + contextBlock }); // Add chat history (excluding the current user message which is last) const historyWithoutCurrent = history.slice(0, -1); for (const msg of historyWithoutCurrent) { if (msg.role === 'user' || msg.role === 'assistant') { // Handle NCLOB content - may be Buffer or string let msgContent = msg.content; if (Buffer.isBuffer(msgContent)) { msgContent = msgContent.toString('utf8'); } else if (typeof msgContent !== 'string') { msgContent = String(msgContent || ''); } messages.push({ role: msg.role, content: msgContent }); } } messages.push({ role: 'user', content: query }); const response = await client.run({ messages, max_tokens: 2000, temperature: 0.3 }); return response.getContent(); } module.exports = { generateRAGResponse };
Summary of Changes
File Layer Purpose db/schema.cds Database Creates the four entities: Documents, DocumentChunks, ChatSessions, ChatMessages srv/document-service.cds Service API Exposes document upload/management endpoints via OData srv/chat-service.cds Service API Exposes chat and RAG pipeline endpoints via OData srv/lib/rag-engine.js Business Logic Calls GPT-4o via SAP AI SDK to generate context-aware answers After applying all four changes, the backend data model, OData APIs, and AI response engine will be fully wired up.
Quick Start (Automated Deployment)
This is an automated deployment process using the setup-deployment.sh. Automated deployment script with --config (generate files) and --deploy (full deployment) modes.
-
Select and open a new Terminal

-
Verify that cloud foundry login was succesfull once the terminal is open by inspecting all running applications in the dev space.
Code Snippet1cf apps
-
Update the user-config.json file with the custom configuration below by copying and replacing the user-config.json template.

Copy the custom configuration below and paste it in the user-config.json template:
Code Snippet123456789101112131415161718{ "// INSTRUCTIONS": "Edit values below, then run: ./setup-deployment.sh --deploy", "USERNAME": "{placeholder|userid}", "REGION": "{placeholder|REGION}", "DATABASE_ID": "{placeholder|DATABASE_ID}", "AICORE_SERVICE_NAME": "default_aicore", "// NAMING_CONVENTION": { "CF_APP_SERVICE": "{USERNAME}-genai-hana-rag-srv", "CF_APP_WEBAPP": "{USERNAME}-genai-hana-rag-app", "CF_APP_DEPLOYER": "{USERNAME}-genai-hana-rag-db-deployer", "HDI_CONTAINER": "{USERNAME}-hana-hdi-rag", "AI_CORE": "default_aicore (shared)", "SERVICE_URL": "https://{USERNAME}-genai-hana-rag-srv.cfapps.{REGION}.hana.ondemand.com", "APP_URL": "https://{USERNAME}-genai-hana-rag-app.cfapps.{REGION}.hana.ondemand.com" } }The user-config.json file should resemble something similar:

-
Run the setup-deployment.sh --config from the terminal, the script will automatically:
- Validate your configuration
- Generate the MTA extension file
- Update the frontend config
- Build the application
- Deploy with namespace isolation
- Bind the shared AI Core service
- Restage the service app
Code Snippet1./setup-deployment.sh --config
-
Once the user configuration is completed continue with the application deployment to the Cloud foundry dev space.
Code Snippet1./setup-deployment.sh --deploy


Run the CAP application
Download the following sample data set Product Catalog and save it you your local machine. The catalog contains extended product catalog information.
-
Upload the product catalog file once the file is saved.



-
Notice the PROCESSING status, this means the document is being processed (chunking, embedding).

-
Once the status changes to READY the document ready for chat.

-
Explore the product catalog by “chatting” to it. This is purely an example, you can play around with this RAG application by uploading sample documents and interacting with is.
Code Snippet1234I need to purchase a wireless mouse in the Berlin area. Provide me with a list of: - warehouses where I can collect the item from - quantity of available stock - item price.

API Reference
Document Service (/api/documents)
| Endpoint | Method | Description |
|---|---|---|
| /Documents | GET | List all documents |
| /Documents('{id}') | GET | Get document by ID |
| /upload | POST | Upload file (multipart/form-data) |
| /getStatus(documentId='{id}') | GET | Get document processing status |
| /deleteDocument | POST | Delete document with cascade (chunks, sessions, messages) |
| /getDeletePreview(documentId='{id}') | GET | Get counts of related data before delete |
Chat Service (/api/chat)
| Endpoint | Method | Description |
|---|---|---|
| /ChatSessions | GET | List all sessions |
| /createSession | POST | Create session linked to a document |
| /updateSession | POST | Update session (reassign document, change title) |
| /getDocumentSessions(documentId='{id}') | GET | Get all sessions for a document |
| /getSessionMessages(sessionId='{id}') | GET | Get messages for session |
| /sendMessage | POST | Send message and get RAG response (scoped to session’s document) |
Technical Details
Embedding Configuration
- Model: text-embedding-3-large
- Dimensions: 3072
- Batch Size: 20 texts per API call
Chunking Configuration
- Chunk Size: ~1000 tokens (~4000 characters)
- Overlap: ~200 tokens (~800 characters)
- Boundary: Sentence-aware splitting
RAG Configuration
- Model: gpt-4o
- Temperature: 0.3
- Max Tokens: 2000
- Context: Top 10 similar chunks
- History: Last 10 messages
Vector Search
- Algorithm: Cosine Similarity
- Function: COSINE_SIMILARITY() in HANA SQL
- Top-K: 10 chunks returned
A Retrieval-Augmented Generation (RAG) application built with SAP Cloud Application Programming Model (CAP), SAP HANA Cloud Vector Engine, and SAP Generative AI Hub.
Overview
This application allows users to:
- Upload documents (PDF, TXT, CSV) up to 10 MB
- Automatically extract text, chunk it, and generate embeddings
- Store embeddings in SAP HANA Cloud Vector Engine
- Chat with uploaded documents using GPT-4o via SAP Generative AI Hub
- Maintain conversation history with document-linked sessions
- Delete documents with cascade deletion of all related data
- View and delete failed (ERROR) documents
Application Architecture
High-Level Component Overview
123456789101112131415161718192021222324252627282930┌─────────────────────────────────────────────────────────────────────────────────┐
│ SAP Business Technology Platform │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Cloud Foundry Environment │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Web App │ │ CAP Service │ │ DB Deployer │ │ │
│ │ │ (UI5/Fiori) │─────▶│ (Node.js) │ │ (HDI Tasks) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ staticfile_ │ │ nodejs_ │ │ nodejs_ │ │ │
│ │ │ buildpack │ │ buildpack │ │ buildpack │ │ │
│ │ └─────────────────┘ └────────┬────────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ └────────────────────────────────────│────────────────────────│─────────────┘ │
│ │ │ │
│ ┌────────────────────────────────────│────────────────────────│─────────────┐ │
│ │ Services │ │ │
│ │ │ │ │ │
│ │ ┌─────────────────┐ ┌───────▼─────────┐ ┌───────▼─────────┐ │ │
│ │ │ SAP AI Core │ │ HANA Cloud │ │ HDI Container │ │ │
│ │ │ (Shared) │◀────▶│ Vector Engine │◀─────│ (Per User) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • GPT-4o │ │ • Vector(3072) │ │ • Tables │ │ │
│ │ │ • text-embed-3 │ │ • COSINE_SIM │ │ • Views │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
RAG Processing Flow
12345678910111213141516171819202122232425262728293031323334┌──────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Document │ │ CAP Service Layer │
│ Upload │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ PDF/TXT/CSV │────▶│ │ file-parser │───▶│ chunker │───▶│ embedder │ │
│ │ │ │ │ │ │ │ │ │
└──────────────┘ │ │ Extract │ │ Split into │ │ Generate │ │
│ │ raw text │ │ ~1000 token │ │ 3072-dim │ │
│ │ │ │ chunks │ │ vectors │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
└───────────────────────────────────────────────│──────────────┘
│
▼
┌──────────────-┐ ┌──────────────────────────────────────────────────────────────┐
│ User │ │ Chat Flow │
│ Question │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ "What is │────▶│ │ embedder │───▶│vector-search│───▶│ rag-engine │ │
│ this about?"│ │ │ │ │ │ │ │ │
│ │ │ │ Embed │ │ Find top-10 │ │ Build prompt│ │
└──────────────-┘ │ │ question │ │ similar │ │ + call GPT │ │
│ │ │ │ chunks │ │ │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌───────────────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Response │──────▶ "This document... │
│ │ + Sources │ covers topic X..." │
│ └─────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
User Workflow
- Upload Document: Click [+] button in Documents panel, select PDF/TXT/CSV file
- Wait for Processing: Status shows “Processing…”, auto-refreshes when complete
- Select Document: Click on READY document in left panel
- Start Chat Session: Click [+New] to create a session for the selected document
- Ask Questions: Type questions in the input bar, responses use only that document’s content
- Delete Document: Click delete button in document header (shows confirmation with counts)
- Delete Failed Document: Select ERROR document, click delete in the error panel
Data Model
The application uses four CDS entities organized into two functional domains.
Simplified ERD (Crow’s Foot Notation)
123456789101112131415161718192021222324252627┌──────────────────┐ ┌──────────────────┐
│ Documents │ │ DocumentChunks │
├──────────────────┤ ├──────────────────┤
│ ID (PK) │───────┬▶│ ID (PK) │
│ fileName │ │ │ document_ID (FK) │
│ fileType │ │ │ content │
│ fileSize │ │ │ chunkIndex │
│ status │ │ │ tokenCount │
│ chunkCount │ │ │ embedding │
│ errorMsg │ │ └──────────────────┘
│ createdAt │ │
│ modifiedAt │ │
└──────────────────┘ │ ┌──────────────────┐
│ │ │ ChatMessages │
│ 1 │ ├──────────────────┤
│ │ ┌────▶│ ID (PK) │
▼ * │ │ │ session_ID (FK) │
┌──────────────────┐ │ │ │ role │
│ ChatSessions │ │ │ │ content │
├──────────────────┤ │ │ │ timestamp │
│ ID (PK) │───────┘ │ │ sources │
│ document_ID (FK) │───────────┘ └──────────────────┘
│ title │ 1 : *
│ createdAt │
│ modifiedAt │
└──────────────────┘
Entity Descriptions
| Entity | Purpose | Key Fields |
|---|---|---|
| Documents | Stores metadata for uploaded files | status tracks processing state (UPLOADED → PROCESSING → READY/ERROR) |
| DocumentChunks | Stores text chunks with vector embeddings | embedding is a 3072-dimension vector for similarity search |
| ChatSessions | Groups chat messages into conversations, linked to a document | document_ID links session to a specific document for scoped chat |
| ChatMessages | Individual messages in a chat session | role is “user” or “assistant”, sources contains JSON of retrieved chunks |
SAP HANA Cloud Table Names
CDS entities are deployed to HANA with uppercase table names:
| CDS Entity | HANA Table |
|---|---|
| genai.rag.Documents | GENAI_RAG_DOCUMENTS |
| genai.rag.DocumentChunks | GENAI_RAG_DOCUMENTCHUNKS |
| genai.rag.ChatSessions | GENAI_RAG_CHATSESSIONS |
| genai.rag.ChatMessages | GENAI_RAG_CHATMESSAGES |
File Descriptions
Database Layer (db/)
| File | Description |
|---|---|
| schema.cds | CDS data model defining four entities: Documents (uploaded files metadata), DocumentChunks (text chunks with Vector(3072) embeddings), ChatSessions (conversation sessions), ChatMessages (individual messages with sources) |
Service Layer (srv/)
| File | Description |
|---|---|
| document-service.cds | OData service definition exposing Documents entity and actions: upload, getStatus, deleteDocument, getDeletePreview |
| document-service.js | Handler implementing document CRUD, cascade delete (chunks → sessions → messages), and delete preview |
| chat-service.cds | OData service definition exposing ChatSessions/ChatMessages and actions: createSession, updateSession, sendMessage, getSessionMessages, getDocumentSessions |
| chat-service.js | Handler implementing document-scoped chat with RAG pipeline integration |
| server.js | Custom Express middleware for multipart file upload (multer) and CORS headers |
Library Files (srv/lib/)
| File | Description |
|---|---|
| file-parser.js | Extracts text from uploaded files: pdf-parse for PDFs, direct buffer conversion for TXT, csv-parse for CSV (converts to “Column: Value” format) |
| chunker.js | Splits text into chunks of ~1000 tokens with ~200 token overlap, breaking at sentence boundaries for better context preservation |
| embedder.js | Wraps SAP AI SDK’s AzureOpenAiEmbeddingClient for text-embedding-3-large model, processes in batches of 20 texts |
| vector-search.js | Executes HANA SQL with COSINE_SIMILARITY() function to find top-K similar chunks to a query embedding |
| rag-engine.js | Wraps SAP AI SDK’s AzureOpenAiChatClient for gpt-4o, constructs RAG prompts with document context and chat history |
| upload-processor.js | Orchestrates async document processing: parse → chunk → embed → store with vector, updates document status |
Configuration Files
| File | Description |
|---|---|
| package.json | Dependencies (@sap/cds, @sap-ai-sdk/foundation-models, multer, pdf-parse, csv-parse) and CDS configuration |
| mta.yaml | Base MTA descriptor defining modules (srv, db-deployer, app) and resources (hana-hdi-rag, aicore) |
| .cdsrc.json | CDS build settings for HANA hdbtable format |
| .npmrc | npm cache directory configuration |
Deployment Files
| File | Description |
|---|---|
| setup-deployment.sh | Automated deployment script with --config (generate files) and --deploy (full deployment) modes |
| user-config.json | User configuration: USERNAME, REGION, DATABASE_ID, AICORE_SERVICE_NAME |
| user-config.mtaext | MTA extension template with placeholders |
| my-deployment.mtaext | Auto-generated MTA extension with user-specific routes and config |
| DEPLOYMENT_GUIDE.md | Detailed multi-user deployment instructions |
Try it out!
This application supports multi-user deployment where each user gets isolated apps and HDI container in a shared SAP BTP Cloud Foundry space.
Configure and setup SAP HANA Cloud Intelligent Application project
Create a new SAP HANA cloud Intelligent Application project using the setup wizard.
- Select to open {placeholder|bas_buildcode}">Business Application Studio to get started.
-
This will bring you into the SAP Build lobby.

-
Select the Create button and select the Clone from Git option.

-
Select the SAP HANA cloud Intelligent Application application type.

-
Complete the following project attributes and name project (_RAG_CAP):
Copy and paste in the following Git repository URL.
Code Snippet1https://github.com/boefbrak/genai_hana_rag_custom_pub.git- Name: {placeholder|userid}_RAG_CAP
- Description: {placeholder|userid} RAG Application with SAP AI core and SAP HANA Cloud
- Dev Space: {placeholder|userid}_DEV

-
Review the project configuration, and create it.


Configure and deploy application to Cloud Foundry
Once the _RAG_CAP project was created successfully the next steps would be to configure user specific settings.
-
Login to the Cloud Foundry account.


-
The Cloud Foundry Sign In panel will appear. Overwrite the Cloud Foundry Endpoint with the following value, and then choose SSO as the authentication method:
Login Option Selection Cloud Foundry Endpoint Authentication method SSO Passcode 
-
Enter academy-platform as the identity provider and then select the Sign in with alternative provider option.

-
Copy the generated passcode to the clipboard.

-
Paste the copied passcode in the Enter your SSO passcode field.

-
Sign into the Cloud Foundry Target by clicking the Sign In button.

-
Proceed by selecting the desired Cloud Foundry Target and Space as seen below.
Cloud Foundry Target Selection Cloud Foundry Organization Space dev 
Bring the project to life
This guide walks you through the code changes needed to bring the project to life. Each section tells you exactly which file to open, what it does, and what code to paste in.
-
Database Schema definition
File to update: db/schema.cds
Defines the four core database tables used by the application. Documents stores metadata about uploaded files (name, type, size, processing status). DocumentChunks holds the individual text segments split from each document, including a 3072-dimension vector embedding used for semantic similarity search. ChatSessions groups conversations and links them to a specific document. ChatMessages stores the full message history (both user and AI replies) along with the source chunks that informed each answer.
Find db/schema.cds and replace its contents with the following code:

Copy the CDS schema definition blow:
Code Snippet1234567891011121314151617181920212223242526272829303132333435namespace genai.rag; using { cuid, managed } from '@sap/cds/common'; entity Documents : cuid, managed { fileName : String(255) @mandatory; fileType : String(10) @mandatory; fileSize : Integer; status : String(20) default 'UPLOADED'; chunkCount : Integer default 0; errorMsg : String(1000); chunks : Composition of many DocumentChunks on chunks.document = $self; } entity DocumentChunks : cuid { document : Association to Documents @mandatory; content : LargeString @mandatory; chunkIndex : Integer @mandatory; tokenCount : Integer; embedding : Vector(3072); } entity ChatSessions : cuid, managed { title : String(200); document : Association to Documents; // Link session to document messages : Composition of many ChatMessages on messages.session = $self; } entity ChatMessages : cuid { session : Association to ChatSessions @mandatory; role : String(20) @mandatory; content : LargeString @mandatory; timestamp : Timestamp @cds.on.insert: $now; sources : LargeString; }
-
Document Service Definition
File to update: srv/document-service.cds
Exposes the Documents entity as an OData service at the /api/documents path. It hides the raw chunks association from API consumers (they only interact with document-level metadata). It also declares three operations: deleteDocument to remove a document and all its related data; getStatus to poll the processing state of an uploaded document; and getDeletePreview to show a summary of what will be deleted (sessions, messages, chunks) before a destructive action is confirmed.
Find srv/document-service.cds and replace its contents with the following code:
Code Snippet1234567891011121314151617181920using { genai.rag as db } from '../db/schema'; service DocumentService @(path: '/api/documents') { entity Documents as projection on db.Documents excluding { chunks }; action deleteDocument(documentId: UUID) returns Boolean; function getStatus(documentId: UUID) returns { status: String; chunkCount: Integer; errorMsg: String; }; function getDeletePreview(documentId: UUID) returns { sessionCount: Integer; messageCount: Integer; chunkCount: Integer; }; }
-
Chat Service Definition
File to update: srv/chat-service.cds
Exposes the chat functionality as an OData service at the /api/chat path. It surfaces ChatSessions and ChatMessages entities for reading. The sendMessage action is the core of the RAG pipeline — it accepts a session ID and a user message, then returns the AI-generated reply alongside the source document chunks that were used to produce it (including similarity scores). The remaining actions and functions handle session lifecycle: creating a new chat tied to a document, updating session metadata, and fetching messages or sessions by ID.
Find srv/chat-service.cds and replace its contents with the following code:
Code Snippet123456789101112131415161718192021222324using { genai.rag as db } from '../db/schema'; service ChatService @(path: '/api/chat') { entity ChatSessions as projection on db.ChatSessions excluding { messages }; entity ChatMessages as projection on db.ChatMessages; action sendMessage(sessionId: UUID, message: String) returns { reply: String; messageId: UUID; sources: array of { chunkId: UUID; documentName: String; content: String; similarity: Double; }; }; action createSession(documentId: UUID, title: String) returns ChatSessions; action updateSession(sessionId: UUID, documentId: UUID, title: String) returns ChatSessions; function getSessionMessages(sessionId: UUID) returns array of ChatMessages; function getDocumentSessions(documentId: UUID) returns array of ChatSessions; }
-
Implement RAG Engine
File to update: srv/lib/rag-engine.js
Implements the AI response logic using the SAP AI SDK’s AzureOpenAiChatClient pointed at the gpt-4o model. The client is initialised once and reused (singleton pattern). The generateRAGResponse function assembles a structured prompt: it injects a system instruction that constrains the model to answer only from provided document context, appends the relevant retrieved chunks (with source attribution and similarity percentages), replays prior chat history so the model is aware of the conversation, and finally adds the user’s current question. Buffer-to-string conversion handles HANA NCLOB columns that may arrive as Node.js Buffer objects rather than plain strings. The model is called with a moderate temperature (0.3) for factual, consistent answers.
Find srv/lib/rag-engine.js and replace its contents with the following code:
Code Snippet12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273const { AzureOpenAiChatClient } = require('@sap-ai-sdk/foundation-models'); let chatClient = null; function getChatClient() { if (!chatClient) { chatClient = new AzureOpenAiChatClient('gpt-4o'); } return chatClient; } const SYSTEM_PROMPT = `You are a helpful AI assistant that answers questions based on the provided document context. Rules: 1. Answer ONLY based on the provided context. If the context doesn't contain enough information, say so clearly. 2. Cite which document(s) your answer is based on when possible. 3. Be concise but thorough. 4. If the user's question is a greeting or general conversation, respond naturally. 5. Maintain a professional and helpful tone.`; async function generateRAGResponse({ query, chunks, history }) { const client = getChatClient(); const contextParts = chunks.map((chunk, idx) => { const similarity = (chunk.similarity * 100).toFixed(1); // Handle NCLOB content - may be Buffer or string let contentStr = chunk.content; if (Buffer.isBuffer(contentStr)) { contentStr = contentStr.toString('utf8'); } else if (typeof contentStr !== 'string') { contentStr = String(contentStr || ''); } return `[Source ${idx + 1}: "${chunk.documentName}", relevance: ${similarity}%]\n${contentStr}`; }); const contextBlock = contextParts.length > 0 ? `\n\n--- DOCUMENT CONTEXT ---\n${contextParts.join('\n\n---\n\n')}\n--- END CONTEXT ---\n\n` : '\n\n[No relevant documents found in the knowledge base.]\n\n'; const messages = []; messages.push({ role: 'system', content: SYSTEM_PROMPT + contextBlock }); // Add chat history (excluding the current user message which is last) const historyWithoutCurrent = history.slice(0, -1); for (const msg of historyWithoutCurrent) { if (msg.role === 'user' || msg.role === 'assistant') { // Handle NCLOB content - may be Buffer or string let msgContent = msg.content; if (Buffer.isBuffer(msgContent)) { msgContent = msgContent.toString('utf8'); } else if (typeof msgContent !== 'string') { msgContent = String(msgContent || ''); } messages.push({ role: msg.role, content: msgContent }); } } messages.push({ role: 'user', content: query }); const response = await client.run({ messages, max_tokens: 2000, temperature: 0.3 }); return response.getContent(); } module.exports = { generateRAGResponse };
Summary of Changes
File Layer Purpose db/schema.cds Database Creates the four entities: Documents, DocumentChunks, ChatSessions, ChatMessages srv/document-service.cds Service API Exposes document upload/management endpoints via OData srv/chat-service.cds Service API Exposes chat and RAG pipeline endpoints via OData srv/lib/rag-engine.js Business Logic Calls GPT-4o via SAP AI SDK to generate context-aware answers After applying all four changes, the backend data model, OData APIs, and AI response engine will be fully wired up.
Quick Start (Automated Deployment)
This is an automated deployment process using the setup-deployment.sh. Automated deployment script with --config (generate files) and --deploy (full deployment) modes.
-
Select and open a new Terminal

-
Verify that cloud foundry login was succesfull once the terminal is open by inspecting all running applications in the dev space.
Code Snippet1cf apps
-
Update the user-config.json file with the custom configuration below by copying and replacing the user-config.json template.

Copy the custom configuration below and paste it in the user-config.json template:
Code Snippet123456789101112131415161718{ "// INSTRUCTIONS": "Edit values below, then run: ./setup-deployment.sh --deploy", "USERNAME": "{placeholder|userid}", "REGION": "{placeholder|REGION}", "DATABASE_ID": "{placeholder|DATABASE_ID}", "AICORE_SERVICE_NAME": "default_aicore", "// NAMING_CONVENTION": { "CF_APP_SERVICE": "{USERNAME}-genai-hana-rag-srv", "CF_APP_WEBAPP": "{USERNAME}-genai-hana-rag-app", "CF_APP_DEPLOYER": "{USERNAME}-genai-hana-rag-db-deployer", "HDI_CONTAINER": "{USERNAME}-hana-hdi-rag", "AI_CORE": "default_aicore (shared)", "SERVICE_URL": "https://{USERNAME}-genai-hana-rag-srv.cfapps.{REGION}.hana.ondemand.com", "APP_URL": "https://{USERNAME}-genai-hana-rag-app.cfapps.{REGION}.hana.ondemand.com" } }The user-config.json file should resemble something similar:

-
Run the setup-deployment.sh --config from the terminal, the script will automatically:
- Validate your configuration
- Generate the MTA extension file
- Update the frontend config
- Build the application
- Deploy with namespace isolation
- Bind the shared AI Core service
- Restage the service app
Code Snippet1./setup-deployment.sh --config
-
Once the user configuration is completed continue with the application deployment to the Cloud foundry dev space.
Code Snippet1./setup-deployment.sh --deploy


Run the CAP application
Download the following sample data set Product Catalog and save it you your local machine. The catalog contains extended product catalog information.
-
Upload the product catalog file once the file is saved.



-
Notice the PROCESSING status, this means the document is being processed (chunking, embedding).

-
Once the status changes to READY the document ready for chat.

-
Explore the product catalog by “chatting” to it. This is purely an example, you can play around with this RAG application by uploading sample documents and interacting with is.
Code Snippet1234I need to purchase a wireless mouse in the Berlin area. Provide me with a list of: - warehouses where I can collect the item from - quantity of available stock - item price.

API Reference
Document Service (/api/documents)
| Endpoint | Method | Description |
|---|---|---|
| /Documents | GET | List all documents |
| /Documents('{id}') | GET | Get document by ID |
| /upload | POST | Upload file (multipart/form-data) |
| /getStatus(documentId='{id}') | GET | Get document processing status |
| /deleteDocument | POST | Delete document with cascade (chunks, sessions, messages) |
| /getDeletePreview(documentId='{id}') | GET | Get counts of related data before delete |
Chat Service (/api/chat)
| Endpoint | Method | Description |
|---|---|---|
| /ChatSessions | GET | List all sessions |
| /createSession | POST | Create session linked to a document |
| /updateSession | POST | Update session (reassign document, change title) |
| /getDocumentSessions(documentId='{id}') | GET | Get all sessions for a document |
| /getSessionMessages(sessionId='{id}') | GET | Get messages for session |
| /sendMessage | POST | Send message and get RAG response (scoped to session’s document) |
Technical Details
Embedding Configuration
- Model: text-embedding-3-large
- Dimensions: 3072
- Batch Size: 20 texts per API call
Chunking Configuration
- Chunk Size: ~1000 tokens (~4000 characters)
- Overlap: ~200 tokens (~800 characters)
- Boundary: Sentence-aware splitting
RAG Configuration
- Model: gpt-4o
- Temperature: 0.3
- Max Tokens: 2000
- Context: Top 10 similar chunks
- History: Last 10 messages
Vector Search
- Algorithm: Cosine Similarity
- Function: COSINE_SIMILARITY() in HANA SQL
- Top-K: 10 chunks returned