Skip to main content

✍️ Rewriting

Purpose: This guide provides step-by-step instructions for implementing the Text Rewriting capability with style controls.

Dependencies:

Note: Text Rewriting is the anchor primitive — it does not depend on Generation. Generation is an extension of rewriting and comes after.

Related Documents:

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 (existing ExceptionResponseHandler)
  • 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 identifier
    • textLength: Length of original text
    • instructionLength: Length of instruction
    • latency: Request duration in milliseconds
    • success: 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:

  1. 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.