티스토리 뷰
Python으로 공부하는 OpenAI 20편 — 이제는 답만 하지 말고, 필요할 때 도구를 호출하게 만들어야 합니다
octo54 2026. 5. 20. 11:22Python으로 공부하는 OpenAI 20편 — 이제는 답만 하지 말고, 필요할 때 도구를 호출하게 만들어야 합니다
여기까지 오면 슬슬 이런 생각이 듭니다.
“챗봇이 말을 잘하는 건 알겠는데… 진짜 서비스는 결국 뭔가를 해야 하지 않나?”
맞아요.
사용자 입장에서는 AI가 설명만 잘하는 것보다, 필요할 때 검색하고, 파일을 찾고, 내 함수나 시스템을 호출해서 실제 행동으로 이어지는 것이 훨씬 크게 느껴집니다.
예를 들면 이런 거죠.
- “내 문서에서 찾아서 답해줘”
- “현재 재고 조회해서 알려줘”
- “이 이메일 초안 만들어줘”
- “오늘 환율 확인해서 계산해줘”
- “이 이미지 보고 요약한 다음, 우리 DB에 저장해줘”
이런 순간부터는 단순 프롬프트만으로는 한계가 옵니다.
그때 필요한 게 tool calling, 그리고 조금 더 넓게 보면 agent-like workflow입니다.
OpenAI 공식 문서는 function calling을 Responses API 기반의 다단계 흐름으로 설명합니다. 큰 흐름은 이렇습니다.
- 모델에 사용할 수 있는 도구를 알려주고
- 모델이 tool call을 반환하면
- 애플리케이션이 실제 코드를 실행하고
- 그 결과를 다시 모델에 전달해
- 최종 답변이나 추가 tool call을 받는 구조입니다. (OpenAI 개발자)
이번 글에서는 이걸 Python + FastAPI 기준으로, 너무 과하지 않게 잡아보겠습니다.
1. 언제 단순 프롬프트를 넘어서 tool calling이 필요한가
처음엔 모델이 똑똑해 보여서,
뭐든 그냥 물어보면 될 것 같아요.
근데 실제 서비스에서는 금방 구분이 생깁니다.
단순 프롬프트로 충분한 경우
- 개념 설명
- 글쓰기 보조
- 일반 요약
- 주어진 텍스트 범위 안에서 답변
tool calling이 필요한 경우
- 최신 정보 조회
- 내 시스템/DB/API 호출
- 문서/파일 검색
- 실제 액션 실행
- 여러 단계 작업 연결
OpenAI 공식 “Using tools” 문서는 Responses API와 Agents에서 도구를 확장 수단으로 설명합니다. 여기에는 built-in tools, function calling, remote MCP servers 등이 포함되고, 모델이 검색, 파일 검색, 외부 함수 호출 같은 일을 할 수 있다고 안내합니다. 또 built-in tools로는 file search, web search, computer use 등이 Responses API에서 지원됩니다. (OpenAI 개발자)
즉, 감각적으로 정리하면 이렇습니다.
모델이 “알아서 말해도 되는 문제”는 프롬프트
모델이 “밖에서 뭔가 가져오거나 실행해야 하는 문제”는 툴
이렇게 나누면 훨씬 편합니다.
2. agent를 너무 무겁게 생각할 필요는 없습니다
“에이전트”라고 하면 갑자기 거대한 시스템처럼 느껴지죠.
전문 용어도 많고요.
근데 OpenAI 공식 Agents SDK 설명을 보면, 에이전트는 결국 계획하고, 도구를 호출하고, 상태를 유지하면서 여러 단계를 처리하는 애플리케이션입니다. 즉, 처음부터 복잡한 멀티에이전트 시스템을 만들라는 뜻이 아니라, 필요할 때 툴을 부르는 응답 흐름부터 시작해도 충분하다는 얘기예요. (OpenAI 개발자)
그래서 저는 주니어 개발자에겐 이렇게 설명하는 편입니다.
- 1단계: 프롬프트 기반 응답
- 2단계: 함수 한두 개를 호출하는 tool calling
- 3단계: 여러 툴을 이어서 쓰는 workflow
- 4단계: 필요하면 Agents SDK 검토
이번 글은 딱 2단계~3단계 초입 정도입니다.
괜히 처음부터 에이전트 프레임워크 전체를 들이붓지 않는 게 오히려 좋아요.
3. 지금 기준으로 가장 중요한 도구 분류
OpenAI 문서를 기준으로 보면 도구는 크게 두 축으로 볼 수 있습니다.
1) 내가 만드는 함수 도구
예:
- get_exchange_rate
- search_inventory
- create_support_ticket
- lookup_order_status
이건 애플리케이션 코드가 직접 실행합니다.
OpenAI function calling 문서가 설명하는 전형적인 흐름이 바로 이거예요. 모델이 함수 호출 의도를 반환하면, 앱이 그 함수를 실행하고 결과를 다시 모델에 넘깁니다. (OpenAI 개발자)
2) OpenAI built-in tools
예:
- file search
- web search
- computer use
이건 Responses API 안에서 모델이 직접 사용할 수 있는 도구입니다.
예를 들어 file search는 vector store에 올린 파일에서 semantic/keyword search를 수행할 수 있고, web search는 최신 웹 정보를 찾게 해줍니다. computer use는 UI를 보고 액션을 제안하는 도구로 문서화되어 있습니다. (OpenAI 개발자)
처음엔 이 둘을 섞지 말고 구분해서 이해하는 게 좋아요.
- 내 함수: 비즈니스 로직 연결
- built-in tools: 검색/탐색/외부 정보 확장
4. 가장 먼저 추천하는 구조
FastAPI 기준으로는 이 정도가 가장 덜 꼬입니다.
app/
├── main.py
├── core/
│ ├── config.py
│ └── dependencies.py
├── api/
│ └── routers/
│ └── assistant_router.py
├── schemas/
│ └── assistant.py
├── services/
│ ├── assistant_service.py
│ ├── tool_executor_service.py
│ └── inventory_service.py
└── adapters/
└── openai_client.py
역할은 단순합니다.
- assistant_router.py
요청/응답만 처리 - assistant_service.py
모델 호출 → tool call 해석 → 재호출 흐름 담당 - tool_executor_service.py
tool name에 맞는 실제 함수 실행 - inventory_service.py
실제 비즈니스 기능 - openai_client.py
OpenAI SDK 캡슐화
이 구조를 추천하는 이유는 하나예요.
모델이 무슨 도구를 고를지와 도구가 실제로 무슨 일을 하는지를 분리해야 나중에 덜 무너집니다.
5. 예제로 만들 기능: 재고 조회 도우미
이번 글에서는 너무 복잡하게 안 가고,
상품 재고 조회 함수를 tool calling으로 연결해보겠습니다.
사용자가:
“레드 키보드 재고 있어?”
라고 물으면,
- 모델이 search_inventory 도구를 호출하고
- 우리 백엔드가 실제 재고 함수를 실행하고
- 그 결과를 다시 모델에 전달해서
- 최종 답변을 받는 흐름입니다.
이 예제가 좋은 이유는,
실제 서비스에서 정말 자주 있는 패턴이기 때문이에요.
- 주문 상태 조회
- 재고 조회
- 환율 조회
- 사내 사용자 조회
- 일정 조회
전부 비슷한 구조로 갑니다.
6. 먼저 스키마부터 가볍게 정의합니다
app/schemas/assistant.py
from pydantic import BaseModel, Field
class AssistantRequest(BaseModel):
message: str = Field(min_length=1, max_length=2000)
class AssistantResponse(BaseModel):
answer: str
여기선 단순히 사용자 메시지와 최종 답만 다룹니다.
tool call 상세는 내부에서 처리합니다.
7. 실제 비즈니스 함수는 OpenAI와 분리해야 합니다
이건 정말 중요합니다.
도구 함수 자체는 OpenAI를 몰라야 해요.
그냥 내 서비스 로직이어야 합니다.
app/services/inventory_service.py
class InventoryService:
def __init__(self) -> None:
self._items = {
"레드 키보드": {"stock": 12, "price": 79000},
"블루 마우스": {"stock": 0, "price": 35000},
"화이트 모니터": {"stock": 5, "price": 249000},
}
def search_inventory(self, product_name: str) -> dict:
item = self._items.get(product_name)
if not item:
return {
"found": False,
"product_name": product_name,
}
return {
"found": True,
"product_name": product_name,
"stock": item["stock"],
"price": item["price"],
}
이 함수는 그냥 재고 조회 기능입니다.
tool calling 구조가 없어도 독립적으로 테스트 가능합니다.
이렇게 해두면 좋습니다.
나중에 OpenAI가 아니라 CLI, batch, admin 화면에서도 재사용할 수 있거든요.
8. 툴 실행기는 “모델이 고른 이름”을 “실제 함수”에 연결합니다
이 계층이 있어야 구조가 덜 꼬입니다.
app/services/tool_executor_service.py
import json
from app.services.inventory_service import InventoryService
class ToolExecutorService:
def __init__(self, inventory_service: InventoryService) -> None:
self.inventory_service = inventory_service
def execute(self, tool_name: str, arguments_json: str) -> str:
arguments = json.loads(arguments_json)
if tool_name == "search_inventory":
result = self.inventory_service.search_inventory(
product_name=arguments["product_name"]
)
return json.dumps(result, ensure_ascii=False)
raise ValueError(f"지원하지 않는 tool입니다: {tool_name}")
이 서비스의 핵심은 단순합니다.
- 모델은 tool_name과 arguments를 준다
- 우리 앱은 그걸 실제 함수로 연결한다
- 결과는 다시 JSON 문자열로 반환한다
OpenAI function calling 문서가 설명하는 애플리케이션 쪽 역할이 바로 이겁니다. 모델은 코드를 직접 실행하지 않고, 앱이 실행해야 합니다. (OpenAI 개발자)
9. OpenAI adapter는 도구 정의를 한 군데서 관리하는 편이 좋습니다
이건 정말 추천합니다.
도구 정의를 서비스마다 흩뿌리면 나중에 수정이 너무 피곤해져요.
app/adapters/openai_client.py
from openai import OpenAI
class OpenAIToolCallingAdapter:
def __init__(self, api_key: str, model: str) -> None:
self.client = OpenAI(api_key=api_key)
self.model = model
def first_response(self, user_message: str):
return self.client.responses.create(
model=self.model,
input=user_message,
tools=[
{
"type": "function",
"name": "search_inventory",
"description": "상품명을 받아 현재 재고와 가격 정보를 조회합니다.",
"parameters": {
"type": "object",
"properties": {
"product_name": {
"type": "string",
"description": "조회할 상품명",
}
},
"required": ["product_name"],
"additionalProperties": False,
},
}
],
)
def followup_response(self, input_items: list[dict]):
return self.client.responses.create(
model=self.model,
input=input_items,
)
OpenAI function calling 문서는 모델에 tools를 선언할 때 함수 이름, 설명, parameters(JSON Schema)를 함께 보내는 흐름을 설명합니다. Responses API에서는 tool call과 tool output이 구분된 item으로 다뤄진다고 migration guide도 설명합니다. (OpenAI 개발자)
여기서 중요한 건 JSON Schema입니다.
모델이 어떤 인자를 어떤 이름으로 줘야 하는지 명확하게 해줍니다.
10. 실제 assistant 서비스는 “두 번 호출” 흐름을 가집니다
이게 tool calling의 핵심입니다.
- 첫 호출: 모델이 답하거나 tool call을 제안
- 앱이 tool 실행
- 두 번째 호출: tool output을 넣고 최종 답 받기
app/services/assistant_service.py
class AssistantService:
def __init__(
self,
llm,
tool_executor,
) -> None:
self.llm = llm
self.tool_executor = tool_executor
def answer(self, user_message: str) -> str:
response = self.llm.first_response(user_message)
tool_call = None
for item in response.output:
if getattr(item, "type", "") == "function_call":
tool_call = item
break
if tool_call is None:
return response.output_text
tool_output = self.tool_executor.execute(
tool_name=tool_call.name,
arguments_json=tool_call.arguments,
)
followup_input = [
{
"type": "function_call_output",
"call_id": tool_call.call_id,
"output": tool_output,
}
]
final_response = self.llm.followup_response(followup_input)
return final_response.output_text
이 흐름이 바로 OpenAI가 공식 문서에서 설명하는 tool calling 사이클입니다.
모델이 tool call을 반환하면, 앱이 실행한 뒤 call_id에 연결되는 output을 다시 보내야 합니다. (OpenAI 개발자)
이거 처음 보면 조금 낯설어요.
근데 한 번 손에 익으면 생각보다 명확합니다.
11. FastAPI 라우터는 여기서도 최대한 얇게 갑니다
app/api/routers/assistant_router.py
from typing import Annotated
from fastapi import APIRouter, Depends
from app.schemas.assistant import AssistantRequest, AssistantResponse
from app.services.assistant_service import AssistantService
router = APIRouter(prefix="/assistant", tags=["assistant"])
def get_assistant_service() -> AssistantService:
raise NotImplementedError("실제 앱에서는 dependencies.py에서 주입")
@router.post("", response_model=AssistantResponse)
def ask_assistant(
request: AssistantRequest,
service: Annotated[AssistantService, Depends(get_assistant_service)],
) -> AssistantResponse:
answer = service.answer(request.message)
return AssistantResponse(answer=answer)
이렇게 두면 라우터는 정말 얇습니다.
tool calling 세부 흐름은 전부 서비스 안에 있습니다.
FastAPI 공식 문서가 큰 앱에서 APIRouter와 의존성을 분리하라고 권장하는 이유와도 딱 맞습니다. (OpenAI 개발자)
12. built-in tools와 내 함수 도구를 섞을 때는 역할을 더 분명히 해야 합니다
여기서부터 조금 더 실무적입니다.
예를 들어 이런 흐름이 있을 수 있어요.
- 문서가 있으면 file search 사용
- 최신 정보면 web search 사용
- 내부 재고는 search_inventory 함수 사용
OpenAI “Using tools” 문서와 built-in tool 문서들을 보면, Responses API에서 file search, web search, computer use 같은 도구를 함께 쓸 수 있습니다. file search는 vector store 기반 검색이고, web search는 최신 웹 검색, computer use는 UI 조작용입니다. (OpenAI 개발자)
이때 주의할 점은 하나예요.
검색 도구와 액션 도구를 머릿속에서 분리해야 한다
- file search / web search: 정보 탐색
- function calling: 내 시스템 액션/조회
- computer use: UI 기반 조작
이걸 안 나누면 나중에 “이건 모델이 검색해야 하나, DB를 불러야 하나, 내부 함수를 써야 하나”가 흐려집니다.
13. Agents SDK는 언제 검토하면 좋나
OpenAI Agents SDK 문서는 에이전트를 “계획하고, 툴을 호출하고, 상태를 유지하는 앱”으로 설명하고, 퀵스타트와 실행 가이드를 제공합니다. 즉, tool calling이 많아지고 handoff나 복수 specialist 흐름이 생기면 Agents SDK가 더 잘 맞을 수 있습니다. (OpenAI 개발자)
하지만 저는 보통 이렇게 권합니다.
Responses + function calling으로 충분한 경우
- 툴 1~3개
- 흐름이 짧음
- 라우팅이 단순함
- 서비스 로직이 앱 코드에서 잘 보임
Agents SDK를 검토할 만한 경우
- 여러 도구를 조합해야 함
- handoff가 필요함
- 관측/trace가 중요함
- 상태 관리가 복잡해짐
- planner-like 흐름이 생김
즉, 처음부터 SDK를 무조건 쓰기보다
tool calling 흐름을 먼저 몸으로 익힌 뒤 올라가는 게 더 좋습니다.
14. 멀티모달 + tool calling이 같이 붙을 때 주의할 점
지난 편에서 이미지와 파일을 같이 다뤘죠.
이제 그 위에 tool calling까지 붙으면 흐름이 더 복잡해집니다.
예를 들어 사용자가:
- 제품 사진을 올리고
- “이 제품이 재고 있는지 알려줘”라고 묻고
- 모델은 이미지에서 제품명을 추론한 뒤
- search_inventory 도구를 호출해야 할 수 있습니다
이런 구조는 충분히 가능합니다. Responses API는 텍스트와 이미지 입력을 함께 받고, tool calling도 같은 인터페이스 안에서 확장할 수 있습니다. built-in tool과 function calling도 함께 다룰 수 있습니다. (OpenAI 개발자)
다만 여기서 중요한 건:
- 입력 해석은 멀티모달 계층
- 도구 선택은 모델
- 도구 실행은 앱
- 최종 응답 생성은 다시 모델
이 단계 구분을 지키는 겁니다.
한 함수 안에 다 밀어 넣으면 진짜 금방 헷갈립니다.
15. 자주 하는 실수들
실수 1. 함수 도구 안에 비즈니스 로직과 OpenAI 로직을 같이 넣는다
도구 함수는 그냥 내 서비스 로직이어야 합니다.
OpenAI 흐름은 assistant/service 계층이 담당하는 편이 좋습니다.
실수 2. tool definition을 여기저기 복붙한다
도구 이름, 설명, JSON Schema는 한 군데서 관리하는 편이 낫습니다.
나중에 함수 인자 이름 하나 바뀌어도 바로 꼬입니다.
실수 3. 모델이 tool call을 했는데 무조건 한 번에 끝난다고 생각한다
공식 문서처럼 tool calling은 multi-step flow입니다. 한 번 더 모델에 결과를 넣어야 최종 답을 만드는 경우가 많습니다. (OpenAI 개발자)
실수 4. built-in search와 내 함수 호출을 같은 성격으로 본다
검색은 정보 수집, 함수 호출은 내부 시스템 연동입니다.
역할이 다릅니다.
실수 5. agent라는 이름부터 붙이고 구조는 안 나눈다
이름보다 단계 분리가 더 중요합니다.
라우터, 서비스, tool executor, adapter 정도만 잘 나눠도 훨씬 오래 갑니다.
16. 지금 단계에서 추천하는 가장 현실적인 출발점
주니어 개발자가 FastAPI에 tool calling을 처음 붙일 때는 이 정도가 제일 좋습니다.
- 함수 도구는 1개부터 시작
예: search_inventory - tool definition은 adapter 한 군데에 모으기
- 실제 비즈니스 함수는 OpenAI와 분리
- assistant service가
- 첫 호출
- tool call 확인
- tool 실행
- 두 번째 호출
을 담당하게 만들기
- 라우터는 얇게 유지
- 테스트는
- fake LLM
- fake inventory service
로 분리해서 작성
이 정도만 해도 “말하는 챗봇”에서 “실제로 일하는 백엔드”로 꽤 넘어갑니다.
17. 오늘 글의 핵심 요약
이번 글에서 꼭 가져가야 할 건 이것입니다.
tool calling은 모델이 직접 일을 하는 게 아니라, 모델이 도구 호출 의도를 반환하고 애플리케이션이 실제 코드를 실행한 뒤 그 결과를 다시 모델에 전달하는 구조다.
정리하면:
- OpenAI function calling은 Responses API에서 multi-step conversation으로 동작합니다. 모델에 tools를 주고, tool call을 받고, 앱이 실행하고, 다시 tool output을 넘겨 최종 응답을 받는 흐름입니다. (OpenAI 개발자)
- built-in tools로는 file search, web search, computer use 등이 있고, 내 함수 도구와 역할이 다릅니다. (OpenAI 개발자)
- Agents SDK는 tool calling이 많아지고 handoff, 상태, trace가 복잡해질 때 검토할 만합니다. (OpenAI 개발자)
- FastAPI에서는 라우터, assistant service, tool executor, adapter를 나눠두는 편이 가장 덜 꼬입니다.
여기까지 오면 이제 진짜 체감이 옵니다.
AI가 똑똑하게 “말하는 것”과
AI가 내 시스템 안에서 “일을 하게 만드는 것”은 다르다는 거요.
저는 이 차이를 이해한 뒤부터 백엔드 구조를 보는 눈이 좀 달라졌습니다.
다음 편 예고
다음 글에서는 여기서 더 실무적으로 들어가겠습니다.
Python + FastAPI에서 OpenAI tool calling과 에이전트 흐름을 평가하고 품질을 점검하는 방법
이 주제로,
- tool selection이 잘 되고 있는지 어떻게 볼지
- 잘못된 tool call을 어떻게 줄일지
- eval과 representative test set을 어떻게 만들지
- “되는 것 같다”가 아니라 “안정적으로 된다”를 어떻게 확인할지
까지 이어가보겠습니다.
출처
- OpenAI Function Calling 가이드 — tool calling의 5단계 흐름, Responses API에서의 함수 호출 방식. (OpenAI 개발자)
- OpenAI Using tools 가이드 — built-in tools, function calling, remote MCP servers 개요. (OpenAI 개발자)
- OpenAI File Search 가이드 — Responses API의 file search built-in tool 설명. (OpenAI 개발자)
- OpenAI Web Search 가이드 — Responses API의 web search tool 설명. (OpenAI 개발자)
- OpenAI Computer Use 가이드 — UI를 다루는 computer use tool 설명. (OpenAI 개발자)
- OpenAI Responses Overview — Responses API가 built-in tools와 function calling을 지원한다고 설명. (OpenAI 개발자)
- OpenAI Agents SDK 가이드 — 에이전트는 계획, tool call, 상태 유지가 가능한 애플리케이션이라고 설명. (OpenAI 개발자)
- OpenAI Migrate to the Responses API — tool calls와 tool outputs가 distinct item이며 call_id로 연결된다고 설명. (OpenAI 개발자)
Python, OpenAI, FastAPI, tool calling, function calling, Responses API, Agents SDK, built-in tools, file search, web search, computer use, AI 백엔드, 에이전트 개발, 주니어 개발자, OpenAI Python SDK
'study > Python으로 시작하는 OpenAI 개발 입문' 카테고리의 다른 글
- Total
- Today
- Yesterday
- rag
- node.js
- llm
- Express
- Prisma
- 개발블로그
- SEO최적화
- seo 최적화 10개
- Python
- NestJS
- nodejs
- CI/CD
- 쿠버네티스
- ai철학
- PostgreSQL
- nextJS
- 딥러닝
- Next.js
- kotlin
- 웹개발
- DevOps
- 백엔드개발
- 생성형AI
- 주니어개발자
- JAX
- LangChain
- flax
- JWT
- REACT
- fastapi
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |

