Skip to main content

GPT-4o와 구조화된 출력으로 신뢰성 높은 앱 만들기

GPT-4o 출력의 일관성을 강제하고, 신뢰할 수 있는 생성형 AI 앱을 만드는 방법을 알아보세요. 이 글은 AI 번역본입니다. 오역이 있을 경우 댓글로 알려주세요.
Created on September 12|Last edited on September 12
세상은 자유형식의 텍스트와 이미지부터 음성·영상의 복잡한 상호작용에 이르기까지 비정형 데이터로 가득합니다. 하지만 이 정보의 진정한 잠재력을 끌어내려면, 이를 구조화하고 체계적으로 정리해야 합니다.
한번 구조화되면 이 데이터는 매우 다양한 방식으로 활용될 수 있습니다. RAG(검색 증강 생성) 시스템부터 추천 엔진, 코드 생성 도구에 이르기까지 폭넓은 활용 사례를 뒷받침합니다. 이 글에서는 구조화된 출력의 개념을 살펴보고, 그 이점을 논의하며, AI 기반 솔루션에서의 효과를 보여 주는 몇 가지 재미있는 프로젝트를 함께 만들어 보겠습니다.



구조화된 출력이란 무엇을 의미할까요?

구조화된 출력은 미리 정의된 스키마를 준수하도록 AI 모델이 생성한 체계적이고 서식화된 데이터를 의미합니다. 이를 통해 생성된 정보가 일관되고 다양한 시스템에서 쉽게 활용될 수 있어, 전반적인 가독성과 사용성이 향상됩니다.
전통적인 AI 애플리케이션에서는 원하는 구조를 따르는 출력을 보장하기 위해 주로 프롬프트 엔지니어링이나 후처리 레이어에 의존해 왔습니다. 이 방식은 오류가 발생하기 쉽고, 특히 복잡한 데이터 구조를 다룰 때 유지 보수가 어렵습니다. OpenAI API에 구현된 구조화된 출력은 JSON 스키마 같은 형식을 사용해 API 내부에서 직접 스키마를 정의할 수 있게 함으로써 이러한 문제를 해결합니다. 이를 통해 모델의 응답이 기대하는 형식과 정확히 일치하도록 보장하고, 오류를 최소화하며, 검증이나 재서식화의 필요성을 줄여 줍니다.
OpenAI API에서 구조화된 출력을 사용하려면 스키마 정의를 다음에 포함하세요 response_format 요청을 보낼 때 해당 매개변수를 포함하세요. 그러면 모델이 지정된 형식과 일치하는 응답만 생성하도록 강제되어, 수동 검증이나 프롬프트 엔지니어링 요령이 필요 없어집니다. 응답이 스키마를 준수하지 않으면, 거부 메시지를 기록하거나 조건부 로직을 적용하는 등 프로그램적으로 처리할 수 있습니다.
구조화된 출력을 적용하는 기본 방법은 두 가지입니다. JSON 스키마를 사용하거나 함수 호출을 사용하는 것입니다. JSON 스키마는 저장이나 표시를 위해 데이터를 특정 구조로 엄격히 맞춰야 할 때 적합합니다. 반면 함수 호출은 모델이 도구나 외부 시스템과 상호작용하도록 할 때 사용합니다. 이번 튜토리얼에서는 JSON 스키마 사용에 집중하겠습니다.
구조화된 출력을 활용하면 누락된 필드나 잘못된 데이터 타입 같은 문제를 최소화하여, 추가적인 오류 처리나 복잡한 후처리 없이도 애플리케이션이 모델의 출력을 매끄럽게 통합할 수 있습니다.

OpenAI API에서 구조화된 출력 통합

