본문 바로가기
프로젝트/나무위키LLM

[ERROR] 왜 key error가 발생했을까?

by 행복한라이언 2024. 1. 30.
728x90
반응형

1. 에러 발생 및 해결(+ 후속과제)

  • 발생: 특정 질문에 대해서는 에러가 나지 않는데 특정 질문에 대해서는 front에서는 key error가 발생하는 문제가 발생했다. wiseman api에서는 400 Bad Requeset 에러가 발생했다. 뭐가 문제일까?
answer = response.json()['answer']
KeyError: 'answer'

400 Bad Request

  • 상황1: front에서 gateway 역할을 하는 novelist api에 query를 요청했을 때 { "answer" :  ~~ } 형식으로 값이 리턴된다. 
  • 상황2: 전체 흐름은 다음과 같다. 
    • 서비스는 front에서 novelist api에 query를 날리면 novelist api는 query를 받은 후에 librarian api에 관련 document를 요청한다. 관련 document, query와 함께 wiseman client에서 wiseman api에 요청을 하고 wiseman에서는 GPT server에 요청해 answer를 얻는다. novelist api는 answer를 front에 전달한다.
    • 추정: 혹시 document가 없어서 문제인지 확인하기 위해서 문제가 발생하는지 프린트 찍어봤는데 document가 None이 발생하지는 않았다. 그리고 librarin api는 elastic에서 관련 documen도 잘 찾아왔다.
response_librarian_client = await librarian_client.search_document_content(query)
document = response_librarian_client

if document is None:
    print("여기가 문젠가?")
    document = ''

 

  • 상황3: 그렇다면 wiseman api에 query와document를 넘겼을 때 문제가 발생해 400 Bad Request 가 발생했고 key error는 이400 Bad Request 에서 비롯되었을 가능성이 매우 높아 결국 이 에러는 고쳐야한다. 이 에러는 현재 wiseman api에서 발생하고 있다. novelist router에서 wiseman_client에서 문제가 발생한 것 외에는 이유가 없어보인다....
    • 배경지식 400 Bad Request - The HyperText Transfer Protocol (HTTP) 400 Bad Request response status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (for example, malformed request syntax, invalid request message framing, or deceptive request routing). 짧게 정리하면 400 Bad Request 에러는 client의 요청에 문제가 있으니까 고치라는 의미이다.
# 400 Bad Request
The request could not be understood by the server due to malformed syntax.
@novelist_router.post("/query")
async def get_resposne(query: Annotated[str, Form()]) -> Union[dict, None]:
    # librarian client가 elsatic에서 document 추출
    response_librarian_client = await librarian_client.search_document_content(query)
    
    document = response_librarian_client
    # 해당 검색이 없는 경우.
    if document is None:
        print("여기가 문젠가?")
        document = ''
        
    reponse_wiseman_client = await wiseman_client.get_resposne(query, document) <- 요기가 문제
    
    return {"answer": reponse_wiseman_client}
  • 상황4: 400 Bad Request의 가장 빈번한 원인은 URL 입력 오류라고 하는데 나의 경우 질문에 따라 에러 발생이 달라졌다. 따라서 URL 입력 오류는 아니다.
  • 상황5: 그럼 이제 wiseman api을 분석을 해보자. 그런데 에러가 발생하는 query에 대해서 포스트맨에서는 "[ERROR] Wiseman-GPT error" 가 리턴되었다. 이 에러가 발생하는 곳으로 가보자. (나중 FastAPI Exception Handler 설정하기)
self.API_URL = self.conf['wisemane_server_api']

async def get_resposne(self, text: str, document: str):
    async with httpx.AsyncClient() as client:
        print("Wiseman Client 작동 확인")
        response = await client.post(
            f"{self.API_URL}/ask",

            data= {"text" : text, "document": document},

            timeout=None
        )

        if response.status_code != 200:
            raise HTTPException(
                status_code= response.status_code,
                detail= "[ERROR] Wiseman-GPT error"
            )

    answer = response.json()['answer']

    return answer

 

  • 상황6: wiseman api에 요청했는 때 빠구 먹은 것이 확실해졌다. wiseman api에서 무슨 문제가 생긴걸까? answer가 찍히는지 확인해보자.
@wiseman_router.post("/ask")
async def get_LLM_answer(text: Annotated[str, Form()], document: Annotated[str, Form()]) -> dict:
    gpt_query = f'''
                query:
                - [{text}]
                
                reference:
                - [{document}]
                '''
    print("시작입니다!")
    # content - gpt api의 결과물을 의미
    answer = await llm_client.get_resposne(gpt_query)
    print(answer)
    print("종료입니다!")
    return {
        "answer" : answer
    }
    
    
