
모델 API는 호출 사이에 아무것도 기억하지 않습니다. 그래서 에이전트는 모델을 부를 때마다 시스템 프롬프트, 도구 정의, 그때까지의 대화 맥락을 전부 다시 보냅니다. 질문 하나에 답 하나가 끝나기까지를 턴, 모델을 한 번 부르고 그 응답에 든 도구 호출을 실행하기까지를 라운드라고 하면, 한 턴은 대개 여러 라운드를 돕니다. 열 번째 라운드의 요청에는 앞선 아홉 라운드의 도구 호출과 결과가 모두 들어 있습니다. 라운드마다 붙는 양이 비슷하면 한 턴에 모델이 읽는 입력 토큰의 합은 라운드 수의 제곱에 비례해 늘어납니다.
이 글은 그 증가가 어디서 오는지와, 그래서 OwlAI가 무엇을 줄이고 무엇을 재는지를 다룹니다. 같은 일을 적은 라운드에 끝내게 하고, 먼저 붙어 여러 번 다시 읽히는 큰 결과를 줄이고, 비용은 호출마다 실제로 보낸 요청 전체로 셉니다. 대화 상태를 프로바이더에 맡기지 않고 매번 직접 보내는 이유는 「OpenAI는 응답을 기본으로 저장한다, 대화 맥락은 직접 보낸다」에서, 대화 맥락을 저장하는 나무 구조는 「고친 질문에 옛 답이 섞이지 않게 대화를 트리로 저장하기」에서 다룹니다.
라운드 N의 요청은 라운드 N-1의 요청에 두 가지를 더한 것이다
채팅에서는 라운드마다 LLM 호출 하나가 노드 하나로 저장됩니다. 노드에는 그 호출에서 새로 보낸 항목과 모델이 돌려준 항목이 함께 들어 있습니다. 다음 라운드를 보낼 때는 대화의 끝 노드에서 부모를 따라 처음까지 거슬러 올라가, 경로의 항목을 시간 순서로 이어 붙이고 이번에 보낼 항목을 맨 뒤에 붙입니다. 플레이북, 테이블 분석, 회의 요약처럼 대화가 없는 작업은 메시지 목록을 메모리에 들고 같은 일을 합니다. 저장하는 곳만 다르고, 라운드마다 앞선 내용을 전부 다시 보내는 것은 같습니다.
그래서 라운드 N의 요청은 라운드 N-1의 요청 뒤에 두 가지가 붙은 모양입니다. 하나는 라운드 N-1에서 모델이 돌려준 도구 호출이고, 다른 하나는 그 도구를 실행한 결과입니다. 시스템 프롬프트와 도구 정의는 라운드마다 다시 실립니다.
규모를 가늠하려고 가정을 하나 두겠습니다. 시스템 프롬프트와 도구 정의가 2만 토큰이고, 라운드마다 도구 결과가 5천 토큰씩 붙는다고 합니다. 도구 호출 항목 자체는 작아서 셈에서 뺍니다. 첫 라운드의 입력은 2만 토큰이고 열 번째 라운드는 6만 5천 토큰입니다. 열 라운드의 입력을 더하면 42만 5천 토큰입니다. 요청에 담긴 서로 다른 내용은 6만 5천 토큰뿐인데, 모델은 그 6.5배를 읽습니다.
고정 구간을 F, 라운드마다 붙는 결과를 D, 라운드 수를 R이라 하면 한 턴의 입력 합은 R×F + D×R(R-1)/2입니다. 뒤 항이 R의 제곱으로 자라서, 라운드가 늘수록 담긴 내용 대비 읽는 양의 배수도 커집니다.
| 라운드 수 | 마지막 라운드 입력 | 한 턴 입력 합 | 담긴 내용 대비 |
|---|---|---|---|
| 5 | 4만 | 15만 | 3.8배 |
| 10 | 6만 5천 | 42만 5천 | 6.5배 |
| 14 | 8만 5천 | 73만 5천 | 8.6배 |
| 20 | 11만 5천 | 135만 | 11.7배 |
14라운드는 실운영 환경에서 관측한 채팅 턴 라운드 수의 p95입니다(「도구가 다 성공해도 같은 결과만 돌아오면 에이전트를 멈춘다」). 스무 턴 가운데 한 턴은 이보다 깊습니다.
같은 일을 적은 라운드에 끝내면 다시 읽는 양이 준다
식에서 R을 줄이는 가장 직접적인 방법은 서로 기다릴 필요가 없는 도구 호출을 한 라운드에 묶는 것입니다. OwlAI는 도구가 있는 모든 호출에서 모델이 한 응답에 여러 도구를 부를 수 있게 열어 두고(병렬 도구 호출), 한 라운드에 온 호출은 동시에 실행합니다.
테이블 세 개의 정보를 각각 조회해야 하는 질문으로 차이를 보겠습니다. 앞의 가정대로 고정 구간 2만 토큰, 조회 결과는 하나에 5천 토큰입니다.
| 방식 | 라운드별 입력 | 한 턴 입력 합 |
|---|---|---|
| 한 라운드에 하나씩 | 2만 → 2만 5천 → 3만 → 3만 5천(답) | 11만 |
| 한 라운드에 셋을 함께 | 2만 → 3만 5천(답) | 5만 5천 |
조회 결과는 같고 마지막 답을 쓸 때 모델이 보는 내용도 같은데, 입력 합은 절반입니다. 왕복이 두 번 줄어 기다리는 시간도 줍니다. 어떤 호출을 묶을지는 모델이 정합니다. 앞선 조회 결과를 보고 다음 SQL을 써야 하는 경우처럼 순서가 필요한 호출은 묶을 수 없기 때문입니다.
라운드 상한도 도구 호출 수가 아니라 LLM 왕복 횟수를 셉니다. 한 라운드에 도구를 열 개 불러도 한 번이고, 요청 전체를 다시 보내는 횟수도 한 번입니다. 상한 값과 그 근거는 「도구가 다 성공해도 같은 결과만 돌아오면 에이전트를 멈춘다」에서 다룹니다.
먼저 붙은 큰 결과일수록 여러 번 다시 읽힌다
식의 D는 라운드마다 같지 않습니다. 실제 턴에서는 대개 몇몇 결과가 나머지보다 훨씬 큽니다. 테이블 전체를 훑은 조회나 긴 문서 하나가 그렇습니다. 이런 결과는 붙은 뒤로 턴이 끝날 때까지 모든 라운드에 다시 실립니다.
10라운드 턴에서 5만 토큰짜리 조회 결과를 생각해 보겠습니다. 첫 라운드에 부른 도구의 결과라면 2라운드부터 10라운드까지 아홉 번 실려 45만 토큰이 됩니다. 같은 결과가 9라운드에 부른 도구에서 나왔다면 10라운드에 한 번 실리고 끝납니다. 결과 하나의 비용은 크기와, 붙은 뒤 남은 라운드 수의 곱입니다.
그래서 큰 결과는 붙기 전에 줄입니다. 도구 결과 하나가 크기 상한을 넘으면 줄여서 싣고, 줄였다는 사실을 모델에게 알립니다. 한 라운드에 여러 도구를 동시에 불렀을 때는 그 라운드에 새로 붙는 결과의 합에도 같은 상한을 겁니다(「큰 도구 결과는 프로바이더가 자르기 전에 줄이고, 줄였다고 알린다」). 대화가 길어져 압축할 때도 오래된 도구 결과부터 덜어 냅니다(「압축 임계값은 컨텍스트 윈도우가 아니라 입력 상한에서 잡는다」). 어느 쪽이든 겨냥하는 것은 같습니다. 남은 라운드마다 다시 실릴 큰 덩어리입니다.
다시 읽는 몫은 앞 라운드의 요청과 같아서 캐시에서 읽을 수 있다
라운드 N의 요청은 라운드 N-1의 요청 뒤에 항목을 붙이기만 한 모양이라, 앞부분이 바이트 단위로 같습니다. 프로바이더의 프롬프트 캐싱과 vLLM의 접두사 캐싱은 이렇게 앞부분이 같은 구간을 다시 계산하지 않고 캐시에서 읽습니다. 캐시에서 읽은 토큰은 입력 처리(프리필)를 건너뛰고, 프로바이더는 정가보다 싸게 청구합니다. 자체 호스팅 모델에서는 GPU 계산 시간이 줄어듭니다.
앞의 10라운드 예에서 42만 5천 토큰 가운데 앞 라운드의 요청과 겹치는 몫은 36만 토큰입니다. 캐시가 라운드마다 살아 있다면 새로 계산하는 것은 6만 5천 토큰, 요청에 담긴 내용만큼입니다. 대화 맥락을 고치지 않고 뒤에만 붙이는 것은 이 셈이 성립하게 하는 조건입니다. 시스템 프롬프트 안의 한 줄이 바뀌어도 그 뒤를 모두 다시 계산하는 문제와 그것을 피하는 블록 배치는 「시스템 프롬프트 한 줄에 캐시 읽기가 0이 된다, 바뀌는 블록은 뒤로 보낸다」에서 다룹니다.
다만 캐시가 줄이는 것은 계산과 단가이고, 읽는 양 자체는 그대로입니다. 요청이 모델의 입력 상한에 다가가는 속도도 같습니다. 그래서 라운드를 줄이고 큰 결과를 줄이는 일은 캐시가 있어도 따로 해야 합니다.
비용은 호출마다 실제로 보낸 요청 전체로 잰다
이 증가를 보려면 무엇을 세는지가 중요합니다. 이번 라운드에 새로 붙은 항목만 세면 라운드마다 비슷한 숫자가 나와서, 앞선 대화가 쌓이는 모습이 보이지 않습니다. 그래서 OwlAI는 호출마다 시스템 프롬프트, 도구 정의, 입력 항목을 모두 포함해 실제로 보낸 요청 전체의 글자 수를 호출 기록에 남깁니다. 라운드마다 입력 토큰, 캐시에서 읽은 토큰, 캐시에 새로 쓴 토큰도 로그로 남깁니다.
입력 토큰을 합칠 때는 프로바이더마다 숫자의 뜻이 다릅니다. OpenAI는 입력 토큰이 총 입력이고 캐시에서 읽은 토큰은 그 일부입니다. Anthropic은 입력 토큰이 캐시에 걸리지 않은 몫만이고, 캐시 읽기와 캐시 쓰기는 별도 숫자입니다. Anthropic의 입력 토큰만 더하면 캐시를 잘 쓴 턴일수록 입력이 작게 보입니다. 그래서 사용량 원장과 라운드 로그는 같은 규칙 하나로 두 형식을 「캐시 읽기와 쓰기를 포함한 총 입력」으로 맞춘 뒤 셉니다.
사용자가 답을 받는 도중에 중단한 호출도 셉니다. 중단한 호출도 프로바이더는 받은 입력과 생성한 출력만큼 청구합니다. 그런데 OpenAI의 스트리밍 응답은 마지막 이벤트에서야 사용량을 알려 주므로, 중간에 끊으면 사용량이 비어 있습니다. 이때 원장은 누적 구조를 그대로 이용해 입력을 추정합니다. 이번 입력은 직전 호출의 입력과 출력에 이번에 새로 붙인 항목을 더한 것이고, 앞부분은 직전 요청과 같으니 캐시에서 읽은 것으로 봅니다. 추정값이 실제로 보낸 요청의 글자 수로 센 값을 넘으면 그 값으로 자릅니다. 글자 수는 세 글자를 한 토큰으로 칩니다. 한국어와 도구 JSON이 섞인 실제 대화에서 OpenAI는 2.2~4.2글자가 한 토큰이었고(2026-09-30 측정), 3은 그 가운데 값입니다. 출력은 스트리밍으로 받은 글자 수로 셉니다.
설계에서 고른 것
라운드마다 요청 전체를 다시 보내는 구조를 골랐습니다. 그 덕분에 대화 상태가 모두 OwlAI 쪽 DB에 있고 어느 프로바이더의 모델이든 같은 경로로 부릅니다(「OpenAI는 응답을 기본으로 저장한다, 대화 맥락은 직접 보낸다」). 대가는 깊은 턴의 입력이 라운드 수의 제곱으로 자란다는 점입니다. 캐시, 병렬 도구 호출, 결과 크기 상한, 압축은 이 비용을 줄일 뿐 없애지 않습니다.
병렬 도구 호출은 열어 둘 뿐 강제하지 않습니다. 어떤 호출이 서로 독립인지는 앞선 결과를 봐야 알 때가 많고, 순서가 필요한 호출을 억지로 묶으면 모델은 앞선 결과 없이 다음 SQL을 지어냅니다. 라운드 수는 모델의 판단에 맡기고, 줄인 결과와 측정으로 그 판단의 비용을 묶습니다.
중단한 호출의 비용은 추정입니다. 입력은 실제로 보낸 요청 크기를 넘지 않게 자르지만, 출력은 스트리밍으로 흘러온 글자만 세므로 추론 토큰을 흘려 보내지 않는 OpenAI 모델에서는 추론 몫이 빠집니다. 원장의 이 값은 실제 청구액의 하한에 가깝습니다.
캐시는 조건이 맞을 때만 다시 읽는 몫을 덜어 줍니다. 사용자가 몇 분 쉬었다 돌아오면 캐시 수명이 지나고, 대화 도중 모델을 바꾸거나 압축이 일어나면 앞부분이 달라져 캐시를 처음부터 새로 씁니다(「시스템 프롬프트 한 줄에 캐시 읽기가 0이 된다, 바뀌는 블록은 뒤로 보낸다」). 도구 결과가 크고 라운드가 깊은 업무를 한 대화에 이어서 몰아넣는 환경이라면, 이런 턴의 입력 합을 라운드별 로그로 먼저 확인하고 긴 작업은 새 대화로 나누는 편이 답도 정확하고 비용도 적습니다. 고객 매뉴얼도 아주 긴 대화는 새 대화로 시작하라고 안내합니다.
어떤 문제를 해결하나요?
대화 엔진의 다른 글

엔지니어링 노트 · 대화 엔진
OpenAI는 응답을 기본으로 저장한다, 대화 맥락은 직접 보낸다
OpenAI Responses API는 store를 따로 정하지 않은 응답을 프로바이더 서버에 저장하고, 공식 문서는 그 응답이 적어도 30일 남는다고 밝힙니다(2026-11-03 확인).

엔지니어링 노트 · 대화 엔진
고친 질문에 옛 답이 섞이지 않게 대화를 트리로 저장하기
사용자가 「지난달 거래처별 매출 상위 10곳」을 물었는데, 모델이 국내 거래처만 담긴 테이블을 조회해 답했다고 해 보겠습니다.

엔지니어링 노트 · 대화 엔진
턴 도중 보낸 메시지는 DB 행에 두고, 행 잠금이 가져갈 쪽을 정한다
딥 리서치는 한 턴이 기본 한 시간까지 돕니다. 턴은 질문 하나를 받아 답을 내기까지의 한 번의 질의응답입니다.
