-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* doc: 리뷰 조회, OCR 파싱 문서 수정 * feat: 리뷰를 생성한다 * test: 리뷰 생성 테스트 오류 수정 * doc: OCR 파싱 문서 추가 * feat: 리뷰 생성, 재생성 시 인메모리 캐싱 * feat: LLM 리뷰 생성 시 해시태그를 반영한다 * feat: 불필요한 쿼리를 수정하고, 재시도 실패 시에도 캐시를 삭제한다
- Loading branch information
Showing
20 changed files
with
366 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# OCR 파싱 API | ||
|
||
OCR로 추출한 텍스트를 요청으로 받아, LLM을 이용해 영수증 내용을 파싱하여 응답합니다. | ||
key:value는 요청 text에 따라 다르게 응답합니다. | ||
파싱 결과에 유효한 key:value가 1개 이상 없다면 실패합니다. | ||
|
||
## Request | ||
|
||
### HTTP METHOD : `GET` | ||
|
||
### url : `https://api.misik.me/reviews/ocr-parsing` | ||
### Http Headers | ||
- device-id: 식별할 수 있는 값 | ||
`미래에도 변하지 않아야함 앱을 삭제했다 다시 깔아도 안변하는 값으로 줄 수 있는지` | ||
|
||
### RequestBody | ||
|
||
```json | ||
{ | ||
"ocrText": "영수증 내용. 품명 단가 수량 카야토스트+음료세트 3,000 ..." | ||
} | ||
``` | ||
|
||
### Response | ||
|
||
#### `Response Status 200 OK` | ||
|
||
```json | ||
{ | ||
"parsed": [ | ||
{ | ||
"key": "품명", | ||
"value": "카야토스트+음료세트" | ||
}, | ||
{ | ||
"key": "품명", | ||
"value": "카야토스트+음료세트" | ||
}, | ||
... | ||
] | ||
} | ||
``` | ||
|
||
#### `Response Status 400 Bad Request` | ||
|
||
```json | ||
{ | ||
"message": "영수증 내용이 없습니다." | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,29 @@ | ||
package me.misik.api.api | ||
|
||
import me.misik.api.domain.request.CreateReviewRequest | ||
import me.misik.api.app.CreateReviewFacade | ||
import me.misik.api.app.ReCreateReviewFacade | ||
import org.springframework.web.bind.annotation.PathVariable | ||
import org.springframework.web.bind.annotation.PostMapping | ||
import org.springframework.web.bind.annotation.RequestBody | ||
import org.springframework.web.bind.annotation.RequestHeader | ||
import org.springframework.web.bind.annotation.RestController | ||
|
||
@RestController | ||
class ReviewController( | ||
private val createReviewFacade: CreateReviewFacade, | ||
private val reCreateReviewFacade: ReCreateReviewFacade, | ||
) { | ||
|
||
@PostMapping("reviews") | ||
fun createReview( | ||
@RequestHeader("device-id") deviceId: String, | ||
@RequestBody createReviewRequest: CreateReviewRequest, | ||
) : Long = createReviewFacade.createReviewInBackground(deviceId, createReviewRequest) | ||
|
||
@PostMapping("reviews/{id}/re-create") | ||
fun reCreateReview( | ||
@RequestHeader("device-id") deviceId: String, | ||
@PathVariable("id") id: Long, | ||
) = reCreateReviewFacade.reCreateReviewInBackground(id) | ||
) = reCreateReviewFacade.reCreateReviewInBackground(deviceId, id) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package me.misik.api.app; | ||
|
||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.flow.filterNot | ||
import kotlinx.coroutines.launch | ||
import me.misik.api.domain.request.CreateReviewRequest | ||
import me.misik.api.core.Chatbot | ||
import me.misik.api.core.GracefulShutdownDispatcher | ||
import me.misik.api.domain.CreateReviewCache | ||
import me.misik.api.domain.Review | ||
import me.misik.api.domain.ReviewService | ||
import me.misik.api.domain.prompt.PromptService | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.stereotype.Service | ||
|
||
|
||
@Service | ||
class CreateReviewFacade( | ||
private val chatbot:Chatbot, | ||
private val reviewService:ReviewService, | ||
private val promptService: PromptService, | ||
private val createReviewCache: CreateReviewCache | ||
) { | ||
|
||
private val logger = LoggerFactory.getLogger(this::class.simpleName) | ||
|
||
fun createReviewInBackground(deviceId:String, createReviewRequest: CreateReviewRequest) : Long { | ||
val prompt = promptService.getByStyle(createReviewRequest.reviewStyle) | ||
val review = reviewService.createReview(deviceId, prompt.command, createReviewRequest) | ||
|
||
createReviewCache.put(review.id, review) | ||
|
||
createReviewWithRetry(review, retryCount = 0) | ||
|
||
return review.id | ||
} | ||
|
||
private fun createReviewWithRetry(review: Review, retryCount: Int) { | ||
CoroutineScope(GracefulShutdownDispatcher.dispatcher).launch { | ||
chatbot.createReviewWithModelName(Chatbot.Request.from(review)) | ||
.filterNot { it.stopReason == ALREADY_COMPLETED } | ||
.collect { | ||
val newText = it.message?.content ?: "" | ||
review.addText(newText) | ||
|
||
val updatedReview = review.copy() | ||
createReviewCache.put(review.id, updatedReview) | ||
} | ||
}.invokeOnCompletion { | ||
if (it == null) { | ||
createReviewCache.get(review.id)?.let { | ||
reviewService.updateAndCompleteReview(it.id, it.text) | ||
} | ||
createReviewCache.remove(review.id) | ||
return@invokeOnCompletion | ||
} | ||
if (retryCount == MAX_RETRY_COUNT) { | ||
logger.error("Failed to create review.", it) | ||
createReviewCache.remove(review.id) | ||
throw it | ||
} | ||
logger.warn("Failed to create review. retrying... retryCount: \"${retryCount + 1}\"", it) | ||
createReviewWithRetry(review, retryCount + 1) | ||
} | ||
} | ||
|
||
private companion object { | ||
private const val MAX_RETRY_COUNT = 3 | ||
private const val ALREADY_COMPLETED = "stop_before" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package me.misik.api.domain | ||
|
||
interface CreateReviewCache { | ||
|
||
fun get(id: Long) : Review | ||
fun put(id: Long, review: Review) | ||
fun remove(id: Long) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.