Files
dela 0335d572de
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
First Commit
2026-10-09 08:38:16 +08:00

9.6 KiB

곁질문 장문 대화와 컨텍스트 예산

한국어 · 中文

2026-09-11 수정. 이전 구현은 요청 JSON 의 글자 수를 그대로 토큰 수로 간주했고 메인 작업의 32K 출력 예약량을 그대로 물려받았기 때문에, HTML·JS·도구 결과가 많을 때 정상적인 곁질문을 미리 거부하는 문제가 있었습니다.

이 문서는 원본 중국어 문서(CONTEXT_BUDGET.zh.md)를 한국어로 옮긴 것입니다. 상류(upstream) 저장소의 변경을 대조하기 쉽도록 원본은 그대로 보존합니다.

오픈소스 구현 대조

  • Grok CLI 곁질문 컨텍스트: 가장 최근의 사용자·어시스턴트 텍스트에서 일부 조각을 발췌하며, 글자 예산은 약 2000 자이고 한 건당 최대 400 자까지 잘라냅니다. 이 경로에서는 연속된 곁질문 문답 기록을 유지하지 않습니다.
  • Grok CLI 독립 요청: 취소 신호를 따로 둡니다. 모델이 지원하면 출력 상한은 2048 토큰입니다. 도구는 제공하지 않습니다.
  • Grok CLI 메인 세션 압축: 토큰을 추정하고, 최근 내용을 남기고, 새 내용을 이전 요약에 반영하며, 턴 경계를 넘는 잘림을 처리합니다.
  • OpenCode 세션 압축: 요청 전체를 추정하고, 출력·버퍼를 예약하며, 최근 내용에 롤링 요약을 더하고, 도구 없는 요약 요청을 보냅니다. 이 구현은 기본적으로 최근 예산 8000, 요약 출력 상한 4096 토큰을 씁니다.
  • OpenCode 오버플로 복구: 어시스턴트 출력이 아직 시작되지 않았을 때만 오버플로 복구를 시도하고, 복구한 뒤의 호출은 같은 오버플로 복구 경로로 다시 들어가지 않습니다.

ARTEX 는 독립 출력 예산, 최근 내용과 롤링 요약, 제한된 복구라는 방식을 참고했습니다. norma v0.3.6 의 구조화 메시지와 도구 짝 맞추기는 그대로 유지하되 Grok 의 텍스트 발췌 방식은 그대로 가져오지 않았고, OpenCode 의 메인 세션 압축 이벤트를 ARTEX 메인 transcript 에 기록하지도 않습니다.

요청 예산과 실행

  • 메시지는 norma 의 방식을 그대로 써서 내용 블록 단위로 UTF-8 바이트를 추정하고 4/3 여유분을 더합니다. 여기에 시스템 프롬프트, 도구 schema, 메시지 포장 오버헤드를 추가로 셈합니다. 이 추정값은 모델의 정확한 토큰 수가 아닙니다.
  • 곁질문 출력은 기본적으로 최대 8192 토큰이며, 메인 설정에 이미 정해진 출력 상한도 넘지 않습니다. 서비스 환경 변수 ARTEX_BTW_MAX_OUTPUT_TOKENS 로 256–32768 범위의 상한을 설정할 수 있고, 제품 기본 모델이나 메인 작업 파라미터는 바꾸지 않습니다.
  • 입력 예산은 컨텍스트 창에서 출력 상한과 안전 여유분을 뺀 값이며, 창 크기를 모를 때는 플랫폼 기본값인 200K 를 씁니다. 안전 여유분은 창의 5% 이고, 최소 128 토큰, 최대 8192 토큰입니다.
  • 성공한 문답은 증가하는 순번에 따라 한 번에 최대 20 묶음까지 불러옵니다. 원문은 최대 20 묶음까지 남기며, 그 토큰 예산은 입력 예산의 1/4 이내이고 16K 를 넘지 않습니다.
  • 예산을 넘긴 문답은 롤링 요약으로 반영합니다. 요약에는 출처 기록과 컨텍스트 시점을 함께 담습니다. 과거의 어시스턴트 답변은 새로운 도구 증거와 같지 않으므로, 충돌이 생기면 가장 최신 메인 스냅샷을 우선합니다.
  • 메인 컨텍스트가 그래도 너무 길면 사본의 오래된 메시지만 요약하고 최근 내용은 최대 8K 토큰까지 남깁니다. 자르는 지점은 도구 호출과 그 결과를 쪼개지 않습니다. 하나의 묶음이 너무 크면 그 묶음 전체를 요약에 넣습니다.
  • 요약 입력은 실제로 남은 창 크기에 맞춰 UTF-8 기준으로 안전하게 나누며, 출력 상한은 2048 토큰입니다. 빈 요약, 잘린 응답, 도구 호출, 요약 예산 초과는 모두 캐시에 쓰지 않습니다. 한 번의 곁질문에서 요약 호출은 최대 12 회이며 동일한 120 초 제한 시간을 함께 적용받습니다. 상한에 이르면 무한히 반복하지 않고 명확히 실패로 끝냅니다.
  • 모델이 처음으로 컨텍스트 초과를 반환하고 아직 텍스트나 도구 호출을 내보내지 않았다면, 더 줄인 뒤 최대 한 번만 다시 시도합니다. 추정 크기가 줄지 않으면 복구를 즉시 멈춥니다. 그 밖의 모델 오류나 일부만 스트리밍된 출력은 이 복구를 일으키지 않습니다.
  • 요약, 실패한 시도, 취소 시점의 사용량을 포함해 이미 집계된 모든 사용량은 같은 곁질문 요청에 누적합니다. Provider 가 사용량을 돌려주지 않으면 0 으로만 기록하며, 추정값을 실제 사용량인 것처럼 꾸미지 않습니다.

