레슨 2: 컨텍스트의 해부: 시스템 프롬프트, 도구, 예시
학습 목표:
- 한 번의 LLM 요청이 싣는 컨텍스트(시스템 프롬프트, 도구 정의, 예시, 메시지 히스토리)를 분해하고, 이 넷이 왜 같은 주의 예산을 쓰고 있는지 설명하기
- 시스템 프롬프트의 '고도' 문제를 진단하기 — 한쪽 끝의 하드코딩되고 부서지기 쉬운 형태와 다른 쪽 끝의 두루뭉술하고 신호가 없는 형태 — 그리고 알맞은 고도로 다시 쓰기
- 컨텍스트 비용의 렌즈로 도구 정의와 예시를 감사하기: 겹치는 기능은 합치고, 돌아오는 것은 덜어내고, 엣지 케이스 목록은 소수의 대표 예시로 갈아 끼우기
전제: 레슨 1을 마쳤고 컨텍스트가 유한한 자원이라는 전제를 받아들이고 있어야 합니다 | 이전: 레슨 1 << | 다음: 레슨 3 >>
보닛을 열어 봅시다: 한 번의 요청에 실제로 무엇이 실리는가
레슨 1에서는 Anthropic의 정의를 빌려 왔습니다. 컨텍스트 엔지니어링이란 "the set of strategies for curating and maintaining the optimal set of tokens (information) during LLM inference"(LLM 추론 중에 최적의 토큰(정보) 집합을 선별하고 유지하기 위한 전략들의 집합)1입니다. 아직 추상적입니다. '최적의 집합'은 실제로 어떤 토큰들로 이루어져 있을까요? 이 레슨이 하는 일은 하나입니다. 보닛을 열어, 요청이 나갈 때마다 윈도에 무엇이 실리는지 보여 드리는 것입니다.
'에이전트 하네스 기초: 루프와 통제'에서 하네스 루프를 손으로 직접 썼으니 요청의 생김새는 익숙할 것입니다. 담고 있는 것을 기준으로 분류하면, 한 번의 요청이 싣는 컨텍스트는 대략 다음 네 덩어리입니다.
여기서 중요한 것은 이것입니다. 이 넷은 서로 밀폐된 네 개의 주머니가 아니라, 하나의 윈도 안에서 이웃하고 있습니다. "LLMs have an "attention budget" that they draw on when parsing large volumes of context,"(LLM은 대량의 컨텍스트를 파싱할 때 끌어다 쓰는 '주의 예산'을 가지고 있다) 그리고 "Every new token introduced depletes this budget by some amount"(새로 들어오는 토큰은 하나하나 이 예산을 얼마간 고갈시킨다)1. 시스템 프롬프트에 군더더기 한 단락이 더 있으면 그만큼이 메시지 히스토리가 받지 못한 주의입니다. 아무도 부르지 않는 도구 열 개가 목록에 앉아 있으면 예시가 받을 몫이 그만큼 묽어집니다. 그리고 레슨 1에서 다뤘듯 "as the number of tokens in the context window increases, the model's ability to accurately recall information from that context decreases"(컨텍스트 윈도 안의 토큰 수가 늘어날수록, 그 컨텍스트에서 정보를 정확히 회상하는 모델의 능력은 떨어진다)1. 이 저하는 추락이 아니라 비탈로 찾아옵니다. "These factors create a performance gradient rather than a hard cliff"(이 요인들은 단단한 절벽이 아니라 성능의 경사를 만든다)1. 그래서 어느 한 덩어리의 낭비도 결코 에러로 자기를 알리지 않습니다. 전체를 조용히 조금 더 멍청하게 만들 뿐이고, 알아챘을 즈음에는 대개 어느 줄이 범인인지 짚을 수 없습니다.
네 덩어리는 자라는 속도도 다릅니다. 시스템 프롬프트, 도구 정의, 예시는 기본적으로 정적입니다. 쓴 만큼의 길이가 그대로 유지됩니다. 반면 메시지 히스토리는 루프 안에서 부풀어 오릅니다. "An agent running in a loop generates more and more data that could be relevant for the next turn of inference"(루프 안에서 도는 에이전트는 다음 추론 턴에 관련이 있을 수 있는 데이터를 점점 더 많이 생성한다)1기 때문입니다. '에이전트 하네스 기초: 루프와 통제'에서 만든 하네스에서, 매 턴의 도구 결과를 messages 배열에 밀어 넣는 그 한 줄이 바로 그 부풀어 오름의 현장입니다. 히스토리를 어떻게 할지는 이 레슨의 일이 아닙니다. 레슨 4 '컴팩션과 노트: 긴 작업을 위한 컨텍스트 관리'가 그것만을 위해 있습니다. 이 레슨은 정적인 세 덩어리를 통제 아래 둡니다. 그것들이야말로 매 턴 빠짐없이 치르는 고정 비용이기 때문입니다.
"context, therefore, must be treated as a finite resource with diminishing marginal returns"(따라서 컨텍스트는 한계 수익이 체감하는 유한한 자원으로 다뤄져야 한다)1를 실행 가능한 것으로 바꾸려면, 덩어리마다 같은 질문을 던져야 합니다. 이 토큰들은 거동의 개선을 얼마나 사 왔는가? 덩어리별로 살펴봅시다.
시스템 프롬프트의 고도: 실패하는 두 가지 방식
Anthropic의 엔지니어링 글은 시스템 프롬프트가 놓인 추상화 수준을 가리켜 '고도'(altitude)라고 부르며, 두 개의 실패 극단을 지목합니다. 한쪽 끝은 "engineers hardcoding complex, brittle logic in their prompts to elicit exact agentic behavior"(정확한 에이전트 거동을 끌어내려고 복잡하고 부서지기 쉬운 로직을 프롬프트에 하드코딩하는 엔지니어들)1입니다. 다른 쪽 끝은 "vague, high-level guidance that fails to give the LLM concrete signals for desired outputs"(원하는 출력에 대한 구체적인 신호를 LLM에 주지 못하는, 두루뭉술하고 고차원적인 지침)1입니다. 같은 주문 지원 에이전트를 두고 시스템 프롬프트를 세 가지 버전으로 써서, 두 함정과 좋은 답을 나란히 놓아 보겠습니다.
너무 낮게 나는 경우 — 로직이 하드코딩됩니다.
규칙 하나하나는 딱 자기가 적어 놓은 경우만 다룹니다. 사용자가 '파손'이 아니라 '상자가 찌그러졌어요'라고 쓰면 어떻게 될까요? 걸리는 규칙이 없으니 규칙 20으로 떨어져 모르는 척을 합니다. 더 나쁜 것은 규칙들이 서로 싸우기 시작한다는 점입니다. 그래서 심판을 볼 규칙 19를 추가하게 되는데, 그 지점에서 여러분은 자연어로 if-else 인터프리터를 쓰고 있는 셈입니다. 하나 추가할 때마다 프롬프트는 길어지고, 더 부서지기 쉬워지고, 주의 면에서 더 비싸집니다. 그러는 동안 적어 두지 않은 경우의 수는 언제나 적어 둔 것보다 많습니다.
너무 높이 떠 있는 경우 — 구호뿐입니다.
토큰은 아꼈지만 모델은 구체적인 신호를 하나도 받지 못합니다. 환불의 경계는 어디입니까? 보상은 얼마까지 승인할 수 있습니까? 무엇을 사람에게 넘겨야 합니까? 전부 추측입니다. '사용자를 만족시키라'를 극단까지 밀면 결코 해 주면 안 될 환불을 해 주는 것도 될 수 있습니다. 그것은 불복종이 아니라, 여러분이 아무 말도 해 주지 않은 결과입니다.
알맞은 고도 — Anthropic의 기준은 "specific enough to guide behavior effectively, yet flexible enough to provide the model with strong heuristics"(거동을 효과적으로 이끌 만큼 구체적이면서, 모델에 강력한 휴리스틱을 제공할 만큼 유연한)1 프롬프트입니다.
이 버전의 구조를 보십시오. 원칙이 일반화를 맡고, 레드라인이 준수를 맡습니다. '상자가 찌그러졌다'는 어느 규칙에도 그대로 적혀 있지 않지만 '파손은 우리 잘못'에 자연스럽게 안착합니다. 정말로 타협할 수 없는 것(보상 상한, 법적 분쟁)은 몇 개의 절대 한계로 못 박혀 있습니다. 규칙 20개짜리 버전의 절반도 안 되는 길이로 엄밀히 더 넓은 범위를 덮습니다.
고도를 판정하는 지름길 질문이 하나 있습니다. 적어 두지 않은 경우를 마주했을 때, 이 프롬프트는 모델에게 추론을 시작할 방향을 주는가? 낮은 버전은 주지 못하고(떨어지는 법만 압니다), 높은 버전은 텅 빈 방향을 주며('만족'), 알맞은 고도의 버전은 전이되는 원칙을 줍니다.
항상 로드되는 계층: CLAUDE.md 규율
시스템 프롬프트는 여러분이 타이핑한 문자열만이 아닙니다. 많은 하네스가 프로젝트 수준의 설정을 시스템 계층에 영구히 세워 두는데, Claude Code의 CLAUDE.md가 대표적인 사례입니다. 문서는 이렇게 말합니다. "CLAUDE.md is a special file that Claude reads at the start of every conversation."(CLAUDE.md는 Claude가 모든 대화의 시작에 읽는 특별한 파일이다)2 '매번 읽는다'는 것은 그 안의 모든 줄이 모든 세션에서 주의 예산을 쓴다는 뜻이고, 그래서 문서가 이 파일에 부과하는 규율은 엄격합니다.
첫 번째 규칙: 넓게 적용되는 것만 넣으십시오. "CLAUDE.md is loaded every session, so only include things that apply broadly."(CLAUDE.md는 매 세션 로드되므로, 넓게 적용되는 것만 포함하라)2 하위 디렉터리 한 곳에만 필요한 빌드 명령, 한 종류의 작업에만 필요한 관례 — 어느 쪽도 항상 로드되는 계층에 자리를 얻을 자격이 없습니다.
두 번째 규칙: 한 줄씩 삭제 테스트를 돌리십시오. "Keep it concise. For each line, ask: "Would removing this cause Claude to make mistakes?" If not, cut it."(간결하게 유지하라. 각 줄마다 물어보라. '이것을 지우면 Claude가 실수를 하게 되는가?' 아니라면 잘라내라)2 이것은 결벽증이 아닙니다. 문서는 대놓고 경고합니다. "Bloated CLAUDE.md files cause Claude to ignore your actual instructions!"(비대해진 CLAUDE.md 파일은 Claude가 여러분의 실제 지시를 무시하게 만든다)2 이것이 주의 예산을 눈에 보이게 만든 것입니다. 넣어도 그만인 줄을 하나 우겨 넣을 때마다, 정말로 중요한 몇 줄이 묽어집니다. 같은 페이지는 컨텍스트 윈도를 아예 "the most important resource to manage,"(관리해야 할 가장 중요한 자원)라고 부르며 "Claude's context window fills up fast, and performance degrades as it fills."(Claude의 컨텍스트 윈도는 빠르게 차오르고, 차오르는 만큼 성능은 저하된다)라고 덧붙입니다2.
그러면 거의 필요 없지만 가끔은 없어서는 안 되는 자료는 어디에 두어야 할까요? Claude Code의 답은 스킬입니다. 필요할 때 로드하는 것입니다. "Claude loads them on demand without bloating every conversation."(Claude는 모든 대화를 비대하게 만들지 않으면서 필요할 때 그것들을 로드한다)2 상주 계층은 최소로 유지하고 나머지는 필요할 때 가져온다는 이 짝이 바로 레슨 3의 주제이니, 여기서는 예약만 해 두겠습니다.
한 가지 곁들이자면, 여러분의 프로젝트에 AGENTS.md나 system_prompt.txt 같은 것이 컨텍스트에 영구히 앉아 있다면 거기에도 똑같은 삭제 테스트를 돌리십시오. 항상 로드되는 계층의 비대함은 가장 교활한 종류입니다. 대화의 어느 한 턴에도 모습을 드러내지 않으면서, 모든 턴에 세금을 매깁니다.
도구도 컨텍스트입니다: 정의도 비용이고, 반환값도 비용입니다
도구는 컨텍스트에 두 번 등장합니다. 정의는 모든 요청에 함께 실려 가고, 반환값은 메시지 히스토리에 들어옵니다. 양쪽 끝이 모두 예산을 씁니다.
앞선 코스 '에이전트 도구 호출: 에이전트가 실제로 일하게 만들기'는 도구 설계의 기능적인 측면 — 파라미터를 어떻게 정의할지, 에러를 어떻게 다룰지 — 를 다뤘습니다. 이 레슨은 각도가 다릅니다. 도구 정의 하나하나는 모델이 읽고 이해해야 하는 토큰의 구간입니다. Anthropic의 기준은 "tools should be self-contained, robust to error, and extremely clear with respect to their intended use"(도구는 자족적이고, 에러에 견고하며, 의도된 용도에 대해 극도로 명확해야 한다)1이며, "building tools that are well understood by LLMs and have minimal overlap in functionality"(LLM이 잘 이해할 수 있고 기능의 겹침이 최소인 도구를 만드는 것)1입니다. 아래는 둘 다 실패하는 한 쌍입니다(가독성을 위해 스키마는 줄였습니다).
두 도구 모두 주문을 조회할 수 있고, 두 설명 모두 두루뭉술하며, 둘 사이의 경계는 작성자조차 못 박아 두지 않았습니다. 이것이 바로 "minimal overlap in functionality"(기능의 겹침이 최소)1 요구가 존재하는 이유입니다. 경계가 흐릿한 도구를 쌓아 두면 '어느 것을 써야 하지?'라는 질문을 매 턴 새로 모델에게 떠넘기는 셈입니다. 하나로 합치고 목적과 거동을 명확히 밝히면 그 질문은 사라집니다.
이 설명은 '무엇을 하는가'보다 많은 것을 말합니다. 돌아오는 것의 모양과, 돌려줄 것이 없을 때 어떻게 되는지까지 밝히고 있습니다. 그것이 "robust to error"(에러에 견고한)1를 문자 그대로 구현한 것입니다. 모델은 빗나감이 어떤 모습인지 추측할 필요가 없으므로, 조회가 비어서 돌아왔을 때 이상한 복구 동작을 지어낼 확률이 훨씬 낮아집니다.
이제 반환값 쪽입니다. Anthropic은 도구가 "returning information that is token efficient and by encouraging efficient agent behaviors"(토큰 효율적인 정보를 반환하고 효율적인 에이전트 거동을 장려하는 것)1를 요구합니다. 주문의 내부 필드 40여 개와 전체 감사 로그를 통째로 게워 내는 도구는 호출될 때마다 가치가 낮은 토큰을 메시지 히스토리에 한 바가지 들이붓습니다. 그리고 그 토큰들은 히스토리에 남아 이후의 모든 턴에서 다시 과세됩니다. 위 예시처럼 '기본은 덜어내고, 필요할 때 detail=true'가 이 문제의 표준적인 처방 모양입니다.
마지막으로 도구 목록 자체에도 뺄셈이 필요합니다. 한 번도 호출되지 않는 도구 열 개도 정의 값은 매 턴 전액 청구됩니다. 도구 목록을 감사하는 것과 CLAUDE.md를 감사하는 것은 같은 동작입니다. 지우면 실수가 생기는가? 아니라면 잘라내십시오.
예시: 대표적인 것을 고르고, 목록을 쌓지 마십시오
예시(few-shot)는 세 번째 정적 비용입니다. 흔한 부패 경로는 이렇습니다. 운영에서 나온 배드케이스마다 대응하는 예시를 프롬프트에 하나씩 추가하고, 반년 뒤에는 30항목짜리 엣지 케이스 카탈로그를 소유하게 됩니다. Anthropic은 이에 대해 단도직입적입니다. "stuff a laundry list of edge cases into a prompt"(엣지 케이스를 빨랫줄처럼 늘어놓아 프롬프트에 우겨 넣기)1를 하지 말고, 대신 "curate a set of diverse, canonical examples that effectively portray the expected behavior of the agent"(에이전트의 기대되는 거동을 효과적으로 그려 보이는, 다양하고 대표적인 예시의 집합을 선별)하라는 것입니다1.
'대표적'이라는 말은 예시 하나가 특정한 상황 하나가 아니라 거동의 부류를 대표한다는 뜻입니다. 지원 에이전트로 돌아가 보면, 예시 세 개로 거동 공간 전체를 짜 넣을 수 있습니다.
- 표준 흐름: 주문을 조회하고, 과실을 가리고, 해결책을 제시하는 전체 여정.
- 먼저 책임지기: 우리가 물건을 잘못 보냈고, 사용자가 요구하기 전에 보상을 제안하는 경우.
- 에스컬레이션: 에이전트의 권한을 넘어서므로 정중히 설명하고 사람에게 넘기는 경우.
'다양하다'는 것은 이 셋이 같은 거동의 세 가지 변주가 아니라 판단의 서로 다른 분기를 덮는다는 뜻입니다.
그러면 엣지 케이스는 어디로 가야 할까요? 대부분은 시스템 프롬프트의 원칙 계층으로 승격되어야 합니다. '사용자가 폭언을 하면 어떻게 하는가'에 300토큰짜리 전체 대화 예시는 필요 없습니다. 원칙 한 줄 — '사용자가 격앙되어 있을 때는 절제를 유지하고 초점을 문제 해결에 둔다' — 이면 충분합니다. 예시는 기대되는 거동이 어떤 모습인지를 가르치고, 원칙은 새로운 것이 나타났을 때 어느 방향으로 추론할지를 가르칩니다. 이미 눈치채셨을지도 모릅니다. 예시 목록에 엣지 케이스를 추가하는 것과 시스템 프롬프트에 분기를 추가하는 것은 같은 동전의 양면입니다. 둘 다 낮은 고도에서 땜질하는 것이고, 원칙 계층으로 올라가야만 구멍이 실제로 메워집니다.
덩어리별 체크리스트: 해부를 실무로 옮기기
이 레슨을 실행 가능한 것으로 압축해 보겠습니다. 컨텍스트에 무언가를 추가하기 전에 해당하는 질문을 돌리십시오.
표 말고도 이 레슨에서 가지고 나갈 원칙이 하나 더 있습니다. Anthropic은 에이전트 시스템을 구축하는 글에서 분수에 관한 감각을 제시합니다. "you should consider adding complexity only when it demonstrably improves outcomes."(복잡성은 그것이 결과를 입증 가능하게 개선할 때에만 더하는 것을 고려해야 한다)3 원문의 맥락은 시스템 아키텍처이지만, 컨텍스트의 모든 덩어리에도 똑같이 적용됩니다. 도구 하나 더, 규칙 하나 더, 예시 하나 더는 전부 더해진 복잡성입니다. 그것이 거동의 개선을 사 온다는 것을 입증하게 한 다음에 태우십시오.
여기까지가 정적인 세 덩어리를 한 바퀴 돈 것입니다. 그런데 기다리고 있는 질문이 하나 더 있습니다. 어떤 정보는 애초에 미리 윈도에 실릴 필요가 없습니다. 모델이 무엇을 필요로 할지 추측하는 대신, 에이전트가 실행 시점에 직접 찾아가게 하는 것입니다. 다음 레슨의 주제가 바로 그것입니다.
정리
- 한 번의 요청이 싣는 컨텍스트는 네 덩어리다 — 시스템 프롬프트, 도구 정의, 예시, 메시지 히스토리. 이들은 하나의 주의 예산을 나눠 쓰며, 새로 들어오는 토큰은 하나하나 그 예산을 얼마간 고갈시킨다1.
- 컨텍스트는 한계 수익이 체감하는 유한한 자원이다1. 덩어리마다 '이 토큰들이 무엇을 사 왔는가'를 묻는 편이, 두루뭉술하게 '프롬프트를 손보는' 것보다 훨씬 실행 가능하다.
- 시스템 프롬프트에는 두 개의 실패 극단이 있다 — 하드코딩된 부서지기 쉬운 분기, 그리고 신호를 싣지 않은 두루뭉술한 구호. 알맞은 고도란 거동을 이끌 만큼 구체적이면서 모델에게 강력한 휴리스틱을 남길 만큼 유연한 것이고1, '원칙 더하기 절대 한계'가 거기에 착지하는 실용적인 구조다.
- CLAUDE.md 같은 항상 로드되는 내용은 모든 대화의 시작에 읽힌다. 넓게 적용되는 것만 넣고 줄마다 삭제 테스트를 돌려야 한다. 비대해진 파일은 모델이 실제 지시를 무시하게 만들기 때문이다2.
- 도구는 정의와 반환값이라는 양 끝에서 예산을 쓴다. 의도된 용도를 극도로 명확하게, 겹침은 최소로, 반환값은 토큰 효율적으로 유지한다1. 한 번도 호출되지 않는 도구도 매 턴 전액이 청구된다.
- 엣지 케이스를 빨랫줄처럼 우겨 넣는 대신, 다양하고 대표적인 예시를 소수만 선별한다1. 엣지 케이스 대부분은 원칙 계층으로 돌아가야 할 것들이다.
- 컨텍스트에 어떤 복잡성이든 더하기 전에 Anthropic의 분수 감각을 떠올린다. 그것이 결과를 입증 가능하게 개선할 때에만 더한다3.
>> 레슨 3: 적시 검색: 에이전트가 스스로 컨텍스트를 가져오게 하기