Skip to content

Commit 6b0ae8f

Browse files
committed
Merge pull request #21 from Nexters/main
release: 0.0.2
2 parents 2e11141 + fb301d1 commit 6b0ae8f

File tree

12 files changed

+211
-10
lines changed

12 files changed

+211
-10
lines changed

docs/api/OCR_파싱_API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ key:value는 요청 text에 따라 다르게 응답합니다.
66

77
## Request
88

9-
### HTTP METHOD : `GET`
9+
### HTTP METHOD : `POST`
1010

1111
### url : `https://api.misik.me/reviews/ocr-parsing`
1212
### Http Headers

src/main/kotlin/me/misik/api/api/ReviewController.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package me.misik.api.api
22

33
import me.misik.api.api.response.ReviewResponse
4-
import me.misik.api.domain.request.CreateReviewRequest
4+
import me.misik.api.api.response.ParsedOcrResponse
55
import me.misik.api.app.CreateReviewFacade
6-
import me.misik.api.app.GetReviewFacade
76
import me.misik.api.app.ReCreateReviewFacade
7+
import me.misik.api.app.GetReviewFacade
8+
import me.misik.api.domain.request.CreateReviewRequest
9+
import me.misik.api.domain.request.OcrTextRequest
810
import me.misik.api.domain.ReviewStyle
911
import me.misik.api.domain.response.ReviewStylesResponse
10-
import org.springframework.web.bind.annotation.*
12+
import org.springframework.web.bind.annotation.GetMapping
1113
import org.springframework.web.bind.annotation.RequestBody
1214
import org.springframework.web.bind.annotation.RequestHeader
15+
import org.springframework.web.bind.annotation.PathVariable
16+
import org.springframework.web.bind.annotation.PostMapping
17+
import org.springframework.web.bind.annotation.RestController
1318

