✍️ Rewriting
Purpose: This guide provides step-by-step instructions for implementing the Text Rewriting capability with style controls.
Dependencies:
- Requires Foundation to be completed first (Phase 1 infrastructure)
- References Text Operations Solution Architecture for design context
Note: Text Rewriting is the anchor primitive — it does not depend on Generation. Generation is an extension of rewriting and comes after.
Related Documents:
- Text Operations Epic - Capability definition
Note: This guide is specific to Text Operations (TextOps) implementation. Once Text Operations is fully implemented and validated, this entire text-intelligence/ folder will be archived.
Overview
What This Guide Covers
Text Rewriting provides the capability to modify existing text with style instructions (humanize, tone adjustment, formality, etc.). This is the anchor primitive — it does not build on Generation. Generation is an extension that comes after.
What's Included:
- Rewriting service method
- Style parameter mapping
- DTOs (request/response)
- Controller endpoint
What's NOT Included:
- ❌ Predefined style presets UI (fork products add these)
- ❌ Style templates library (fork products add these)
- ❌ Multiple variations generation (fork products add this if needed)
Prerequisites
- Phase 1 Foundation completed (ChatModel bean configured, Ollama running)
- Understanding of prompt engineering for text transformation
- Familiarity with style parameter mapping
Implementation Steps
Step 1: Create Rewrite DTOs
File: server/src/main/java/com/saas/springular/common/ai/model/TextRewriteRequest.java
Key Points:
- ✅ Use Jakarta Validation annotations
- ✅ Reference constants for size limits
- ✅ Provide clear validation messages
Create request DTO:
package com.saas.springular.common.ai.model;
import com.saas.springular.common.ai.constants.AiConstants;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record TextRewriteRequest(
@NotBlank(message = "Text to rewrite is required")
@Size(max = AiConstants.MAX_PROMPT_LENGTH, message = "Text must not exceed 4000 characters")
String text,
@NotBlank(message = "Instruction is required")
@Size(max = 500, message = "Instruction must not exceed 500 characters")
String instruction // e.g., "Make it more formal", "Humanize this text", "Make it casual"
) {}
Validation Notes:
@NotBlank: Rejects null, empty, or whitespace-only strings (automatically trims)@Size: Enforces maximum length (4000 characters for text, 500 for instruction)- Validation errors are automatically handled by Spring's
@ControllerAdvice(existingExceptionResponseHandler) - Response includes field-level error messages in standard format
File: server/src/main/java/com/saas/springular/common/ai/model/TextRewriteResponse.java
Create response DTO:
package com.saas.springular.common.ai.model;
public record TextRewriteResponse(
String rewrittenText,
String originalText,
String instruction
) {}
Step 2: Extend Service Interface
File: server/src/main/java/com/saas/springular/common/ai/service/TextIntelligenceService.java
Add method:
package com.saas.springular.common.ai.service;
public interface TextIntelligenceService {
String generateText(String prompt, String tone);
/**
* Rewrite text according to an instruction.
*
* @param text The original text to rewrite
* @param instruction The rewriting instruction (e.g., "Make it more formal", "Humanize this")
* @return Rewritten text
*/
String rewriteText(String text, String instruction);
}
Step 3: Implement Rewrite Method
File: server/src/main/java/com/saas/springular/common/ai/service/impl/TextIntelligenceServiceImpl.java
Add implementation:
package com.saas.springular.common.ai.service.impl;
// ... existing imports ...
@Service
@RequiredArgsConstructor
@Slf4j
public class TextIntelligenceServiceImpl implements TextIntelligenceService {
// ... existing fields ...
@Qualifier("creative")
private final ChatLanguageModel creativeChatModel;
@Override
public String rewriteText(String text, String instruction) {
long startTime = System.currentTimeMillis();
String modelName = "ollama-llama2:7b"; // Creative model identifier
try {
String prompt = buildRewritePrompt(text, instruction);
String result = creativeChatModel.chat(prompt);
long latency = System.currentTimeMillis() - startTime;
log.info("Text rewriting completed: operation=text-rewriting, model={}, textLength={}, instructionLength={}, latency={}ms, success=true",
modelName, text.length(), instruction.length(), latency);
return result;
} catch (Exception e) {
long latency = System.currentTimeMillis() - startTime;
String errorCategory = categorizeError(e);
log.error("Text rewriting failed: operation=text-rewriting, model={}, textLength={}, instructionLength={}, latency={}ms, success=false, errorCategory={}",
modelName, text.length(), instruction.length(), latency, errorCategory, e);
throw new RuntimeException("Text rewriting failed", e);
}
}
private String buildRewritePrompt(String text, String instruction) {
return String.format(
"Rewrite the following text according to this instruction: %s\n\nOriginal text:\n%s\n\nRewritten text:",
instruction,
text
);
}
private String categorizeError(Exception e) {
// Categorize error for structured logging
String exceptionName = e.getClass().getSimpleName();
if (exceptionName.contains("Timeout")) {
return "timeout";
} else if (exceptionName.contains("Invocation")) {
return "provider-failure";
} else {
return "unknown";
}
}
// ... existing methods ...
}
Step 4: Add Controller Endpoint
File: server/src/main/java/com/saas/springular/common/ai/controller/AIController.java
Add endpoint:
@RestController
@RequestMapping("/api/ai/text")
@RequiredArgsConstructor
public class AIController {
private final TextIntelligenceService textIntelligenceService;
// ... existing generateText endpoint ...
@PostMapping("/rewrite")
public ResponseEntity<TextRewriteResponse> rewriteText(
@Valid @RequestBody TextRewriteRequest request) {
String rewrittenText = textIntelligenceService.rewriteText(
request.text(),
request.instruction()
);
return ResponseEntity.ok(new TextRewriteResponse(
rewrittenText,
request.text(),
request.instruction()
));
}
}
Production Readiness
Input Validation
- ✅ DTOs use Jakarta Validation annotations (
@NotBlank,@Size) - ✅ Size limits enforced (
MAX_PROMPT_LENGTH= 4000 characters for text, 500 for instruction) - ✅ Required fields validated automatically
- ✅ Validation errors handled by existing
@ControllerAdvice(ExceptionResponseHandler) - ✅ Error responses follow standard format
Error Handling
- ✅ Provider errors caught and categorized (timeout, provider-failure, unknown)
- ✅ User-friendly error messages returned
- ✅ Errors logged with structured format
- ✅ Errors automatically handled by existing
ExceptionResponseHandler
Logging
- ✅ Structured logging with required fields:
operation: Operation type (e.g., "text-rewriting")model: Model identifiertextLength: Length of original textinstructionLength: Length of instructionlatency: Request duration in millisecondssuccess: Boolean (true/false)errorCategory: Error category if failed
- ✅ Text content NOT logged at INFO level (only lengths logged)
- ✅ Error categories logged for debugging
Token Tracking (Preparation)
Token tracking hooks are prepared but not fully implemented (see Usage Tracking guide):
// In service implementation (future)
// Token tracking will be added in Usage Tracking guide
// Interface preparation: record usage when tokens are available from provider
Full token tracking implementation is in the Usage Tracking guide.
Testing
Unit Tests
File: server/src/test/java/com/saas/springular/common/ai/service/impl/TextIntelligenceServiceImplTest.java
Add test methods:
@ExtendWith(MockitoExtension.class)
class TextIntelligenceServiceImplTest {
// ... existing test setup ...
@Test
void rewritesText() {
// arrange
String originalText = "This is a test message";
String instruction = "Make it more formal";
String expectedRewritten = "This is a formal test message";
when(creativeChatModel.chat(anyString())).thenReturn(expectedRewritten);
// act
String result = service.rewriteText(originalText, instruction);
// assert
assertThat(result).isEqualTo(expectedRewritten);
}
@Test
void handlesHumanizeInstruction() {
// arrange
String originalText = "We apologize for the inconvenience caused";
String instruction = "Humanize this text";
String expectedRewritten = "Sorry about that!";
when(creativeChatModel.chat(anyString())).thenReturn(expectedRewritten);
// act
String result = service.rewriteText(originalText, instruction);
// assert
assertThat(result).isEqualTo(expectedRewritten);
}
}
Integration Test
File: server/src/test/java/com/saas/springular/common/ai/controller/AIControllerTest.java
Add test:
@SpringBootTest
@AutoConfigureMockMvc
class AIControllerTest {
// ... existing test setup ...
@Test
void rewritesText() throws Exception {
// arrange
String requestBody = """
{
"text": "This is a test message",
"instruction": "Make it more formal"
}
""";
// act & assert
mockMvc.perform(post("/api/ai/text/rewrite")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andExpect(status().isOk())
.andExpect(jsonPath("$.rewrittenText").exists())
.andExpect(jsonPath("$.originalText").value("This is a test message"))
.andExpect(jsonPath("$.instruction").value("Make it more formal"));
}
}
Validation
Manual Testing
Test Endpoint: Use curl or Postman:
curl -X POST http://localhost:8080/api/ai/text/rewrite \
-H "Content-Type: application/json" \
-d '{
"text": "We apologize for the inconvenience caused",
"instruction": "Humanize this text"
}'
Expected Response:
{
"rewrittenText": "Sorry about that!",
"originalText": "We apologize for the inconvenience caused",
"instruction": "Humanize this text"
}
Style Parameter Patterns
Common Instructions
Formality:
- "Make it more formal"
- "Make it more casual"
- "Make it professional"
Tone:
- "Humanize this text"
- "Make it friendlier"
- "Make it more authoritative"
Structure:
- "Make it more concise"
- "Expand with more detail"
- "Simplify this text"
Note: The core service accepts free-form instructions. Fork products can add preset mappings or UI dropdowns, but the core stays flexible.
Time Estimate
Total Time: 1-2 hours
Breakdown:
- DTOs: 15 minutes
- Service method: 20 minutes
- Controller endpoint: 15 minutes
- Unit tests: 20 minutes
- Integration tests: 20 minutes
- Manual testing: 15 minutes
Next Steps
After Text Rewriting is complete:
- Usage Tracking: See Usage Tracking
Troubleshooting
Issue: Rewritten text doesn't follow instruction
Solution: Refine prompt template. Consider adding examples to the prompt for better results.
Issue: Creative model too verbose
Solution: Adjust prompt to include length constraints or use balanced model instead.
Issue: Validation errors on request
Solution: Verify request DTO validation annotations and request body format.