저는 최근 Gemini 2.0의 네이티브 도구 호출 기능을 프로젝트에 적용하면서 몇 가지 예상치 못한 오류를 만나았습니다. 가장 기억에 남는 것은 400 Bad Request: Invalid signature 오류였는데, HolySheep AI를 사용하면 인증 방식이 달라져서 발생했습니다. 이 튜토리얼에서는 실제 프로덕션 환경에서 검증한 Gemini 2.0 도구 호출의 완벽한 구현 방법과 자주 발생하는 문제 해결 방법을 정리하겠습니다.
Gemini 2.0 도구 호출이란?
Gemini 2.0부터 Google은 OpenAI 스타일의 함수 호출 기능을 네이티브로 지원합니다. 이전 버전에서는 복잡한 프롬프트 엔지니어링이 필요했지만, 이제 모델이 구조화된 JSON 형태로 도구 호출 요청을 반환하고, 개발자가 해당 결과를 다시 모델에 전달하는 방식입니다.
환경 설정 및 필수 사전 조건
HolySheep AI를 통해 Gemini 2.0 API에 접근하면 단일 API 키로 여러 모델을 관리할 수 있어 매우 편리합니다. 먼저 필요한 패키지를 설치합니다.
pip install openai python-dotenv requests
프로젝트 루트에 .env 파일을 생성합니다.
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
MODEL=gemini-2.0-flash
Python 실전 구현
1. 기본 도구 호출 설정
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
client = OpenAI(
api_key=os.getenv("HOLYSHEEP_API_KEY"),
base_url="https://api.holysheep.ai/v1"
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "특정 도시의 현재 날씨를 조회합니다",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "도시 이름 (예: 서울, 도쿄)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "온도 단위"
}
},
"required": ["location"]
}
}
}
]
messages = [
{"role": "user", "content": "서울 날씨가 어떻게 돼?"}
]
response = client.chat.completions.create(
model=os.getenv("MODEL"),
messages=messages,
tools=tools,
tool_choice="auto"
)
print("도구 호출 응답:")
print(response.choices[0].message)
이 코드를 실행하면 모델이 get_weather 도구를 호출하려고 시도합니다. 응답은 다음과 같은 형태입니다:
ChatCompletionMessageToolCall(
id='call_abc123',
function=Function(
arguments='{"location": "서울", "unit": "celsius"}',
name='get_weather'
),
type='function'
)
2. 다중 도구 호출 및 순차 처리
실무에서는 하나의 요청에 여러 도구를 연결해야 하는 경우가 많습니다. 아래는 복잡한 워크플로우를 처리하는 완전한 예제입니다.
import json
from datetime import datetime
def execute_tool_calls(tool_calls, available_tools):
results = []
for tool_call in tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name == "get_weather":
result = simulate_weather_api(arguments["location"], arguments.get("unit", "celsius"))
elif function_name == "get_forecast":
result = simulate_forecast_api(arguments["location"], arguments.get("days", 7))
elif function_name == "send_notification":
result = simulate_notification(arguments["message"], arguments.get("channel", "email"))
else:
result = {"error": f"Unknown function: {function_name}"}
results.append({
"tool_call_id": tool_call.id,
"function_name": function_name,
"result": result
})
return results
def simulate_weather_api(location, unit):
return {
"location": location,
"temperature": 23,
"unit": unit,
"condition": "맑음",
"timestamp": datetime.now().isoformat()
}
def simulate_forecast_api(location, days):
return {
"location": location,
"forecast": [f"Day {i+1}: {20+i}도" for i in range(days)]
}
def simulate_notification(message, channel):
return {
"status": "sent",
"channel": channel,
"message_id": f"msg_{datetime.now().timestamp()}"
}
full_messages = [{"role": "user", "content": "서울 날씨 확인하고 예보도 조회한 다음 Slack으로 알림 보내줘"}]
response = client.chat.completions.create(
model=os.getenv("MODEL"),
messages=full_messages,
tools=tools + [
{
"type": "function",
"function": {
"name": "get_forecast",
"description": "향후 일주일 날씨 예보를 조회합니다",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"},
"days": {"type": "integer", "default": 7}
},
"required": ["location"]
}
}
}
],
{
"type": "function",
"function": {
"name": "send_notification",
"description": "사용자에게 알림을 전송합니다",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string"},
"channel": {"type": "string", "enum": ["email", "sms", "slack"]}
},
"required": ["message"]
}
}
}
)
tool_calls = response.choices[0].message.tool_calls
results = execute_tool_calls(tool_calls, tools)
full_messages.append(response.choices[0].message)
for result in results:
full_messages.append({
"role": "tool",
"tool_call_id": result["tool_call_id"],
"content": json.dumps(result["result"])
})
final_response = client.chat.completions.create(
model=os.getenv("MODEL"),
messages=full_messages,
tools=tools
)
print("최종 응답:", final_response.choices[0].message.content)
3. 스트리밍 모드에서의 도구 호출
실시간 응답이 필요한 채팅 인터페이스에서는 스트리밍 모드를 사용해야 합니다.
stream = client.chat.completions.create(
model=os.getenv("MODEL"),
messages=[{"role": "user", "content": "날씨 알려줘"}],
tools=tools,
stream=True
)
accumulated_content = ""
current_tool_calls = []
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
accumulated_content += delta.content
print(delta.content, end="", flush=True)
if delta.tool_calls:
for tool_call_delta in delta.tool_calls:
if tool_call_delta.index >= len(current_tool_calls):
current_tool_calls.append({
"id": "",
"function": {"name": "", "arguments": ""}
})
if tool_call_delta.id:
current_tool_calls