-
프레임워크 종속 없는 경량 개인 비서 에이전트: TypeScript·Bun 헥사고날 아키텍처와 결정론적 하네스 구현 가이드
AI 2026. 10. 7. 20:12반응형프레임워크 종속 없는 경량 개인 비서 에이전트: TypeScript·Bun 헥사고날 아키텍처와 결정론적 하네스 구현 가이드
핵심 요약: LangChain, LlamaIndex, CrewAI 등 시중의 고수준 AI 프레임워크는 빠른 데모 제작에는 유리하지만, 프로덕션 환경에서는 거대한 블랙박스 프롬프트, 런타임 오버헤드, 벤더 종속성이라는 치명적인 기술 부채를 남깁니다. 진정으로 신뢰할 수 있고 오래 지속되는 자율 비서 에이전트를 구축하기 위해서는 "외부 라이브러리 종속을 최소화하고, 도메인 코어와 외부 인프라를 엄격히 분리하는 헥사고날(Ports & Adapters) 아키텍처"가 필수적입니다.
본 글에서는 고성능 자바스크립트 런타임인 Bun과 TypeScript를 기반으로, 외부 프레임워크 없이 바닥부터 구현하는 개인 비서 에이전트의 전체 설계 청사진을 다룹니다. 결정론적 컨텍스트 예산 산출(Context Budgeting), 인간 개입을 보장하는 유한 상태 머신(FSM)과 안전 정책 엔진, LLM SDK와 HTTP Fetch 간 하이브리드 포트 설계, 그리고 1주 만에 검증 가능한 MVP 로드맵까지 실전 엔지니어링 관점에서 상세히 분석합니다.
1. 프롤로그: 왜 고수준 프레임워크를 걷어내야 하는가?
최근 AI 에이전트 개발 씬을 지배하는 가장 큰 착각 중 하나는 "에이전트를 만들려면 LangChain이나 LlamaIndex 같은 전문 프레임워크가 필수적이다"라는 생각입니다. 수많은 튜토리얼이 프레임워크의 고수준 추상화 클래스를 상속받아 단 10줄 만에 에이전트를 조립하는 마법을 보여줍니다.
하지만 토이 프로젝트를 벗어나 실제 일상 업무를 보조하는 개인 비서 에이전트를 운영하기 시작하면, 이러한 고수준 프레임워크는 심각한 병목으로 돌변합니다.
[ 기존 고수준 프레임워크 접근법 ] 사용자 코드 -> 거대한 프레임워크 추상화 (수십 개의 숨겨진 프롬프트 & 래퍼) -> 블랙박스 에이전트 루프 -> 벤더 종속 SDK ❌ 문제점: 디버깅 불가, 런타임 메모리 팽창, 예기치 않은 프롬프트 변조, 프레임워크 버전 업그레이드 시 파괴적 변경 [ 경량 헥사고날 아키텍처 접근법 ] 사용자 CLI -> [Agent Core (순수 도메인 & 제어 루프)] --(포트 인터페이스)--> [어댑터 (Bun Native / MCP / LLM)] ✅ 장점: 0개의 프레임워크 종속, 100% 투명한 프롬프트 및 토큰 제어, 밀리초 단위 기동 속도, 손쉬운 인메모리 테스트고수준 프레임워크가 프로덕션에서 야기하는 3대 고통
- 숨겨진 프롬프트와 불투명한 컨텍스트 제어:
프레임워크 내부 깊숙한 곳에서 자체 정의된 기본 시스템 프롬프트와 툴 호출 템플릿이 암묵적으로 주입됩니다. 이로 인해 모델이 왜 엉뚱한 결정을 내렸는지 역추적하기 어렵고, 토큰 최적화가 사실상 불가능해집니다. - 비대한 의존성과 런타임 오버헤드:
수백 개의 서브 패키지와 트랜스파일러, 헬퍼 라이브러리가 얽혀 패키지 용량이 수백 메가바이트로 불어납니다. 콜드 스타트 지연이 발생하고 패키지 간 버전 충돌(Dependency Hell)이 일상화됩니다. - 취약한 장기 진화 가능성:
AI 생태계는 주 단위로 모델 API와 프로토콜(예: Anthropic Tools, OpenAI Structured Outputs, MCP 등)이 진화합니다. 무거운 프레임워크에 코어 비즈니스 로직이 강하게 결합되어 있으면, 프레임워크 메인테이너가 새 표준을 어댑팅해줄 때까지 손을 놓고 기다려야 합니다.
"에이전트의 본질은 복잡한 프레임워크가 아니라, 무상태(Stateless) LLM을 둘러싼 결정론적 제어 루프(Deterministic Control Loop)에 불과합니다."
외부 프레임워크를 걷어내고, 웹 표준과 견고한 소프트웨어 아키텍처 패턴으로 시스템을 직접 조립할 때 비로소 진정한 통제권과 신뢰성을 확보할 수 있습니다.
2. 왜 TypeScript와 Bun인가?
우리는 런타임 환경으로 전통적인 Node.js나 Python 대신 Bun + TypeScript 조합을 채택합니다.
비교 항목 Python (Poetry/venv) Node.js (ts-node / build) Bun (Native TS) TypeScript 실행 미지원 (타입 힌트 수준) 빌드 스텝 또는 트랜스파일 필요 별도 빌드 없이 .ts즉시 네이티브 실행기동 속도 (Startup) ~150ms - 300ms ~200ms - 400ms ~10ms 미만 (경량 CLI 비서에 최적) 내장 SQLite 지원 sqlite3(C 바인딩)외부 패키지 설치 필요 bun:sqlite네이티브 내장 (고속 로컬 메모리)Web 표준 Fetch requests,httpx의존버전별 상이 (Undici) Web Standard Fetch 완벽 내장 (스트리밍 지원) 패키지 설치 속도 느림 보통 초고속 (npm 대비 최대 30배 빠름) 개인 비서 에이전트는 터미널에서 즉각 호출되어 할 일을 정리하고 메모를 남기는 CLI 중심의 대화형 도구로 동작하는 경우가 많습니다. Bun의 제로 오버헤드 기동 속도, 내장 SQLite, 네이티브 TypeScript 지원은 외부 도구 의존성을 극도로 줄이면서도 뛰어난 개발자 경험을 선사합니다.
3. 핵심 아키텍처: 의존성 역전과 헥사고날(Ports & Adapters)
소프트웨어 엔지니어링에서 시간이 지나도 바꾸기 가장 어려운 것은 기술 스택 이름이 아니라 의존성의 방향(Dependency Direction)입니다.
에이전트의 두뇌 역할을 하는 비즈니스 로직(목표 분해, 계획 수립, 컨텍스트 구성)이 특정 LLM SDK나 특정 데이터베이스, 특정 CLI 라이브러리에 직접 의존하면 백엔드를 교체할 때마다 에이전트의 핵심 코드를 전면 재작성해야 합니다.
이를 방지하기 위해 헥사고날(Ports & Adapters) 아키텍처를 적용합니다.
3.1 헥사고날 아키텍처 구성도
🔍 클릭하여 확대flowchart LR User["사용자 (CLI / 터미널)"] subgraph Core["Agent Core (Domain & UseCase)"] Agent["에이전트 루프 (Agent Loop)"] ContextManager["컨텍스트 매니저 (Context Manager)"] Planner["플래너 (Planner)"] Policy["안전 정책 엔진 (Safety Policy)"] end subgraph Ports["포트 인터페이스 (Ports)"] ContextPort["ContextPort"] LLMPort["LLMPort"] ToolPort["ToolPort"] MemoryPort["MemoryPort"] EventPort["EventPort"] end subgraph Adapters["어댑터 구현체 (Adapters)"] CompressionAdapter["컨텍스트 압축 어댑터"] LLMAdapterSDK["LLM SDK 어댑터 (OpenAI/Anthropic)"] LLMAdapterFetch["LLM HTTP Fetch 어댑터 (표준 REST)"] MCPAdapter["MCP 도구 어댑터 (Model Context Protocol)"] LocalToolAdapter["로컬 도구 어댑터 (File/Shell)"] MemoryAdapterSQLite["SQLite 메모리 어댑터 (bun:sqlite)"] MemoryAdapterFile["파일 메모리 어댑터 (Markdown/JSON)"] EventBus["인메모리 이벤트 버스"] end User --> Agent Agent --> ContextManager ContextManager --> ContextPort ContextPort --> CompressionAdapter ContextManager --> Planner Agent --> Planner Agent --> Policy Agent --> LLMPort Agent --> ToolPort Agent --> MemoryPort Agent --> EventPort LLMPort --> LLMAdapterSDK LLMPort --> LLMAdapterFetch ToolPort --> MCPAdapter ToolPort --> LocalToolAdapter MemoryPort --> MemoryAdapterSQLite MemoryPort --> MemoryAdapterFile EventPort --> EventBus3.2 아키텍처 계층 원칙
- Agent Core의 순수성 (Pure Core):
src/core/하위의 도메인 모델과 유스케이스는 외부 라이브러리 타입을 일절 import하지 않습니다. 심지어 OpenAI나 Anthropic의 공식 SDK 타입도 직접 참조하지 않고, 우리가 정의한LLMPort인터페이스의 요청/응답 타입만을 사용합니다. - 포트(Port) 뒤로 모든 I/O 은닉:
- LLM 추론:LLMPort
- 도구 실행:ToolPort
- 기억 저장 및 조회:MemoryPort
- 컨텍스트 압축:ContextPort
- 관측 및 로깅:EventPort - 가벼운 DDD(Domain-Driven Design)와 선택적 이벤트:
초기 MVP 단계에서 엔티티-애그리게이트-리포지토리 풀세트 DDD는 과설계(Over-engineering)입니다. 용어의 명확한 정의(유비쿼터스 언어)와 경계(Bounded Context)만 채택합니다. 이벤트 역시 전면 Event-Driven 대신,TaskPlanned,ToolExecuted,MemoryUpdated와 같은 내부 관측 지점에만 선택적으로 적용합니다.
4. LLM 통신 전략: SDK vs 직접 HTTP Fetch의 하이브리드 어댑터
에이전트를 만들 때 맞닥뜨리는 대표적인 고민은 "공식 SDK를 쓸 것인가, 직접 fetch로 호출할 것인가?"입니다.
SDK vs 직접 HTTP 호출 비교
- 공식 SDK (
@anthropic-ai/sdk,openai등): - 장점: 빠른 개발 속도, 타입 정의 제공, 자동 재시도 및 스트리밍 헬퍼 지원.
- 단점: 벤더 종속 심화, 내부 블랙박스 동작, Bun 환경에서의 미세한 호환성 이슈 발생 가능성.
- 직접 HTTP Fetch (
fetch()): - 장점: 외부 의존성 제로, 요청/응답 페이로드의 완전한 로깅 및 검증, 재시도/타임아웃의 세밀한 제어, 벤더 교체 용이.
- 단점: SSE(Server-Sent Events) 스트리밍 파싱 및 툴콜 직렬화를 직접 구현해야 하는 초기 개발 비용.
실전 솔루션: 런타임 하이브리드 주입
우리는 이 둘을 이분법적으로 선택하지 않습니다.
LLMPort인터페이스를 단단히 정의해 두고, 환경 변수(LLM_BACKEND=sdk|http)에 따라 어댑터 구현체를 동적으로 갈아끼우도록 설계합니다.// src/llm/port.ts export interface LLMMessage { role: 'system' | 'user' | 'assistant' | 'tool'; content: string; toolCallId?: string; toolCalls?: Array<{ id: string; name: string; arguments: Record<string, unknown>; }>; } export interface LLMResponse { content: string; toolCalls?: Array<{ id: string; name: string; arguments: Record<string, unknown>; }>; usage: { promptTokens: number; completionTokens: number; totalTokens: number; }; } export interface LLMPort { chat(messages: LLMMessage[], tools?: ToolDefinition[]): Promise<LLMResponse>; }초기 개발과 기능 검증은 SDK 어댑터로 몇 시간 만에 끝내고, 운영 안정성과 완전한 제어권이 필요한 시점에는 Core를 단 한 줄도 건드리지 않고 HTTP 어댑터로 전환할 수 있습니다.
5. 컨텍스트 엔지니어링: 결정론적 예산 할당(Context Budgeting)
멀티턴 대화가 진행되면 컨텍스트 윈도가 급격히 고갈됩니다. 단순 FIFO(선입선출) 슬라이딩 윈도로 이전 대화를 잘라내면, 초반에 합의된 시스템 지침이나 미완료 TODO가 소실됩니다.
이를 방지하기 위해 사용자 입력을 수신하자마자 수학적 공식에 기반한 가용 예산(Context Budgeting)을 산출하고 압축을 실행합니다.
5.1 컨텍스트 예산 산출 공식
$$\text{입력 가용 예산} = \text{total\_budget} - (\text{system\_budget} + \text{tool\_schema\_budget} + \text{response\_reserved})$$
파라미터 기본 권장값 역할 및 튜닝 가이드 total_budget모델 스펙 참조 단일 요청에 투입할 최대 컨텍스트 토큰 상한선 system_budget고정 예약 시스템 페르소나, 안전 규칙, 장기 사용자 선호 (압축/삭제 불가) tool_schema_budget15%등록된 도구(File, Bash, MCP) JSON 스키마 주입 공간 response_reserved20%모델의 최종 출력 및 도구 호출 인자 생성을 위한 버퍼 N8최근 대화 중 원문 그대로 보존할 최신 턴 수 compression_target_ratio0.35이전 대화 압축 시 목표 축소 비율 (원문의 35%로 압축) recall_top_k5로컬 벡터/키워드 검색을 통해 동적으로 재주입할 장기 기억 개수 max_recompression_depth2요약본의 재요약 허용 최대 깊이 (정보 왜곡 및 드리프트 방지) 5.2 구조화 압축의 3대 품질 보존 원칙
fact / decision / todo삼분법 요약:
이전 대화를 자유 서술형 줄글로 요약하면 핵심 제약이 희석됩니다. 반드시 객관적 사실(fact), 확정된 결정사항(decision), 아직 실행되지 않은 작업(todo)의 3개 구조화 필드로 나누어 보존합니다.- 원문 포인터 유지 (Provenance Pointer):
압축 요약 노드마다 원본 대화 턴의 고유 ID(turn_id)를 메타데이터로 남겨두어, 필요할 경우 에이전트가 원문을 다시 역추적할 수 있게 합니다. - 체크섬 기반 요약 캐시 (Checksum Caching):
이미 요약된 과거 대화 구간은 SHA-256 체크섬을 매겨 캐싱합니다. 매 턴마다 불필요하게 LLM을 호출하여 과거 대화를 중복 요약하는 비용 낭비를 차단합니다.
6. 에이전트 루프: 결정론적 유한 상태 머신(FSM)과 안전 정책 엔진
에이전트의 실행 루프를 단순한
while(true)문에 맡겨두면 루프 탈출 조건이 모호해지고 예외 상황에서 먹통이 되기 쉽습니다. 에이전트는 명확한 상태 머신(Finite State Machine)에 따라 전이해야 합니다.6.1 단일 턴 처리 상태 전이도
🔍 클릭하여 확대stateDiagram-v2 [*] --> IDLE: 대기 상태 IDLE --> CONTEXT_BUILD: 사용자 입력 수신 CONTEXT_BUILD --> PLANNING: 컨텍스트 예산 계산 및 구조화 압축 완료 PLANNING --> POLICY_CHECK: 도구 실행 계획 수립됨 PLANNING --> RESPONSE_READY: 도구 불필요 (직접 답변 가능) POLICY_CHECK --> AWAIT_APPROVAL: 위험 도구 실행 (사용자 승인 필수) POLICY_CHECK --> TOOL_EXEC: 안전 도구 실행 (자율 실행) POLICY_CHECK --> RESPONSE_READY: 정책 위반 거절 (대안 제시) AWAIT_APPROVAL --> TOOL_EXEC: 사용자가 실행을 승인함 AWAIT_APPROVAL --> RESPONSE_READY: 사용자가 거절함 (계획 수정) TOOL_EXEC --> REPLAN: 도구 실행 결과 관측 REPLAN --> PLANNING: 후속 도구 실행 필요 REPLAN --> RESPONSE_READY: 모든 실행 완료, 답변 준비 RESPONSE_READY --> MEMORY_UPDATE: 최종 텍스트 응답 생성 MEMORY_UPDATE --> DONE: 상태 및 메모리 영속화 완료 DONE --> IDLE: 단일 목표 달성 완료 DONE --> WAIT_INPUT: 멀티턴 추가 대화 대기 WAIT_INPUT --> CONTEXT_BUILD: 사용자 추가 입력 도착6.2 안전 우선(Human-in-the-loop) 정책 엔진
개인 비서 에이전트는 로컬 파일 시스템과 셸 명령에 접근할 수 있으므로, 권한 격리가 허술하면 시스템에 치명적인 손상을 입힐 수 있습니다.
- Tier 1 (Read-Only):
read_file,list_directory,search_notes→ 자율 실행 허용 - Tier 2 (Write-Safe):
write_note,append_todo,create_draft→ 격리된 작업 디렉터리 내 자율 실행 - Tier 3 (Destructive / Execution):
delete_file,git push, 임의의run_command→AWAIT_APPROVAL상태로 전이하여 터미널에서 사용자 확인(Y/N) 강제
6.3 도구 및 MCP 프로토콜 실행 시퀀스
외부 확장 도구는 오픈 표준인 MCP(Model Context Protocol)를 통해 연결됩니다.
Agent Core는 도구가 로컬 내장 함수인지 외부 MCP 서버인지 알 필요 없이, 동일한ToolPort규격을 통해 투명하게 상호작용합니다.🔍 클릭하여 확대sequenceDiagram participant U as 사용자 (User) participant A as 에이전트 루프 (Agent Loop) participant P as 정책 엔진 (Policy) participant T as ToolPort participant M as MCP Adapter participant S as 외부 MCP Server participant R as MemoryPort U->>A: "내일 발표 자료 초안을 노션에 작성해줘" A->>P: 실행 계획 검사 (mcp_notion_create_page) P-->>A: 검사 완료: 승인 필요 없음 (Safe Write) A->>T: execute("mcp_notion_create_page", args) T->>M: callTool("mcp_notion_create_page", args) M->>S: JSON-RPC tools/call 요청 S-->>M: 도구 실행 성공 결과 반환 M-->>T: 정규화된 결과(Normalized Result) T-->>A: 도구 출력 수신 A->>R: 실행 이력 및 생성 문서 ID 저장 A-->>U: "발표 자료 초안이 노션에 생성되었습니다."
7. 권장 프로젝트 구조 (Bun + TypeScript)
외부 프레임워크 없이 자체 구현할 때 가장 이상적인 디렉터리 구조입니다:
personal-assistant/ ├── package.json # Bun 프로젝트 설정 (의존성 최소화) ├── tsconfig.json # strict: true ├── bun.lockb ├── src/ │ ├── core/ # [순수 도메인] 외부 라이브러리 참조 절대 금지 │ │ ├── agent.ts # 에이전트 인스턴스 및 상태 관리 │ │ ├── loop.ts # FSM 기반 상태 전이 루프 │ │ ├── planner.ts # 사용자 목표 분해 및 도구 계획 │ │ └── context.ts # 토큰 예산 산출 및 압축 조정 │ ├── ports/ # [인터페이스 정의] │ │ ├── llm.port.ts │ │ ├── tool.port.ts │ │ ├── memory.port.ts │ │ ├── context.port.ts │ │ └── event.port.ts │ ├── adapters/ # [인프라 구현체] │ │ ├── llm/ │ │ │ ├── sdk.adapter.ts # 공식 SDK 래퍼 │ │ │ └── http.adapter.ts # Bun Native fetch 래퍼 │ │ ├── tools/ │ │ │ ├── local-files.ts # 로컬 파일 I/O │ │ │ └── safe-shell.ts # 화이트리스트 셸 실행기 │ │ ├── mcp/ │ │ │ ├── client.ts # MCP 클라이언트 │ │ │ └── mcp.adapter.ts # ToolPort에 MCP 도구 매핑 │ │ └── memory/ │ │ ├── sqlite.adapter.ts # bun:sqlite 기반 영속화 │ │ └── json.adapter.ts # 로컬 JSON 파일 백업 │ ├── safety/ # [보안 & 가드레일] │ │ ├── policy.ts # 권한 등급 및 차단 목록 │ │ └── confirm.ts # CLI 사용자 승인 대화상자 │ └── cli.ts # 진입점 (CLI I/O 처리) └── tests/ └── core/ # In-Memory 어댑터 기반의 초고속 단위 테스트
8. 실전 1주 MVP 로드맵
거창한 시스템을 한 번에 만들려다 좌초하지 않기 위해, 1주일 동안 단계별로 동작하는 소프트웨어를 완성하는 실행 계획을 권장합니다.
[ 1주 MVP 빌드 스프린트 ] Day 1-2: CLI 골격 & LLMPort 연결 (기본 질의응답 루프) Day 3: Local ToolPort & 파일 읽기/쓰기 + 안전 정책 엔진 Day 4: bun:sqlite 기반 단기/장기 메모리 영속화 Day 5: MCP 프로토콜 어댑터 연동 (외부 도구 1종 결합) Day 6-7: 5대 핵심 시나리오 엔드투엔드 검증 & 타임아웃 복원력 확보- Day 1~2: 기본 루프와 LLM 포트 구축
- 터미널 입출력 인터페이스 구현.
LLMPort정의 및 SDK 기반 어댑터 연동.- 사용자 입력을 받아 모델의 응답을 출력하는 기본 단일 턴 루프 완성.
- Day 3: 파일 도구와 안전 차단 정책
read_file,write_note2개 도구 구현.- 위험 파일 수정 차단 및 경로 탈출(
../) 방어 검증. - 모델의 함수 호출(Function Calling) 결과를 가로채 도구를 실행하고 결과를 재주입하는 루프 연결.
- Day 4: SQLite 메모리 계층
bun:sqlite를 활용한 단기 세션 저장소 및 장기 메모리 테이블 설계.- 대화 턴 영속화 및 직전 대화 복원 기능 구현.
- Day 5: MCP 어댑터 통합
- 표준 MCP 클라이언트를 구축하고 최소 1개의 외부 MCP 서버(예: Fetch, SQLite, Obsidian 등) 연동.
- Day 6~7: 복원력 및 통합 시나리오 테스트
- "문서 읽고 TODO 추출하기", "일정 요약하고 메모 남기기" 등 5가지 실전 시나리오 검증.
- API 타임아웃, 툴 실행 실패 시 에러 복구(Re-planning) 동작 검증.
9. 에필로그: 변경 비용을 0으로 만드는 에이전트 설계
우리가 소프트웨어 아키텍처에 공을 들이는 유일한 이유는 "미래의 변경 비용을 낮추기 위해서"입니다. AI 에이전트 엔지니어링 역시 마찬가지입니다.
스스로에게 다음 네 가지 질문을 던져보십시오:
- 새로운 최신 LLM이 출시되었을 때, Agent Core 코드를 수정하지 않고 어댑터만 추가하여 갈아끼울 수 있는가?
- 외부 LLM API 호출 없이, 순수 인메모리 Mock 어댑터만으로 에이전트 루프의 모든 상태 전이를 1초 만에 유닛 테스트할 수 있는가?
- 도구의 개수가 10개에서 100개로 늘어나도, 수학적 컨텍스트 예산 산출 공식에 의해 토큰 오버플로 없이 안전하게 동작하는가?
- 에이전트가 예상치 못한 위험한 작업을 시도할 때, 코드 레벨에서 결정론적으로 사용자 승인을 강제할 수 있는가?
이 질문들에 모두 "그렇다"고 답할 수 있다면, 여러분의 에이전트는 특정 프레임워크의 유행이나 벤더의 API 변경에 흔들리지 않는 단단한 엔지니어링 자산이 될 것입니다.
지금 바로 복잡한 외부 프레임워크의 마법을 걷어내고, Bun과 TypeScript의 가벼운 날개 위에 여러분만의 견고한 헥사고날 에이전트를 올려보시기 바랍니다.
반응형'AI' 카테고리의 다른 글
하네스가 곧 회사다: SaaS의 종말이 아닌 '비즈니스 하네스'로의 진화와 Top-Level 소유 전략 (0) 2026.10.05 AI 시대의 코드 리뷰 전략: 인지 부채(Cognitive Debt) 방지와 2단계 에이전틱 파이프라인 (0) 2026.10.04 Kev: Qwen 3.5 기반 1-Pass 오픈 의사결정 모델과 에이전트 도구 호출 60% 절감 아키텍처 (0) 2026.10.02 로컬 LLM의 에이전틱 한계 극복: 30B 체급 기준선과 쿠버네티스 분산 병렬 서빙 아키텍처 (0) 2026.10.01 Computer Use를 활용한 바이브 코딩 앱 사용성 테스트: 정적 린터를 넘어 에이전트 동적 QA와 자율 PR 완결까지 (0) 2026.09.28 - 숨겨진 프롬프트와 불투명한 컨텍스트 제어: