4장. LLM API의 공통 구조
4장. LLM API의 공통 구조

모델 이름은 달라도, 뼈대는 놀랄 만큼 비슷하다
OpenAI, Claude, Gemini는 서로 다른 회사의 제품이지만, 개발자가 실제로 다루는 방식은 생각보다 비슷하다. 완전히 같은 표준이 공식 문서로 굳어져 있는 것은 아니지만, 현실의 LLM API 세계에는 채팅형 요청과 응답을 중심으로 한 사실상의 공통 구조가 자리 잡았다.1
이 사실이 왜 중요할까. 이유는 단순하다. 공통 구조를 이해한 팀은 새 벤더를 붙일 때 문법 몇 개만 다시 배우면 된다. 반대로 벤더별 문서만 외운 팀은 모델을 바꿀 때마다 처음부터 다시 시작하는 기분을 느낀다. 실무에서는 이 차이가 꽤 크다. API를 한 번 붙이는 일보다, 바꾸고 확장하고 운영하는 일이 훨씬 더 오래가기 때문이다.
이 장의 목표는 특정 회사의 문법을 암기하는 것이 아니다. 그 아래에 있는 공통 원리를 잡는 것이다. 한마디로 말해, LLM API는 결국 입력을 구조화해 보내고, 생성 방식을 조절하고, 결과와 메타데이터를 받는 인터페이스다.
LLM API는 결국 요청과 응답의 왕복이다
LLM API 호출을 어렵게 들으면 뭔가 거대한 AI 파이프라인처럼 보이지만, 뼈대만 보면 익숙한 웹 API와 크게 다르지 않다. 애플리케이션이 HTTP 요청을 보내고, 보통 JSON 형식으로 입력을 담고, API 키로 인증한 뒤, 모델이 응답을 생성해 다시 JSON으로 돌려준다.2
요청에는 대체로 이런 정보가 들어간다.
어떤 모델을 쓸지
어떤 지시를 따를지
사용자가 무엇을 입력했는지
응답 길이와 생성 방식은 어떻게 조절할지
응답에는 대체로 이런 정보가 들어온다.
생성된 텍스트
왜 응답이 끝났는지에 대한 이유
몇 토큰을 썼는지
어떤 모델이 응답했는지
요청 식별자 같은 운영용 메타데이터
여기서 중요한 점이 하나 있다. 오늘날 대부분의 LLM API는 "채팅형" 인터페이스를 쓴다. 그런데 이 채팅형 인터페이스는 챗봇을 만들 때만 쓰는 것이 아니다. 문서 요약, 분류, 정보 추출, 코드 생성처럼 대화가 아닌 작업에도 널리 쓰인다. 왜냐하면 이 형식이 지시문과 실제 입력 데이터를 자연스럽게 분리해 주기 때문이다.3
예를 들어 고객 문의를 요약하는 요청도 결국 이런 식으로 생각할 수 있다.
{
"model": "some-llm",
"instruction": "고객 문의를 2문장으로 요약하고 긴급도를 low, medium, high 중 하나로 분류하라.",
"input": "업데이트 후 앱이 실행되지 않습니다. 재설치도 해봤지만 안 됩니다. 업무가 멈췄어요.",
"temperature": 0.2,
"max_output_tokens": 150
}벤더별 실제 필드 이름은 다를 수 있어도, 개발자가 머릿속에서 다뤄야 하는 구조는 거의 같다.
메시지 구조는 LLM API의 중심축이다
LLM API를 처음 접하면 가장 자주 보게 되는 것이 messages 같은 배열이다. 이 구조는 단순한 채팅 로그가 아니라, 모델에게 "지금 무슨 상황인지"를 설명하는 작은 무대다.
보통 메시지에는 다음과 같은 역할이 등장한다.
system 또는 instruction 계층: 모델의 역할, 규칙, 답변 형식 같은 상위 지시를 담는다.
user 계층: 실제 사용자 입력이나 처리할 원문을 담는다.
assistant 계층: 이전에 모델이 했던 말을 다시 넣어, 문맥을 이어 간다.
tool 계층: 외부 함수나 시스템과 연결될 때 사용되는 결과 메시지를 담는다.
이 네 가지를 모두 항상 쓰는 것은 아니다. 단발성 요청이라면 상위 지시 하나와 사용자 입력 하나만 있어도 충분한 경우가 많다. 예를 들어 "아래 회의록을 요약해 줘" 같은 작업은 사실상 두 줄짜리 대화로 처리할 수 있다.
핵심은 역할 구분이다. 규칙은 규칙대로, 원문은 원문대로, 이전 응답은 히스토리대로 분리해야 모델의 행동을 예측하기 쉬워진다. 초보자가 자주 하는 실수는 모든 것을 한 덩어리 문자열에 넣는 것이다. 그러면 사람이 읽기에도 복잡하고, 모델이 어떤 문장을 지시로 받아들일지 어떤 문장을 데이터로 받아들일지 흐려진다.
벤더마다 이름은 다르지만, 개념은 같다
OpenAI, Claude, Gemini는 겉모습이 조금씩 다르다. 특히 상위 지시를 어디에 두는지, 본문 텍스트를 어떤 필드에 넣는지에서 차이가 난다. 하지만 개념으로 보면 같은 그림이다.6
| 공통 개념 | OpenAI 계열에서 자주 보이는 형태 | Claude 계열에서 자주 보이는 형태 | Gemini 계열에서 자주 보이는 형태 |
|---|---|---|---|
| 상위 지시 | system 메시지 또는 유사한 지시 필드 | top-level system | system_instruction |
| 사용자 입력 | messages 안의 role: user | messages 안의 role: user | contents 안의 사용자 파트 |
| 이전 대화 맥락 | assistant 메시지 포함 | 이전 메시지 포함 | 이전 콘텐츠 포함 |
| 텍스트 본문 | content | content[].text 형태 | parts[].text 형태 |
| 최대 출력 길이 | max_tokens류 | max_tokens | maxOutputTokens |
이 표를 보면 감이 온다. 벤더별 문법 차이는 분명 있지만, 내부적으로는 같은 질문을 던지고 있다.
"어떤 모델에게, 어떤 규칙으로, 어떤 입력을 보여주고, 어느 정도 길이로 답하게 할 것인가?"
실무에서는 이 공통 질문을 먼저 코드로 추상화해 두는 편이 좋다. 그래야 나중에 모델을 바꾸더라도 애플리케이션의 핵심 로직은 그대로 두고, 어댑터만 바꾸면 된다.
토큰은 비용이자, 속도이자, 기억력이다
LLM API를 이해하려면 토큰을 빼놓을 수 없다. 토큰은 모델이 텍스트를 읽고 쓰는 작은 단위다. 글자 하나일 수도 있고, 단어 일부일 수도 있고, 짧은 단어 전체일 수도 있다. 대부분의 벤더는 입력 토큰과 출력 토큰을 기준으로 과금하며, 한 번의 요청에서 모델이 볼 수 있는 총량도 토큰으로 제한한다.4
여기서 꼭 구분해야 할 것이 있다. 토큰은 요청마다 오가는 텍스트의 양이고, 모델 파라미터는 모델이 학습한 내부 가중치의 수다. 둘은 완전히 다른 개념이다.5
토큰을 이해할 때는 세 가지를 함께 봐야 한다.
입력 토큰: 시스템 지시, 사용자 질문, 대화 히스토리, 첨부 텍스트까지 포함한 전체 입력량
출력 토큰: 모델이 실제로 생성한 응답량
컨텍스트 윈도: 한 번의 호출에서 모델이 동시에 볼 수 있는 최대 총량
이 개념을 서비스 관점으로 바꿔 말하면 더 실감 난다. 대화가 길어질수록 입력 토큰은 늘어난다. 긴 문서를 통째로 붙이면 비용과 지연 시간이 같이 올라간다. 응답 길이를 제한하지 않으면 모델이 친절하게 너무 많이 말해서 돈도 더 들고 화면도 지저분해질 수 있다.
그래서 토큰은 단순한 과금 단위가 아니다. 서비스의 반응 속도, 운영 비용, 그리고 모델이 "얼마나 많은 문맥을 기억한 채 답할 수 있는가"를 동시에 좌우하는 핵심 변수다.
생성 파라미터는 모델의 성격을 조절하는 손잡이다
LLM API 요청 본문에는 대개 몇 개의 파라미터가 따라붙는다. 이 파라미터들은 모델이 어떻게 답할지 성격을 정하는 손잡이와 비슷하다. 벤더마다 지원 범위나 이름이 조금씩 다르지만, 자주 만나는 핵심은 비슷하다.7
| 파라미터 | 의미 | 실무 감각 |
|---|---|---|
temperature | 응답의 무작위성 조절 | 낮으면 안정적, 높으면 창의적 |
top_p | 후보 토큰 범위를 확률 기준으로 제한 | temperature와 비슷한 계열의 제어 장치 |
max_tokens / maxOutputTokens | 최대 출력 길이 제한 | 비용과 응답 길이 제어에 직결 |
stop / stopSequences | 특정 문자열에서 생성 중단 | 목록 길이 제어, 후처리 안정화에 유용 |
stream | 결과를 한 번에 받을지, 조각내어 받을지 | 체감 속도 개선에 중요 |
response_format류 | JSON 등 구조화된 형식 요구 | 자동화 파이프라인 안정성 향상 |
초보자에게 가장 유용한 감각은 이것이다.
요약, 분류, 추출처럼 정확하고 일관된 출력이 필요하면 temperature를 낮게 둔다. 반대로 광고 문구 초안, 아이디어 발산, 카피라이팅처럼 다양성이 중요한 작업은 조금 높인다. max_tokens는 항상 의식적으로 정하는 편이 좋다. 지정하지 않으면 모델은 꽤 수다스러울 수 있다.
그리고 한 가지 팁이 있다. temperature와 top_p는 둘 다 생성 다양성을 조절하는 장치라서, 처음부터 둘을 동시에 과하게 만지면 감이 흐려진다. 처음에는 하나를 중심으로 조절하고, 나머지는 기본값에 두는 편이 디버깅하기 쉽다.
응답 구조는 본문과 메타데이터로 나뉜다
많은 초보자는 응답 JSON에서 본문 텍스트만 꺼내고 끝낸다. 물론 데모는 그렇게 해도 된다. 하지만 실무에서는 메타데이터가 정말 중요하다.
OpenAI 계열에서는 보통 choices[0].message.content 같은 위치에서 본문을 읽고, Claude 계열에서는 content[0].text, Gemini 계열에서는 candidates[0].content.parts[0].text 비슷한 형태로 본문을 꺼내는 경우가 많다.6 겉모습은 다르지만 공통적으로는 다음 요소를 포함한다.
실제 응답 텍스트
종료 이유(
finish_reason류)토큰 사용량
모델명과 요청 식별자
종료 이유는 은근히 중요하다. stop처럼 정상 종료면 괜찮지만, 길이 제한 때문에 끊긴 경우라면 UI나 후처리 로직이 다르게 움직여야 한다. 예를 들어 보고서 생성 기능이라면 "계속 생성" 버튼을 보여줄 수도 있고, 데이터 추출 기능이라면 max_tokens를 늘려 재시도해야 할 수도 있다.
토큰 사용량도 꼭 저장해 두는 편이 좋다. 운영 단계에 들어가면 "어떤 기능이 유독 비싼가", "어떤 고객이 요청량이 많은가", "왜 오늘 응답이 느린가" 같은 질문이 끝없이 나온다. 이때 입력 토큰, 출력 토큰, 전체 토큰 로그가 있어야 원인을 찾을 수 있다. 실제 운영 환경에서는 요청과 응답 메시지, 토큰 사용량을 함께 로깅해 분석하는 패턴이 널리 쓰인다.8
스트리밍은 품질이 아니라 체감 속도의 기술이다
같은 답변이라도 한 번에 통째로 보여주면 사용자는 기다린다고 느낀다. 반대로 몇 글자씩 흘려보내면 "오, 빠르다"는 느낌을 받는다. 이것이 스트리밍의 힘이다.
중요한 점은 스트리밍이 모델을 더 똑똑하게 만들지는 않는다는 것이다. 다만 사용자가 기다리는 경험을 바꿔 준다. 채팅 UI, 실시간 보조 작성, 코드 생성 화면 같은 곳에서는 특히 효과가 크다. 반면 백그라운드 배치 작업이나 JSON 추출 작업에서는 스트리밍이 굳이 필요 없을 수 있다.
즉, 스트리밍은 모델 품질 옵션이 아니라 UX 옵션이다. 이 구분을 이해하면 어디에 써야 하는지 훨씬 명확해진다.
실무에서는 공통 인터페이스로 정규화하라
여기까지 이해했다면, 이제 중요한 설계 원칙 하나가 보인다. 애플리케이션이 OpenAI, Claude, Gemini의 원시 응답 형식을 직접 다루게 두지 말고, 그 위에 내부 표준 인터페이스를 하나 두는 것이 좋다.
예를 들면 내부적으로는 이런 형태를 목표로 할 수 있다.
{
"provider": "openai",
"model": "gpt-4o-mini",
"instruction": "...",
"messages": ["..."],
"params": {
"temperature": 0.2,
"max_output_tokens": 300,
"stream": false
}
}응답도 내부적으로는 이렇게 맞춰 둘 수 있다.
{
"text": "최종 응답 본문",
"finish_reason": "stop",
"usage": {
"input_tokens": 120,
"output_tokens": 45,
"total_tokens": 165
},
"raw": {}
}이렇게 해두면 프런트엔드, 비즈니스 로직, 로깅 계층은 벤더별 차이를 거의 몰라도 된다. 교체 비용이 줄고, 실험 속도는 빨라지고, 멀티 벤더 전략도 한결 쉬워진다. 나중에 8장, 9장, 10장에서 Claude와 Gemini로 확장할 때도 바로 이 발상이 큰 힘을 발휘한다.
초보자가 자주 헷갈리는 세 가지
마지막으로, 현장에서 정말 자주 나오는 오해 세 가지만 짚고 넘어가자.
첫째, 채팅형 API는 챗봇 전용이 아니다. 요약, 분류, 추출, 재작성 같은 대부분의 업무 자동화도 채팅형 구조로 충분히 처리한다. 중요한 것은 채팅처럼 보이는 겉모습이 아니라, 지시와 입력과 히스토리를 분리할 수 있다는 점이다.3
둘째, 토큰과 모델 파라미터는 다르다. 토큰은 지금 이 요청에서 오가는 텍스트의 양이고, 파라미터는 모델 자체의 규모다.45
셋째, 벤더가 바뀐다고 서비스 구조까지 다 바뀌는 것은 아니다. 공통 구조를 먼저 이해하고 내부 인터페이스를 정규화해 두면, 달라지는 것은 주로 인증 방식, 필드 이름, 일부 옵션 지원 범위다.16
정리
LLM API를 잘 다루는 사람은 특정 회사의 문법을 많이 외운 사람이 아니다. 공통 구조를 먼저 보고, 차이는 나중에 처리하는 사람이다.
정리해 보면, OpenAI·Claude·Gemini를 관통하는 핵심은 이렇다. 요청은 대체로 메시지 구조를 중심으로 짜고, 토큰은 비용과 문맥의 단위이며, 파라미터는 생성 성격을 조절하고, 응답은 본문과 메타데이터로 읽어야 한다. 이 네 가지만 제대로 이해해도 새 벤더를 붙일 때 학습 비용이 크게 줄어든다.
다음 장에서는 이 공통 원리를 바탕으로, 실제로 첫 번째 LLM API 기능을 구현해 보겠다. 이제부터는 "AI가 신기하다"의 단계에서 한 발 더 나아가, "서비스 코드 안에서 안정적으로 움직인다"의 단계로 들어간다.
참고
1LLM API의 사실상 공통 표준과 OpenAI-compatible 설명
2LLM API의 요청-응답 구조와 HTTP·JSON 기반 동작 설명
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.