영속화와 화면

side_question_sessions.memory 에는 오래된 문답 요약, 반영된 순번(ordinal), 그리고 스냅샷 신원별로 캐시한 메인 컨텍스트 요약을 저장합니다. side_question_requests.context_info 에는 준비 단계, 실제로 다시 실은 묶음 수, 요약 사용 내역, 예산 추정값을 저장합니다.

요약은 원래 요청이 아직 실행 중이고 정리 버전이 일치할 때만 저장합니다. 비우기는 캐시도 함께 지우며, 뒤늦게 도착한 쓰기가 이미 정리된 데이터를 되살리지 않습니다. 새 스냅샷은 이전 스냅샷의 요약을 재사용하지 않습니다. 요약 필드는 v3 작업 아카이브와 함께 저장하며, 필드가 빠진 오래된 v3 를 복원할 때는 빈 객체로 채우고 v1·v2 는 계속 호환합니다.

POST 는 요청을 먼저 받아들여 돌려주고, 준비와 압축은 백그라운드에서 실행하며 수용 잠금(admission lock)이나 데이터베이스 트랜잭션을 쥐지 않습니다. SSE 와 기록 화면은 준비, 문답 정리, 사본 압축, 응답 단계를 표시합니다. 압축 실패는 해당 곁질문 요청의 실패 종료 상태로 저장합니다. 프런트엔드는 오류를 남기고 이번에 실패한 질문의 초안을 복구하며, 떠 있는 알림으로 입력창을 가리지 않습니다. 기록 폴링도 더 이상 제출 오류를 지우지 않습니다.

검증 기록

  • 19·20·21·50 묶음 재생, 20 묶음을 넘는 오래된 결론 보존, 요약 캐시의 재시작 후 재사용: 자동화 통과.
  • 아주 긴 중국어 답변과 코드 컨텍스트, 요청 예산 분할, 도구 짝 맞추기, 스냅샷 불변, 새 스냅샷의 캐시 무효화: 자동화 통과.
  • 요약 실패·취소·잘림·초과 길이·도구 반환, 비우기 경쟁, 호출 상한, 단 한 번의 오버플로 복구와 일부 스트림 재시도 안 함: 자동화 통과.
  • 독립 PostgreSQL 에서의 페이지 나누기, 재시작, v1·v2·v3 아카이브, 요약 캐시와 예산 메타데이터의 아카이브 복원, 새 필드가 없는 오래된 v3, 20 개 부모 세션이 네 개의 동시 실행 자리를 공유하는 경우: 통과.
  • Go 후보 서비스 빌드, 프런트엔드 TypeScript 검사, 수정한 컴포넌트의 Biome 검사, 그리고 독립 디렉터리에서의 Next.js 프로덕션 빌드: 통과.
  • 내장 브라우저로 독립 UI 픽스처를 써서 1280×720 과 390×844 해상도에서 문답 정리 단계, 요약 범위 안내, 실패 후 초안 복구, 떠 있는 오류 알림이 없는지, 가로 넘침이 없는지, 콘솔 오류가 없는지를 검증했습니다. 임시 픽스처는 제거했습니다.
  • 로컬에 이미 있는 Worker 스냅샷을 읽기 전용으로 재생했을 때 새 예산 검사가 통과했습니다. 예를 들어 Worker #3 의 293085 자 스냅샷은 더 이상 로컬 글자 수 계산 때문에 잘못 거부되지 않습니다. 이 항목에는 외부 모델 호출이 없었습니다.
  • 이번에 비공개 Worker 스냅샷을 Grok 으로 보내려던 실제 대화 테스트는 자동 승인 심사에서 거부되어 실행되지 않았으므로, 통과 항목으로 치지 않습니다.
  • 2026-09-11 00:37 에 사용자 요청으로 로컬 백엔드를 재시작했고, 원래의 데이터베이스·데이터 디렉터리·로그인 설정을 그대로 썼습니다. 실행 파일과 후보 바이너리의 SHA-256 이 일치했고, 백엔드와 프런트엔드 프록시의 /api/health 가 모두 정상을 반환했습니다.

검증 명령(독립 테스트 DB 만 사용):

go test -race ./sidequestion ./db ./server -run 'TestSide|TestCheckpoint|TestSnapshot|TestBuildRequest|TestService|TestMainSide|TestTaskArchive' -count=1
go build ./cmd/artex
npx tsc --noEmit
npm run build -- --webpack

프런트엔드 프로덕션 빌드는 독립 사본을 써서 현재 미리 보기의 .next 를 덮어쓰지 않도록 했습니다. 후보 서비스는 /private/tmp/artex-btw-budget-candidate 에 있고, /private/tmp/artex-btw-preview/artex 로 복사해 실행했습니다. 원래 바이너리는 같은 디렉터리의 artex.before-context-budget 로 백업했습니다.