OpenAI와 호환되는 API 리레이를 공식 API와 비교하는 방법 (Python 스크립트 포함)
요약
OpenAI 호환 API 리레이는 비용 절감에 유용하지만, 요청과 응답 과정에서 문제가 발생할 수 있습니다. 본 게시물은 Python 스크립트를 사용하여 동일한 요청을 공식 API와 리레이 양쪽에 전송하고, 모델명, 토큰 카운트, 매개변수 전달 여부를 비교하는 방법을 제시합니다.
핵심 포인트
- 리레이 검증의 핵심은 두 엔드포인트에 동일한 요청을 보내 응답 차이를 분석하는 것입니다.
- 응답된 'model' 필드를 비교하여 리레이가 실제 모델 정보를 왜곡하는지 확인해야 합니다.
- 프롬프트 토큰 수를 비교하여 리레이가 숨겨진 시스템 프롬프트를 추가 과금하는지 점검할 수 있습니다.
- max_tokens와 같은 매개변수가 실제로 작동하는지 테스트하여 누락 여부를 검증합니다.
저렴한 OpenAI와 호환되는 리레이(relay)는 어디에나 있습니다. 단순히 base_url만 교체하고 코드를 그대로 사용하며 비용을 절감할 수 있습니다. 하지만 요청과 모델 사이에서 실제로 무슨 일이 일어나는지는 알기 어렵습니다. 리레이는 완벽하게 정직할 수도 있지만, 나중에 더 나쁜 답변, 높은 청구서, 또는 세 번째 도구 호출에서 작동이 멈추는 에이전트처럼 보이는 방식으로 조용히 문제가 발생시킬 수도 있습니다.
본 게시물에서는 리레이가 가장 자주 잘못하는 점들을 정리하고, 사용자가 몇 분 만에 직접 각 항목을 확인할 수 있는 방법을 제시합니다. 이 과정에는 자체적으로 실행할 수 있는 Python 스크립트 하나만 필요하며, 저희의 도구가 필요하지 않습니다.
아이디어: 동일한 요청, 두 개의 엔드포인트
아래의 모든 검사는 동일한 요청을 리레이와 공식 API 양쪽에 전송한 다음, 두 응답을 비교합니다. 단일 응답만으로는 많은 것을 알 수 없으며, 두 응답 간의 차이가 핵심입니다.
필요한 것:
- 리레이의 base URL과 API 키
- 동일한 모델에 대한 공식 API 키 (기준선 역할을 하므로, 소액의 사용량만으로 충분합니다)
pip install openai
export RELAY_BASE_URL=https://your-relay.example/v1
export RELAY_API_KEY=...
...
전체 스크립트는 마지막에 있습니다. 각 검사가 무엇을 확인하는지 알려드립니다.
1. 어떤 모델이 답변했는가?
모든 chat completion 응답에는 model 필드가 있습니다. 양쪽에 모두 사소한 질문을 던지고 비교해 보세요.
r = client.chat.completions.create(model=MODEL, messages=[{"role": "user", "content": "단 하나의 단어로 답변하세요: ok"}], max_tokens=5)
print(r.model)
만약 gpt-4.1-mini를 요청했는데 리레이의 응답이 gpt-4o-mini-2024-07-18이라고 나온다면, 이는 리레이에 대해 질문해 볼 가치가 있습니다. 다만 두 가지 주의사항이 있습니다:
- 공식 API 자체도 종종 날짜가 찍힌 스냅샷 이름(
gpt-4.1-mini-2025-04-14)을 반환하므로, 접미사만 다르다고 해서 무조건 의심할 수는 없습니다. - 리레이는 이 필드에 어떤 내용을든 작성할 수 있습니다. 일치하는 이름은 약한 증거이며, 불일치하는 이름은 강력한 증거입니다.
모델에게 "어떤 모델입니까?"라고 물어보는 것은 시간 낭비입니다. 모델들은 직접 호출되더라도 이 질문에 대답하는 데 서툽니다.
2. 토큰 카운트가 진짜인가?
토큰에 대해 비용을 지불하며, 리레이가 이를 보고합니다. 양쪽에 동일한 프롬프트를 temperature=0으로 전송하고 usage.prompt_tokens를 비교하세요.
동일한 모델과 메시지라면 프롬프트 토큰 수는 공식 API와 거의 정확하게 일치해야 합니다. 프롬프트는 고정되어 있으므로 무작위성이 없습니다. 동일한 입력에 대해 공식 API보다 지속적으로 20~30% 더 많은 프롬프트 토큰을 보고하는 리레이는 과금하고 있거나, 사용자가 알아야 할 자체 숨겨진 시스템 프롬프트를 프롬프트에 감싸고 있는 것입니다.
완성 토큰(Completion tokens)은 실행마다 약간씩 달라지므로, 하나가 아닌 여러 요청을 통해 판단하세요.
3. 매개변수가 전달되나요?
일부 리레이는 지원하지 않거나 자신들에게 비용이 드는 매개변수를 누락하거나 재작성합니다. 가장 쉽게 테스트할 수 있는 것은 max_tokens인데, 그 효과가 명확하기 때문입니다:
r = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": "Count from 1 to 200, separated by spaces."}]
...
공식 API는 일찍 멈추고 finish_reason="length"를 반환합니다. 만약 리레이가 모든 200개의 숫자를 반환하고 finish_reason="stop"이라면, 이는 사용자의 제한을 무시했으며 다른 매개변수도 아마 무시할 가능성이 높습니다. 앱이 temperature, seed, response_format 또는 logprobs에 의존한다면, 같은 방식으로 테스트하세요. 해당 매개변수가 출력을 눈에 띄게 변경하는 요청을 선택하여 양쪽을 비교해야 합니다.
4. 도구 호출(tool call)이 실제로 작동하나요?
에이전트가 실패하는 지점입니다. "도구를 지원한다"고 주장하는 리레이는 다음과 같은 행동을 할 수 있습니다:
- 구조화된
tool_calls배열 대신 도구 호출을 일반 텍스트로 반환할 수 있습니다. - 유효한 JSON이 아닌 인수를 반환할 수 있습니다.
- 첫 번째 호출은 잘 처리하지만, 결과를 담아
tool메시지를 다시 보낼 때 두 번째 차례에서 오류를 일으킬 수 있습니다.
따라서 첫 번째 응답뿐만 아니라 전체 왕복(round trip)을 테스트하세요:
msgs = [{"role": "user", "content": "What's the weather in Lisbon? Use the tool."}]
r = client.chat.completions.create(model=MODEL, messages=msgs, tools=TOOLS, temperature=0)
call = r.choices[0].message.tool_calls[0]
...
공식 API가 이를 완료하는데 리레이(relay)가 그렇지 못하다면, 해당 리레이는 랜딩 페이지에 무엇이라고 되어 있든 에이전트 워크로드(agent workloads)를 처리할 준비가 되지 않은 것입니다. 체인 호출(chained calls)(모델이 도구를 호출하고, 결과를 읽은 다음, 또 다른 것을 호출하는 과정)은 단일 호출보다 실패율이 높으므로, 이를 신뢰한다면 테스트를 두 개의 도구로 확장해야 합니다.
5. 긴 컨텍스트가 정말 존재하는가?
더 저렴한 모델을 제공하거나 비용 절감을 위해 긴 요청을 잘라내는 리레이는 종종 장문 입력에서 그 한계를 드러냅니다. 수천 단어의 채움 텍스트(filler) 속에 임의 코드를 숨기고 이를 되찾도록 요청해 보세요:
code = "".join(random.choices(string.ascii_uppercase + string.digits, k=8))
filler = ["Paragraph %d is ordinary filler text about nothing in particular." % i for i in range(600)]
filler.insert(random.randint(100, 500), "The secret code is %s. Remember it." % code)
최신 모델들은 이 길이에서도 코드를 안정적으로 찾아냅니다. 공식 API가 성공하는데 리레이가 실패한다면, 무언가가 잘리거나 대체되고 있다는 의미입니다. 실제 사용하는 컨텍스트 크기에 맞춰 채움 텍스트의 양을 늘려보세요.
6. 얼마나 느린가?
모든 리레이는 한 번의 홉(hop)을 추가하므로 약간의 추가 지연 시간(latency)은 정상입니다. 스트리밍(streaming)으로 첫 토큰까지 걸리는 시간을 측정하고, 몇 차례 실행한 값의 중앙값(median)을 구하여 비교해 보세요:
t0 = time.perf_counter()
for chunk in client.chat.completions.create(model=MODEL, messages=msgs, stream=True):
if chunk.choices and chunk.choices[0].delta.content:
...
공식 시간 위에 수백 밀리초가 더해지는 것은 라우팅 비용일 수 있습니다. 몇 초에 달하거나 실행마다 시간이 크게 변동한다면, 일반적으로 업스트림 큐잉(queueing upstream) 또는 전 세계를 돌아가는 경로 때문입니다. 단 한 번이 아니라 사용자들이 활발하게 활동하는 시간대에 테스트를 실행하세요.
결과 해석하기
한 번의 실패한 검사가 최종 판결은 아닙니다. 리레이(relays)도 다른 모든 것들처럼 나쁜 순간을 가질 수 있습니다. 스크립트를 여러 번, 다른 날에 실행해 보세요. 중요한 패턴들은 다음과 같습니다:
- 공식 API보다 토큰 수가 지속적으로 높은 경우: 보내지 않은 토큰에 대해 비용을 지불하고 있을 수 있습니다.
- 매개변수가 무시되는 경우: 앱이 공식 API를 대상으로 테스트할 때와 프로덕션 환경에서 다르게 작동합니다.
- 툴 왕복(tool round trip) 실패: 에이전트(agents)를 여기에 사용하지 마세요.
- 공식 API는 통과하지만 장문 컨텍스트 회상(long-context recall)에 실패하는 경우: 아마도 잘림(truncation)이거나 다른 모델일 가능성이 높습니다.
그리고 명백한 위험성: 리레이는 사용자의 프롬프트와 키를 볼 수 있습니다. 취소할 수 있는 키를 사용하고, 지출 한도를 설정하며, 리레이 사용을 중단하면 해당 키를 교체(rotate)하세요.
전체 스크립트
상단에 표시된 대로 실행해 보세요. 각 검사마다 한 줄씩 출력되며, 공식 API와 나란히 리레이의 결과가 표시됩니다.
"""Compare an OpenAI-compatible relay against the official API, side by side.
pip install openai
...
"""
실패하는 리레이를 대상으로 한 샘플 출력 (이것은 실제 제공업체가 아닌 스크립트 테스트용으로 작성한 모의 데이터입니다):
model_field {'relay': 'gpt-4o-mini-2024-07-18', 'official': 'gpt-4.1-mini'}
token_counts {'relay': (473, 8), 'official': (364, 8)}
max_tokens {'relay': ('stop', 200), 'official': ('length', 10')}
...
## 직접 실행하기를 원치 않는 경우
공개: 저는 [byok.icu](https://byok.icu/?utm_source=devto&utm_medium=article&utm_campaign=relay-check)에서 작업하고 있습니다. 이 도구는 이러한 검사들의 더 긴 버전(체인된 툴 호출 및 오류 복구를 포함한 7가지 에이전트 테스트 케이스)을 사용자의 리레이와 공식 API를 대상으로 나란히 실행하여, 그 이유와 함께 100점 만점의 점수를 제공합니다. 사용자 키는 해당 한 번의 실행에만 사용되며 저장되지 않습니다. 또한 Anthropic과 호환되는 엔드포인트도 지원합니다. 위의 스크립트는 기본 사항을 다루므로, 적합한 것을 사용하세요.
리레이가 오작동하는 다른 방법을 발견했다면 댓글로 알려주시면 감사하겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기