OpenAI API에서 구조화된 출력을 통합하면 모델의 응답을 견고하게 제어할 수 있습니다. 모델이 따라야 할 스키마를 정의하면 필수 필드나 유효한 열거형 값 등 핵심 요소가 항상 포함되도록 보장할 수 있습니다. 이는 긴 프롬프트로 원하는 형식을 유도하거나, 응답을 일일이 수동으로 검증해야 했던 기존 방식과는 다른 접근입니다.
구조화된 출력은 AI 기반 애플리케이션의 전반적인 효율성과 신뢰성을 높여 주는 여러 핵심 이점을 제공합니다. 미리 정의된 스키마를 준수함으로써 데이터 처리가 개선되어, 생성되는 모든 응답이 기대하는 형식을 따르게 됩니다. 그 결과 복잡한 오류 처리나 검증 과정이 불필요해져, 개발자의 시간과 노력을 절약할 수 있습니다. 따라서 애플리케이션이 더 예측 가능하게 동작하며, 버그와 불일치를 줄일 수 있습니다.
또한 구조화된 출력이 제공하는 우수한 시스템 통합 기능은 시스템 간 데이터 교환을 원활하게 합니다. 형식이 일관되면 데이터 불일치나 통합 문제의 가능성이 줄어들어, 다양한 애플리케이션, 데이터베이스, API 간에 구조화된 정보를 쉽게 전달할 수 있습니다.
마지막으로, 향상된 사용자 경험도 중요한 장점입니다. 구조화된 출력은 응답의 가독성을 높이고, 개발자가 모델 출력의 각 요소를 구분해 제시할 수 있는 직관적인 인터페이스를 설계하도록 돕습니다. 그 결과 정보 전달이 더 명확해지고, 사용자와의 상호작용이 보다 매력적으로 개선됩니다.

실전 예제

이 섹션에서는 실제 애플리케이션에서 구조화된 출력의 강력함과 유연성을 보여 주는 세 가지 주요 사용 사례를 살펴보겠습니다. 각 사례는 구조화된 출출을 활용해 데이터 일관성을 강화하고, 다양한 상황에서 구조화된 응답을 더 쉽게 다룰 수 있���록 하는 방법을 강조합니다.

분류: 연구 논문 색인 만들기

구조화된 출력을 활용해 연구 논문을 미리 정의된 폭넓은 범주의 집합으로 분류하여, 사실상 연구 논문 색인을 구축하는 방법을 보여 드리겠습니다. 구조화된 JSON 스키마 형식을 사용하면 각 문서가 일관되게 분류되며, 결과는 “지도 학습”, “강화 학습”, “자연어 처리”와 같은 범주를 준수하도록 보장됩니다.
이러한 구조화된 분류 과정은 연구 문서를 체계적으로 정리하고 검색할 수 있게 하여, 색인된 콘텐츠를 더 쉽게 탐색하고 분석할 수 있도록 합니다. 이렇게 구축된 색인은 더 큰 지식 관리 시스템의 근간으로 활용될 수 있으며, 후속 분석과 다양한 애플리케이션을 위한 견고한 기반을 제공합니다.
다음 코드는 AI 연구 논문을 불러온 뒤 OpenAI의 구조화된 출력을 사용해 분류합니다.
import os
import json
import arxiv
import shutil
from PyPDF2 import PdfReader
from openai import OpenAI
import weave

# Initialize Weave and OpenAI
weave.init("paper_classification")
api_key = os.getenv('OPENAI_API_KEY')

model = "gpt-4o-mini"
client = OpenAI(api_key=api_key)

# Directory to download and categorize papers
download_dir = "./arxiv_papers"
if not os.path.exists(download_dir):
os.makedirs(download_dir)

