티스토리 뷰

 

오랜만에 GPT 관련 글을 쓰는 것 같다. 마지막 글이 2월이니 넉달만에 쓰게 됐다.

 

이 사이 GPT4o가 나오고, assistant가 v2로 업데이트되었고, 여러 모델들이 업데이트 되었다.

 

AI가 천천히 일상에 스며들고 있는데, 개발하는 입장에서는 좋은 소식이 4월 중 몇가지 들렸다.

 

사실 GPT4 Model은 API 사용료가 꽤 비싼데,

 

GPT4o 모델을 제공한다는 것과 Batch API를 통해 훨씬 저렴하게 API를 사용할 수 있게 됐다.

 

무려 50%나 비용절감을 해준다고한다. https://help.openai.com/en/articles/9197833-batch-api-faq

 

하지만 Batch API는 사용 방법이 복잡하고, 제한적이라 정리가 필요할 것 같아서 정리를 하고 가려고 한다.

 

그렇지만 못할 정도는 아니고 API 개수가 몇 개 안되니가 스텝별로 진행해보자.

 

1. Create Batch

https://platform.openai.com/docs/api-reference/batch/create

 

Batch API를 사용하기 위해서는, 먼저 Files API를 알아야 한다. 규격에 맞게 File을 업로드해야한다.

 

Create File API를 이용해 규격에 맞는 파일을 써야하고, purpose도 batch로 지정한다.

// batch 규격에 맞는 JSON 파일
{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo-0125", "messages": [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}}
{"custom_id": "request-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo-0125", "messages": [{"role": "system", "content": "You are an unhelpful assistant."},{"role": "user", "content": "Hello world!"}],"max_tokens": 1000}}

 파일이 정상적으로 Create되면,  아래와 같이 ID를 얻을 수 있다. 

 

 

이 ID와 endpoint와 completion_window를 파라미터로 받는 Batches API를 만든다.

 

completion_window는 전체 batch가 끝나는 시간을 제한해둔 변수이다. 현재는 24h밖에 사용하지 못 한다.

 

늘 사용하던데로 FeignClient를 사용했다.

 

Request

data class BatchesCreateDto (
    @JsonProperty("input_file_id")
    val inputFileId: String,
    val endpoint: String,
    @JsonProperty("completion_window")
    val completionWindow: String
) {
    constructor(createBatchesRequest: CreateBatchesRequest) : this(
        inputFileId = createBatchesRequest.inputFileId,
        endpoint = createBatchesRequest.endpoint,
        completionWindow = createBatchesRequest.completionWindow
    )
}

