본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 12. 13:41

AI 에이전트를 위한 도구 호출 강화의 세 가지 계층

요약

AI 에이전트가 제품에 깊숙이 통합되면서 도구 호출(Tool Calling) 기능의 중요성이 커지고 있습니다. 그러나 대부분의 개발자는 프롬프트 조정이나 모델 선택에만 집중하고, 에이전트를 둘러싼 '도구 하네스(Tool Harness)'와 보안 측면을 간과하는 경향이 있습니다. 이 글은 AI 에이전트의 신뢰성과 보안성을 확보하기 위해 도구 호출 기능을 설계할 때 반드시 고려해야 할 세 가지 계층적 방어 메커니즘을 제시합니다.

핵심 포인트

  • AI 에이전트 개발 시, 단순한 프롬프트 지시가 아닌 코드 레벨에서 작동하는 '도구 하네스'를 구축하여 보안과 신뢰성을 확보해야 합니다.
  • 첫 번째 계층은 접근 제어(Access Control)에 관한 것으로, 도구 스키마에서 `user_id`와 같은 식별자 매개변수를 제거하고, 대신 인증된 세션으로부터 직접 식별자를 주입하여 권한을 통제해야 합니다.
  • 두 번째 계층은 행동 가드레일(Behavioral Guardrails)로, 도구가 호출되는 순서나 전제 조건(예: 파일을 읽기 전에 수정하는 것)과 같은 논리적 제약 조건을 코드 레벨에서 강제해야 합니다.
  • 세 번째 계층 (본문에는 내용이 잘렸으나 추론 가능)은 아키텍처 또는 실행 흐름 전체를 감싸는 더 포괄적인 통제 메커니즘을 의미하며, 에이전트의 전반적인 행동 범위를 제한하는 역할을 할 것입니다.

현재 소프트웨어 엔지니어링 분야에서 우리는 제품에 많은 AI 에이전트를 구축하고 있습니다. 그리고 제품에 AI 에이전트가 있다는 것은 제품을 살아있게 유지하는 방법입니다. 그렇죠? 세상은 그렇게 움직이고 있습니다. 모두가 AI 에이전트를 만드는 데 바쁘고 — 프롬프트를 조정하거나, 도구 호출을 제공하거나, 모델 선택과 매개변수에 집중하고 있지만 — 대부분의 개발자가 간과하는 한 가지 중요한 영역이 있습니다. 바로 도구 하네스(Tool harness)와 보안입니다. 프롬프트가 아닙니다. 모델도 아닙니다. 당신의 도주 주변을 감싸는 하네스 — 그것을 어떻게 설계하고, 제약하며, 에이전트가 실제로 무엇을 할 수 있는지 통제하는 방식입니다. 그리고 이것을 건너뛰면 보안과 신뢰성 측면에서 많은 비용을 치르게 될 것입니다.

도구 하네스란 무엇인가요?

AI 에이전트에게 도구를 제공할 때, 단순히 함수를 주는 것이 아닙니다. 경계를 주는 것입니다. 그것이 무엇에 접근할 수 있는지, 무엇에 접근할 수 없는지, 그리고 행동할 때 어떻게 행동해야 하는지에 대한 일련의 규칙을 주는 것입니다. 우리 대부분은 그렇게 생각하지 않습니다. 우리는 도구를 작성하고, 에이전트에 연결한 다음, 넘어갑니다. 하네스 — 즉 제약 조건, 접근 통제, 행동 가드레일(behavioral guardrails) — 은 프롬프트에 맡겨집니다. 그것이 실수입니다. 프롬프트는 무시될 수 있습니다. 조작될 수 있습니다. 무시될 수도 있습니다. 하네스는 코드 레벨, 실행 레벨, 아키텍처 레벨에서 존재해야 합니다. 그리고 올바르게 구축하는 방법은 다음과 같습니다. 세 가지 계층이 있습니다.

계층 1: 식별자 매개변수 제거 — 서버 측에 주입하기

첫 번째 계층은 접근 제어에 관한 것입니다. 그리고 그것은 도구 스키마(tool schema)에서 시작됩니다. 예를 들어, 당신이 투두 리스트를 만들고 있다고 가정해 봅시다.