# List of machine learning categories
categories = [
"Supervised Learning", "Unsupervised Learning", "Reinforcement Learning", "Deep Learning",
"Natural Language Processing", "Computer Vision", "Graph Neural Networks", "Transfer Learning",
"Meta-Learning", "Few-Shot Learning", "Self-Supervised Learning", "Representation Learning",
"Multi-Modal Learning", "Generative Adversarial Networks (GANs)", "Bayesian Methods",
"Probabilistic Models", "Federated Learning", "Privacy-Preserving ML", "Fairness and Bias in ML",
"Explainable AI", "Optimization Algorithms", "Adversarial Robustness", "Causal Inference",
"Anomaly Detection", "Time Series Analysis", "Graph-Based Learning", "Knowledge Graphs",
"Ontology Learning", "Recommender Systems", "Information Retrieval", "Domain Adaptation",
"Semi-Supervised Learning", "Data Augmentation Techniques", "Multi-Agent Systems",
"Human-in-the-Loop Learning", "Curriculum Learning", "Active Learning", "Imitation Learning",
"Inverse Reinforcement Learning", "Policy Optimization", "Robustness to Distribution Shifts",
"Neural Architecture Search (NAS)", "Hyperparameter Optimization", "Neurosymbolic AI",
"Neural Ordinary Differential Equations", "Memory-Augmented Networks", "Recurrent Neural Networks (RNNs)",
"Long Short-Term Memory (LSTM)", "Transformer Models", "Attention Mechanisms",
"Pre-trained Language Models (e.g., BERT, GPT)", "Contrastive Learning", "Energy-Based Models",
"Neural Style Transfer", "Object Detection", "Segmentation Models", "Image Generation", "3D Vision",
"Motion Prediction", "Speech Recognition", "Speech Synthesis", "Emotion Recognition",
"Text Generation", "Summarization", "Machine Translation", "Question Answering", "Dialogue Systems",
"Conversational AI", "Autonomous Systems", "Robotics and Control", "Game Theory in ML",
"Synthetic Data Generation", "Biomedical Data Analysis", "Bioinformatics", "Healthcare Applications of ML",
"Drug Discovery", "Predictive Maintenance", "Financial Modeling", "Climate Modeling",
"Physics-Informed Learning", "Chemistry Applications", "Material Science Applications",
"Social Network Analysis", "Sentiment Analysis", "Text Mining", "Data Mining", "Complex Systems",
"Ensemble Methods", "Evolutionary Algorithms", "Quantum Machine Learning", "ML System Performance Optimization",
"ML in Edge Computing", "ML for Internet of Things (IoT)", "Multi-Task Learning", "Continual Learning",
"Neural-Symbolic Learning", "Vision-Language Models", "Zero-Shot Learning", "Learning from Demonstration",
"Neural Network Pruning"
]

# Define a function to read the first 1000 characters of a PDF
def read_pdf_first_1000_chars(pdf_path):
try:
with open(pdf_path, 'rb') as file:
reader = PdfReader(file)
text = ""
for page in reader.pages:
text += page.extract_text()
if len(text) >= 1000:
return text[:1000]
except Exception as e:
print(f"Failed to read {pdf_path}: {e}")
return ""

# Define a function to categorize a paper based on its content using structured output
@weave.op
def categorize_paper(text):
# Define the JSON schema for structured output with enum categories (not required but helpful)
category_schema = {
"type": "json_schema",
"json_schema": {
"name": "paper_category_response",
"schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": categories, # Use the list of categories as enum options
"description": "The category of the research paper"
}
},
"required": ["category"], # Ensure that the response contains a category
"additionalProperties": False,
"strict": True
}
}
}

# Create the prompt for categorizing the text
prompt = f"""
Based on the following text from a research paper, categorize it into one of the following machine learning topics: {', '.join(categories)}.
Please respond with a JSON object in the format: {{"category": "Category Name"}}.

Research Paper Content:
{text}
"""
# Make the API request to categorize the text using structured output
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "You are a categorization assistant."},
{"role": "user", "content": prompt}
],
response_format=category_schema, # Use structured output format with enum
max_tokens=50,
temperature=0.3
)
# Parse the model's response to extract the category
result = response.choices[0].message.content.strip()
try:
result_json = json.loads(result)
category = result_json.get("category", "Uncategorized")
except json.JSONDecodeError:
category = "Uncategorized"

return category

