LLM Function Calling이란? (함수 호출의 개념, 코드 예시)

2026. 3. 15. 13:20·[IT] 질문과 답변/생성형AI

LLM Function Calling이란?

유데미 강의에서 칸반 AI 챗봇에게 "카드 옮겨줘"라고 했더니 실제로 카드가 이동했다. 어떻게 동작하는 걸까?


 

들어가며

AI 챗봇에게 "gather customer signals 카드를 Done으로 옮겨줘"라고 입력했더니, 정말로 칸반 보드의 카드가 이동했다.

단순한 채팅 응답이 아니라 실제 데이터베이스가 변경된 것이다.

이게 가능한 이유가 바로 Function Calling(Tool Use) 이다.


핵심 개념: LLM은 직접 실행하지 않는다

가장 먼저 이해해야 할 것은, Claude(또는 GPT)가 직접 DB를 건드리지 않는다는 점이다.

LLM이 하는 일은 딱 하나다:

"나는 이 함수를, 이 인자로 호출하고 싶다"는 의도를 JSON으로 반환하는 것

 

실제 실행은 백엔드 서버가 한다.


전체 흐름

사용자: "gather customer signals를 Done으로 옮겨줘"
  ↓
백엔드: 메시지 + 사용 가능한 tools 목록 → Claude API 전송
  ↓
Claude: tool_use 블록 반환 (move_card 함수 호출 의도 + 인자)
  ↓
백엔드: tool_use 블록 파싱 → 실제 DB UPDATE 실행
  ↓
백엔드: 실행 결과 → Claude API 재전송 (tool_result)
  ↓
Claude: "카드를 Done으로 이동했습니다!" (자연어 최종 응답)
  ↓
사용자: 응답 수신

단계별 코드로 보기

1단계: 백엔드가 tools 목록과 함께 메시지 전송

일반 API 호출과의 차이는 tools 파라미터가 추가된다는 것이다.

import anthropic
import json

client = anthropic.Anthropic(api_key="...")

tools = [
    {
        "name": "move_card",
        "description": "칸반 카드를 다른 컬럼으로 이동시킵니다",
        "input_schema": {
            "type": "object",
            "properties": {
                "card_title": {
                    "type": "string",
                    "description": "이동할 카드 제목"
                },
                "target_column": {
                    "type": "string",
                    "enum": ["backlog", "in_progress", "review", "done"]
                }
            },
            "required": ["card_title", "target_column"]
        }
    }
]

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1000,
    tools=tools,  # ← 핵심
    system=f"현재 보드 상태: {json.dumps(board_state)}",
    messages=[{"role": "user", "content": user_message}]
)

2단계: Claude가 반환하는 응답 구조

"카드 옮겨줘"라는 요청에 Claude는 텍스트가 아닌 아래 구조를 반환한다.

# response.content
[
    ContentBlock(
        type="tool_use",       # 텍스트 응답이 아님!
        id="toolu_01abc...",
        name="move_card",
        input={
            "card_title": "gather customer signals",
            "target_column": "done"
        }
    )
]

3단계: 백엔드가 실행하고 결과를 Claude에게 재전달

async def handle_response(response, user_message, board_state):

    if response.stop_reason == "tool_use":
        tool_block = next(b for b in response.content if b.type == "tool_use")

        # 실제 DB 업데이트
        if tool_block.name == "move_card":
            result = move_card_in_db(
                card_title=tool_block.input["card_title"],
                target_column=tool_block.input["target_column"]
            )

        # 결과를 Claude에 다시 전달 → 자연어 응답 생성
        final_response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1000,
            tools=tools,
            messages=[
                {"role": "user", "content": user_message},
                {"role": "assistant", "content": response.content},
                {
                    "role": "user",
                    "content": [{
                        "type": "tool_result",
                        "tool_use_id": tool_block.id,
                        "content": json.dumps(result)
                    }]
                }
            ]
        )

        return final_response.content[0].text
        # → "gather customer signals 카드를 Done으로 이동했습니다!"

드래그앤드롭 방식과 뭐가 다른가

자연스러운 의문이다. 그냥 드래그앤드롭 이벤트마다 REST API 호출하면 되는 거 아닌가?

맞다. 오히려 그게 더 단순하다.

