Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

Diff not rendered.
Diff not rendered.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ Message processData(Message message) {
// it only sets two exchange properties, runHeadline and runSummary,
// which show up in Monitor -> the message's trace -> Properties for any
// step after this one. Edit the two path constants if your buckets differ.
// The sender keeps the source file in place (Post-Processing: Keep File
// and Process Again), so every poll reprocesses the same file.
// Read Sales File keeps the source file in place (Post-Processing: Keep
// File), so it stays in daily/ for the next run.
def sourcePath = "regional-landing-bkt/daily/ (kept for the next run)"
def curatedPath = "curated-corp/profit-margins/sales_" + message.getProperty("businessDate") + ".json"

Expand Down
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/*
* AttachDiagnosis.groovy
* Exception subprocess 1 - runs after the "Get AI Diagnosis" process call.
* Exception subprocess 1 - runs after the AI Adapter's Request Reply.
* Reads the AI response, parses the JSON diagnosis, and writes:
* - MPL attachment "AI Resolution" (the problem and numbered fix steps)
* - MPL attachment "AI Diagnosis" (human-readable report)
* - custom header properties AI_Status, AI_Category, AI_Confidence, AI_NeedsHuman
* - the message body: the resolution text followed by the original payload (personal data masked)
*
* Handles three cases:
* 1. aiStatus = UNAVAILABLE / CONTEXT_FAILED -> attaches a "diagnosis unavailable" note
* 2. Response is (or contains) valid diagnosis JSON -> formatted report
* 3. Response is not parseable -> attaches raw response marked "unstructured"
* 1. aiStatus = CONTEXT_FAILED (Capture Error failed) -> attaches a "diagnosis unavailable" note
* 2. Response is (or contains) valid diagnosis JSON -> formatted report
* 3. Response is not parseable -> attaches raw response marked "unstructured"
*
* The response parser accepts either the model's text directly, or a full
* orchestration response (final_result / orchestration_result / choices[0].message.content).
Expand Down Expand Up @@ -36,59 +38,70 @@ def Message processData(Message message) {
r << "Error class : " << origClass << "\n"
r << "Error : " << origMsg << "\n\n"

// Case 1 - AI not called or AI call failed
String resolutionText = null

if (status != "OK") {
// Case 1 - Capture Error failed, so the AI had no error to diagnose
r << "AI diagnosis unavailable (" << status << ").\n"
r << "Reason: " << (message.getProperty("aiError") ?: message.getProperty("contextError") ?: "-") << "\n"
r << "Reason: " << (message.getProperty("contextError") ?: "-") << "\n"
r << "The original error above is unaffected - investigate manually.\n"
log?.addAttachmentAsString("AI Diagnosis", r.toString(), "text/plain")
log?.addCustomHeaderProperty("AI_Status", status)
return message
}
resolutionText = r.toString()
} else {
String body = message.getBody(String) ?: ""
String content = extractContent(body)
def diag = parseJson(content)

String body = message.getBody(String) ?: ""
String content = extractContent(body)
def diag = parseJson(content)
if (diag instanceof Map && diag.root_cause) {
// Case 2 - structured diagnosis
String category = (diag.category ?: "OTHER").toString()
String confidence = (diag.confidence ?: "LOW").toString()
String needsHuman = (diag.needs_human == null ? "true" : diag.needs_human.toString())

if (diag instanceof Map && diag.root_cause) {
// Case 2 - structured diagnosis
String category = (diag.category ?: "OTHER").toString()
String confidence = (diag.confidence ?: "LOW").toString()
String needsHuman = (diag.needs_human == null ? "true" : diag.needs_human.toString())
r << "Root cause : " << diag.root_cause << "\n"
r << "Category : " << category << "\n"
r << "Confidence : " << confidence << "\n"
r << "Needs human : " << needsHuman << "\n\n"
r << "Suggested fix steps:\n"
def steps = (diag.fix_steps instanceof List) ? diag.fix_steps : [diag.fix_steps ?: "-"]
steps.eachWithIndex { s, i -> r << " " << (i + 1) << ". " << s << "\n" }

r << "Root cause : " << diag.root_cause << "\n"
r << "Category : " << category << "\n"
r << "Confidence : " << confidence << "\n"
r << "Needs human : " << needsHuman << "\n\n"
r << "Suggested fix steps:\n"
def steps = (diag.fix_steps instanceof List) ? diag.fix_steps : [diag.fix_steps ?: "-"]
steps.eachWithIndex { s, i -> r << " " << (i + 1) << ". " << s << "\n" }
// Separate, resolution-only attachment for clean viewing
StringBuilder res = new StringBuilder()
res << "RESOLUTION\n"
res << "==========\n\n"
res << "Problem:\n " << diag.root_cause << "\n\n"
res << "What to do:\n"
steps.eachWithIndex { s, i -> res << " " << (i + 1) << ". " << s << "\n" }
res << "\nCategory: " << category << " | Confidence: " << confidence
res << " | Needs human: " << needsHuman << "\n"
res << "\n---\nAI-generated suggestion - verify before acting.\n"
log?.addAttachmentAsString("AI Resolution", res.toString(), "text/plain")
resolutionText = res.toString()

// Separate, resolution-only attachment for clean viewing
StringBuilder res = new StringBuilder()
res << "RESOLUTION\n"
res << "==========\n\n"
res << "Problem:\n " << diag.root_cause << "\n\n"
res << "What to do:\n"
steps.eachWithIndex { s, i -> res << " " << (i + 1) << ". " << s << "\n" }
res << "\nCategory: " << category << " | Confidence: " << confidence
res << " | Needs human: " << needsHuman << "\n"
res << "\n---\nAI-generated suggestion - verify before acting.\n"
log?.addAttachmentAsString("AI Resolution", res.toString(), "text/plain")
log?.addCustomHeaderProperty("AI_Status", "OK")
log?.addCustomHeaderProperty("AI_Category", category)
log?.addCustomHeaderProperty("AI_Confidence", confidence)
log?.addCustomHeaderProperty("AI_NeedsHuman", needsHuman)
} else {
// Case 3 - model did not return the expected JSON
r << "AI response (unstructured - did not match expected JSON):\n"
r << (content ?: body) << "\n"
log?.addCustomHeaderProperty("AI_Status", "UNSTRUCTURED")
}

log?.addCustomHeaderProperty("AI_Status", "OK")
log?.addCustomHeaderProperty("AI_Category", category)
log?.addCustomHeaderProperty("AI_Confidence", confidence)
log?.addCustomHeaderProperty("AI_NeedsHuman", needsHuman)
} else {
// Case 3 - model did not return the expected JSON
r << "AI response (unstructured - did not match expected JSON):\n"
r << (content ?: body) << "\n"
log?.addCustomHeaderProperty("AI_Status", "UNSTRUCTURED")
r << "\n---\nAI-generated suggestion - verify before acting.\n"
log?.addAttachmentAsString("AI Diagnosis", r.toString(), "text/plain")
if (resolutionText == null) resolutionText = r.toString()
}

r << "\n---\nAI-generated suggestion - verify before acting.\n"
log?.addAttachmentAsString("AI Diagnosis", r.toString(), "text/plain")
// Message body: the resolution, then the original payload (masked), so both show in the Monitor
StringBuilder out = new StringBuilder(resolutionText)
out << "\nORIGINAL PAYLOAD (personal data masked)\n"
out << "=======================================\n"
out << maskedPayload(message) << "\n"
message.setBody(out.toString())

} catch (Exception e) {
log?.addAttachmentAsString("AI Diagnosis", "Could not build AI diagnosis report: " + e.getMessage(), "text/plain")
Expand All @@ -97,6 +110,16 @@ def Message processData(Message message) {
return message
}

// The masked payload that Capture Error saved inside the aiErrorContext property
String maskedPayload(Message message) {
try {
def ctx = new JsonSlurper().parseText(message.getProperty("aiErrorContext") as String)
return (ctx.payloadExtract ?: "-").toString()
} catch (ignored) {
return "-"
}
}

// Pull the model's text out of the response, whatever shape the adapter returns
String extractContent(String body) {
try {
Expand Down Expand Up @@ -127,4 +150,4 @@ def parseJson(String s) {
} catch (ignored) {
return null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import com.sap.gateway.ip.core.customdev.util.Message

def Message processData(Message message) {
def log = messageLogFactory.getMessageLog(message)
String status = message.getProperty("aiStatus") ?: "OK"
String report = "aiStatus: " + status + "\n" +
"aiError: " + (message.getProperty("aiError") ?: "-") + "\n\n" +
"AI response body:\n" + (message.getBody(String) ?: "")
String report = "AI response body:\n" + (message.getBody(String) ?: "")
log?.addAttachmentAsString("AI output (debug)", report, "text/plain")
return message
}
}

This file was deleted.

Loading