Skip to content

Commit

Permalink
refactor: OcrParser prompt를 DB로 관리하도록 변경 (#41)
Browse files Browse the repository at this point in the history
* refactor: OcrParser prompt를 DB로 관리한다

* refactor: ddl확장자 제거

* feat: OCR parsing이 400, 500을 검증하도록 한다
  • Loading branch information
devxb authored Feb 15, 2025
1 parent 74e18d0 commit 0a67675
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package me.misik.api.api.response

data class ParsedOcrResponse(
val parsed: List<KeyValuePair>
val status: Boolean,
val parsed: List<KeyValuePair>,
) {
data class KeyValuePair(
val key: String,
val value: String,
)
}
}
19 changes: 14 additions & 5 deletions src/main/kotlin/me/misik/api/app/CreateReviewFacade.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import me.misik.api.core.OcrParser
import me.misik.api.domain.CreateReviewCache
import me.misik.api.domain.Review
import me.misik.api.domain.ReviewService
import me.misik.api.domain.prompt.Prompt
import me.misik.api.domain.prompt.PromptService
import me.misik.api.domain.prompt.PromptType
import me.misik.api.domain.request.CreateReviewRequest
import me.misik.api.domain.request.OcrTextRequest
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service


@Service
class CreateReviewFacade(
private val chatbot: Chatbot,
Expand Down Expand Up @@ -73,25 +74,33 @@ class CreateReviewFacade(
}
}

fun parseOcrText(ocrText: OcrTextRequest): ParsedOcrResponse = parseOcrWithRetry(ocrText, 0)
fun parseOcrText(ocrText: OcrTextRequest): ParsedOcrResponse {
val prompt = promptService.findAllByType(PromptType.OCR).first()
return parseOcrWithRetry(prompt, ocrText, 0)
}

private fun parseOcrWithRetry(ocrText: OcrTextRequest, retryCount: Int): ParsedOcrResponse {
private fun parseOcrWithRetry(
prompt: Prompt,
ocrText: OcrTextRequest,
retryCount: Int,
): ParsedOcrResponse {
return runCatching {
val response = ocrParser.createParsedOcr(OcrParser.Request.from(ocrText.text))
val response = ocrParser.createParsedOcr(OcrParser.Request.of(prompt, ocrText.text))

val responseContent = response.result?.message?.content ?: ""
logger.info("ocr responseContent $responseContent")

val parsedOcr = objectMapper.readValue(responseContent, ParsedOcrResponse::class.java)
?: throw IllegalStateException("Invalid OCR text format")

require(parsedOcr.status) { "Wrong ocr request \"$ocrText\"" }
require(parsedOcr.parsed.isEmpty().not()) { "Parsed OCR content is empty" }

parsedOcr
}.getOrElse {
logger.error("OCR Parsing fail", it)
if (retryCount < MAX_RETRY_COUNT) {
return@getOrElse parseOcrWithRetry(ocrText, retryCount + 1)
return@getOrElse parseOcrWithRetry(prompt, ocrText, retryCount + 1)
}
throw it
}
Expand Down
34 changes: 8 additions & 26 deletions src/main/kotlin/me/misik/api/core/OcrParser.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.misik.api.core

import me.misik.api.domain.prompt.Prompt
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.service.annotation.PostExchange

Expand Down Expand Up @@ -33,32 +34,12 @@ fun interface OcrParser {
}

companion object {
val cachedParsingSystemMessage = Message.createSystem(
"""
리뷰에 쓸만한 정보를 추출해줘. key에는 방문 장소명, 품명 등이 포함될 수 있어. key는 최대 3개만 뽑아줘.
응답 형식은 반드시 다음과 같은 JSON이야. 응답에는 해당 JSON만 있어야해.
{
"parsed": [
{
"key": "품명",
"value": "카야토스트+음료세트"
},
{
"key": "가격",
"value": "3000"
},
...
]
}
응답의 총 길이는 300자를 넘으면 안돼.
"""
)

fun from(ocrText: String): Request {
fun of(prompt: Prompt, ocrText: String): Request {
return Request(
messages = listOf(
cachedParsingSystemMessage,
Message.createUser(ocrText)
Message.createSystem(prompt.command),
Message.createUser(ocrText),
)
)
}
Expand All @@ -71,15 +52,16 @@ fun interface OcrParser {
) {
data class Status(
val code: String,
val message: String
val message: String,
)

data class Result(
val message: Message?
) {
data class Message(
val role: String,
val content: String
val content: String,
)
}
}
}
}
8 changes: 6 additions & 2 deletions src/main/kotlin/me/misik/api/domain/prompt/Prompt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ import me.misik.api.domain.ReviewStyle
@Entity
@Table(
name = "prompt",
uniqueConstraints = [UniqueConstraint(columnNames = ["style"])]
uniqueConstraints = [UniqueConstraint(columnNames = ["style"])],
)
class Prompt(
@Id
@Column(name = "id")
val id: Long,

@Column(name = "style", columnDefinition = "VARCHAR(20)", nullable = false)
@Enumerated(EnumType.STRING)
@Column(name = "style", columnDefinition = "VARCHAR(20)", nullable = false)
val style: ReviewStyle,

@Column(name = "command", columnDefinition = "TEXT", nullable = false)
val command: String,

@Enumerated(EnumType.STRING)
@Column(name = "type", columnDefinition = "VARCHAR(20)", nullable = false)
val type: PromptType,
) : AbstractTime()
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ import org.springframework.data.repository.query.Param
interface PromptRepository : JpaRepository<Prompt, Long> {
@Query("SELECT p FROM Prompt p WHERE p.style = :reviewStyle")
fun findByReviewStyleOrNull(@Param("reviewStyle") reviewStyle: ReviewStyle?): Prompt?
}

@Query("SELECt p FROM Prompt p WHERE p.type = :promptType")
fun findAllByType(@Param("promptType") promptType: PromptType): List<Prompt>
}
7 changes: 5 additions & 2 deletions src/main/kotlin/me/misik/api/domain/prompt/PromptService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import org.springframework.transaction.annotation.Transactional
@Service
@Transactional(readOnly = true)
class PromptService(
private val promptRepository: PromptRepository
private val promptRepository: PromptRepository,
) {

fun getByStyle(reviewStyle: ReviewStyle): Prompt = promptRepository.findByReviewStyleOrNull(reviewStyle)
?: throw IllegalArgumentException("Cannot find prompt by review style \"$reviewStyle\"")
}

fun findAllByType(promptType: PromptType): List<Prompt> = promptRepository.findAllByType(promptType)
}
7 changes: 7 additions & 0 deletions src/main/kotlin/me/misik/api/domain/prompt/PromptType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.misik.api.domain.prompt

enum class PromptType {
OCR,
REVIEW_CREATE,
;
}
File renamed without changes.

0 comments on commit 0a67675

Please sign in to comment.