# Define a function to move the PDF to the appropriate category folder
def move_pdf_to_category(pdf_path, category):
category_dir = os.path.join(download_dir, category.replace(" ", "_"))
if not os.path.exists(category_dir):
os.makedirs(category_dir)
shutil.move(pdf_path, os.path.join(category_dir, os.path.basename(pdf_path)))
print(f"Moved {pdf_path} to {category_dir}")

# Download recent papers from arXiv
query = "machine learning"
max_results = 1 # Change to a larger number as needed
search = arxiv.Search(
query=query,
max_results=max_results,
sort_by=arxiv.SortCriterion.SubmittedDate
)

# Iterate through each result and categorize the paper
for result in search.results():
print(f"Downloading: {result.title}")
paper_id = result.entry_id.split('/')[-1]
pdf_url = result.pdf_url
filename = f"{paper_id}.pdf"
result.download_pdf(dirpath=download_dir, filename=filename)
# Read the first 100 characters of the downloaded PDF
pdf_path = os.path.join(download_dir, filename)
text_snippet = read_pdf_first_1000_chars(pdf_path)
if text_snippet:
print(f"Categorizing paper: {filename}")
# Use the categorize_paper function to get the category
category = categorize_paper(text_snippet)
print(f"Assigned Category: {category}")
# Move the PDF to the appropriate category folder
move_pdf_to_category(pdf_path, category)
else:
print(f"Failed to extract text from {filename}")

이 스크립트는 미리 정의된 머신러닝 범주를 기준으로 연구 논문을 분류하기 위해 구조화된 출력을 사용합니다. 또한 W&B Weave를 활용해 분류 함수의 다양한 입력과 출력을 기록·추적하여, 모델 예측을 모니터링하고 디버그하기 쉽게 만듭니다.
연구 논문을 다운로드하면 PyPDF2로 본문 일부를 추출한 뒤 이를 통해 전달합니다 categorize_paper 함수입니다. 이 함수는 열거형 범주가 포함된 JSON 스키마를 설정하여, 모델 출력이 정의된 구조를 준수하도록 보장합니다. OpenAI API를 사용해 모델이 JSON 형식의 응답을 생성하면, 이를 파싱해 논문의 범주를 결정합니다. Weave는 이러한 결과를 추적하여 모든 추론과 분류 과정을 다시 확인하거나 공유할 수 있게 하며, 투명성과 분석의 용이성을 제공합니다. 이는 대규모의 체계적인 연구 논문 데이터베이스를 구축하는 데 특히 유용합니다.

검색 증강 생성 시스템에서의 활용: 구조화된 데이터베이스 구축

이전 프로젝트에서, RAG 기반의 레스토랑 메뉴를 구축했습니다, 이는 사용자가 자연어 쿼리로 메뉴 항목을 검색할 수 있게 해 줍니다. 이를 위해 먼저 JSON 객체 형태의 구조화된 항목 목록을 만들어야 했습니다.
이를 위해서는 메뉴 PDF처럼 구조화되지 않은 본문을 입력했을 때 LLM이 구조화된 JSON 객체를 내도록 유도하는 매우 복잡한 프롬프트를 만들어야 했습니다. 여기서는 복잡한 프롬프트 없이 구조화된 출력을 활용하여, 이후 RAG 시스템에서 사용할 동일한 구조화된 데이터베이스를 만드는 데 집중하겠습니다. 이 단계에서는 메뉴 항목을 구조화된 JSON 형식으로 정리하여 데이터가 깔끔하고 체계적이며 다루기 쉽게 되도록 보장합니다.
구조화가 끝나면 이 대용량 JSONL 객체(기본적으로 JSON 객체들의 목록)는 벡터화 준비가 완료됩니다. 이는 RAG(검색 증강 생성) 시스템을 구축하는 데 있어 핵심 단계입니다. 벡터화 이전에 데이터가 미리 정의된 스키마를 준수하도록 보장하면, 일관성을 유지할 수 있고 RAG 시스템이 의존하는 검색 및 생성 과정의 전반적 효율을 높일 수 있습니다. 스크립트는 구조화된 출력을 사용해 메뉴 데이터를 저장하기 전에 깔끔한 형식으로 정리되도록 보장합니다. 이렇게 만든 데이터베이스는 이후 RAG 시스템의 핵심 구성 요소로 사용됩니다.
import os
import json
import PyPDF2
import re
from openai import OpenAI
import weave

