AI 서비스와 도구 (2): API와 모델 선택#

이 장은 두 부분으로 되어 있습니다. 앞부분은 OpenAI API를 예로 들어 LLM API로 무엇을 할 수 있는지 감을 잡는 부분이고, 뒷부분은 여러 벤더와 모델 가운데 내 서비스에 맞는 것을 고르는 기준입니다. 개발자가 아니더라도 API의 개념만 알고 있다면, “내가 무엇을 할 수 있는지"를 이해하고 방향을 잡는 데 도움이 될 것입니다.
API로 AI를 쓴다는 것#
OpenAI를 비롯한 AI 서비스 제공자들은 직접 ChatGPT와 같은 사용자향 서비스를 제공하기도 하지만, 외부 개발사에게 API를 제공하여 AI 서비스 생태계를 확장합니다. 직접 AI 모델을 구축하는 대신 API를 이용하여 빠르게 뛰어난 품질의 AI 서비스를 제공할 수 있기 때문에, 스타트업에게는 매우 합리적인 선택입니다.
그리고 (좀 다른 이야기이긴 합니다만) 개발자가 아닌 분들이 프로그래밍 학습의 시작점을 물어 올 때, 필자는 예전에는 HTML과 JavaScript로 동적인 페이지를 만들어 보거나 Python으로 웹페이지를 스크래핑하는 작은 목표를 추천했습니다. 그런데 AI API를 연동해 본 뒤로 추천 순위가 바뀌었습니다. AI API를 연동해 보는 것이 쉽고, 재미있고, 동기 부여가 된다는 측면에서 가장 추천할 만합니다. 아래 예시 코드들이 길지 않고 직관적이라는 점을 보면 그 이유를 알 수 있을 것입니다.
OpenAI API로 할 수 있는 것들#
Text generation#
기본적으로, 텍스트 데이터를 입력하여 텍스트 응답을 생성하는 API가 제공됩니다. OpenAI Python SDK에서는 Responses API(client.responses.create)를 중심으로 사용합니다. 이전의 Chat Completions API도 계속 제공되지만, 새로 개발한다면 Responses API가 권장됩니다. instructions는 “상황 설정(시스템 지침)“에 해당하고, input은 사용자 입력(또는 대화 히스토리)을 의미합니다.
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5-mini",
instructions="You are a helpful assistant.",
input=[
{"role": "user", "content": "Who won the world series in 2020?"},
{"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
{"role": "user", "content": "Where was it played?"},
],
)
print(response.output_text)여기서 눈여겨볼 것은 role입니다. role이 user이면 “사용자가 한 말”, assistant이면 “모델이 이전에 했던 답변(대화 기록)“을 의미합니다. 그리고 흔히 말하는 시스템 지침은 대화를 시작하기 전에 AI에게 미리 주는 공통 지침(역할 / 말투 / 금지 사항 / 출력 형식)인데, 이 예시에서는 instructions에 넣어 따로 관리하고 있습니다. 3장에서 본 컨텍스트의 다섯 층이 코드에서는 이렇게 instructions(고정 지침)와 input(대화 기록 + 현재 요청)으로 나뉩니다.
Function calling#
2장에서 설명한 Function Calling을 API 수준에서 보면 아래 흐름입니다. [사용자(클라이언트) ↔ 서버 ↔ 모델] 구조를 가정합니다.
- 사용자가 질의한 내용을 서버가 받아서 모델에게 전달할 때, 함수(function)의 이름, 설명, 매개변수 명세를 같이 전달합니다. 함수는 여러 개일 수 있고, 모두 서버에 구현되어 있습니다.
- 모델이 응답을 생성할 때, 함수 호출이 필요한 상황이면 “어떤 함수를 어떤 인자로 호출해야 하는지"를 응답에 포함하여 서버에게 전달합니다.
- 서버가 해당 함수를 호출합니다. 함수에는 필요한 알고리즘과 외부 API 호출 로직 등이 구현되어 있습니다.
- 서버가 함수의 실행 결과를 다시 모델에게 전달하면, 모델이 비로소 최종 응답을 생성하고, 이것을 서버가 받아서 사용자에게 전달합니다.
from openai import OpenAI
import json
client = OpenAI()
tools = [{
"type": "function",
"name": "get_weather",
"description": "Get current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}]
response = client.responses.create(
model="gpt-5-mini",
input=[{"role": "user", "content": "What's the weather in Seoul?"}],
tools=tools,
)
# 모델이 함수 호출을 요청했는지 확인하고, 서버가 실제 함수를 실행합니다.
for item in response.output:
if item.type == "function_call" and item.name == "get_weather":
args = json.loads(item.arguments)
result = {"city": args["city"], "temp_c": 27, "sky": "clear"} # 실제로는 날씨 API 호출
final = client.responses.create(
model="gpt-5-mini",
previous_response_id=response.id,
input=[{"type": "function_call_output", "call_id": item.call_id, "output": json.dumps(result)}],
)
print(final.output_text)GPTs에서 내 GPT를 만들 때 외부 API 호출을 위해 작업(Actions)을 추가하는 것은, 서버 입장에서 보면 “모델이 필요할 때 호출할 수 있는 도구(함수)를 정의해 주는 것"과 같은 개념입니다.
Image generation#
텍스트(프롬프트)를 입력하여 이미지를 생성하는 API가 제공됩니다. 이미지 생성은 텍스트 생성 모델과는 별개의 전용 이미지 모델을 사용합니다. gpt-image 계열 모델은 생성된 이미지를 Base64 문자열로만 반환합니다(URL 반환은 구형 dall-e 계열에만 해당). 그래서 받은 데이터를 디코딩해 파일로 저장하는 과정이 필요합니다.
import base64
from openai import OpenAI
client = OpenAI()
response = client.images.generate(
model="gpt-image-1.5",
prompt="a white siamese cat",
size="1024x1024",
quality="medium", # low / medium / high
n=1,
)
image_bytes = base64.b64decode(response.data[0].b64_json)
with open("siamese_cat.png", "wb") as f:
f.write(image_bytes)위 코드를 실행하면 생성된 이미지가 PNG 파일로 저장됩니다. 원하는 디테일을 얻기 위해서는 프롬프트 엔지니어링이 중요합니다. 텍스트 프롬프트로 생성하는 것 외에도, 기존 이미지와 마스킹 영역을 제공하여 해당 영역을 편집한 이미지를 생성할 수도 있습니다(client.images.edit()). 이 책의 장별 삽화도 같은 방식(다른 벤더의 이미지 모델)으로, 스타일 참조 이미지 한 장을 주고 장마다 다른 장면을 지시해 만들었습니다.
Vision#
이미지 데이터와 그에 대한 요청사항(텍스트 프롬프트)을 함께 입력해 텍스트 응답을 생성할 수 있습니다. 이미지에 대한 설명을 작성하거나, 이미지 안에 특정 객체가 있는지를 판별하거나, 여러 개의 이미지를 서로 비교하는 등의 요청을 할 수 있습니다.
from openai import OpenAI
client = OpenAI()
img_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
response = client.responses.create(
model="gpt-5-mini",
input=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "What's in this image?"},
{"type": "input_image", "image_url": img_url},
],
}
],
)
print(response.output_text)[응답 예시]
This image depicts a picturesque scene of a wooden boardwalk path stretching through a grassy field. The sky is blue with some scattered clouds, and the surrounding vegetation is lush and green, suggesting a summer or late spring setting.Text to speech#
텍스트 데이터를 입력하여, 그에 대한 음성 파일을 생성하는 API를 제공합니다. 이 기능은 텍스트 생성 모델이 아니라 TTS 전용 모델(gpt-4o-mini-tts, 그리고 이전 세대의 tts-1, tts-1-hd)을 사용합니다. 최신 모델은 instructions 파라미터로 말투나 감정 같은 발화 스타일을 지시할 수 있습니다. 여러 개의 미리 설정된 목소리 중 하나를 선택할 수 있고, 여러 음성 파일 포맷(MP3, Opus, AAC, FLAC, WAV, PCM) 중 하나를 선택할 수 있으며, 다양한 언어를 지원합니다. 긴 글의 경우 파일 생성에 시간이 꽤 걸릴 수 있기 때문에, 사용자에게 빠르게 응답할 수 있도록 스트리밍 기능도 지원합니다.
from pathlib import Path
from openai import OpenAI
client = OpenAI()
speech_file_path = Path(__file__).parent / "text_to_speech_result.mp3"
with client.audio.speech.with_streaming_response.create(
model="gpt-4o-mini-tts",
voice="alloy",
input="Today is a wonderful day to build something people love!",
) as response:
response.stream_to_file(speech_file_path)Speech to text#
음성 데이터를 입력하면, 그에 대한 텍스트 데이터를 생성하는 API를 제공하며, 다양한 언어를 지원합니다. 이 기능은 전사 전용 모델(예: gpt-4o-transcribe, gpt-4o-mini-transcribe, 그리고 이전 세대의 whisper-1)을 사용합니다.
from pathlib import Path
from openai import OpenAI
client = OpenAI()
audio_file = Path("text_to_speech_result.mp3")
transcription = client.audio.transcriptions.create(
model="gpt-4o-transcribe",
file=audio_file,
)
print(transcription.text)whisper-1 모델을 사용하면 각 단어가 노출되는 시간 정보(Timestamp)를 같이 제공받을 수 있어서, 음성 파일의 특정 위치에 대해 텍스트의 위치도 바로 찾을 수 있습니다(최신 전사 모델은 json / text 형식만 반환하므로, 단어 단위 타임스탬프가 필요하면 whisper-1을 씁니다). 또한 음성 파일과 함께 텍스트 프롬프트를 제공하여 변환 완성도를 높일 수 있습니다. 예를 들어 “달리"라는 음성을 “DALI"로 적을지 “DALL-E"로 적을지 모델 입장에서는 명확하지 않은데, “이 음성은 인공지능에 대한 발언이고 DALL-E, GPT 모델 등을 이야기한다"는 프롬프트를 주면 실수를 줄일 수 있습니다.
Embeddings#
1장에서 설명한 임베딩도 전용 API로 제공됩니다. 텍스트를 넣으면 숫자 벡터가 나오고, 이 벡터를 저장해 두었다가 질문 벡터와의 거리를 계산해 “의미가 가까운 문서"를 찾습니다. RAG의 색인과 검색 단계가 이 API 위에 세워집니다.
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
model="text-embedding-3-small",
input="환불 신청은 구매일로부터 7일 이내에 가능합니다.",
)
vector = response.data[0].embedding # 길이 1536의 float 리스트Moderation#
텍스트 데이터를 입력하여, 유해한 내용이 있는지를 검사하는 API를 제공합니다. 성적인 내용이 있는지, 혐오스러운 내용이 있는지 등에 대하여 카테고리를 구분하여 점수를 산출합니다. 사용자 입력을 모델에 넣기 전이나, 모델 출력을 사용자에게 보여 주기 전의 안전장치로 씁니다.
from openai import OpenAI
client = OpenAI()
response = client.moderations.create(input="Hello world!!")
print(response.results[0].flagged) # True / False대화 상태 유지와 내장 도구#
위에서는 단발성 요청을 처리하는 API를 알아봤는데요, 자체 앱에 ChatGPT와 같은 챗봇을 구축하려면 “이전 대화를 기억하는 것"과 “파일 검색이나 코드 실행 같은 내장 도구"가 필요합니다. 과거에는 이를 위해 별도의 Assistants API가 제공되었지만, 이 API는 2026년 8월에 종료되었습니다. 지금은 Responses API 하나로 같은 일을 합니다.
- 대화 상태:
previous_response_id로 직전 응답에 이어 붙이거나, Conversations API로 서버에 대화를 만들어 두고conversation파라미터로 이어갑니다. 대화 기록을 매번 클라이언트가 다시 보낼 필요가 없습니다. - 내장 도구:
tools에file_search(업로드한 파일 검색),code_interpreter(Python 실행),web_search(웹 검색) 같은 OpenAI가 호스팅하는 도구를 지정하면, 모델이 필요할 때 알아서 호출합니다. 앞서 설명한 Function calling과 달리 도구 실행을 OpenAI 서버가 대신해 줍니다.
from openai import OpenAI
client = OpenAI()
# 대화 상태를 서버에 보관할 Conversation을 생성합니다.
conversation = client.conversations.create()
while True:
user_input = input("\nuser > ")
if user_input.lower() in ["exit", "quit"]:
break
response = client.responses.create(
model="gpt-5-mini",
instructions="You are a personal math tutor. Write and run code to answer math questions.",
tools=[{"type": "code_interpreter", "container": {"type": "auto"}}],
conversation=conversation.id,
input=user_input,
)
print("assistant >", response.output_text)파라미터 이름과 도구 종류는 바뀔 수 있으므로, 실제 구현 시에는 공식 문서를 확인하기 바랍니다.
다른 벤더의 API도 구조는 같습니다#
Anthropic(Claude), Google(Gemini) 같은 다른 벤더의 API도 “텍스트 생성 / 비전 / 도구 호출 / 임베딩 / 내장 도구"의 큰 틀은 같습니다. 파라미터 이름과 응답 형식만 다릅니다. 예를 들어 같은 텍스트 생성을 Anthropic SDK로 쓰면 아래와 같습니다.
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=1024,
system="You are a helpful assistant.",
messages=[{"role": "user", "content": "Where was the 2020 World Series played?"}],
)
print(message.content[0].text)instructions가 system으로, input이 messages로 바뀌었을 뿐 구조는 동일합니다. 그래서 한 벤더의 API를 익히면 다른 벤더로 옮기는 비용은 크지 않습니다. 다만 뒤에서 설명할 프롬프트 캐싱의 동작 방식이나 도구 호출 응답 형식 같은 세부는 벤더마다 다르므로, 실제로 벤더를 바꿀 때는 그 차이를 흡수하는 얇은 어댑터 계층을 두는 편이 안전합니다.
모델을 고르는 기준#
API로 할 수 있는 일을 알았다면, 다음 질문은 “어느 벤더의 어느 모델을 쓸 것인가"입니다. 1인 스타트업이 실제로 마주치는 결정 기준을 정리하면 아래와 같습니다.
| 기준 | 확인할 것 | 왜 중요한가 |
|---|---|---|
| 등급(티어) | 같은 세대 안의 최상위 / 균형형 / 경량 모델 | 작업마다 필요한 지능이 다릅니다. 분류나 요약은 경량으로 충분하고, 복잡한 추론만 최상위가 필요합니다 |
| 비용 구조 | 입력 / 출력 단가, 캐시 읽기 단가, 배치 할인 | 에이전트 작업은 입력이 출력보다 압도적으로 많아 캐시 단가가 실비용을 좌우합니다 |
| 컨텍스트 길이 | 최대 토큰 수와 긴 문서에서의 실제 품질 | 크다고 다 좋은 것이 아니라, 긴 입력에서 핵심을 놓치지 않는지가 관건입니다 |
| 도구 호출 · 구조화 출력 | Function Calling 안정성, JSON 스키마 준수 | 제품에 넣으려면 “말을 잘하는 것"보다 “형식을 지키는 것"이 중요합니다 |
| 지연과 처리량 | 첫 토큰까지의 시간, 분당 토큰 한도 | 대화형 UI는 지연이, 배치 처리는 처리량이 병목입니다 |
| 데이터 정책 · 리전 | 입력이 학습에 쓰이는지, 데이터가 어느 리전에 머무는지 | B2B 고객이나 규제 산업을 상대하면 이 기준이 나머지를 압도합니다 |
| 생태계 | SDK 품질, 코딩 에이전트, MCP 지원 | 내가 이미 쓰는 도구와 궁합이 맞으면 운영 비용이 줄어듭니다 |
필자가 실무에서 적용하는 원칙은 세 가지입니다.
첫째, 작업을 나누고 등급을 섞습니다. 하나의 제품 안에서도 사용자 질의 분류, 문서 요약, 최종 답변 생성은 요구하는 지능이 다릅니다. 모든 호출에 최상위 모델을 쓰면 비용이 몇 배로 뛰고, 모든 호출에 경량 모델을 쓰면 품질이 무너집니다. 호출 지점마다 “이 작업에 필요한 최소 등급"을 정하고, 평가(evals)로 그 선택이 맞는지 확인합니다.
둘째, 벤더 종속을 어댑터 한 층으로 막습니다. 모델은 몇 달마다 바뀌고 가격도 바뀝니다. 프롬프트와 비즈니스 로직이 특정 SDK의 응답 형식에 직접 묶여 있으면 벤더를 바꾸는 비용이 커집니다. 호출 부분을 얇게 감싸 두면 “이번 달은 어느 모델이 싸고 좋은가"를 설정 하나로 바꿀 수 있습니다.
셋째, 배포 형태는 보안 요구가 정합니다. 모델을 어디서 돌릴지는 아래 스펙트럼 위의 한 점을 고르는 일입니다.
관리형 플랫폼에 대해 한 가지 오해를 바로잡아 두면, AWS Bedrock 같은 서비스는 “모델 벤더로 가는 전용선"이 아닙니다. 모델 자체가 클라우드 사업자의 인프라 안에서 실행되기 때문에, 데이터가 모델 벤더에게 직접 전송되지 않고 지정한 리전 밖으로 나가지 않으며, 입출력이 학습에 쓰이지 않는다는 것이 핵심 가치입니다. 보안에 민감한 B2B 고객을 상대한다면 이 한 줄이 영업의 성패를 가르기도 합니다.
구독과 API는 다른 상품입니다#
마지막으로, 개발자가 자주 빠지는 함정 하나를 짚어 두겠습니다. 코딩 에이전트의 월 구독이 같은 양을 API로 쓰는 것보다 훨씬 싸다 보니, “구독 계정을 내 서비스의 백엔드에 연결하면 싸게 쓸 수 있지 않을까"라는 생각이 들기 쉽습니다. 결론은 “안 된다"입니다. 구독은 제공자가 통제하는 제품 안에서 개인이 쓰는 권리이고, 내 제품이 사용자 대신 모델을 호출하는 일은 API라는 별도의 경제 위에 세워야 합니다. 약관도 그렇게 되어 있고, 서드파티 경로로 구독 한도를 쓰려는 시도는 차단되어 왔습니다.
그래서 1인 스타트업의 AI 비용은 두 항목으로 나뉩니다. 내가 일하는 데 쓰는 구독(코딩 에이전트, 채팅 서비스)과 제품이 사용자를 위해 호출하는 API입니다. 두 항목을 섞지 않고 따로 관리하는 것이 비용 설계의 출발점이며, API 쪽 비용을 줄이는 구체적인 방법(캐싱, 배치, 등급 라우팅)은 실행 편의 “제품에 AI 기능 넣기"에서 다룹니다.
정리#
- LLM API는 텍스트 생성, 도구 호출, 이미지, 비전, 음성, 임베딩, 모더레이션, 내장 도구로 구성되며, 벤더가 달라도 구조는 같습니다.
- 모델 선택은 등급, 비용 구조, 컨텍스트, 도구 호출 안정성, 지연, 데이터 정책, 생태계의 일곱 기준으로 합니다.
- 작업마다 등급을 섞고, 벤더 종속은 어댑터 한 층으로 막고, 배포 형태는 보안 요구에 맞춥니다.
- 내가 쓰는 구독과 제품이 호출하는 API는 다른 상품이며, 비용도 따로 관리합니다.