Azure OpenAI Structured Outputs
Today Key : structured, outputs, azure, openai, ai, 구조화, 출력, chatgpt, gpt
이번 포스팅에서는 지난 8월 6일에서 ChatGPT에 추가 된 API에서의 Structured Outputs에 대한 내용입니다.
Structured Outputs이 무엇인지 간단하게 살펴보고, 사용 예시를 몇 가지 정리했습니다.
Structured outputs
지원 모델 및 API 버전
Structured outputs 사용 예시-1
chat.completion.parse 함수를 사용해서, gpt-4o를 호출하면서
response_format에 앞서 BaseModel을 상속해서 만든 CalendarEvent를 지정합니다.
from pydantic import BaseModel
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint = "Azure Endpoint",
api_key="API Key",
api_version="2024-08-01-preview"
)
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
completion = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "system", "content": "주어진 내용에서 행사 정보를 추출합니다."},
{"role": "user", "content": "앨리스와 밥은 금요일에 과학 박람회에 갈 예정입니다."},
],
response_format=CalendarEvent,
)
event = completion.choices[0].message.parsed
print(event)
이제 메시지를 살펴보면,
System Message로는 주어진 내용에서 행사 정보를 추출하도록 되어 있고,
User Message는 행사와 관련된 문장인 ' 앨리스와 밥은 금요일에 과학 박람회에 갈 예정입니다' 가 있습니다.
출력된 결과 값을 확인하면 response_format으로 지정한 CalendarEvent의 데이터 구조에 맞춰서
행사명(name), 날짜(Date), 참가자(participants) 행태로 구조화된 답변이 된 것을 볼 수 있습니다.
[실행 결과]
이번에는 문장을 조금더 복잡하게 테스트를 해보겠습니다.
System Message에서는 추출을 한국어로 하도록 추가하였습니다.
def parse_chat(query: str) -> CalendarEvent:
completion = client.beta.chat.completions.parse(
model="gpt-4o", #
messages=[
{"role": "system", "content": "주어진 내용에서 행사 정보를 추출합니다.추출은 한국어로 합니다."},
{"role": "user", "content": query},
],
response_format=CalendarEvent,
)
return completion.choices[0].message.parsed
행사와 관련된 User Message에는 참가자와 요일에 대한 정보를 조금 헛갈릴 수 있도록 다음과 같이 정했습니다.
'앨리스와 밥과 존은 화요일에 카페에서 만나 목요일에 엑스포를 가기로 결정했는 데, 밥은 빼고 2명은 참석하기로 했다.'
query = "앨리스와 밥과 존은 화요일에 카페에서 만나 목요일에 엑스포를 가기로 결정했는 데, 밥은 빼고 2명은 참석하기로 했다."
event = parse_chat(query)
print(event)
이번에도 처음보다는 조금 더 복잡한 문장으로 메시지를 주었으나,
다음과 같이 구조화 된 형태로 잘 찾아낸 것을 볼 수 있었습니다.
[실행 결과]
Structured outputs 사용 예시-2
from pydantic import BaseModel
from openai import AzureOpenAI
client = AzureOpenAI(
azure_endpoint = "Azure Endpoint",
api_key="API Key",
api_version="2024-08-01-preview"
)
class CurrentTime(BaseModel):
city: str
class SearchBing(BaseModel):
search_word: str
tools = [openai.pydantic_function_tool(CurrentTime), openai.pydantic_function_tool(SearchBing)]
def getCurrentTime(city: str) -> str:
return f"현재 {city}의 시간은 10시입니다."
def getSearchResult(search_word: str) -> str:
return f"{search_word}에 대한 검색 결과입니다."
Message에서는 워싱턴의 시간은 몇 시인지 확인하도록 하였고,
chat.completions에서 위에서 생성한 tools를 추가하였습니다.
그리고, 결과 값에서 Fuction의 name과 arguments를 확인해보면,
messages = []
messages.append({"role": "system", "content": "당신은 유용한 도우미입니다. 제공된 도구를 사용하여 사용자를 지원하세요."})
messages.append({"role": "user", "content": "워싱턴의 시간은 몇시인간요?"})
response = client.chat.completions.create(
model="gpt-4o", # replace with the model deployment name of your gpt-4o 2024-08-06 deployment
messages=messages,
tools=tools
)
func_name = response.choices[0].message.tool_calls[0].function.name
func_args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
print(f"Function Name : {func_name}")
print(f"Function Arguments : {func_args}")
앞서 BaseModel을 상속해서 만든 Class 중에서 현재 시간과 관련이 있는 CurrentTime 클래스 이름과
해당 클래스엔 선언된 city 라는 데이터에 User Message에서 물었던 '워싱턴'이라는 도시가 있는 것을 볼 수 있습니다.
간단하게, Class로 어떤 함수를 호출 할 것인지 확인하여
argments로 받아온 값으로 함수를 호출할 수 있습니다.
if func_name == "CurrentTime":
print(getCurrentTime(func_args["city"]))
elif func_name == "SearchBing":
print(getSearchResult(func_args["search_word"]))
함수 호출에 대한 결과도 잘 나오는 것을 볼 수 있습니다.
이번에는 User Message를 검색과 관련된 함수를 호출하도록 변경하였습니다.
messages = []
messages.append({"role": "system", "content": "당신은 유용한 도우미입니다. 제공된 도구를 사용하여 사용자를 지원하세요."})
messages.append({"role": "user", "content": "gpt search가 어떤거야?"})
response = client.chat.completions.create(
model="gpt-4o", # replace with the model deployment name of your gpt-4o 2024-08-06 deployment
messages=messages,
tools=tools
)
func_name = response.choices[0].message.tool_calls[0].function.name
func_args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
print(f"Function Name : {func_name}")
print(f"Function Arguments : {func_args}")
이번에는 SearchBing이라는 클래스를 가져오고,
해당 SearchBing에 선언된 데이타인, search_word에
User Message에 포함된 GPT Search라는 값을 매핑하여 잘 가져온 것을 볼 수 있습니다.
동일하게 class명으로 호출할 함수명을 확인하여, 호출할 수 있습니다.
if func_name == "CurrentTime":
print(getCurrentTime(func_args["city"]))
elif func_name == "SearchBing":
print(getSearchResult(func_args["search_word"]))
결괏 값도 정상적으로 잘 호출된 것을 볼 수 있습니다.
Structured outputs 사용 예시-3
논문의 내용을 '제목', '저자', '요약', '키워드' 형태로 정리하려고 합니다.
BaseModel을 상속해서, '제목', '저자', '요약', '키워드' 를 저장하기 위한 구조를 선언합니다.
System Message에서는 비정형 데이터를 구조화 하도록 적절하게 선언해주었습니다.
User Message는 논문의 내용을 갖고 있는 변수있습니다.
웹페이지나, 문서 파일 등에서 눈문의 내용을 가져오시면 됩니다.
response_format 값에 위에서 선언한 ResearchPaperExtraction을 연결해 줍니다.
class ResearchPaperExtraction(BaseModel):
title: str
authors: list[str]
abstract: str
keywords: list[str]
system_msg = """당신은 구조화된 데이터 추출 전문가입니다.
연구 논문의 비구조화된 텍스트를 제공받으면, 이를 주어진 구조로 변환해야 합니다.
주어진 구조로 변환 시에 내용은 한국어로 정리합니다."""
completion = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "system", "content": system_msg},
{"role": "user", "content": paper_text}
],
response_format=ResearchPaperExtraction,
)
research_paper = completion.choices[0].message.parsed
print(f"논문 제목 : {research_paper.title}")
print(f"논문 저자 : {research_paper.authors}")
print(f"논문 요약 : {research_paper.abstract}")
print(f"논문 키워드 : {research_paper.keywords}")
이제 결괏 값을 확인해 보면, 텍스트 형태의 논문이
'제목, '저자', '요약', '키워드' 형태로 잘 구조화된 형태로 가져올 수 있음을 확인 할 수 있습니다.
Structured outputs 사용 예시-4
Chain of Thought를 위해서 Model이 생각하는 단계를 Step이라는 클래스로 생성합니다.
그리고, Step을 리스트 형태로 값고 있는 MathReasoning 클래스를 만들어서,
이를 response_format 형태로 사용하여,
모델이 추론식에 단계적으로 풀어나갈 수 있도록 구조를 잡고, 최종 답을 찾아내게 합니다.
class Step(BaseModel):
explanation: str
output: str
class MathReasoning(BaseModel):
steps: list[Step]
final_answer: str
completion = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "system", "content": "당신은 유용한 수학 교사입니다. 사용자에게 단계별로 해결 방법을 안내하세요.모든 답변은 한국어로 해주세요."},
{"role": "user", "content": "8x + 7 = -23을 어떻게 풀 수 있나요?"}
],
response_format=MathReasoning,
)
math_reasoning = completion.choices[0].message.parsed
print(math_reasoning)
생성된 답변을 확인해보면 다음과 같이
User Message의 계산 문제를 단계적으로 하나씩 해결해 나간 것을 볼 수 있습니다.
Structured outputs 사용 예시-5
class Community_Title(BaseModel):
company : str
title_name : str
class Profile(BaseModel):
name: str
community_title: List[Community_Title]
blog : str
def parse_profile(query: str) -> Profile:
completion = client.beta.chat.completions.parse(
model="gpt-4o", #
messages=[
{"role": "system", "content": "사용자 프로파일을 만드는 유용한 도우미입니다.추출은 한국어로 합니다."},
{"role": "user", "content": query},
],
response_format=Profile,
)
return completion.choices[0].message.parsed
User Message로 사용 할 query 값에 사용자에 대한 소개를 작성하여,
Structured outputs를 활용하여, 답변을 생성한 후
Structured outputs로 선언한 구조대로 출력을 해봅니다.
query = """ZIGI라는 닉네임을 가진 고재성은
zigispace.net 블로그를 운영하고 있습니다.
커뮤니티 활동으로는 Microsoft의 MVP와, AWS의 Community Builder를 하고 있습니다.
연락을 위해서는 010-1234-5678,
zigi@zigispace.net 으로 하시면 됩니다."""
profile = parse_profile(query)
print(f"Name : {profile.name}")
for title in profile.community_title:
print("Community Title")
print(f"- Company : {title.company}")
print(f"- Title : {title.title_name}")
print(f"Blog : {profile.blog}")
그러면, 다음과 같이 원하는 구조대로 답변이 출력되는 것을 볼 수 있으며
Community Title의 경우에는 중첩된 구조대로 잘 구조화 된 것도 확인 할 수 있습니다.