방식 의도 파악 주체 적합한 케이스
드래그앤드롭 프론트엔드 (UI가 이미 앎) 명확한 단일 액션
Function Calling LLM (자연어 해석) 자연어에서 의도 추출

 

핵심 차이는 "의도 파악의 주체" 다.

드래그앤드롭은 사용자가 카드를 Done 컬럼에 갖다놓는 순간, 프론트엔드가 이미 {card_id, target_column: "done"}을 알고 있다. LLM이 끼어들 여지가 없다.

반면 "gather customer signals 옮겨줘"라는 자연어에는 어떤 카드인지, 어디로인지가 사용자 문장 안에 들어있다. 이걸 파싱해서 구조화된 액션으로 변환하는 것이 LLM의 역할이다.


여기서 드는 의문: LLM이 엉뚱한 tool을 선택하면?

실제로 일어나는 문제다. 프로덕션에서는 반드시 방어 레이어가 필요하다.

1단계: 프롬프트로 행동 제한

system = """
당신은 칸반 보드 어시스턴트입니다.
카드 이동 요청이 명확하지 않으면 반드시 사용자에게 확인을 요청하세요.
절대 추측으로 tool을 실행하지 마세요.
"""

2단계: 백엔드에서 실행 전 검증

if tool_block.name == "move_card":
    card = db.query(Card).filter(Card.title == card_title).first()
    if not card:
        return "해당 카드를 찾을 수 없습니다. 정확한 카드 이름을 알려주세요."

3단계: 위험도에 따라 tool 분리

# 읽기 전용 → 자동 실행 OK
{"name": "get_board_state", ...}
{"name": "search_cards", ...}

# 상태 변경 → 확인 후 실행
{"name": "move_card", ...}

# 파괴적 작업 → 별도 권한 체크
{"name": "delete_card", ...}

정리

  • LLM Function Calling의 핵심은 "LLM이 어떤 함수를 호출할지 판단하는 것" 이고, 실제 실행은 백엔드가 담당한다
  • 드래그앤드롭과 Function Calling은 대체 관계가 아니라 진입 경로가 다른 것이다
  • 프로덕션에서는 LLM의 판단을 그대로 신뢰하지 말고, 백엔드에서 반드시 검증 레이어를 추가해야 한다

Udemy "AI Coder: Vibe Coder to Agentic Engineer" 강의를 수강하며 정리한 내용입니다.

'[IT] 질문과 답변 > 생성형AI' 카테고리의 다른 글

[IT Insight] 구글, 카카오가 '소셜 로그인'을 무료로 퍼주는 진짜 이유 (OAuth의 비즈니스 모델)  (1) 2026.03.28
'[IT] 질문과 답변/생성형AI' 카테고리의 다른 글
  • [IT Insight] 구글, 카카오가 '소셜 로그인'을 무료로 퍼주는 진짜 이유 (OAuth의 비즈니스 모델)
팩맨
팩맨
안녕하세요, 평범한 직장인을 꿈꾸는 백엔드 개발자입니다.
  • 최근 글

  • 최근 댓글

  • 태그

    HTTP
    웹개발자CS
    네트워크cs
    백엔드네트워크
    도커개념강의
    도커핵심개념
    JVM
    JAVA스터디
    웹개발자를위한네트워크
    도커컨테이너
    도커개념
    네트워크개념
    네트워크CS지식
    java
    네트워크기초
    jvm가비지콜렉터
    Docker
    네트워크공부
    가비지콜렉터
    dns란
    웹개발자네트워크
    도커개념정리
    도커
    네트워크
    네트워크기초지식
    도커이미지
    CS지식
    도커기본개념
    CS네트워크
    인프런도커
  • 인기 글

  • 공지사항

  • 링크

  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 분류 전체보기
      • 개발자 이모저모
      • AI한테 배우는 CS지식
        • 웹개발자가 알아야 할 네트워크
      • 개발스터디 정리
        • [인프런] 개발자를 위한 쉬운 도커
        • [Learn Linux TV] 리눅스 커맨드 기초..
        • [인프런] 기초부터 이해하는 쉬운 쿠버네티스 - ..
      • [IT] 질문과 답변
        • 생성형AI
  • 전체
    오늘
    어제
  • 팩맨
    완벽주의 버리기
    팩맨
  • hELLO· Designed By정상우.v4.10.6
팩맨
LLM Function Calling이란? (함수 호출의 개념, 코드 예시)
상단으로

티스토리툴바