본문 바로가기

AI Agent

[실전 가이드] gpt-oss-20b, 120b 운영 시 무한 루프와 JSON 출력 문제 해결하기 (sglang)

반응형

안녕하세요! 오늘은 gpt-oss-20b 모델을 서비스에서 직접 운영하면서 겪은 문제들과, 이를 해결하기 위한 팁들을 정리해봤슴다.

이 모델은 OpenAI 모델들과는 성격이 아주 달라서 똑같이 다루면 실패하기 쉬워요. 프롬프트만 고치는 게 아니라, 설정과 구조를 어떻게 바꿔야 하는지 하나씩 설명해 드릴게요!

1. gpt-oss의 핵심 특성 요약

gpt-oss는 이런 친구입니다.

  • reasoning 중심 구조: 이 모델은 대답하기 전에 속으로 생각(Reasoning)을 아주 깊게 하는 구조로 되어 있습니다.
  • 프롬프트로 reasoning을 “끄는 것”이 거의 불가능: 우리가 "생각하지 말고 바로 말해!"라고 아무리 부탁해도 이 기능을 끄기가 참 어렵습니다.
  • JSON 출력, strict format 요구에 매우 취약: JSON 같은 딱딱한 형식을 맞춰달라고 하면 힘들어하는 경향이 있습니다.
  • 👉 그래서 이 모델은 프롬프트를 고치는 것보다, 모델 파라미터런타임 설정을 직접 만져주는 것이 훨씬 더 중요합니다.
  • Qwen 같은 다른 LLM은 프롬프트에 ~~이런 형식으로 output 주고 ~~~ 하지말고 ~~~ 해. 라고 하면 웬만큼 알아듣고 바꾸는 척이라도 합니다. 근데 gpt-oss는 프롬프트만으로 Structured Output 관련 지시를 늘어놓는다면 들은 척도 안합니다.

2. 가장 치명적인 문제: Reasoning 폭주와 반복 루프

2.1 대표적인 증상

  • content = None: 실제 대답 내용은 비어 있습니다.
  • reasoning_content에만 수천 토큰 출력: 속마음만 잔뜩 써놓습니다.
  • finish_reason = length: 너무 길게 말하다가 중간에 끊깁니다.
  • JSON schema 요구 → JSON 미출력: 형식을 지키지 않습니다.
  • 동일 문장/판단을 수십~수백 번 반복: 똑같은 말을 계속 되풀이합니다.

2.2 실제 관찰된 반복/폭주 예시

Context 8: 10.23. 행사참석 (10.24). Not Kim. (이런 똑같은 문장이 수백 번 반복됩니다.)

또는 이런 식이죠:

We need to extract Kim’s activities. 👉 출력은 멈추지 않는데, 우리가 원하는 진짜 결과는 끝까지 나오지 않습니다.

3. 왜 이런 문제가 생길까요? (원인 정리)

프롬프트가 잘못된 게 아니니 걱정 마세요!

  • ❌ 잘못된 생각: "프롬프트를 더 세게 쓰면 되겠지?", "무조건 JSON으로만 쓰라고 하면 되겠지?"라고 생각하시면 안 됩니다.
  • ✅ 실제 원인: 이 모델은 생각을 먼저 해야 하는 reasoning-first 모델입니다. 그런데 temperature=0으로 설정하고 긴 컨텍스트를 주면, 생각의 굴레에 갇혀서 빠져나오지 못하게 됩니다. 결국 reasoning 토큰이 출력 토큰을 다 써버리게 되는 것이죠.

4. 반드시 적용해야 하는 핵심 해결책

4.1 max_model_len을 반드시 줄여주세요! (가장 중요)

설정값에서 --max-model-len 4096으로 줄여야 합니다. 컨텍스트가 너무 길어지면 생각이 끊기지 않고 계속 이어져서 폭주하기 때문입니다.

max_model_len 안정성
8192 ❌ 거의 반드시 폭주합니다.
6144 ❌ 반복이 자주 생깁니다.
4096 가장 안정적입니다.
3072 ✅ 매우 안정적입니다.

4.2 temperature = 0을 사용하지 마세요!

  • temperature: 0.1 ~ 0.2
  • top_p: 0.7
  • repetition_penalty: 1.2 ~ 1.3 이렇게 설정하면 모델이 똑같은 생각의 길로만 빠져드는 것을 막아줍니다. 근데 큰 소용은 없었습니다.

