[Agent] LLM 토큰 계측 신설 + prompt caching·대화 윈도우로 요청 본문 82% 감소 [ #343 ] - #354
Merged
Conversation
응답의 usage 를 파싱·저장하는 코드가 저장소에 하나도 없었다. 토큰을 얼마나 쓰는지 아무도 몰랐고, 절감 작업의 효과를 잴 수단도 태스크당 예산 상한이 셀 대상도 없었다. 8-1 계측 - 호출 한 건마다 llm_usage 에 행 하나(V63). agent_runs 누적 컬럼이 아닌 이유는 마이그레이션 주석에 적었다 — 태스크 밖 호출, 보존 정책에 의한 삭제, 라운드별 캐시 적중 가시성, 뜨거운 행 UPDATE 회피. - 제공자마다 다른 usage 필드를 한 모양으로 정규화한다. Anthropic 의 input_tokens 는 캐시분 제외, OpenAI 의 prompt_tokens 는 포함이라 그대로 더하면 뜻이 섞인다. - LlmPort 시그니처에 taskId 를 더하는 대신 실행 스레드의 스코프로 귀속한다. 호출부가 전부 병렬 작업 접촉 파일이고, 시그니처에 실으면 새 호출부가 빠뜨릴 수 있다. - 계측 실패는 삼킨다. 이미 성공한 LLM 호출이 계측 때문에 실패해선 안 된다. 8-2 prompt caching - tools 마지막·system·최근 user 턴 둘에 cache_control(상한 4개를 정확히 채운다). - 이 루프의 접두는 고정이다 — 시스템 프롬프트와 도구 정의가 상수이고 트랜스크립트는 덧붙기만 한다. 그 사실을 테스트로 못 박았다(접두 바이트 동일·append-only). - 지금 설정된 claude-opus-4-5 는 최소 캐시 접두가 4096 토큰이라 system(≈1.1K)·tools 단독 지점은 항목을 만들지 못한다. 값을 하는 것은 굴러가는 user 지점이다. 8-4 출력 상한 - 도구 없는 완성 호출의 max_tokens 1024 → 설정 가능(기본 4096). 다단계 계획 JSON 이 잘리면 교정 재시도가 전체 컨텍스트를 한 번 더 보냈다 — 아끼는 출력보다 치르는 입력이 컸다. 곁들여: 파싱 실패 로그가 제공자 응답 전체를 찍던 것을 앞부분만 남기게 잘랐다. Anthropic 엔드포인트를 설정으로 뺐다 — 나가는 요청 본문을 실제로 검사하는 테스트는 그래야 가능하다. 측정(목 서버로 실제 나가는 본문): CODE 10라운드 96,290 bytes 중 79,389 bytes(82.4%)가 다음 라운드에 브레이크포인트 뒤 접두로 들어간다. Claude-Session: https://claude.ai/code/session_01APAyBZVYxUZzZsVyEXy6Qr
8-3 대화 이력 윈도우 - 최근 20턴 + 24,000자 상한. 규칙은 새 클래스(ConversationWindow)에 두고 호출부는 AgentMessageService 한 곳만 고친다 — 규칙이 흩어지면 곧 서로 다른 창이 된다. - **동작 변경이다.** 창 밖으로 밀려난 앞부분을 에이전트는 못 본다. 긴 대화에서 초반에만 나온 전제는 사용자가 다시 말해야 할 수 있다. 요약으로 접두를 대체하는 길도 있지만 요약용 LLM 호출을 하나 더 늘리는 일이라, 비용을 줄이려는 이 작업에서는 택하지 않았다. - 마지막 턴은 상한을 넘어도 남긴다. 그것이 지금 처리할 요청 그 자체다. - getUserIntentHistory 는 표시를 먼저 붙이고 자른다. 순서가 반대면 창 안의 마지막 턴에 "[지금 처리할 요청]" 이 붙어 이미 처리된 옛 요청이 새 요청으로 둔갑한다. 8-6 태스크당 누적 토큰 상한 - 기본 1,000,000 토큰(0 이면 없음). 재시도 곱셈(provider 3 × round 40 × task 3)에 태스크 단위 상한이 없었다. - 이전 실행이 쓴 양을 llm_usage 에서 읽어 이어 센다 — 실행마다 0 에서 다시 세면 상한이 재시도 횟수만큼 곱해져 상한이 아니게 된다. - 상한에 걸리면 사용자에게 사용량/상한이 그대로 보인다. catch-all 의 "작업 중 오류가 발생했습니다" 접두가 붙지 않도록 전용 분기를 뒀고, 빌드실패 복구 경로로도 보내지 않는다 — 누적이 이어지므로 재시도해도 첫 호출에서 같은 상한에 다시 걸린다. 8-5 배포 실패 분석 - 제공자 ANTHROPIC 고정 + 최상위 기본 모델을 걷어내고 default-provider(GLM)를 따른다. 12,000자 로그를 한 번 요약하는 데 최상위 모델을 쓸 근거가 없었다. - **되돌리는 법**: qeploy.ai.failure-analysis.provider=ANTHROPIC, .model=claude-opus-4-5-20251101 (또는 QEPLOY_AI_FAILURE_ANALYSIS_* 환경변수). 분석 품질이 떨어지면 재배포 없이 설정만으로 돌아간다. 테스트로 양쪽을 고정했다. 측정: 120턴 대화 요청 본문 41,571 → 7,131 bytes (82.8% 감소). Claude-Session: https://claude.ai/code/session_01APAyBZVYxUZzZsVyEXy6Qr
8-7. LLM 원문 응답 전체를 INFO 로 찍고 있었다. 계획 JSON 에는 사용자가 무엇을 만들라고 했는지가 그대로 들어가고, 교정 재시도 로그에는 모델이 쓴 응답이 통째로 들어간다 — 운영 로그 수집기로 사용자 요청과 생성 코드가 흘러나가는 경로였다. - INFO 에는 길이만 남긴다(응답이 잘렸는지 같은 운영 질문에는 답할 수 있어야 한다). - 원문 미리보기는 DEBUG 로 내리고 300자로 자른다. 파싱 실패 경로가 원문을 가장 길게 찍던 곳이라 그 두 곳도 같이 잘랐다. - 로그를 실제로 가로채 검증한다. 무엇이 찍히는지는 그것 말고 확인할 방법이 없다. Claude-Session: https://claude.ai/code/session_01APAyBZVYxUZzZsVyEXy6Qr
#336 이 "키를 인스턴스에 고정하지 않는다" 를 못박는 테스트를 넣었고, 이 단위가 같은 클라이언트에 LlmUsageRecorder 를 주입하면서 생성자가 바뀌었다. 두 브랜치가 각각은 초록이지만 합치면 컴파일이 깨진다. Claude-Session: https://claude.ai/code/session_01APAyBZVYxUZzZsVyEXy6Qr
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
요약
LLM 토큰을 얼마나 쓰는지 아무도 몰랐다 — 응답의
usage를 파싱·저장하는 코드가 저장소에 하나도 없었다(#343). 계측을 먼저 넣고, 그 위에서 줄였다.실측 (목 서버로 나가는 실제 요청 본문 측정)
실제 LLM 키가 없어 실호출은 못 했다. 캐시 적중률 자체는 실키 환경에서
cache_read_input_tokens로 확인해야 하고, 그 계측이 이번에 들어갔다.8-1 저장 설계 — 신규
llm_usage테이블(V63), 호출 한 건당 행 하나agent_runs누적 컬럼을 택하지 않은 이유 넷:agent_runs는 보존 스케줄러(Issue BF: [DB] 성능 U3 — 웹훅마다 projects 풀스캔, webhook_deliveries 무한 성장, 중복 인덱스 #338)가 지운다. 비용 이력이 함께 사라지면 "지난달 대비" 에 답할 수 없다FK 는 걸지 않았다(잠금 그래프 격리 + 2번).
귀속은
LlmPort시그니처가 아니라 실행 스레드 스코프(LlmUsageScope)로 했다. 호출부가 전부 병렬 접촉 파일이고, 시그니처에 실으면 새 호출부가 빠뜨릴 수 있다. 스코프 없는 호출도UNSCOPED로 기록된다 — 귀속만 없고 합계에서 사라지지 않는다.8-2 캐시가 실제로 사는 근거 — 접두가 고정이다
SYSTEM_PROMPT·TOOLS가static final상수이고 트랜스크립트는 덧붙기만 한다. 이걸 추측하지 않고 테스트로 못박았다 — 5라운드 동안tools+system+model직렬화 바이트가 동일함, 그리고 라운드 N+1 의 앞부분이 라운드 N 과cache_control마커를 빼고 완전 일치함(append-only).배치는 tools 마지막 1 + system 1 + 굴러가는 user 2 = 정확히 상한 4개.
claude-opus-4-5-20251101은 최소 캐시 접두가 4096 토큰이다. CODE 시스템 프롬프트는 4,280자(≈1.1K 토큰), Decision 은 14,848자(≈3.8K 토큰) — 둘 다 그 미만이라 system/tools 단독 지점은 캐시 항목을 만들지 못한다(오류도 과금도 없다). 실제로 값을 하는 것은 굴러가는 user 지점이고, 트랜스크립트가 4096 토큰을 넘는 2~3라운드째부터 시스템·도구까지 함께 캐시된다(더 긴 접두가 앞을 포함하므로). 최소 접두가 낮은 모델(512)로 설정을 바꾸면 코드 변경 없이 1라운드부터 적중한다.8-3 윈도우로 잃는 것 (동작 변경)
최근 20턴 + 24,000자. 창 밖 앞부분을 에이전트가 못 본다 — 긴 대화에서 초반에만 나온 전제(예: "이 앱은 사내용")는 사용자가 다시 말해야 할 수 있다. 마지막 턴은 상한을 넘어도 남긴다(그것이 지금 처리할 요청).
getUserIntentHistory는 표시를 먼저 붙이고 자른다 — 순서가 반대면 창 안 마지막 턴에 "[지금 처리할 요청]" 이 붙어 옛 요청이 새 요청으로 둔갑한다.규칙은 새 클래스
ConversationWindow, 호출부는AgentMessageService한 곳만.8-5 모델 고정 해제 · 되돌리는 법
배포 실패 분석이
ANTHROPIC+claude-opus-4-5로 박혀 있었고 설정의defaultProvider는 GLM 이었다. 12,000자 로그를 한 번 요약하는 데 최상위 모델을 쓸 근거가 없다.되돌리려면:
qeploy.ai.failure-analysis.provider=ANTHROPIC,.model=claude-opus-4-5-20251101(또는QEPLOY_AI_FAILURE_ANALYSIS_*환경변수). 재배포 없이 설정만으로 복귀. 기본 동작과 되돌린 동작 양쪽을 테스트로 고정했다.8-6 상한 초과 시 사용자가 보는 것
채팅에
TASK_FAILED로 그대로 뜬다:catch-all 의 "작업 중 오류가 발생했습니다" 접두가 붙지 않도록 전용 분기를 뒀고, 빌드실패 복구(재시도) 경로로도 보내지 않는다. 누적은
llm_usage에서 읽어 재시도를 건너 이어 세므로 재시도로 상한을 우회할 수 없다. 기본 1,000,000(0이면 끔).계약·동작 변화
신규 설정 키 5개(
ai.completion-max-tokens,ai.code-agent.max-task-tokens,ai.failure-analysis.provider/model,ai.anthropic.base-url), 신규 테이블 1개.노출 API 스키마 변화 없음 — 다만 배포 실패 분석 응답의
provider/model값이 ANTHROPIC/claude-opus-4-5 → GLM/z-ai/glm-4.6 으로 바뀐다. 새 실패 사유 문구 1종(토큰 상한).검증
안 한 것
LlmPort.complete반환형 변경 — 호출부 6곳이 전부 병렬 접촉 파일. 스코프 방식이 충돌도 적고 누락 위험도 낮다llm_usage보존 정책 — 지금 붙이면 비용 이력이 사라져 8-1 목적과 충돌. 증가량은 태스크당 수십 행. 후속 과제cache_controlTTL 1h — 기본 5분 유지. CODE 라운드 간격은 수초~수십초라 충분하고 1h 는 쓰기 프리미엄이 더 비싸다api.md계약 변경이라 별도 과제Closes #343
https://claude.ai/code/session_01APAyBZVYxUZzZsVyEXy6Qr