| name | sap-llm-engineering |
| description | LLM engineering patterns for SAP AI — eval harnesses, prompt optimization, RAG pipelines, training data curation, agent quality measurement |
| trigger | {"keywords":["prompt engineering abap","evaluate abap generation","llm for sap","fine-tune abap model","rag pipeline sap","genai evaluation","agent eval sap"],"intent":"Building eval-first LLM workflows for SAP ABAP/BTP/CPI code generation"} |
SAP LLM Engineering — Eval-First Patterns for SAP AI
Adapts Karpathy's LLM engineering methodology (build eval first, iterate on
metrics not vibes) to SAP ABAP/BTP/CPI contexts. The eval IS the product —
prompts are just configuration.
Prerequisites
abaplint installed and pinned to a specific version (rule sets evolve)
- SAP ADT or abaplint CLI for syntax validation
- LLM API access (for generation + optional embedding)
sap-crew-analysis skill loaded (companion for code-quality scoring)
- 20% of training data held out as eval set — never evaluate on training prompts
Core Principle
Build eval → Measure baseline → Improve prompt → Re-measure → Repeat
(NOT: Write prompt → Hope it works → Manual review)
Steps
1. Build Evaluation Harness (Before Any Prompt Work)
Define what "good ABAP" means across 7 dimensions:
EVAL_DIMENSIONS = {
'abaplint_pass': 'abaplint --format json → zero errors',
'syntax_ok': 'ADT syntax check passes',
'unit_test_pass': 'ABAP Unit test methods all green',
'clean_abap_score': 'Clean ABAP compliance ≥ 70/100',
'bapi_correctness': 'Import/export params match BAPI signature',
'security_ok': 'No SQL injection, no auth-check gaps',
'performance_ok': 'No SELECT *, no SELECT-in-loop',
}
Run eval on every LLM output:
def evaluate(generated_code: str, spec: dict) -> dict:
results = {
'abaplint': run_abaplint(generated_code),
'syntax': run_adt_syntax_check(generated_code),
'security': audit_security(generated_code),
'performance': audit_performance(generated_code),
}
if spec.get('bapi_name'):
results['bapi'] = verify_bapi_signature(generated_code, spec['bapi_name'])
return {
'pass': all(r['pass'] for r in results.values()),
'score': sum(r['score'] for r in results.values()) / len(results),
'details': results,
}
2. Establish Baseline
python eval_abap_llm.py --prompt baseline.json --eval-set holdout_20pct.json
3. Optimize Prompt (Iterate on Failures)
PROMPT_BAPI = """
You are an expert ABAP developer. Write a method calling {bapi_name}.
Requirements:
- Modern ABAP (inline declarations, NEW constructor)
- Always call BAPI_TRANSACTION_COMMIT with WAIT = 'X'
- Check BAPIRET2 tables, not just RETURN
- Handle errors with CX_STATIC_CHECK
- Include ABAP Unit test class with {min_tests} methods
ABAP version: {version} Package: {package}
"""
def optimize(template, spec, eval_fn, max_iter=10):
best_score, best_prompt = 0, None
for i in range(max_iter):
code = call_llm(template.format(**spec))
result = eval_fn(code, spec)
if result['score'] > best_score:
best_score, best_prompt = result['score'], template
if result['pass']:
break
spec = inject_failure_feedback(spec, result)
return best_prompt, best_score
4. Curate Training Data (For Fine-Tuning)
Extract (instruction, signature, body) triples from existing ABAP repos:
QUALITY_FILTERS = [
'abaplint_pass',
'syntax_check_pass',
'unit_test_pass',
'not_generated_code',
'clean_abap_score >= 70',
]
5. Build RAG Pipeline for SAP Knowledge
Chunk SAP docs by entity type for better retrieval:
CHUNK_STRATEGIES = {
'bapi': {'split_on': 'FUNCTION ', 'max': 4000, 'meta': ['BAPI_NAME', 'MODULE']},
'cds_view': {'split_on': 'DEFINE VIEW ', 'max': 2000, 'meta': ['ENTITY_NAME']},
'sap_note': {'split_on': '## Solution', 'max': 8000, 'meta': ['NOTE_NUMBER', 'VERSION']},
'abap_class': {'split_on': 'METHOD ', 'max': 1500, 'meta': ['CLASS_NAME', 'METHOD_NAME']},
}
Query with context:
def generate_with_rag(question: str) -> str:
context = vector_store.similarity_search(question, k=5)
return call_llm(build_rag_prompt(question, context))
6. Evaluate Agent (Not Just Code)
AGENT_EVAL_CASES = [
{'task': 'Create ZCL_MATERIAL_HELPER with BAPI_MATERIAL_SAVEDATA wrapper',
'criteria': ['syntax_valid', 'abaplint_pass', 'bapi_params_correct',
'has_error_handling', 'has_unit_test', 'clean_abap_70plus']},
{'task': 'Debug ST22 dump SYNTAX_ERROR in ZREPORT_MM line 245',
'criteria': ['reads_dump', 'identifies_root_cause', 'proposes_fix']},
]
7. Integration with sap-crew-analysis
sap-llm-engineering optimizes the PROMPT; sap-crew-analysis evaluates the CODE.
Loop: optimize prompt → generate → evaluate code → feed back → iterate.
Pitfalls
-
Eval data leakage inflates scores
- Cause: Eval prompts overlap with training/few-shot examples in the prompt.
- Solution: Hold out 20% of cases in a separate file. Never include them in few-shot examples.
-
LLM hallucinates non-existent BAPI names
- Cause: Generic LLMs trained on web data invent plausible-sounding BAPI names.
- Solution: Validate every BAPI name against the SAP system via
mcp_sap_bapi_call or RFC_FUNCTION_SEARCH before accepting generated code.
-
abaplint rule changes break eval reproducibility
- Cause: abaplint updates change rule severity between versions.
- Solution: Pin abaplint version in
requirements.txt. Re-baseline when upgrading.
-
Security dimension silently skipped
- Cause: Security checks are slower and teams deprioritize them.
- Solution: Make
security_ok a hard gate — eval fails if security check doesn't run. LLMs can introduce SQL injection in ABAP via dynamic WHERE clauses.
-
Token budget exhausted before convergence
- Cause: Full eval harness burns ~50K tokens per iteration × 10+ iterations.
- Solution: Budget 15-20 iterations. Use cheaper model for initial screening, expensive model for final candidates.
-
Generic code LLMs score poorly on ABAP
- Cause: ABAP syntax is underrepresented in general training data.
- Solution: Use extensive few-shot examples (5+ ABAP snippets). Consider fine-tuned models or RAG-augmented generation.
Verification