4.3 max_tokens는 짧게 잡아주세요!

max_tokens를 512에서 800 정도로 짧게 주어야 합니다. 너무 길면 생각만 하다가 끝나버리지만, 짧게 끊어주면 그제야 JSON이라도 출력해 줍니다.


5. sglang 사용 시 주의사항

5.1 reasoning-parser의 영향

  • ✅ reasoning-parser 포함: 생각(reasoning)은 활발해지지만, 반복이나 폭주 위험이 커집니다.
  • ✅ reasoning-parser 제거: reasoning 을 아예 안합니다. content에 바로 결과가 나오고, JSON 출력 성공률도 올라갑니다.
  • 📌 서비스에서 쓸 때는 제거하는 것이 더 안전합니다!
  • 그런데 머리 써서 해내야 하는 작업인 경우 결과가 엄청나게 안 좋아집니다.
  • 저는 reasoning-parser gpt-oss 를 넣어주고, json_schema 전달로 어느정도 해결했습니다.

5.2 sglang 권장 설정

  • enable_thinking = False
  • stream = False
  • ⚠️ 이것만으로는 부족하니, 반드시 reasoning-parser 제거와 토큰 제한을 같이 해주세요.

6. JSON Schema 설계 원칙

6.1 절대 하면 안 되는 것들

  • nullable (string | null) 쓰지 않기
  • oneOfanyOf 쓰지 않기
  • 너무 깊은 중첩 구조나 optional 필드 많이 만들지 않기

6.2 권장하는 원칙

  • 구조를 하나로 고정하세요.
  • 값이 없으면 null 대신 빈 글자("")나 빈 리스트([])를 사용하세요.
  • additionalProperties = false 설정을 꼭 넣어주세요.

7. 프롬프트는 최대한 간단하게!

규칙을 10개 넘게 쓰거나 "절대"라고 강조하는 건 효과가 없어요.

  • 역할은 딱 1줄로 정해주기
  • 출력 형식만 명확히 하기
  • 나머지는 schema에게 맡기기

다른 LLM 들은 프롬프트에 자세하게 써주면 써줄수록 원하는 결과가 나올 확률이 조금이나마 올라갑니다.

근데 이놈은 다릅니다. 프롬프트에 자세하게 쓰면 쓸수록 반복/폭주 뜁니다.

이놈은 프롬프트 간소화를 통해 핵심만 넣어줘야 원하는 결과에 가까워 집니다.


8. gpt-oss에서 JSON Schema 넘기는 방법 (중요!)

이 모델은 프롬프트에 형식을 써주는 것만으로는 말을 잘 안 듣습니다. 반드시 서버 레벨에서 response_format이나 json_schema를 강제로 정해줘야 합니다.

8.1 왜 서버에서 강제해야 할까요?

schema를 API로 넘기면 모델이 이를 "반드시 지켜야 할 약속"으로 강하게 인식합니다. 덕분에 reasoning 단계에서 멈추지 않고 JSON을 끝까지 출력할 확률이 아주 높아집니다. 여기서 schema는 힌트가 아니라 출력 제약입니다.

8.2 vLLM / OpenAI 호환 API 사용 시

response_format 안에 schema를 꼼꼼히 넣어주세요. (위의 규칙대로 requiredadditionalProperties를 잘 챙겨야 합니다!)

8.3 "final"이라는 이름의 마법

가장 바깥쪽 키(key)를 "final"로 감싸보세요. gpt-oss는 이 이름을 보면 "아, 이제 생각을 마치고 결과를 내보내야겠다!"라고 인식하는 stop-token 같은 역할을 해준답니다.

반드시 감싸는걸 추천합니다.

json_schema에 final이 없어도 지맘대로 final이라는 이름의 key를 만들고 그 value에 답변을 집어넣습니다.

그럴바에는 아예 json_schema에 final을 포함 시키니 파싱 에러 발생 횟수가 현저하게 줄어들었습니다.


9. 최종 결론

구분 담당 역할
프롬프트 무엇을 할지 알려줍니다.
JSON Schema 어떻게 출력할지(탈출구 역할) 정해줍니다.
모델 파라미터 폭주를 막는 안전장치입니다.

한 줄 요약: gpt-oss 사용이 강제되는 환경만 아니었으면 프로젝트가 1달은 일찍 끝났을 겁니다. 다른 모델 좋은거 쓰세요...

반응형