직원이 교육비 한도를 묻고, 봇이 분기당 30만 원이라고 답했다. 그러자 곧바로 다음 질문이 왔다. “그건 언제부터인가요?”
직원에게는 방금 들은 말에 이어 묻는 당연한 되물음이었다. 그런데 봇 쪽에서는 앞 문장이 남아 있지 않으면 무엇의 시행일인지조차 알 수 없다.
답하려면 방금 주고받은 대화가 필요하다. 검색한 정책과 티켓 조회 결과도 후보에 든다. 전부 붙이면 답이 좋아질 것 같지만 길이 제한을 넘거나, 개정 전 정책이 섞이거나, 다른 사용자의 대화가 들어간다. 그래서 무엇을 넣을지만이 아니라 무엇을 빼고 어떤 순서로 넣을지까지 정해야 한다.
“그건 언제부터인가요?” 한 줄에 답하는 데도 판단이 네 번 들어간다. 방금 한 대화를 넣을지 고르고, 정책 문서보다 앞에 둘지 뒤에 둘지 정하고, 개정 전 정책이 잡혀 있으면 새것으로 갈고, 다른 사람의 대화가 섞이지 않게 막는다.
컨텍스트 엔지니어링은 모델이 이번 작업에 쓸 정보를 고르고, 배치하고, 갱신하고, 격리하는 일이다. 프롬프트 문장을 다듬는 일도 그 일부다. 다만 정보가 어디서 왔는지, 지금 읽을 권한이 있는지, 언제까지 유효한지, 빠지면 어떻게 할지가 함께 있어야 한다. 문맥 구성기는 권한을 먼저 검사하고, 질문과 출력 공간을 확보한 뒤, 우선순위대로 자료를 넣는다.
세어야 할 토큰
토크나이저는 글자도 단어도 아닌 중간 단위로 글을 쪼갠다. 학습 말뭉치에서 자주 붙어 나온 문자열을 하나의 조각으로 묶어 어휘를 만들고, 어휘에 없는 문자열은 더 작은 조각으로 나눠 표현한다. 바이트 페어 인코딩(BPE)이 대표적인 방식이다. 어휘를 말뭉치에서 만들기 때문에 그 말뭉치에 자주 나온 표현은 조각 하나로 들어가고 드문 표현은 여럿으로 나뉜다. 그래서 영어 위주로 만든 어휘에서는 한국어가 불리하다. 조각의 출발점이 문자가 아니라 UTF-8 바이트인 경우가 많은데 한글 한 글자는 3바이트라, 어휘에 없는 한 글자가 조각 셋이 되기도 한다.
같은 뜻의 문장도 언어에 따라 토큰 수가 다르고 같은 한국어 안에서도 사내 약어나 티켓 번호처럼 드문 문자열에서는 조각이 여러 개로 늘어난다. 한국어 글자 수를 고정 비율로 나눠 토큰 수라고 부르면 안 된다. 같은 길이라도 토크나이저와 문자 조합에 따라 다르고 실제 요청에는 코드·식별자·숫자·여러 언어가 섞인다.
로컬 토크나이저는 본문의 토큰만 계산하지만 최종 요청에는 지시문·역할·구분자가 더 들어간다. 제공자의 토큰 세기 기능을 쓰면 그 차이를 모델 기준으로 알 수 있다. 입력 토큰 세기 참조
토큰을 세는 호출도 데이터를 밖으로 보내는 요청이다. 읽을 권한이 없는 문서를 토큰 세기 API에 보내고 생성 직전에만 빼는 순서는 안 된다. 빠르게 확인할 때는 글자 수를 세는 함수를 대신 넣되, 그 값이 실제 토큰 수가 아니라는 표시를 결과에 함께 남긴다.
예산 규칙
모델의 문맥 한도를 , 출력에 남길 예산을 , 여유분을 , 최종 입력의 토큰 수를 라고 하면 구성기가 지키는 규칙은 하나다.
후보를 하나 더한 전체 요청의 토큰을 다시 센다. 개별 자료 길이를 더하기만 하면 구분자와 메타데이터가 빠지고, 이어 붙인 자리에서 토큰 경계가 달라지는 것도 놓친다. 큰 자료가 안 들어가도 뒤의 작은 자료까지 포기하지 않는다. 그 자료만 건너뛰고 다음을 시도한다. 다만 이 정책이 늘 최선은 아니다. 꼭 필요한 큰 문서가 빠지면 답을 못 한다. 건너뛰어도 되는 자료와 꼭 있어야 하는 자료는 평가와 작업별 필수 근거 규칙으로 가른다.
| 예산 구성 | 처리 | 잘못 처리했을 때 |
|---|---|---|
| 필수 지시문과 질문 | 먼저 세고 초과 시 중단 | 질문 조건을 잃는다 |
| 문서·이전 대화 | 허용 여부와 우선순위로 선택 | 불필요한 정보가 핵심 근거를 밀어낸다 |
| 출력 공간 | 입력 선택 전에 예약 | 입력은 들어가도 답변이 중간에 끊긴다 |
| 여유분 | 확인한 오차에 맞춰 설정 | 여유가 모자라거나 입력을 필요 이상으로 줄인다 |
구성기가 내놓는 계획에는 최종 입력, 고른 자료의 식별자, 예산 때문에 빠진 식별자, 입력 토큰 수, 출력 예약량, 세는 방법이 들어간다. 권한이 없어 빠진 식별자는 사용자에게 돌려주지 않는다. 그런 자료가 있다는 사실도 정보다. 계획을 그대로 생성 호출에 넘긴다. 한 번 세고 난 뒤에는 문서나 지시문을 덧붙이지 않는다. 마지막으로 센 요청과 실제로 보낸 요청이 글자까지 같은지 확인한다.
잘라내기와 요약의 손실
“교육비를 지원한다. 단, 구매 전 승인받은 경우에 한한다.” 뒷문장만 잘리면 적용 범위가 바뀐다.
구성기는 자료를 문장 중간에서 자르지 않고 블록 단위로 넣거나 뺀다. 블록 안의 뜻은 지키지만 큰 문서를 통째로 버릴 수 있다. 그래서 문서 구조를 따라 잘게 나누는 청킹이 필요하다. 요약은 모델이 만든 데이터라 원본보다 믿을 수 없다. 사용자가 “구매하지 말고 절차만”이라고 정정했는데 요약에 최초 요청만 남으면 잘못된 행동을 부른다. 요약에는 출처 범위·생성 시점·원본 리비전을 함께 두고 티켓 확인 여부나 인증된 사용자처럼 결정적인 상태는 서버 데이터로 둔다. 요약은 업무 데이터베이스를 대신하지 못한다.
핵심 근거가 예산 때문에 빠졌으면 모델이 일반 지식으로 그럴듯한 답을 만들어 내기를 기대하지 말고, 다시 검색하거나 질문 범위를 좁히거나 근거 부족으로 처리한다.
사용량과 비용
입력 토큰 , 그중 캐시로 처리된 입력 , 출력 토큰 , 백만 토큰당 단가를 각각 , , 라고 하면 단순 요금 모형은 다음과 같다.
캐시 입력은 전체 입력의 부분집합이라 따로 더하면 중복이다. 캐시 쓰기·도구 실행·저장처럼 별도 요금이 있으면 이 식만으로 청구액이 나오지 않는다. 입력 1,000, 캐시 400, 출력 200토큰에 가상 단가 2·0.5·8을 넣으면 다음과 같다.
일반 입력: (1000 - 400) × 2 / 1000000 = 0.0012
캐시 입력: 400 × 0.5 / 1000000 = 0.0002
출력: 200 × 8 / 1000000 = 0.0016
합계: 0.0030
단가는 가정이고 화폐 계산에는 부동소수점 대신 십진 자료형을 쓴다. 사용량이 하나라도 미상이면 비용도 미상으로 두고, 캐시 입력이 전체 입력보다 크거나 값이 음수면 데이터 오류로 본다. 비용 기록은 이미 쓴 금액을 남길 뿐이라, 한도를 넘길 요청을 미리 막지는 못한다. 여러 동시 요청의 금액을 미리 잡아 두는 중앙 예산 관리자는 아직 없다. 제품 지표로는 요청당 비용 외에 성공한 업무당 비용을 보되, 실패한 요청의 비용도 분자에 넣는다. 성공이 0건이면 계산 불가로 표시한다.
대화 상태와 캐시
이력 블록은 조직·사용자·세션이 모두 일치할 때만 허용한다. 사용자만 비교하면 같은 사람의 다른 대화가 섞이고 세션만 비교하면 다른 조직에서 우연히 같은 문자열을 쓴 대화가 섞인다. 요청 주체는 인증과 권한 조회를 거친 값이라는 전제로 만든다. HTTP 본문에 담겨 온 사용자 이름과 읽기 가능 문서 목록을 그대로 주체로 삼으면 이 검사가 있어도 접근 통제가 없는 것과 같다. 대화 상태 관리 안내
응답 캐시의 키에 질문 문자열만 쓰면 “내 신청 상태는?”에 다른 사용자의 결과가 나간다. 권한이 회수되면 그 문서로 만든 응답과 요약도 무효가 되어야 한다. 검색에서만 새 권한을 보고 오래된 캐시를 돌려주면 통제가 우회된다. 제공자의 프롬프트 캐시는 같은 접두사를 재사용하므로 안정된 부분을 앞에 두면 유리하지만, 적중 여부와 요금 효과는 사용량으로 확인한다. 프롬프트 캐시 안내
문맥 구성 순서
문맥 블록에는 식별자·조직·종류·텍스트·우선순위·필요한 접근 범위가 있다. 문서 블록은 허용된 문서 식별자에 속해야 하고, 이력 블록은 사용자와 세션이 일치해야 하고, 모르는 종류는 뺀다. 우선순위는 아직 호출자가 주는 정수다. 지금 구현에서는 정책 문서를 긴 대화보다 먼저 시도하고, 같은 순위는 식별자순이라 입력 순서가 바뀌어도 결과가 같다.
입력은 JSON으로 직렬화한다. 문서의 따옴표와 개행이 구조를 깨뜨리지 않게 하려는 장치다. 문서 안의 지시를 모델이 따르지 못하게 막는 기능은 아니다. JSON을 닫는 것처럼 보이는 문자열을 문서에 넣어 본다. 파싱한 결과에서 그 문자열이 문맥 텍스트 안에 그대로 남아 있는지, 최상위에 지시문 필드가 새로 생기지는 않았는지 확인한다.
문맥이 실패하는 자리
| 실패 유형 | 필요한 관측 | 다음 조치 |
|---|---|---|
| 후보 누락 | 검색 또는 자료 조회 결과 | 공급 단계 개선 |
| 권한 제외 | 제한된 내부 권한 진단 | 권한 정책 검토, 임의 우회 금지 |
| 예산 제외 | 허용된 후보와 제외 사유 | 청킹·선택 정책·예산 재검토 |
| 요약 손실 | 원본과 요약의 조건 비교 | 요약 규칙·상태 분리 |
| 생성 오류 | 실제로 전달한 문맥 | 모델·프롬프트·평가 개선 |
| 문맥 혼입 | 조직·사용자·세션 범위 | 조직·세션 범위 분리 |
문맥 길이를 늘리면 예산 제외는 줄지만 비용·지연·불필요한 정보가 늘어난다. 늘린 뒤에는 같은 질문 집합으로 다시 비교한다. 세는 횟수도 비용에 들어간다. 후보마다 세기 API를 호출하면 후보 수만큼 요청과 지연이 늘어서, 문서가 많아지면 로컬 추정으로 후보를 줄인 뒤 최종 요청만 세는 개선을 검토한다. 이때 추정치와 실제 토큰 수의 차이를 기록해야 그 개선의 위험이 보인다. 세기 요청이 실패하면 ‘아마 들어갈 것’이라고 보고 생성하지 않는다. 호출자에게 실패를 넘긴다.
정책 문서에는 소유 부서와 시행일, 티켓 결과에는 조회한 사용자와 시점, 대화 요약에는 원본 범위와 생성 시점이 함께 있어야 한다. 이 정보를 잃으면 문장만 남은 자료가 어디에 쓰일 수 있는지 판단할 수 없다. 문맥 저장소나 데이터 카탈로그를 들여도 요청 시점의 권한 검사는 따로 연결해야 한다. 이 규칙은 작은 데이터 구조 하나로도 지킬 수 있다. 필요가 커지면 데이터 수명 관리·출처 연결·접근 통제·조회 지연·운영 부담을 기준으로 도구를 고른다.
“그건 언제부터인가요?”는 봇이 문서 본문만 보고는 답할 수 없는 질문이었다. 개발팀이 시행일과 소유 부서를 문맥에 함께 넣고 나서야 봇이 그 날짜를 말했다.