|
|
@@ -5,6 +5,24 @@ protocol PunctuationCheckServicing {
|
|
|
}
|
|
|
|
|
|
struct PunctuationCheckService: PunctuationCheckServicing {
|
|
|
+ private static let chunkMaxWords = 280
|
|
|
+ private static let maxConcurrentChunks = 4
|
|
|
+ private static let systemPrompt = """
|
|
|
+ You are a professional punctuation checker. Analyze the user's text for punctuation \
|
|
|
+ issues only. Respond with JSON only using this schema:
|
|
|
+ {
|
|
|
+ "correctedText": "corrected version of this chunk only",
|
|
|
+ "issues": [
|
|
|
+ {
|
|
|
+ "original": "problematic phrase",
|
|
|
+ "suggestion": "corrected phrase",
|
|
|
+ "explanation": "brief reason for the change"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ If there are no issues, return the original chunk in correctedText and an empty issues array.
|
|
|
+ """
|
|
|
+
|
|
|
private let openAI: OpenAIChatServicing
|
|
|
|
|
|
init(openAI: OpenAIChatServicing = OpenAIChatService.shared) {
|
|
|
@@ -17,31 +35,62 @@ struct PunctuationCheckService: PunctuationCheckServicing {
|
|
|
throw GrammarCheckServiceError.emptyInput
|
|
|
}
|
|
|
|
|
|
- let response: TextIssuesResponse = try await openAI.completeJSON(
|
|
|
- systemPrompt: """
|
|
|
- You are a professional punctuation checker. Analyze the user's text for punctuation \
|
|
|
- issues only. Respond with JSON only using this schema:
|
|
|
- {
|
|
|
- "correctedText": "full corrected version of the input",
|
|
|
- "issues": [
|
|
|
- {
|
|
|
- "original": "problematic phrase",
|
|
|
- "suggestion": "corrected phrase",
|
|
|
- "explanation": "brief reason for the change"
|
|
|
+ let chunks = trimmed.chunked(maxWords: Self.chunkMaxWords)
|
|
|
+ if chunks.count == 1 {
|
|
|
+ let response: TextIssuesResponse = try await openAI.completeJSON(
|
|
|
+ systemPrompt: Self.systemPrompt,
|
|
|
+ userPrompt: trimmed,
|
|
|
+ maxTokens: 1_000,
|
|
|
+ as: TextIssuesResponse.self
|
|
|
+ )
|
|
|
+
|
|
|
+ return GrammarCheckResult(
|
|
|
+ correctedText: response.correctedText,
|
|
|
+ issues: response.issues.map {
|
|
|
+ GrammarIssue(original: $0.original, suggestion: $0.suggestion, explanation: $0.explanation)
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ var correctedChunks: [String] = []
|
|
|
+ var allIssues: [GrammarIssue] = []
|
|
|
+
|
|
|
+ for batchStart in stride(from: 0, to: chunks.count, by: Self.maxConcurrentChunks) {
|
|
|
+ let batchEnd = min(batchStart + Self.maxConcurrentChunks, chunks.count)
|
|
|
+ let batch = Array(chunks[batchStart..<batchEnd])
|
|
|
+
|
|
|
+ let batchResults = try await withThrowingTaskGroup(of: (Int, TextIssuesResponse).self) { group in
|
|
|
+ for (offset, chunk) in batch.enumerated() {
|
|
|
+ let originalIndex = batchStart + offset
|
|
|
+ group.addTask {
|
|
|
+ let response: TextIssuesResponse = try await self.openAI.completeJSON(
|
|
|
+ systemPrompt: Self.systemPrompt,
|
|
|
+ userPrompt: chunk,
|
|
|
+ maxTokens: 550,
|
|
|
+ as: TextIssuesResponse.self
|
|
|
+ )
|
|
|
+ return (originalIndex, response)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var indexedResults: [(Int, TextIssuesResponse)] = []
|
|
|
+ for try await item in group {
|
|
|
+ indexedResults.append(item)
|
|
|
}
|
|
|
- ]
|
|
|
+ return indexedResults.sorted { $0.0 < $1.0 }
|
|
|
}
|
|
|
- If there are no issues, return the original text in correctedText and an empty issues array.
|
|
|
- """,
|
|
|
- userPrompt: trimmed,
|
|
|
- as: TextIssuesResponse.self
|
|
|
- )
|
|
|
+
|
|
|
+ correctedChunks.append(contentsOf: batchResults.map(\.1.correctedText))
|
|
|
+ allIssues.append(contentsOf: batchResults.flatMap { result in
|
|
|
+ result.1.issues.map {
|
|
|
+ GrammarIssue(original: $0.original, suggestion: $0.suggestion, explanation: $0.explanation)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
return GrammarCheckResult(
|
|
|
- correctedText: response.correctedText,
|
|
|
- issues: response.issues.map {
|
|
|
- GrammarIssue(original: $0.original, suggestion: $0.suggestion, explanation: $0.explanation)
|
|
|
- }
|
|
|
+ correctedText: correctedChunks.joined(separator: "\n\n"),
|
|
|
+ issues: allIssues
|
|
|
)
|
|
|
}
|
|
|
}
|