본문 바로가기
Q&A해결2026년 7월 22일2분 읽기

함수콜링에서 tool 인자(arguments)가 가끔 깨진 JSON으로 와서 json.loads가 터집니다

g
gpt_taeho
조회 846댓글 2

OpenAI 함수콜링으로 유저 요청을 툴 호출로 라우팅하는 걸 만들고 있어요. 90%는 잘 되는데, 열 번에 한 번쯤 tool_calls의 arguments 문자열을 json.loads 하면 Unterminated string 이나 Expecting ',' delimiter 같은 에러가 납니다.

스트리밍으로 받고 있어서 delta로 오는 arguments 조각을 문자열로 계속 이어 붙인 다음, 매 청크마다 파싱을 시도하는 구조입니다. 대략 이런 식이에요.

buf += delta.tool_calls[0].function.arguments
try:
    args = json.loads(buf)   # 여기서 가끔 터짐
except Exception:
    pass

실패한 걸 로그로 찍어보면 {"query": "서울 날씨 이렇게 문자열이 중간에 뚝 끊긴 상태예요. 모델이 이상한 JSON을 뱉는 건지, 제 파싱 로직이 문제인지 헷갈립니다. 인자 스키마는 필드 두어 개짜리로 단순한데도 이러네요.

댓글 2

김김영삼3개월 전
모델이 깨진 JSON을 주는 게 아니라, 아직 안 끝난 조각을 파싱하고 있어서 그래요. 스트리밍 arguments는 토큰 단위로 쪼개져 오는데 중간 청크는 당연히 미완성 JSON입니다. 매 청크마다 json.loads 하면 대부분의 청크에서 실패하는 게 정상이에요.

제대로 하려면 조각은 그냥 이어 붙이기만 하고, 파싱은 그 tool call이 끝났을 때 딱 한 번만 하세요. 스트림에서 해당 tool_use 블록이 종료되는 시점(스트리밍 종료 이벤트, finish_reason)이 왔을 때 완성된 buffer를 한 번 파싱하면 됩니다. 중간에 except pass로 삼키니까 진짜 문제는 안 보이고 성공한 것만 우연히 잡히던 거고요.

그리고 스트리밍이 굳이 필요 없으면 스트리밍 없이 받으면 SDK가 이미 파싱된 input 객체를 통째로 줍니다. 직렬화된 문자열을 손으로 파싱할 필요 자체가 없어요.

추가로 인자가 진짜로 잘려 오는 케이스도 있는데, 그건 max_tokens에 걸려서 출력이 중간에 끊긴 경우예요. stop_reason 혹은 finish_reason이 length/max_tokens인지 꼭 확인하세요. 이건 파싱으로 못 살리고 max_tokens를 올려야 합니다.
ggpt_taeho(guest)3개월 전
말씀대로 청크마다 파싱하던 걸 종료 시점에 한 번만 하도록 고쳤더니 에러가 사라졌어요. except pass로 덮고 있어서 진짜 원인이 안 보였던 것도 정확하네요. 감사합니다!
Ctrl+Enter로 등록