| name | cap-llm-plugin |
| description | Use when building AI/LLM features inside a SAP CAP application: the CAP LLM Plugin (@sap/cds-plugin-llm or @sap-ai-sdk), RAG (Retrieval-Augmented Generation), vector embeddings, chat completions, orchestration, SAP AI Core, or GenAI Hub integration from within a CAP service.
|
| metadata | {"version":"1.1.0","keywords":["SAP AI SDK","AI Core","RAG","embeddings","HANA Vector Engine","LLM","generative AI","orchestration","chat completion","vector search"],"related":{"cap-mcp-server":"MCP server for AI agent integration with CAP","btp-service-bindings":"AI Core service binding configuration","btp-deployment":"deploy AI-enabled CAP apps to BTP"}} |
CAP LLM Plugin — AI Integration Best Practices
Primary reference: https://cap.cloud.sap/docs/plugins/#ai
SAP AI SDK: https://sap.github.io/ai-sdk/
@cap-js/ai — Built-in AI Plugin (recommended for field recommendations)
For UI field recommendations and standard AI Core integration, use the first-party plugin:
npm install @cap-js/ai
Fields with @Common.ValueList or @cds.odata.valuelist annotations automatically get AI-powered recommendations in Fiori draft-enabled UIs — no custom handler needed:
annotate Books with {
genre @Common.ValueList: {
CollectionPath: 'Genres',
Parameters: [{ $Type: 'Common.ValueListParameterInOut',
ValueListProperty: 'code', LocalDataProperty: genre_code }]
}
}
For custom LLM integration (RAG, chat completions, orchestration), use the SAP AI SDK described below.
Setup: SAP AI SDK for CAP (recommended, 2025+)
npm install @sap-ai-sdk/langchain @sap-ai-sdk/foundation-models
Bind aicore service in mta.yaml:
- name: my-cap-app-aicore
type: org.cloudfoundry.managed-service
parameters:
service: aicore
service-plan: extended
Chat completion (OpenAI-compatible via AI Core)
const { AzureOpenAiChatClient } = require('@sap-ai-sdk/foundation-models')
module.exports = class AssistantService extends cds.ApplicationService {
async init() {
this.on('askQuestion', this.onAskQuestion)
return super.init()
}
async onAskQuestion(req) {
const { question, context } = req.data
const client = new AzureOpenAiChatClient({ modelName: 'gpt-4o' })
const response = await client.invoke([
{
role: 'system',
content: 'You are a helpful assistant for SAP procurement processes. ' +
'Answer based only on the provided context.'
},
{
role: 'user',
content: `Context:\n${context}\n\nQuestion: ${question}`
}
])
return { answer: response.content }
}
}
RAG: vector embeddings with HANA Cloud Vector Engine
Note (April 2026+): The Vector type is now also supported on H2, SQLite, and PostgreSQL for local development — not just HANA Cloud. Use SQLite locally, HANA in production without model changes.
// Schema for document chunks with vector embeddings
entity DocumentChunks : cuid {
content : LargeString;
sourceDoc : String(500);
embedding : Vector(1536); // dimension depends on embedding model
}
const { AzureOpenAiEmbeddingClient } = require('@sap-ai-sdk/foundation-models')
async function embedAndStore(text, sourceDoc) {
const client = new AzureOpenAiEmbeddingClient({ modelName: 'text-embedding-3-small' })
const [vector] = await client.embedDocuments([text])
await INSERT.into(DocumentChunks).entries({
content: text,
sourceDoc: sourceDoc,
embedding: vector
})
}
async function retrieveRelevant(question, topK = 5) {
const client = new AzureOpenAiEmbeddingClient({ modelName: 'text-embedding-3-small' })
const [queryVector] = await client.embedDocuments([question])
return cds.db.run(
`SELECT TOP ${topK} content, sourceDoc,
COSINE_SIMILARITY(embedding, TO_REAL_VECTOR(?)) AS score
FROM MY_DOCUMENTCHUNKS
ORDER BY score DESC`,
[JSON.stringify(queryVector)]
)
}
Orchestration with LangChain (via SAP AI SDK)
const { ChatPromptTemplate } = require('@langchain/core/prompts')
const { StringOutputParser } = require('@langchain/core/output_parsers')
const { AzureOpenAiChatClient } = require('@sap-ai-sdk/langchain')
async function runRAGChain(question) {
const llm = new AzureOpenAiChatClient({ modelName: 'gpt-4o' })
const chunks = await retrieveRelevant(question)
const context = chunks.map(c => c.CONTENT).join('\n\n---\n\n')
const prompt = ChatPromptTemplate.fromMessages([
['system', 'Answer the question using only this context:\n{context}'],
['human', '{question}']
])
const chain = prompt.pipe(llm).pipe(new StringOutputParser())
return chain.invoke({ context, question })
}
Streaming responses (SSE)
this.on('streamAnswer', async (req, res) => {
const client = new AzureOpenAiChatClient({ modelName: 'gpt-4o' })
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
const stream = await client.stream([{ role: 'user', content: req.data.question }])
for await (const chunk of stream) {
res.write(`data: ${JSON.stringify({ token: chunk.content })}\n\n`)
}
res.write('data: [DONE]\n\n')
res.end()
})
Token budget management
function buildPrompt(systemPrompt, context, question, maxContextTokens = 3000) {
const budget = maxContextTokens * 4
const truncated = context.length > budget
? context.slice(0, budget) + '\n[...context truncated...]'
: context
return [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: `Context:\n${truncated}\n\nQuestion: ${question}` }
]
}
Local development mock
{
"cds": {
"requires": {
"aicore": {
"[development]": {
"kind": "aicore",
"credentials": {
"clientid": "...",
"clientsecret": "...",
"url": "https://YOUR_TENANT.authentication.eu10.hana.ondemand.com",
"serviceurls": { "AI_API_URL": "https://api.ai.eu10.aws.ml.hana.ondemand.com" }
}
}
}
}
}
}
Use a .env file (never commit) with real AI Core credentials for local testing.
Common mistakes to avoid
- ❌ Sending raw user input directly to the LLM without sanitization (prompt injection risk)
- ❌ Storing embeddings as JSON strings instead of using HANA
Vector type
- ❌ Re-embedding the same documents on every request — precompute and cache
- ❌ Not limiting context size — long prompts = slow responses + high cost
- ❌ Exposing LLM errors verbatim to end users — raw model output or stack traces can reveal internal prompt instructions or configuration details
- ❌ Forgetting to bind the
aicore service instance in mta.yaml before deploy
- ❌ Using synchronous calls for long-running LLM tasks — use background jobs or streaming