1419
@RestController
1520
class ReviewController(
@@ -42,4 +47,8 @@ class ReviewController(
4247
@PathVariable("id") id: Long,
4348
): ReviewResponse = ReviewResponse.of(getReviewFacade.getReview(id))
4449

50+
@PostMapping("reviews/ocr-parsing")
51+
fun parseOcrText(
52+
@RequestBody ocrText: OcrTextRequest,
53+
) : ParsedOcrResponse = createReviewFacade.parseOcrText(ocrText)
4554
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package me.misik.api.api.response
2+
3+
data class ParsedOcrResponse(
4+
val parsed: List<KeyValuePair>
5+
) {
6+
data class KeyValuePair(
7+
val key: String,
8+
val value: String,
9+
)
10+
}

src/main/kotlin/me/misik/api/app/CreateReviewFacade.kt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
package me.misik.api.app;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper
34
import kotlinx.coroutines.CoroutineScope
45
import kotlinx.coroutines.flow.filterNot
56
import kotlinx.coroutines.launch
6-
import me.misik.api.domain.request.CreateReviewRequest
7-
import me.misik.api.core.Chatbot
8-
import me.misik.api.core.GracefulShutdownDispatcher
7+
import me.misik.api.api.response.ParsedOcrResponse
98
import me.misik.api.domain.CreateReviewCache
9+
import me.misik.api.domain.request.CreateReviewRequest
10+
import me.misik.api.domain.request.OcrTextRequest
1011
import me.misik.api.domain.Review
1112
import me.misik.api.domain.ReviewService
1213
import me.misik.api.domain.prompt.PromptService
14+
import me.misik.api.core.Chatbot
15+
import me.misik.api.core.OcrParser
16+
import me.misik.api.core.GracefulShutdownDispatcher
1317
import org.slf4j.LoggerFactory
1418
import org.springframework.stereotype.Service
1519

1620

1721
@Service
1822
class CreateReviewFacade(
1923
private val chatbot:Chatbot,
24+
private val ocrParser: OcrParser,
2025
private val reviewService:ReviewService,
2126
private val promptService: PromptService,
22-
private val createReviewCache: CreateReviewCache
27+
private val createReviewCache: CreateReviewCache,
28+
private val objectMapper: ObjectMapper,
2329
) {
2430

2531
private val logger = LoggerFactory.getLogger(this::class.simpleName)
@@ -64,6 +70,20 @@ class CreateReviewFacade(
6470
}
6571
}
6672

73+
fun parseOcrText(ocrText: OcrTextRequest): ParsedOcrResponse {
74+
val response = ocrParser.createParsedOcr(OcrParser.Request.from(ocrText.text))
75+
val responseContent = response.result?.message?.content ?: ""
76+
77+
val parsedOcr = objectMapper.readValue(responseContent, ParsedOcrResponse::class.java)
78+
?: throw IllegalStateException("Invalid OCR text format")
79+
80+
if (parsedOcr.parsed.isEmpty()) {
81+
throw IllegalArgumentException("Parsed OCR content is empty")
82+
}
83+
84+
return parsedOcr
85+
}
86+
6787
private companion object {
6888
private const val MAX_RETRY_COUNT = 3
6989
private const val ALREADY_COMPLETED = "stop_before"

src/main/kotlin/me/misik/api/core/Chatbot.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ fun interface Chatbot {
1010
@PostExchange("/testapp/v1/chat-completions/HCX-003")
1111
fun createReviewWithModelName(@RequestBody request: Request): Flow<Response>
1212

13+
1314
data class Request(
1415
val messages: List<Message>,
1516
val maxTokens: Int = 100,
@@ -57,3 +58,4 @@ fun interface Chatbot {
5758
)
5859
}
5960
}
61+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package me.misik.api.core
2+
3+
data class ErrorResponse(
4+
val message: String,
5+
) {
6+
7+
companion object {
8+
fun from(exception: Exception): ErrorResponse =
9+
ErrorResponse(exception.message ?: exception.localizedMessage)
10+
}
11+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package me.misik.api.core
2+
3+
import org.springframework.web.bind.annotation.RequestBody
4+
import org.springframework.web.service.annotation.PostExchange
5+
6+
fun interface OcrParser {
7+
8+
@PostExchange("/testapp/v1/chat-completions/HCX-003")
9+
fun createParsedOcr(@RequestBody request: Request): Response
10+
11+
data class Request(
12+
val messages: List<Message>,
13+
val maxTokens: Int = 100,
14+
val includeAiFilters: Boolean = true,
15+
) {
16+
data class Message(
17+
val role: String,
18+
val content: String,
19+
) {
20+
21+
companion object {
22+
23+
fun createSystem(content: String) = Message(
24+
role = "system",
25+
content = content,
26+
)
27+
28+
fun createUser(content: String) = Message(
29+
role = "user",
30+
content = content,
31+
)
32+
}
33+
}
34+
35+
companion object {
36+
val cachedParsingSystemMessage = Message.createSystem(
37+
"""
38+
리뷰에 쓸만한 정보를 추출해줘. key에는 방문 장소명, 품명 등이 포함될 수 있어. key는 최대 3개만 뽑아줘.
39+
응답 형식은 반드시 다음과 같은 JSON이야. 응답에는 해당 JSON만 있어야해.
40+
{
41+
"parsed": [
42+
{
43+
"key": "품명",
44+
"value": "카야토스트+음료세트"
45+
},
46+
{
47+
"key": "가격",
48+
"value": "3000"
49+
},
50+
...
51+
]
52+
}
53+
응답의 총 길이는 300자를 넘으면 안돼.
54+
"""
55+
)
56+
57+
fun from(ocrText: String): Request {
58+
return Request(
59+
messages = listOf(
60+
cachedParsingSystemMessage,
61+
Message.createUser(ocrText)
62+
)
63+
)
64+
}
65+
}
66+
}
67+
68+
data class Response(
69+
val status: Status?,
70+
val result: Result?
71+
) {
72+
data class Status(
73+
val code: String,
74+
val message: String
75+
)
76+
data class Result(
77+
val message: Message?
78+
) {
79+
data class Message(
80+
val role: String,
81+
val content: String
82+
)
83+
}
84+
}
85+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package me.misik.api.core.advice
2+
3+
import me.misik.api.core.ErrorResponse
4+
import org.slf4j.LoggerFactory
5+
import org.springframework.http.HttpStatus
6+
import org.springframework.web.bind.annotation.ExceptionHandler
7+
import org.springframework.web.bind.annotation.ResponseStatus
8+
import org.springframework.web.bind.annotation.RestControllerAdvice
9+
10+
@RestControllerAdvice
11+
class GlobalExceptionHandler {
12+
13+
private val logger = LoggerFactory.getLogger(this::class.simpleName)
14+
15+
@ResponseStatus(HttpStatus.BAD_REQUEST)
16+
@ExceptionHandler(IllegalArgumentException::class)
17+
fun handleIllegalArgumentException(exception: IllegalArgumentException): ErrorResponse {
18+
logger.error(exception.message, exception)
19+
return ErrorResponse.from(exception)
20+
}
21+
22+
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
23+
@ExceptionHandler(IllegalStateException::class)
24+
fun handleIllegalStateException(exception: IllegalStateException): ErrorResponse {
25+
logger.error(exception.message, exception)
26+
return ErrorResponse.from(exception)
27+
}
28+
}

src/main/kotlin/me/misik/api/domain/ReviewService.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ class ReviewService(
4747
fun getById(id: Long): Review = reviewRepository.findByIdOrNull(id)
4848
?: throw IllegalArgumentException("Cannot find review by id \"$id\"")
4949

50-
5150
fun getReview(id: Long) = reviewRepository.findById(id)
5251
?: throw IllegalArgumentException("Cannot find review by id \"$id\"")
5352
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package me.misik.api.domain.request
2+
3+
data class OcrTextRequest(
4+
val text: String,
5+
)

0 commit comments

Comments
 (0)