# this is for a menu rag system which allows users to search for menu items using
# natural langauge
# Initialize Weave
weave.init("menu_standardization")

# Use your hardcoded OpenAI API key (set your key here)
api_key = os.getenv('OPENAI_API_KEY')
client = OpenAI(api_key=api_key)

# Define a function to split the text into manageable chunks
def split_text(text, chunk_size=4000, overlap=500):
chunks = []
start = 0
while start < len(text):
if start + chunk_size > len(text):
chunks.append(text[start:])
else:
end = start + chunk_size
chunks.append(text[start:end + overlap])
start += chunk_size
return chunks

# Define a function to read and process the menu PDF
def read_and_process_menu(pdf_path):
menu_data = []

# Define the structured output schema expecting an array of objects
menu_items_schema = {
"type": "json_schema",
"json_schema": {
"name": "menu_items_response",
"schema": {
"type": "object",
"properties": {
"items": { # The root property is an array named "items"
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The name of the menu item"
},
"description": {
"type": "string",
"description": "A detailed description of the menu item"
},
"keywords": {
"type": "string",
"description": "Comma-separated keywords for the menu item (e.g., 'dessert, chicken, side')"
}
},
"required": ["title", "description", "keywords"], # All three fields are required
"additionalProperties": False
}
}
},
"required": ["items"], # Ensure that "items" is included in the response
"additionalProperties": False
}
}
}

# Read the PDF and extract text
with open(pdf_path, 'rb') as file:
reader = PyPDF2.PdfReader(file)
for page_num in range(len(reader.pages)):
page_text = reader.pages[page_num].extract_text()
text_chunks = split_text(page_text)

for chunk in text_chunks:
# Create the prompt for each chunk
prompt_text = "Convert the following menu text into a JSON array with each item containing 'title', 'description', and 'keywords'."

# Make the API request using the structured output format
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant that structures menu items into JSON array format."},
{"role": "user", "content": prompt_text + " Here is the text: " + chunk}
],
response_format=menu_items_schema # Use structured output schema
)

# Parse the model's response to extract the structured output
result = response.choices[0].message.content.strip()
try:
# Parse the structured JSON response
parsed_response = json.loads(result)

# Expecting an "items" key containing the list of menu items
menu_items = parsed_response.get("items", [])
# Add the page number to each menu item
for item in menu_items:
item['page'] = page_num + 1

# Append the parsed items to the menu data
menu_data.extend(menu_items)

except json.JSONDecodeError as e:
print(f"JSON parsing failed for page {page_num + 1}: {e}")
print(f"Response content: {result}")

# Remove duplicates based on title
unique_menu_data = {item['title']: item for item in menu_data}
menu_data = list(unique_menu_data.values())

return menu_data

# Replace 'menu.pdf' with the path to your PDF file
pdf_path = './menu.pdf'
menu_items = read_and_process_menu(pdf_path)

# Save the results to a JSON file
with open('gpt4_menu_data.json', 'w') as json_file:
json.dump(menu_items, json_file, indent=4)

print("Menu items successfully processed and saved to gpt4_menu_data.json")

