본문으로 바로가기
AI

AI 에이전트 설계 — Tool Use와 Function Calling의 모든 것

A
AlwaysCorp AI연구팀· LLM 애플리케이션·에이전트 시스템
||11분 읽기
#AI에이전트#FunctionCalling#LLM#ToolUse#Claude#GPT#MCP#ReAct

챗봇과 에이전트는 어디서 갈라지는가

"읽고 쓰는" LLM과 "행동하는" LLM

저희 팀에서 withu_apps 모노레포에 AI 기능을 붙이기 시작한 건 2024년 말이었습니다. 처음에는 mind_test 결과에 해설을 덧붙이는 단순한 쓰임이었지만, 얼마 지나지 않아 "LLM이 스스로 DB를 뒤지고, 결과를 요약하고, 푸시 알림까지 트리거하게 하면 어떨까?" 하는 이야기가 나왔습니다. 바로 그 지점에서 챗봇과 에이전트의 경계가 분명해졌습니다.

2022년 ChatGPT 이후 2년 동안의 LLM 활용은 대부분 "대화" 수준에 머물렀습니다. 입력이 들어오면 텍스트를 생성하고 끝나는 단발성 호출. 하지만 2024년 이후의 주류 패턴은 다릅니다. LLM이 외부 세계의 도구를 호출하고, 결과를 관찰하고, 다음 행동을 결정하는 에이전트로 전환되었습니다.

이 전환의 기술적 기반이 Tool Use(도구 사용), 또는 OpenAI 용어로 Function Calling입니다. 이름은 다르지만 메커니즘은 같습니다. LLM이 자연어 대신 구조화된 JSON을 출력하여 외부 함수를 호출하는 것입니다.

왜 이것이 혁명적인가

기존 LLM은 모델이 학습한 시점 이전의 정보만 알고 있었습니다. Tool Use는 이 제약을 근본적으로 깹니다. 검색 도구를 쥐어 주면 실시간 정보에 접근하고, 데이터베이스 도구를 주면 기업 내부 데이터에 답하고, 코드 실행 도구를 주면 계산을 정확히 수행합니다. LLM이 "모든 것을 외우는" 대신 "필요할 때 찾아보는" 방식으로 패러다임이 바뀐 것입니다.

---

Function Calling의 내부 동작

3단계 파이프라인

Anthropic Claude와 OpenAI GPT 모두 본질적으로 같은 3단계 구조를 따릅니다.

  1. 도구 정의 주입: 사용 가능한 도구의 이름·설명·JSON Schema를 시스템 프롬프트에 주입
  2. 모델의 선택: 사용자 질문을 받은 모델이 "답변을 즉시 생성" 또는 "도구 호출" 중 선택
  3. 결과 통합: 외부 시스템이 도구를 실행하고 결과를 모델에 다시 전달, 모델은 이를 바탕으로 최종 답변 생성

```typescript const tools = [{ name: "get_weather", description: "특정 도시의 현재 날씨를 조회합니다.", input_schema: { type: "object", properties: { city: { type: "string", description: "한국어 도시 이름" }, unit: { type: "string", enum: ["C", "F"], default: "C" }, }, required: ["city"], }, }]; ```

모델이 "도쿄 날씨 어때?"를 받으면 다음과 같은 구조화된 출력을 만듭니다.

```json { "type": "tool_use", "name": "get_weather", "input": { "city": "도쿄", "unit": "C" } } ```

모델은 어떻게 도구를 선택하는가

학습 과정에서 모델은 "도구 설명을 읽고 필요 여부를 판단하는" 능력을 강화학습으로 습득합니다. 핵심은 도구 설명 자체가 프롬프트라는 점입니다. 설명이 모호하면 모델이 엉뚱한 상황에서 도구를 호출하거나, 필요할 때 호출하지 않습니다.

좋은 도구 설명의 3요소:

  • 언제 써야 하는가
  • 언제 쓰면 안 되는가
  • 반환 형식은 어떤 모양인가

``` description: "도시의 현재 날씨를 조회합니다. 사용자가 '오늘 날씨', '지금 비 와?' 처럼 실시간 날씨를 물을 때 사용하세요. 과거 날씨나 예보에는 적절하지 않습니다. 반환값은 기온, 습도, 설명을 포함하는 객체입니다." ```

---

ReAct 루프, 에이전트의 기본 패턴

Reasoning + Acting

2022년 Yao 등의 논문 ReAct: Synergizing Reasoning and Acting in Language Models(ICLR 2023)가 제안한 패턴은 지금까지 에이전트 아키텍처의 기본형입니다.

반복 구조:

  1. Thought — 현재 상황 분석
  2. Action — 도구 호출
  3. Observation — 결과 관찰
  4. 종료 조건을 만족할 때까지 반복

``` Thought: 사용자가 서울과 도쿄의 날씨를 비교하길 원한다. 두 도시의 날씨를 각각 조회해야 한다. Action: get_weather(city="서울") Observation: {"temp": 12, "desc": "맑음"} Thought: 서울 정보 확보. 이제 도쿄를 조회한다. Action: get_weather(city="도쿄") Observation: {"temp": 18, "desc": "흐림"} Thought: 두 도시 정보를 모두 확보. 이제 사용자에게 답변한다. Final: 서울은 12°C 맑음, 도쿄는 18°C 흐림입니다. 도쿄가 6°C 더 따뜻합니다. ```