시작입니다!
GPT Client
  • 상황7: "시작입니다" 출력 이후에 llm_client_get_response로 들어갔다. 그런데 그 이후 print(answer)와 "종료입니다"가 출력되지 않았다. 즉 llm_client_get_response에서 에러가 발생했을 것이다.
    • 밑의 코드가 llm_client의 get_response 메서드이다.  그러면 여기서 에러가 발생한 것인가? 그렇다는건 GPT api에 요청을 실패한 것이다. 왜 api가 거부했을까?
async def get_resposne(self, text: str):
    async with httpx.AsyncClient() as client:
        print("GPT Client")
        response = await client.post(
            self.OPENAI_API_URL,
            headers={"Authorization": f"Bearer {self.OPENAI_API_KEY}"},
            json={"model": self.OPENAI_API_MODEL,
                "messages": [
                    {"role": "user",
                    "content": text}
                ]},
            timeout=None
        )

        if response.status_code != 200:
            raise HTTPException(status_code=response.status_code,
                                detail="[ERROR] LLMClient!")

        GPT_answer = response.json()['choices'][0]['message']['content']

        return GPT_answer
  • 상황8: GPT server에서 받은 response에 대해서 살펴보자. Body를 살펴보면 왜 이 에러가 발생했는지 근본적으로 이해할 수 있었다.  내가 방금 key error 난 gpt에게 보낸 query의 길이는  278024였다. 아무튼 message를 살펴보면 max 4097 token인데 내가 보낸 쿼리가 29021 token( query + @ )라서 에러가 났다고 써져있다.
Status Code: 400
Headers: Headers([('date', 'Fri, 02 Feb 2024 13:16:27 GMT'), ('content-type', 'application/json'), ('content-length', '282'), ('connection', 'keep-alive'), ('access-control-allow-origin', '*'), ('openai-organization', 'user-jbidsaiizu0k3amdimd7mh4p'), ('openai-processing-ms', '49'), ('openai-version', '2020-10-01'), ('strict-transport-security', 'max-age=15724800; includeSubDomains'), ('x-ratelimit-limit-requests', '10000'), ('x-ratelimit-limit-tokens', '60000'), ('x-ratelimit-remaining-requests', '9999'), ('x-ratelimit-remaining-tokens', '43158'), ('x-ratelimit-reset-requests', '8.64s'), ('x-ratelimit-reset-tokens', '16.842s'), ('x-request-id', '20f03e071c5610ba08c4197738f6e960'), ('cf-cache-status', 'DYNAMIC'), ('set-cookie', '__cf_bm=x4u7mgnXbf8xatPh.Pv8MekEGMDdg0Yxir_.13Oj21Q-1706879787-1-AerfoWKjyfHChFBMVS/PXJBAgmuqlrYNq3nJfgQsvrHF4gkycqGgMjC0tyvf5kIO0AxDkvQcMrtyX2dNEMFIgdc=; path=/; expires=Fri, 02-Feb-24 13:46:27 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None'), ('set-cookie', '_cfuvid=.5oQ5QgmwGk3cWT1h1JlmzB8jf8mWOq08Z8wTSuBw1I-1706879787339-0-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None'), ('server', 'cloudflare'), ('cf-ray', '84f2b569eefb931d-ICN'), ('alt-svc', 'h3=":443"; ma=86400')])
Body: {
  "error": {
    "message": "This model's maximum context length is 4097 tokens. However, your messages resulted in 29021 tokens. Please reduce the length of the messages.",
    "type": "invalid_request_error",
    "param": "messages",
    "code": "context_length_exceeded"
  }
}
  • 불완전한 해결: 기존의 model은 gpt-3.5-turbo 였는데 max_token의 수를 늘리기 위해서 model을 gpt-3.5-turbo-0125로 우선 변경하였다. 진짜 큰 문서가 아닌 이상 이제 에러가 발생하지는 않는다. 하지만 16,385 token을 넘는 문서가 존재하기 때문에 이 부분은 추후 반드시 해결해야한다.

 

★ 후속과제

- 16,385 token이 넘는 경우에는 어떻게 해야할까? 비단 나무위키 문서뿐 아니라 우리가 평상시에 접하는 문서들도 이것보다 클 수 있다. 따라서 검색 성능을 유지한 채로 큰 문서를 작은 문서로 쪼개어 색인하는 방법에 대한 고민이 반드시 필요하다!

 

2. 참고사이트

https://blog.npcode.com/2013/04/23/400-bad-request%EC%99%80-403-forbidden%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%97%90-%EB%8C%80%ED%95%B4/

 

400 Bad Request와 403 Forbidden의 의미에 대해

HTTP/1.1 HTTP/1.1을 정의한 최신 명세인 RFC 2616에 따르면, 흔히 알고 있는 것과는 달리 의외로 403 Forbidden은 권한(authorization)에 대한 에러가 아니다. 그냥 요청은 이해하지만 수행을 거절(refuse)하겠다

blog.npcode.com

 

728x90
반응형