5

OpenAI Function Calling 완전 정복

Day 2: Tool 정의 & Function Calling

학습 목표
  • OpenAI Function Calling API의 전체 흐름을 이해한다
  • tool_choice 옵션의 차이를 구분한다
  • 병렬 Tool 호출 패턴을 이해한다

Function Calling이란?

LLM에게 "이런 Tool들이 있어"라고 알려주면, LLM이 스스로 언제 어떤 Tool을 쓸지 결정한다.

이것이 OpenAI의 Function Calling 이다.


전체 API 호출 구조

from openai import OpenAI
import json

client = OpenAI()

# 1. Tool 정의
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_equipment_status",
            "description": "설비 상태 조회...",
            "parameters": {
                "type": "object",
                "properties": {
                    "equipment_id": {
                        "type": "string",
                        "description": "설비 ID"
                    }
                },
                "required": ["equipment_id"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "check_inventory",
            "description": "부품/자재 재고 확인...",
            "parameters": {
                "type": "object",
                "properties": {
                    "item_code": {
                        "type": "string",
                        "description": "부품 코드"
                    }
                },
                "required": ["item_code"]
            }
        }
    }
]

# 2. LLM 호출 (tools 전달)
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "설비 관리 Agent입니다."},
        {"role": "user", "content": "CNC-001 상태 알려줘"}
    ],
    tools=tools,          # Tool 목록
    tool_choice="auto"    # LLM이 자동 판단
)

# 3. 결과 확인
message = response.choices[0].message
if message.tool_calls:
    # Tool 호출 필요
    for tc in message.tool_calls:
        print(f"Tool: {tc.function.name}")
        print(f"Args: {tc.function.arguments}")
else:
    # 직접 응답
    print(message.content)

tool_choice 옵션

옵션의미사용 시기
"auto"LLM이 알아서 결정일반적인 경우 (권장)
"none"Tool 사용 금지일반 대화만 원할 때
"required"반드시 Tool 사용항상 조회가 필요할 때
{"type": "function", "function": {"name": "..."}}특정 Tool 강제테스트, 특수 상황
# 예: 설비 상태 질문이면 반드시 Tool 호출
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    tools=tools,
    tool_choice="required"  # 무조건 Tool 호출
)

제조 현장 팁: 추측이 아니라 실제 데이터를 조회하게 하려면 "required"가 쓸모 있다. 다만 첫 턴에만 걸어야 한다. 루프 안에서 매 턴 "required"를 그대로 재사용하면, 도구 결과를 받아 최종 답을 쓰려는 마지막 턴에서도 또 도구를 부르게 되어 max_iterations까지 돌고 끝난다.

choice = "required" if iteration == 1 else "auto"
response = client.chat.completions.create(..., tool_choice=choice)

병렬 Tool 호출

하나의 질문에서 여러 Tool이 동시에 호출될 수 있다.

# 질문: "CNC-001이랑 CNC-003 상태 비교해줘"

# LLM 응답: 2개의 tool_calls
message.tool_calls = [
    {
        "id": "call_001",
        "function": {
            "name": "get_equipment_status",
            "arguments": '{"equipment_id": "CNC-001"}'
        }
    },
    {
        "id": "call_002",
        "function": {
            "name": "get_equipment_status",
            "arguments": '{"equipment_id": "CNC-003"}'
        }
    }
]

# 처리: 각 호출 결과를 개별 메시지로 추가
for tc in message.tool_calls:
    args = json.loads(tc.function.arguments)
    result = get_equipment_status(**args)
    messages.append({
        "role": "tool",
        "tool_call_id": tc.id,   # 반드시 매칭!
        "content": result
    })

여기서 "병렬"은 모델 쪽 이야기다. 모델이 한 번에 여러 tool_calls를 냈다는 뜻이지, 위 for 문이 동시에 실행된다는 뜻이 아니다. 실제로 동시에 부르려면 concurrent.futures.ThreadPoolExecutor나 asyncio로 우리가 직접 묶어야 한다. API의 parallel_tool_calls 파라미터는 모델이 한 번에 여러 호출을 내도 되는지를 정할 뿐 실행 방식을 바꾸지 않는다(parallel_tool_calls=False로 두면 한 턴에 하나씩만 낸다).


메시지 흐름 정리

일반 대화:
  user -> assistant

Tool 1회 호출:
  user -> assistant(tool_calls) -> tool(결과) -> assistant(최종답)

Tool 2회 연쇄 호출:
  user -> assistant(tool_calls 1) -> tool(결과 1)
       -> assistant(tool_calls 2) -> tool(결과 2)
       -> assistant(최종답)

병렬 Tool 호출:
  user -> assistant(tool_calls [A, B])
       -> tool_A(결과), tool_B(결과)
       -> assistant(최종답)

Anthropic Claude의 Tool Use (비교)

OpenAI와 거의 동일하지만 형식이 조금 다르다.

import anthropic

client = anthropic.Anthropic()

# Claude Tool 정의
tools = [
    {
        "name": "get_equipment_status",
        "description": "설비 상태 조회...",
        "input_schema": {           # OpenAI: parameters
            "type": "object",
            "properties": {
                "equipment_id": {
                    "type": "string",
                    "description": "설비 ID"
                }
            },
            "required": ["equipment_id"]
        }
    }
]

# 모델 ID는 이 상수 한 곳에서만 관리한다. 스냅샷 이름은 자주 바뀌므로
# 분기마다 공급자 문서에서 현재 모델 목록을 확인하고 갱신한다.
CLAUDE_MODEL = "claude-sonnet-4-20250514"

response = client.messages.create(
    model=CLAUDE_MODEL,
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "CNC-001 상태 알려줘"}
    ]
)
항목OpenAIAnthropic
Tool 정의 키parametersinput_schema
응답 구조tool_calls 배열content 블록
Tool 결과 role"tool""user" (tool_result)
모델gpt-4o-miniclaude-sonnet

핵심 개념은 같다. Schema를 정의하고, 모델이 판단하고, 결과를 돌려준다.

그런데 같은 도구를 두 번 쓴 셈이다. get_equipment_status 하나를 OpenAI용 (parameters)과 Anthropic용(input_schema)으로 각각 맞춰 적었고, 결과를 돌려주는 메시지 형식도 다르게 짰다. 도구가 다섯이고 공급자가 둘이면 이 작업이 열 번이다. 공급자가 셋으로 늘거나 도구를 새로 붙일 때마다 같은 일을 되풀이한다.

이 반복을 없애려고 도구 쪽에 공통 규격을 두자는 것이 **MCP(Model Context Protocol)**의 출발점이다. Week 5 첫날에 이것부터 다룬다. 오늘은 "도구를 붙일 때마다 통합을 새로 쓴다"는 불편을 몸으로 겪어 두는 것으로 충분하다.

AI로 학습하기 — 꿀팁
tool_choice 옵션 비교 코드 생성AI 학습 팁

OpenAI Function Calling의 tool_choice 옵션별 동작 차이를 제조 Tool 예시로 직접 코드화해두면 실습에 바로 활용됩니다.

설비 상태 조회(get_equipment_status)와 알람 발생(trigger_alarm) 두 Tool을 정의하고, tool_choice를 'auto', 'none', {'type': 'function', 'function': {'name': '...'}}로 각각 설정했을 때 OpenAI API 호출 코드와 예상 응답 차이를 Python 코드로 보여줘. 제조 현장에서 각 옵션을 언제 써야 하는지도 주석으로 달아줘.
이 팁이 도움이 됐나요?
용어