병렬 도구 호출

Claude 3.5 이후, GPT-4 Turbo 이후로는 한 턴에 여러 도구를 병렬 호출할 수 있습니다. 위 예시처럼 독립적인 두 호출은 순차가 아닌 병렬로 실행되어 총 지연이 절반으로 줄어듭니다. 런타임 구현에서는 `Promise.all` 패턴으로 간단히 구현됩니다.

---

실전 설계 원칙

1. 도구의 입도(granularity)

너무 작은 도구는 호출 횟수를 폭발시키고, 너무 큰 도구는 모델의 판단을 방해합니다. 경험칙:

  • 하나의 도구는 하나의 비즈니스 개념만 다뤄야 함
  • 입력 파라미터는 3~5개 이내
  • 성공·실패의 의미가 명확해야 함

``` 나쁜 예: "do_everything(action, ...)" 좋은 예: "search_product(query, limit)", "get_product_detail(id)" ```

2. 오류 복구 구조

도구 호출은 실패합니다. 네트워크, 권한, 잘못된 인자, 외부 API 장애. 모델이 이 실패를 "관찰"로 받고 스스로 회복하게 해야 합니다.

```json { "type": "tool_result", "is_error": true, "content": "Invalid city name: '도쿠'. Suggestions: '도쿄', '교토'." } ```

에러 메시지 자체가 다음 시도를 유도하는 힌트가 되도록 설계하는 것이 핵심입니다. 단순한 "Error: failed" 메시지는 에이전트를 무한 루프로 몰아넣습니다.

3. 루프 안전장치

ReAct 루프는 구조적으로 무한 루프에 빠질 위험이 있습니다. 반드시 다음 두 장치를 둡니다.

  • 최대 턴 수 제한 (보통 15~25턴)
  • 동일 도구·동일 인자 반복 감지 → 조기 종료

4. 비결정성을 받아들여라

같은 입력에도 모델의 도구 선택이 매번 완벽히 같지 않습니다. temperature를 0으로 두어도 LLM은 100% 결정적이지 않습니다. 따라서 결과의 기능적 동등성을 평가하는 테스트를 작성해야 합니다. "같은 문자열을 뱉었는가"가 아니라 "올바른 도구를 올바른 순서로 호출했는가"를 검증합니다.

---

MCP, Tool Use의 표준화를 시도하다

등장 배경

2024년 Anthropic이 공개한 Model Context Protocol (MCP)은 Tool Use의 표준 인터페이스입니다. 지금까지는 OpenAI·Anthropic·Google의 Function Calling 포맷이 각각 달랐고, 내부 도구를 공유하려면 매번 어댑터를 작성해야 했습니다.

MCP는 이 문제를 해결합니다. MCP 서버는 도구와 리소스를 정의해 제공하고, MCP 호환 클라이언트(Claude Desktop, Cursor, Zed 등)는 어떤 서버든 연결해 사용할 수 있습니다.

```json { "name": "mcp-server-db", "version": "0.1.0", "tools": [ { "name": "query_customers", "description": "고객 데이터베이스 조회", "inputSchema": { ... } } ] } ```

장점

  • 재사용: 한 번 만든 도구를 여러 에이전트가 공유
  • 보안 경계: MCP 서버가 별도 프로세스로 분리되어 권한 관리가 쉬움
  • 생태계: Slack, GitHub, PostgreSQL 등 공식 MCP 서버가 계속 공개되고 있음

---

실전 체크리스트

| 항목 | 점검 | |---|---| | 도구 설명에 "언제 쓰지 말 것인가" 포함됨 | ☐ | | JSON Schema에 enum·pattern으로 인자 제약 | ☐ | | 성공/실패 응답 구조 일관성 | ☐ | | 에러 메시지가 다음 행동을 유도 | ☐ | | 최대 루프 턴 수 제한 설정 | ☐ | | 결정적 테스트 대신 행동 기반 평가 | ☐ | | 민감 작업은 인간 승인 절차 | ☐ | | 비용 상한(토큰·호출 횟수) 제한 | ☐ | | 관측성: 각 턴의 Thought·Action 로깅 | ☐ | | MCP로 도구 재사용 가능성 고려 | ☐ |

---

마무리하며

에이전트의 성능은 모델의 크기나 프롬프트 솜씨보다 도구 설계의 완성도에 더 크게 좌우됩니다. 경험이 많은 팀일수록 프롬프트를 길게 쓰기보다 도구 설명을 정교하게 다듬는 데 시간을 투자하는 편입니다.

좋은 도구 하나가 난해한 프롬프트 한 페이지보다 더 큰 개선을 냅니다. 만든 에이전트가 기대만큼 작동하지 않는다면, 모델 교체나 프롬프트 재작성에 앞서 도구 설명을 두세 번 다시 써 보시길 권합니다. 경험적으로, 많은 경우 문제는 그쪽에 있었습니다.

A

AlwaysCorp AI연구팀

LLM 애플리케이션·에이전트 시스템

얼웨이즈 블로그에서 유용한 정보와 인사이트를 공유합니다.