read_and_process_menu 함수는 메뉴 PDF를 읽고 다음을 사용해 텍스트를 추출합니다 PyPDF2모델의 토큰 제한에 맞추기 위해 이를 여러 청크로 분할합니다. 각 청크는 OpenAI의 구조화된 출력을 사용해 정의한 스키마를 준수하며, 메뉴 데이터를 구조화된 JSON 배열로 반환하라는 지침이 포함된 프롬프트로 처리됩니다. title, description, keywords 같은 필드를 포함하는 스키마를 강제하면 데이터가 일관된 구조를 보장받게 되며, 이는 RAG 시스템을 위한 벡터화 전에 매우 중요합니다.
이 작업은 각 카테고리별 하위 디렉터리를 포함한 폴더를 생성하고, 관련 논문을 해당하는 폴더로 이동합니다. 완료.
우리는 호출하므로 weave.init()그리고 OpenAI API를 사용하므로, API에 대한 모든 호출은 Weave에 자동으로 로깅되며, 나중에 우리 함수의 입력과 출력을 확인할 수 있습니다.
스크립트를 실행한 뒤 Weave 내부 화면은 다음과 같습니다.



음성 명령으로 코드 생성: 양식 빌더를 위한 오디오 지침을 구조화된 JSON으로 변환하기

음성 명령을 받아 구조화된 폼 스키마로 변환하고, 이를 인터랙티브한 HTML 폼으로 렌더링하는 시스템을 구축하겠습니다. OpenAI의 Whisper API로 오디오를 전사하고, OpenAI 모델의 구조화된 출력 기능을 결합해 사용하면, 사용자가 폼의 세부 정보를 말로 입력해도 자동으로 동작하는 웹 폼으로 변환할 수 있습니다.
폼 스키마가 생성되면, Flask 앱으로 전달되어 폼 필드를 동적으로 생성하고 웹 인터페이스에 렌더링합니다. Flask 앱은 JSON 객체를 읽어 각 필드의 타입에 따라 텍스트 필드, 드롭다운, 라디오 버튼 같은 HTML 컴포넌트로 변환합니다. 이렇게 생성된 폼은 웹 인터페이스에서 작성하고 제출할 수 있어, 생성된 스키마와 쉽게 상호작용할 수 있습니다.
이 프로젝트는 두 가지 핵심 구성 요소로 이루어져 있습니다: 음성 인식 처리 모듈 그리고 폼 생성기 각 모듈이 맞물려 동작하며 음성 명령을 손쉽게 구조화된 출력으로 변환합니다. 이를 통해 음성 기반 입력을 활용해 소프트웨어와 상호작용하는 새로운 방식을 열어 주며, 빠른 프로토타이핑과 UI 설계를 가능하게 합니다.
코드는 다음과 같습니다:
import openai
import sounddevice as sd
import numpy as np
import scipy.io.wavfile as wav
import tempfile
from openai import OpenAI
import os
import json
import weave

# Set your OpenAI API key (replace 'YOUR_API_KEY' with your actual key)
api_key = os.getenv('OPENAI_API_KEY')
client = OpenAI(api_key=api_key)

# Parameters for audio recording
SAMPLE_RATE = 24000 # Sample rate for recording
RECORD_DURATION = 10 # Maximum duration to record in seconds

# Function to record audio using sounddevice and save it as a temporary .wav file


from flask import Flask, render_template_string, request

app = Flask(__name__)

# Store the form schema in a global variable
form_schema = None

def generate_form_from_json(json_input):
"""Generates an HTML form based on the given JSON input."""
form_html = '<form method="POST" action="/submit">\n'
for field in json_input["fields"]:
field_type = field.get("type", "text")
field_label = field.get("label", "")
field_name = field.get("name", "")

if field_type == "text":
form_html += f'<label>{field_label}</label><br>\n'
form_html += f'<input type="text" name="{field_name}" required><br><br>\n'
elif field_type == "dropdown":
form_html += f'<label>{field_label}</label><br>\n'
form_html += f'<select name="{field_name}">\n'
for option in field.get("dropdown_options", []):
form_html += f'<option value="{option}">{option}</option>\n'
form_html += '</select><br><br>\n'
elif field_type == "multiple_choice":
form_html += f'<label>{field_label}</label><br>\n'
for option in field.get("dropdown_options", []):
form_html += f'<input type="radio" name="{field_name}" value="{option}" required>{option}<br>\n'
form_html += '<br>\n'
elif field_type == "yes_no":
form_html += f'<label>{field_label}</label><br>\n'
form_html += f'<input type="radio" name="{field_name}" value="Yes" required> Yes\n'
form_html += f'<input type="radio" name="{field_name}" value="No" required> No<br><br>\n'
form_html += '<input type="submit" value="Submit">\n'
form_html += '</form>'
return form_html

