OpenAI의 구조화된 출력(Structured Outputs)을 사용해보자

하나둘셋·2024년 12월 18일

이 게시물을 읽기 전 JSON에 대해 잘 모르겠다면 이전 게시물을 참고하길 바란다.

JSON 개념 정리와 자주 일어나는 JSONDecodeError에 대해


구조화된 출력(Structrued Outputs)?

우리가 사용하는 OpenAI 모델은 웹 서버와의 통신을 용이하게 하기 위해 구조화된 출력(Structured Outputs)을 사용해야 했다.

구조화된 출력(Structured Outputs)은 LLM의 답변을 지정된 JSON 형식에 맞게 생성시켜주는 기능이다. 아래의 형태처럼 일반적인 긴 줄글의 답변이 아닌 유효한 JSON 형식의 답변으로 모델이 생성할 수 있다.

{
  "steps": [
    {
      "explanation": "Subtract 31 from both sides to isolate the term with x.",
      "output": "8x + 31 - 31 = 2 - 31"
    },
    {
      "explanation": "This simplifies to 8x = -29.",
      "output": "8x = -29"
    },
    {
      "explanation": "Divide both sides by 8 to solve for x.",
      "output": "x = -29 / 8"
    }
  ],
  "final_answer": "x = -29 / 8"
}

OpenAI API에서 구조화된 출력은 gpt-4o로 시작되는 모델에서만 사용이 가능하다.

  • o1-2024-12-17 이후 모델
  • gpt-4o-mini-2024-07-18 이후 모델
  • gpt-4o-2024-08-06 이후 모델

원래는 JSON 모드를 통해 답변을 출력하다가 더 정확하고 유효한 JSON 답변을 출력하기 위해서 구조화된 출력을 사용하기 시작했다.구조화된 출력 기능은 개발자가 제공한 스키마와 일치하도록 제한하고, 복잡한 스키마를 더 잘 이해하도록 모델을 훈련시킨 결과이다.

OpenAI가 구조화된 출력을 평가한 결과 gpt-4o-2024-08-06 모델에서 100%의 신뢰성을 가진 출력을 얻을 수 있었다고 한다.

OpenAI의 Structured Outputs에 대해서는 아래의 문서를 참고하면 더 구체적인 개념과 사용법을 알 수 있다.



구조화된 출력 사용 예시

구조화된 출력을 사용하는 경우는 두가지 인데 하나는 함수 호출을 사용할 때와 JSON 스키마 응답 형식을 사용할 때이다. 나는 함수 호출은 다른 방식을 이용했고, 모델이 함수의 작동 결과를 참고하여 답변을 생성할 때 JSON 형식으로 생성하도록 사용하였다.

1. 출력 구조 정의

먼저 모델이 출력할 때 따라야 하는 JSON 스키마로서 데이터 구조를 정의해줘야 한다. 정의하는 방법은 SDK를 사용하여 Pydantic을 사용할 수 있지만 나는 사용자 질문에 따라 답변하는 JSON 값이 미묘하게 다르기 때문에 더 정확한 출력 유도를 위해서 JSON 스키마 여러개를 직접 정의하였다.

myFormat = {
    "type": "json_schema",
    "json_schema" : {
        "name" : "A_unique_answer",
        "schema" : {
            "type": "object",
            "properties": {
                "response" : {
                    "type": "string",
                    "description" : "put a short sentence that gives information about the places the user like"
                },
                "additionalInfo": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties" : {
                            "name" : {
                                "type": "string",
                                "description" : "The name of the place"
                            },
                            "explanation" : {
                                "type": "string",
                                "description" : "A brief description of the place."
                            }
                        },
                        "additionalProperties": False,
                        "required" : ["place_name", "explanation"]
                    }
                }
            },
            "additionalProperties": False,
            "required" : ["response", "additionalInfo"] 
        },
        "strict" : True
    }

구조화된 출력을 사용하기 위해서는 모든 필드를 required로 지정해야 하고 strictTrue로 설정하여 제공된 스키마에 출력을 일치하도록 맞춘다. JSON 스키마에 정의되지 않은 추가적인 키/값 생성을 막기 위해서 additionalPropertiesFalse로 설정하였다.

이렇게 스키마를 정의하고 myFormat 변수에 저장하였다.


2. 호출 API에 출력 구조 제공

messages = [
	{"role":"system", "content": "당신은 사용자의 취향을 파악하여 사용자가 좋아할 만한 장소를 추천해주는 봇입니다."},
    {"role": "user", "content":f"{reference}"}
]

response = client.chat.completions.create(
		model=model,
		messages=messages,
    	response_format=myFormat
)

위에서 정의한 스키마를 OpenAPI를 호출할 때 response_format 파라미터에 넣어준다. 이렇게 넣어주면 내가 지정한 JSON 형식으로 답변을 생성한다. Pydantic 객체로 출력 구조를 정의했으면 client.chat.completions.parse() 메소드를 이용해 JSON 응답을 내가 만든 객체로 변환해 가져올 수 있다.


3. 생성된 출력 확인

answer = response.choices[0].message.content
print(answer)

출력)
'{
	"response" : "사용자가 좋아할 만한 장소를 알려드립니다.",
    "additionalInfo" :   
    	[
    		{
 				"name" : "하늘하늘 카페",
                "explanation" : "따뜻한 무드의 카페로 가족과 함께 방문하기 좋은 곳입니다."
    		},
            {
 				"name" : "바다의 울림",
                "explanation" : "사용자는 귀여운 물품을 좋아하기 때문에 바다와 관련된 물품을 파는 곳에서 다양한 굿즈를 얻을 수 있습니다."
    		},
            ...
        ]
 }'

위의 방법대로 답변을 확인해 봤을 때, 지정한 JSON 형식의 문자열로 답변이 잘 생성된 것을 알 수 있었다.


def change_to_dict(answer):
    try:
        response_dict = json.loads(answer, strict=False)
        return response_dict
    except Exception as e:
        print(f"Error escaping JSON strings: {e}")
        return response

생성한 JSON 문자열을 json.loads() 함수를 이용해 파이썬 딕셔너리로 변환할 수 있다. 이때, 유효하지 않은 JSON 형식의 데이터일 경우 오류가 발생하는데 Structured Outputs로 생성한 답변은 오류가 발생하지 않았다.

profile
하나씩 뚝딱뚝딱

0개의 댓글