Function Calling(Tool Use)은 LLM이 외부 함수·API를 쓸 수 있게 하는 메커니즘입니다. 핵심은 모델이 함수를 실행하지 않는다는 것입니다. 모델은 "어떤 함수를 어떤 인자로 부르고 싶다"는 호출 의도를 구조화된 데이터로 반환할 뿐이고, 실제 실행·권한 검사·결과 반환은 애플리케이션 코드가 합니다.
LLM은 텍스트를 생성하는 모델이라 스스로 DB를 조회하거나 메일을 보낼 수 없습니다. Function Calling은 이 한계를 이렇게 풉니다.
{"name": "get_weather", "arguments": {"city": "서울"}} 같은 구조화된 호출 요청을 반환합니다.정보가 부족하면(예: 도시 이름이 없음) 모델은 도구를 부르지 않고 사용자에게 되묻는 것도 선택할 수 있습니다. 이 루프를 반복하는 것이 곧 Agent의 핵심 루프입니다.
⚠️ 함정: "모델이 API를 호출했다"는 표현 때문에 모델에게 권한이 있다고 착각하기 쉽습니다. 실행권은 항상 코드에 있습니다. 그래서 권한 검사, 인자 검증, 사람 승인은 프롬프트가 아니라 도구를 실행하는 코드에서 해야 합니다. 모델이 만든 인자는 사용자 입력과 똑같이 신뢰할 수 없는 입력으로 취급하세요.
LLM API는 상태가 없으므로, 두 번째 요청에는 원래 질문·모델의 호출 요청·도구 결과를 모두 다시 보내야 합니다. 호출 요청마다 붙는 ID로 어떤 결과가 어떤 호출에 대한 것인지 연결합니다.
도구는 이름, 설명, 파라미터(JSON Schema) 세 가지로 정의합니다. 모델은 코드가 아니라 이 텍스트만 보고 도구를 고르고 인자를 채웁니다.
제공자마다 필드 이름이 조금씩 다를 뿐 구조는 같습니다.
| 항목 | Anthropic Messages API | OpenAI Responses API |
|---|---|---|
| 도구 정의 | {name, description, input_schema} | {type: "function", name, description, parameters} |
| 모델의 호출 요청 | content 블록 type: "tool_use" (id, name, input) | output 항목 type: "function_call" (call_id, name, arguments JSON 문자열) |
| 결과 반환 | user 메시지에 type: "tool_result" (tool_use_id, content) | 입력 항목 type: "function_call_output" (call_id, output) |
| 호출 발생 신호 | stop_reason: "tool_use" | output에 function_call 항목 존재 |
| 스키마 엄격 준수 | 도구에 strict: true | 도구에 strict: true |
직접 루프를 쓰는 대신 Anthropic SDK의 Tool Runner(Python은 client.beta.messages.tool_runner)나 LangChain 같은 프레임워크가 이 루프를 대신 돌려 주기도 합니다. 동작을 이해하려면 한 번은 직접 작성해 보는 것을 권합니다.
"서울이랑 부산 날씨"처럼 서로 독립적인 호출은 모델이 한 응답에 여러 호출 요청을 담아 보낼 수 있습니다. 코드는 이를 동시에 실행하고 모든 결과를 한 번에 돌려주면 됩니다.
⚠️ 함정: Anthropic API에서 병렬 호출 결과를 여러 user 메시지로 나눠서 보내면, 모델이 이후 병렬 호출을 덜 하게 됩니다. 결과는 반드시 한 메시지에 모으고, 실패한 호출도 빼지 말고 에러 결과로 채워 넣으세요. 모든 호출 ID에 대응하는 결과가 없으면 요청 자체가 거부될 수 있습니다.
순서가 중요하거나(조회 후 수정) 동시 실행이 위험하면 병렬 호출을 끕니다: Anthropic은 tool_choice에 disable_parallel_tool_use: true, OpenAI는 parallel_tool_calls: false.
| 의도 | Anthropic | OpenAI | 쓰임 |
|---|---|---|---|
| 모델이 알아서 판단 (기본) | {"type": "auto"} | "auto" | 대부분의 경우 |
| 반드시 도구 하나 이상 호출 | {"type": "any"} | "required" | 도구 호출이 곧 목적인 파이프라인 |
| 특정 도구를 강제 | {"type": "tool", "name": "..."} | {"type": "function", "name": "..."} | 추출 전용 단계 |
| 도구 사용 금지 | {"type": "none"} | "none" | 도구 정의는 유지한 채 이번 턴만 텍스트로 |
⚠️ 함정: 강제 호출(
any/tool)은 모델이 "정보가 부족하니 되묻겠다"는 선택을 할 수 없게 만듭니다. 빈 값이나 추측한 값으로 인자를 채우는 원인이 됩니다. 또한 일부 최신 모델은 강제 호출 자체를 지원하지 않으니 사용 전에 모델 문서를 확인하세요.
둘 다 JSON을 받는다는 점에서 헷갈리지만 목적이 다릅니다.
| 구분 | 구조화 출력 (Structured Outputs) | Function Calling |
|---|---|---|
| 해결하는 문제 | 응답 형식을 스키마에 맞추기 | 외부 행동을 통제된 방식으로 일으키기 |
| 결과물 | 모델의 최종 답 자체가 JSON | "이 함수를 이 인자로 불러 달라"는 요청 |
| 이후 흐름 | 코드가 읽고 끝 | 코드가 실행 → 결과를 모델에 되돌림 → 루프 |
| 설정 위치 | 응답 형식 파라미터 (예: Anthropic output_config.format) | tools 목록 |
| 대표 용도 | 분류, 정보 추출, UI에 뿌릴 데이터 | DB 조회, 주문 생성, 메일 발송, 검색 |
판단 기준은 간단합니다. 결과가 현실 세계를 읽거나 바꾸면 Function Calling, 모델 답의 모양만 맞추면 구조화 출력입니다. 둘을 함께 쓸 수도 있습니다(도구로 조회하고, 최종 답은 스키마에 맞춰 반환).
과거에는 JSON을 안정적으로 받으려고 "추출 전용 도구를 만들어 강제 호출"하는 우회를 썼지만, 지금은 주요 제공자가 스키마를 강제하는 구조화 출력(constrained decoding) 과 도구의 strict 모드를 지원하므로 그 우회는 대부분 필요 없습니다. 더 깊은 비교는 JSON 출력과 Function Calling의 차이와 JSON 안정 출력 — 4층 방어선을 보세요.
모델에게 도구 정의는 유일한 사용 설명서입니다. Anthropic은 이를 사람용 UI(HCI)만큼 공들여야 하는 ACI(Agent-Computer Interface) 라고 부릅니다.
search_orders, cancel_order.github_create_issue, jira_create_issue.process, handle, do_action처럼 모호한 이름은 피합니다.enum, minimum/maximum, format으로 가능한 값을 스키마에서 좁힙니다. 설명으로 부탁하는 것보다 확실합니다.description과 예시 값을 둡니다. 형식이 있는 값(ID, 날짜)은 특히 중요합니다.required에, 추가 필드는 additionalProperties: false로 막습니다.date보다 "YYYY-MM-DD".⚠️ 함정: 기존 REST API를 그대로 1:1 도구로 노출하면 대부분 잘 동작하지 않습니다. API는 프로그램이 조합하기 좋게 잘게 쪼개져 있지만, 모델에게는 작업 단위(예: "일정 잡기" = 참석자 조회 + 빈 시간 찾기 + 이벤트 생성)가 더 쓰기 쉽습니다.
더 자세한 설계 기준은 Agent가 호출할 도구를 아는 법과 도구 호출 Schema 설계를 참고하세요.
에러는 모델이 스스로 고칠 수 있는 에러와 코드가 처리해야 하는 에러로 나눕니다.
| 유형 | 예 | 처리 |
|---|---|---|
| 인자 오류 | 스키마 위반, 날짜 형식 틀림, 존재하지 않는 ID | 무엇이 왜 틀렸는지 에러 결과로 반환 → 모델이 수정해 재호출 |
| 업무 규칙 위반 | 출고된 주문 취소 시도 | 규칙과 대안을 담아 반환: "이미 출고됨. request_return 사용" |
| 권한 거부 | 다른 고객 주문 조회 | 거부 사실만 반환, 내부 정보는 노출하지 않음 |
| 일시 장애 | 타임아웃, 429, 5xx | 코드에서 백오프 재시도. 반복 실패 시에만 모델에 알림 |
| 알 수 없는 도구 | 모델이 없는 도구 이름을 만들어냄 | 사용 가능한 도구 목록과 함께 에러 반환 |
⚠️ 함정: 도구가 HTTP 200을 반환했다고 작업이 성공한 것은 아닙니다. 응답 본문에 실패가 담겨 있거나, 결과가 비어 있거나, 다른 대상이 바뀌었을 수 있습니다. 중요한 쓰기 작업은 결과를 다시 조회해 검증하세요. → 200 응답과 Agent 작업 실패
| 구분 | RAG | Function Calling |
|---|---|---|
| 목적 | 모델에 지식을 공급 | 모델이 행동을 요청 |
| 데이터 신선도 | 인덱스 갱신 주기에 의존(지연 가능) | 실시간·준실시간(API 직접 호출) |
| 누가 검색을 결정하나 | 보통 코드가 매 요청마다 검색 | 모델이 필요할 때 호출 |
| 구현 복잡도 | 청킹·임베딩·검색 파이프라인 구축 | 명확한 인터페이스와 파라미터 정의 |
| 부작용 | 없음(읽기 전용) | 있을 수 있음(쓰기·결제·발송) |
| 적합한 시나리오 | 정적인 지식(문서, FAQ, 정책) | 동적인 상호작용(주문 조회, 날씨, 예약) |
둘은 경쟁 관계가 아닙니다. 검색 자체를 도구로 노출(search_docs)하면 모델이 필요할 때만, 필요한 검색어로 RAG를 호출하는 Agentic RAG가 됩니다.
여러 애플리케이션에서 같은 도구를 재사용하고 싶다면 도구를 표준 프로토콜로 노출하는 MCP를 보세요.