// file도 필요하니 코드에 포함
@FeignClient(name = "OpenAiClient", url = "https://api.openai.com/v1", configuration = [OpenAiHeaderConfiguration::class])
interface OpenAiFeignClient {
    @PostMapping("/files", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
    fun uploadFile(@RequestPart file: MultipartFile, @RequestPart("purpose") purpose: String): Any

    @PostMapping("/batches")
    fun createBatches(@RequestBody batchesCreateDto: BatchesCreateDto): Any
}

Response

{
    "id": "batch_J52fTEP5cz30kXXj9bvwrLY7",
    "object": "batch",
    "endpoint": "/v1/chat/completions",
    "errors": null,
    "input_file_id": "file-LVMBPSLw2kcIjw5tDGPE3jNq",
    "completion_window": "24h",
    "status": "validating",
    "output_file_id": null,
    "error_file_id": null,
    "created_at": 1717746791,
    "in_progress_at": null,
    "expires_at": 1717833191,
    "finalizing_at": null,
    "completed_at": null,
    "failed_at": null,
    "expired_at": null,
    "cancelling_at": null,
    "cancelled_at": null,
    "request_counts": {
        "total": 0,
        "completed": 0,
        "failed": 0
    },
    "metadata": null
}

 

2. Retrieve batch

Create의 Response를 확인해보면 batch_J52fTEP5cz30kXXj9bvwrLY7 라는 Batch ID를 얻을 수 있다.

 

이 ID를 이용해 Batch의 상태를 조회해 볼 수 있다.

 

아마 데이터가 두 개뿐이라 글을 작성하는 동안 완료가 됐을 것이다.

 

Request

@GetMapping("/batches/{batchId}")
fun getBatchesList(@PathVariable batchId: String) : Any

Response

{
    "id": "batch_J52fTEP5cz30kXXj9bvwrLY7",
    "object": "batch",
    "endpoint": "/v1/chat/completions",
    "errors": null,
    "input_file_id": "file-LVMBPSLw2kcIjw5tDGPE3jNq",
    "completion_window": "24h",
    "status": "in_progress",
    "output_file_id": null,
    "error_file_id": null,
    "created_at": 1717746791,
    "in_progress_at": 1717746792,
    "expires_at": 1717833191,
    "finalizing_at": null,
    "completed_at": null,
    "failed_at": null,
    "expired_at": null,
    "cancelling_at": null,
    "cancelled_at": null,
    "request_counts": {
        "total": 2,
        "completed": 1,
        "failed": 0
    },
    "metadata": null
}

 

작업을 2개 걸어뒀는데, 하나만 끝나서 현재 status가 in_progress로 출력된다. 

 

3. List batch

현재 API Key를 사용하는 모든 batch의 list를 보여준다.

 

마침 타이밍 좋게 두 개 다 종료가 됐다. 

 

Request

@GetMapping("/batches")
fun getBatchList() : Any

Response

{
    "object": "list",
    "data": [
        {
            "id": "batch_J52fTEP5cz30kXXj9bvwrLY7",
            "object": "batch",
            "endpoint": "/v1/chat/completions",
            "errors": null,
            "input_file_id": "file-LVMBPSLw2kcIjw5tDGPE3jNq",
            "completion_window": "24h",
            "status": "completed",
            "output_file_id": "file-BvpH9Eej1MRyaDjyVo4ilzUL",
            "error_file_id": null,
            "created_at": 1717746791,
            "in_progress_at": 1717746792,
            "expires_at": 1717833191,
            "finalizing_at": 1717748144,
            "completed_at": 1717748144,
            "failed_at": null,
            "expired_at": null,
            "cancelling_at": null,
            "cancelled_at": null,
            "request_counts": {
                "total": 2,
                "completed": 2,
                "failed": 0
            },
            "metadata": null
        }
    ],
    "first_id": "batch_J52fTEP5cz30kXXj9bvwrLY7",
    "last_id": "batch_J52fTEP5cz30kXXj9bvwrLY7",
    "has_more": false
}

 

4. 결과 확인

결과확인은 두 가지 방법으로 할 수 있다.

 

첫 번째는 OpenAI Playground에 접속해서 다운로드해서 확인하는 것

 

우측 하단 Download output으로 확인

 

두 번째는 조금 귀찮긴 하지만 /files/{file_id}/content API로 확인해 볼 수 있다.

@FeignClient(name = "OpenAiClient", url = "https://api.openai.com/v1", configuration = [OpenAiHeaderConfiguration::class])
interface OpenAiFeignClient {
    @GetMapping("/files/{fileId}/content")
    fun getFilesContent(@PathVariable fileId: String): ByteArray
}

fun getFilesContent(fileId: String): ResponseEntity<Any> {
    return try {
        val uploadResult = openAiFeignClient.getFilesContent(fileId)
        ResponseEntity.ok(saveFile(uploadResult, "outputFile.json"))
    } catch (e: Exception) {
        ResponseEntity(e.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
}

fun saveFile(byteArray: ByteArray, filePath: String): String {
    val file = File(filePath)
    file.writeBytes(byteArray)
    return "File saved successfully at $filePath"
}

 

outputFile.json을 열어보면 아래와 같은 결과를 확인해 볼 수 있다.

{"id": "batch_req_BgPY4YQ3SZKIIraS3mi3LXKD", "custom_id": "request-1", "response": {"status_code": 200, "request_id": "9ab692590d97c6d8f4eae8cd6bf511a4", "body": {"id": "chatcmpl-9XOixW6L21pHgrABo1FNWuUNqqWVS", "object": "chat.completion", "created": 1717746891, "model": "gpt-3.5-turbo-0125", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello! How can I assist you today?"}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 20, "completion_tokens": 9, "total_tokens": 29}, "system_fingerprint": null}}, "error": null}
{"id": "batch_req_uepcDhYJTcWIHurmA9FgxjaF", "custom_id": "request-2", "response": {"status_code": 200, "request_id": "fe4ecf39607983548008df2efac35f4d", "body": {"id": "chatcmpl-9XOimMdUsAyEZkt5GopN7RIuNzTHS", "object": "chat.completion", "created": 1717746880, "model": "gpt-3.5-turbo-0125", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Hello! How can I not assist you today?"}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 22, "completion_tokens": 10, "total_tokens": 32}, "system_fingerprint": null}}, "error": null}

 

5. Cancel batch

마지막으로 batch를 중지하는 API이다. in-progress 중일 때 이 API를 호출하면 status가 cancelling이 되고, 10분이 지나면 cancelled가 된다.

 

Request

@PostMapping("/batches/{batchId}/cancel")
fun cancelBatches(@PathVariable batchId: String) : Any

Response 

{
    "id": "batch_85AKedsacbOsGY2vJjAgfVLQ",
    "object": "batch",
    "endpoint": "/v1/chat/completions",
    "errors": null,
    "input_file_id": "file-LVMBPSLw2kcIjw5tDGPE3jNq",
    "completion_window": "24h",
    "status": "cancelling", // 10분 후 cancelled가 됨
    "output_file_id": null,
    "error_file_id": null,
    "created_at": 1717751256,
    "in_progress_at": 1717751256,
    "expires_at": 1717837656,
    "finalizing_at": null,
    "completed_at": null,
    "failed_at": null,
    "expired_at": null,
    "cancelling_at": 1717751266,
    "cancelled_at": null,
    "request_counts": {
        "total": 2,
        "completed": 0,
        "failed": 0
    },
    "metadata": null
}

 

완료된 Batch ID를 넣으면 아래와 같이 출력된다.

 

Error

[409 Conflict] during [POST] to [https://api.openai.com/v1/batches/batch_J52fTEP5cz30kXXj9bvwrLY7/cancel] [OpenAiFeignClient#cancelBatches(String)]: [{
  "error": {
    "message": "Cannot cancel a batch with status 'completed'.",
    "type": "invalid_request_error",
    "param": null,
    "code": null
  }
}]

 

마지막으로 Batch 삭제는 Playground > Storage > Batch Id 선택 > 우측 상단 쓰레기통 모양을 눌러주면 된다.

 

Github

https://github.com/imsosleepy/openai-api-repository/blob/main/src/main/kotlin/com/openai/client/OpenAiFeignClient.kt

 

마치며

오픈 소스 기여를 위해 Batch API를 써볼 필요가 있어서 포스팅까지 해봤다.

 

오랜만에 막힘없이 할 수 있는 CRUD 구현 작업을 하니 재밌긴하다.

 

다만, 이 포스팅보다는 100배는 중요하다고 생각하는 오픈 소스 기여가 잘 진행될지 모르겠다...

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함