@app.route('/form', methods=['GET'])
def form():
"""This route will render the form based on the global JSON schema."""
global form_schema
if form_schema is None:
return "No form schema provided."
form_html = generate_form_from_json(form_schema)
return render_template_string(form_html)

@app.route('/submit', methods=['POST'])
def submit():
form_data = request.form.to_dict()
return f"Form submitted successfully with data: {form_data}"

def run_form_app(json_input, host='127.0.0.1', port=5000):
"""Runs the Flask app with a provided JSON schema."""
global form_schema
form_schema = json_input
app.run(host=host, port=port)


def record_audio():
print("Press Enter to begin recording...")
input() # Wait for the Enter key to start recording
print("Recording... Speak into the microphone.")

# Record audio using sounddevice directly in PCM16 format
audio_data = sd.rec(int(RECORD_DURATION * SAMPLE_RATE), samplerate=SAMPLE_RATE, channels=1, dtype='int16')
sd.wait() # Wait until recording is finished

# Save audio to a temporary .wav file
temp_wav_file = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
wav.write(temp_wav_file.name, SAMPLE_RATE, audio_data)
print(f"Audio recording saved to: {temp_wav_file.name}")

return temp_wav_file.name # Return the path of the recorded file

# Function to send the audio file to the Whisper API for transcription
def transcribe_audio(file_path):
with open(file_path, 'rb') as audio_file:
# Call the Whisper API for transcription
transcription = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file
)
return transcription.text # Return the transcription text

# Function to handle the model inference and generate the form schema

@weave.op
def generate_form_schema(user_prompt):
# Make the API call to generate the form schema using the transcribed prompt
response = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=[
system_message,
{"role": "user", "content": user_prompt} # Use transcribed text as user prompt
],
response_format=form_schema # Specify the response format using the structured output schema
)

# Extract the parsed form schema from the response
response_content = response.choices[0].message.content

# Parse the JSON content into a Python dictionary
generated_form_schema = json.loads(response_content)

return generated_form_schema

# Set up the system message to guide the model
system_message = {
"role": "system",
"content": """
You are a helpful assistant that generates form schemas in JSON format. The form schema should include:

1. Use 'type', 'label', and 'name' for all fields.
2. For dropdown fields, include a 'dropdown_options' property with a list of choices.
3. Example structure:
{
"form": {
"fields": [
{
"type": "text",
"label": "Player's Name",
"name": "player_name"
},
{
"type": "dropdown",
"label": "Position",
"name": "position",
"dropdown_options": ["Forward", "Midfielder", "Defender", "Goalkeeper"]
},
{
"type": "yes_no",
"label": "Previous Experience",
"name": "previous_experience"
}
]
}
}
"""
}

# Create the JSON schema for structured outputs with a root object that contains a 'form' property
form_schema = {
"type": "json_schema",
"json_schema": {
"name": "form_response",
"schema": {
"type": "object",
"properties": {
"form": {
"type": "object",
"properties": {
"fields": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["text", "dropdown", "multiple_choice", "yes_no"]
},
"label": {"type": "string"},
"name": {"type": "string"},
"dropdown_options": {
"type": ["array", "null"],
"items": {"type": "string"},
"description": "Options for dropdown fields, if applicable"
}
},
"required": ["type", "label", "name"],
"additionalProperties": False
}
}
},
"required": ["fields"],
"additionalProperties": False
}
},
"required": ["form"],
"additionalProperties": False,
"strict": True
}
}
}

# Main function to record audio, transcribe it, and generate the form schema
if __name__ == "__main__":
# Record audio and save it to a temporary file
recorded_audio_path = record_audio()