AI 에이전트와 연동된 프롬프트(prompt)입니다. 이에게 list_tasks라는 도구(tool)를 제공한다고 가정해 봅시다. 스키마는 다음과 같습니다: { "name" : "list_tasks" , "parameters" : { "user_id" : "string" , "filters" : { "status" : "string" , "due_before" : "string" } } }. 괜찮아 보이죠? 하지만 그렇지 않습니다. user_id가 스키마에 포함되어 있기 때문에, 에이전트는 원하는 어떤 사용자 ID든 전달할 수 있습니다. 악의적인 프롬프트(malicious prompt), 혼란스러운 모델(confused model), 또는 프롬프트 주입(prompt injection) — 이 중 어느 것이든 여러분의 에이전트가 절대 접근해서는 안 되는 데이터를 가져오게 할 수 있습니다. 인증(authentication)이 없습니다. 권한 부여(authorization)도 없습니다. 해결책은 스키마에서 모든 식별자 매개변수(identity params)를 제거하는 것입니다. user_id, account_id, workspace_id, knowledge_base_id와 같은 것들은 누가 무엇을 볼지 범위를 정의합니다. 에이전트가 범위를 결정하게 두어서는 안 됩니다. 여러분이 결정해야 합니다. { "name" : "list_tasks" , "parameters" : { "filters" : { "status" : "string" , "due_before" : "string" } } }. 그리고 도구가 실행될 때, 인증된 세션(authenticated session)으로부터 직접 식별자를 주입하세요: async function list_tasks ( params : { filters : Filters }, session : Session ) { const userId = session . userId ; // 이 값은 에이전트가 아니라 여러분이 제어합니다 return db . tasks . findMany ({ where : { userId , ... params . filters , }, }); } 에이전트는 자신이 무엇을 필요로 하는지만 말합니다. 어떤 데이터가 건드려질지 결정하는 것은 여러분입니다. 이것이 바로 안전장치(harness)입니다. 💡 레이어 2: 코드 수준에서 행동 제약 조건 강제하기 (Enforce Behavioral Constraints at the Code Level) 두 번째 계층은 도구가 무엇에 접근할 수 있는지뿐만 아니라, 어떻게 작동해야 하는지에 관한 것입니다. 만약 Claude Code를 사용해 보셨다면, 다음과 같은 오류 메시지를 본 적이 있을 겁니다: "A file cannot be written before it has been read." (파일은 읽히기 전에 쓰여질 수 없습니다.)

이는 프롬프트 지시사항이 아닙니다. 이는 도구 자체에 내재된 하드 제약 조건입니다. Claude Code의 개발자들은 매우 인간적인 행동, 즉 파일을 열고, 읽고, 이해한 다음 편집하는 과정을 가져와 실행 수준에서 강제했습니다. 이것이 바로 우리가 우리만의 도구로 해야 할 일입니다. 예를 들어, update_task라는 도구가 있다면, 에이전트가 이를 임의로 호출하도록 두지 마십시오. 코드 수준에서 '읽기 우선(read-first)' 제약 조건을 강제하십시오: async function update_task ( params : UpdateTaskParams , session : Session ) { const lastRead = await cache . get ( task_read: ${ params . task_id } : ${ session . userId } ); if ( ! lastRead || Date . now () - lastRead > 60 _000 ) { throw new Error ( " Task must be read before it can be updated. Call get_task first. " ); } return db . tasks . update ({ where : { id : params . task_id }, data : params . updates , }); } 프롬프트에서도 언급할 수는 있지만, 이 검사는 코드에 존재해야 합니다. 모델이 놓치거나 무시할 수 있는 시스템 프롬프트가 아니라요. 실행 계층(execution layer)에 장치가 있어야 합니다. 🔒 레이어 3: 추론 에이전트를 사용한 사전 비행 검증 (Pre-flight Validation with a Reasoning Agent) 이것은 더 진보된 방식입니다. 아직 제 자체 제품에는 구현하지 못했지만, 작동할 것이라는 것을 압니다. 아이디어는 다음과 같습니다. 어떤 도구 호출이 실행되기 전에, 에이전트가 그 이유—왜 해당 도구를 호출하는지에 대한 짧은 설명—를 제시하도록 요구하는 것입니다. { "name" : "list_tasks" , "parameters" : { "reason" : "string" , "filters" : { "status" : "string" , "due_before" : "string" } } } 이는 에이전트가 행동하기 전에 생각하도록 강제합니다. 그것은 어쩌면

실제로 그 이유가 유효하지 않다는 것을 깨닫고 도구를 전혀 호출하지 않기로 결정합니다. 더 나아가서, 도구 이름(tool name), 이유(reason), 그리고 대화 컨텍스트(conversation context)를 받아 해당 호출이 실제로 정당한지 판단하는 가벼운 검증 에이전트(validation agent)를 실행할 수 있습니다: async function validateToolCall ( toolName : string , reason : string , context : string ) { const response = await llm . complete ({ model : " fast-small-model " , prompt : Tool requested: ${ toolName } Reason given: ${ reason } Conversation context: ${ context } Is this tool call justified? Reply YES or NO with a brief explanation. , }); return response . text . startsWith ( " YES " ); } 만약 검증 에이전트가 '아니요(no)'라고 말하면 — 도구는 실행되지 않습니다. 이는 환각된 도구 호출(hallucinated tool calls), 프롬프트 주입 시도(prompt injection attempts), 그리고 에이전트가 필요성보다는 습관적으로 도구를 호출하는 경우를 포착합니다. 🛡️ 마무리하며 우리는 오늘날 너무 많은 에이전트를 설계하고 있습니다. 그리고 빠르게 진행하고 있습니다. 하지만 하네스(harness) — 즉 보안, 제약 조건, 접근 통제(access controls) — 가 뒤처지고 있습니다. 최소한, 우리가 에이전트에게 주면 안 되는 것에 대한 접근 권한을 주고 있지 않은지 확실해야 합니다. 우리가 구축하는 도구들이 어떻게 사용되어야 하는지에 대한 의견을 갖도록 해야 합니다. 프롬프트에만 존재하는 것이 아니라 아키텍처 수준에서 존재하는 가드레일(guardrails)이 있어야 합니다. 식별자 매개변수(identity params)를 제거하세요. 코드에서 행동 제약 조건(behavioral constraints)을 강제하세요. 실행 전에 추론 검토 지점(reasoning checkpoint)을 추가하세요. 이 세 가지 계층은 단순히 에이전트를 더 안전하게 만드는 것 이상을 할 것입니다.

이것은 더 신뢰할 수 있고, 예측 가능하며, 문제가 발생했을 때 디버깅하기 훨씬 쉬워질 것입니다. 그리고 저를 믿으세요. — 무언가 잘못될 겁니다. 중요한 것은 여러분의 테스트 환경(harness)이 그것을 감당할 준비가 되어 있었는지 여부입니다. 이 글이 유익하셨기를 바라며, 더 많은 기술 콘텐츠는 제 소셜 미디어를 팔로우해주세요. 다음 블로그에서 뵙겠습니다 👋🏻

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
2

댓글

0