M5 AP-002 Externen Prompt geladen und deterministische KI-Anfrage
aufgebaut
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package de.gecheckt.pdf.umbenenner.application.service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.AiRequestRepresentation;
|
||||
import de.gecheckt.pdf.umbenenner.domain.model.PromptIdentifier;
|
||||
|
||||
/**
|
||||
* Composes deterministic AI request representations from prompt and document text.
|
||||
* <p>
|
||||
* This service builds the exact request that will be sent to the AI service,
|
||||
* ensuring that the composition is deterministic and reproducible across batch runs.
|
||||
* The request is constructed from:
|
||||
* <ul>
|
||||
* <li>The loaded prompt content (from the external prompt file)</li>
|
||||
* <li>The stable prompt identifier (derived from the prompt source)</li>
|
||||
* <li>The extracted document text (already limited to max characters if needed)</li>
|
||||
* <li>The exact character count that was sent (for traceability)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* <strong>Order and structure:</strong> The composition follows a fixed, documented order
|
||||
* to ensure that another implementation would not need to guess how the prompt and document
|
||||
* are combined. The prompt is presented first, followed by the document text, with clear
|
||||
* structural markers to distinguish between them.
|
||||
* <p>
|
||||
* <strong>JSON-only response expectation:</strong> The request is constructed with the
|
||||
* explicit expectation that the AI will respond with a JSON object containing:
|
||||
* <ul>
|
||||
* <li>{@code title} — mandatory, max 20 characters (base title)</li>
|
||||
* <li>{@code reasoning} — mandatory, the AI's explanation</li>
|
||||
* <li>{@code date} — optional, should be in YYYY-MM-DD format if present</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This service is stateless and thread-safe. It performs no I/O and makes no external calls.
|
||||
*/
|
||||
public class AiRequestComposer {
|
||||
|
||||
/**
|
||||
* Composes a deterministic AI request representation.
|
||||
* <p>
|
||||
* The composition order is fixed:
|
||||
* <ol>
|
||||
* <li>Prompt content</li>
|
||||
* <li>Separator: newline</li>
|
||||
* <li>Prompt identifier (for reference/traceability)</li>
|
||||
* <li>Separator: newline</li>
|
||||
* <li>Document text section marker</li>
|
||||
* <li>Document text content</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* This fixed order ensures that:
|
||||
* <ul>
|
||||
* <li>The prompt guides the AI</li>
|
||||
* <li>The document text is clearly separated and identified</li>
|
||||
* <li>Another implementation knows exactly where each part begins</li>
|
||||
* <li>The composition is deterministic and reproducible</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param promptIdentifier the stable identifier for this prompt; must not be null
|
||||
* @param promptContent the prompt template content; must not be null
|
||||
* @param documentText the extracted document text; must not be null
|
||||
* @return an AiRequestRepresentation with sentCharacterCount set to documentText.length()
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public static AiRequestRepresentation compose(
|
||||
PromptIdentifier promptIdentifier,
|
||||
String promptContent,
|
||||
String documentText) {
|
||||
|
||||
Objects.requireNonNull(promptIdentifier, "promptIdentifier must not be null");
|
||||
Objects.requireNonNull(promptContent, "promptContent must not be null");
|
||||
Objects.requireNonNull(documentText, "documentText must not be null");
|
||||
|
||||
// The complete request text is composed in a deterministic order:
|
||||
// 1. Prompt content (instruction)
|
||||
// 2. Newline separator
|
||||
// 3. Prompt identifier (for reference)
|
||||
// 4. Newline separator
|
||||
// 5. Document text section marker
|
||||
// 6. Newline separator
|
||||
// 7. Document text content
|
||||
//
|
||||
// This order is fixed so that another implementation knows exactly where
|
||||
// the prompt and document text are positioned.
|
||||
StringBuilder requestBuilder = new StringBuilder();
|
||||
requestBuilder.append(promptContent);
|
||||
requestBuilder.append("\n");
|
||||
requestBuilder.append("--- Prompt-ID: ").append(promptIdentifier.identifier()).append(" ---");
|
||||
requestBuilder.append("\n");
|
||||
requestBuilder.append("--- Document Text ---");
|
||||
requestBuilder.append("\n");
|
||||
requestBuilder.append(documentText);
|
||||
|
||||
// Record the exact character count of the document text that was included.
|
||||
// This is the length of the document text (not the complete request).
|
||||
int sentCharacterCount = documentText.length();
|
||||
|
||||
return new AiRequestRepresentation(
|
||||
promptIdentifier,
|
||||
promptContent,
|
||||
documentText,
|
||||
sentCharacterCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the complete request text that will be sent to the AI.
|
||||
* <p>
|
||||
* This is a helper method that builds the exact string that would be included in the
|
||||
* HTTP request to the AI service. It follows the same deterministic order as
|
||||
* {@link #compose(PromptIdentifier, String, String)}.
|
||||
*
|
||||
* @param promptIdentifier the stable identifier for this prompt; must not be null
|
||||
* @param promptContent the prompt template content; must not be null
|
||||
* @param documentText the extracted document text; must not be null
|
||||
* @return the complete, deterministically-ordered request text for the AI
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public static String buildCompleteRequestText(
|
||||
PromptIdentifier promptIdentifier,
|
||||
String promptContent,
|
||||
String documentText) {
|
||||
|
||||
Objects.requireNonNull(promptIdentifier, "promptIdentifier must not be null");
|
||||
Objects.requireNonNull(promptContent, "promptContent must not be null");
|
||||
Objects.requireNonNull(documentText, "documentText must not be null");
|
||||
|
||||
StringBuilder requestBuilder = new StringBuilder();
|
||||
requestBuilder.append(promptContent);
|
||||
requestBuilder.append("\n");
|
||||
requestBuilder.append("--- Prompt-ID: ").append(promptIdentifier.identifier()).append(" ---");
|
||||
requestBuilder.append("\n");
|
||||
requestBuilder.append("--- Document Text ---");
|
||||
requestBuilder.append("\n");
|
||||
requestBuilder.append(documentText);
|
||||
|
||||
return requestBuilder.toString();
|
||||
}
|
||||
|
||||
private AiRequestComposer() {
|
||||
// Static utility class – no instances
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.service.DocumentProcessingCoordinator}
|
||||
* — Per-document idempotency, status/counter mapping and consistent
|
||||
* two-level persistence</li>
|
||||
* <li>{@link de.gecheckt.pdf.umbenenner.application.service.AiRequestComposer}
|
||||
* — Deterministic composition of AI request representations from prompt and document text</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Document processing flow ({@code DocumentProcessingCoordinator})</h2>
|
||||
|
||||
Reference in New Issue
Block a user