# Transcribe the recorded audio to text
user_prompt = transcribe_audio(recorded_audio_path)
print(f"Transcribed user prompt: {user_prompt}")

# Generate the form schema using the transcribed text
generated_form_schema = generate_form_schema(user_prompt)

# Display the generated schema to the user
print("Generated form schema:")
print(json.dumps(generated_form_schema['form'], indent=2)) # Pretty print the dictionary

# Launch the form builder with the generated schema
run_form_app(generated_form_schema['form'])
음성 명령 전사를 위해 Whisper API를 선택했습니다. 이 방식은 음성을 텍스트로 변환하는 데 간단하고 신뢰할 수 있기 때문입니다. 이렇게 얻은 텍스트(사실상 사용자 프롬프트)는 미리 정의된 폼 스키마를 생성하는 데 사용되며, 기존 워크플로에 손쉽게 통합할 수 있습니다.
OpenAI는 이제 텍스트, 이미지, 오디오를 한 세션에서 실시간으로 처리할 수 있는 Streaming API 기반의 완전한 멀티모달 옵션을 제공합니다. 다만 아직 베타이며, 제 개인적인 견해로는 2단계 방식에서 Whisper를 사용하는 것에 비해 설정이 더 복잡합니다. Streaming API는 모델과 사용자가 여러 차례 대화를 주고받는 애플리케이션에 적합합니다. 그러나 여기서는 단일 오디오 프롬프트에 대해 오디오를 텍스트로 전사하는 데 특화된 Whisper의 장점을 활용하는 방식을 선택했습니다.
구조화된 출력을 활용하면 생성된 폼 JSON 객체가 미리 정의된 스키마를 따르도록 보장할 수 있어, 기존 개발 워크플로에 손쉽게 통합할 수 있습니다. 이 구성은 음성 인식 전사와 구조화된 출력을 결합해, 개발자가 음성 지시만으로도 코드를 생성하면서 형식과 구조를 수동 코딩 없이 정확하게 제어할 수 있음을 보여 줍니다.
Weave는 폼 생성 과정의 여러 단계에서 입력과 출력을 기록하는 데에도 사용되어, 모델 성능을 추적·분석·시각화하기 쉽게 해 줍니다. Weave를 다음과 같이 통합하면 @weave.op 데코레이터를 사용하면 전사된 오디오, 생성된 폼 스키마, 그리고 모델의 응답을 단계별로 기록할 수 있습니다. 이를 통해 데이터 흐름을 모니터링하고, 문제를 디버깅하며, 결과를 협업자와 쉽게 공유할 수 있습니다. 또한 음성 명령에서 렌더링된 폼으로 변환되는 과정을 개발자가 다시 확인할 수 있어 투명성을 제공하고, 애플리케이션의 신뢰성을 높여 줍니다.
다음은 오디오만으로 생성한 폼의 스크린샷입니다:


결론

구조화된 출력은 AI를 다루는 일을 단순하게 만들어 줍니다. 지저분하고 예측하기 어려운 응답을 처리하는 대신, 특정 형식을 강제해 데이터를 쓰기 쉽게 만들고 후처리로 인한 골칫거리를 줄일 수 있습니다. 논문을 분류하든, 메뉴 항목을 정리하든, 음성 명령을 코드로 변환하든, 구조화된 출력은 반복적이고 번거로운 작업을 크게 덜어 줍니다. 신뢰할 수 있는 결과를 간단히 얻고, 오류 수정에 시간을 낭비하지 않고 중요한 일에 집중할 수 있는 방법입니다. 결국 과장 없이 업무 흐름을 매끄럽게 하는 실용적인 도구일 뿐입니다. 읽어 주셔서 감사합니다.





이 글은 AI 번역본입니다. 오역이나 부자연스러운 표현이 있으면 댓글로 알려 주세요. 원문 보고서는 다음 링크에서 확인할 수 있습니다: 원문 보고서 보기