~14 min · raw-http, tool-loop, function-calling, no-sdk
Level 0불씨
0 XP0/35 lessons0/10 achievements
0/140 XP to next level140 XP to go0% complete
SDK 없이도 같은 반복이 돌아가
트랙 5의 에이전트형 도구 호출 반복은 원시 HTTP에서도 똑같이 동작해. 전송 형식은 SDK가 대신 만들어 주던 바로 그 구조야. 제약된 환경에서 돌리거나, 전송 계층 문제를 디버깅하거나, Gemini SDK가 없는 언어로 반복을 구현할 때 유용해.
전송 구조
도구 선언은 요청 본문의 tools 필드에 들어가. 모델 응답에는 functionCall part가 오고, 다음 사용자 차례에 functionResponse part로 답해. SDK와 같고 다만 모든 구조를 직접 적는다는 차이뿐이야.
Code
원시 HTTP 도구 호출 반복 — 약 50줄·python
import httpx, json
API_KEY = '...' # from env
MODEL = 'gemini-2.5-flash'
BASE = f'https://generativelanguage.googleapis.com/v1beta/models/{MODEL}'
TOOLS = [{
'functionDeclarations': [{
'name': 'get_weather',
'description': 'Get current weather for a location.',
'parameters': {
'type': 'object',
'properties': {
'location': {'type': 'string', 'description': 'City name'},
},
'required': ['location'],
},
}]
}]
def execute(name, args):
if name == 'get_weather':
# Pretend we called a real weather API
return {'temperature_c': 22, 'conditions': 'clear', 'location': args['location']}
return {'error': f'Unknown tool: {name}'}
def run_tool_loop(prompt: str, max_iter: int = 10) -> str:
contents = [{'role': 'user', 'parts': [{'text': prompt}]}]
headers = {'x-goog-api-key': API_KEY, 'Content-Type': 'application/json'}
with httpx.Client(timeout=60) as client:
for _ in range(max_iter):
resp = client.post(
f'{BASE}:generateContent',
headers=headers,
json={'contents': contents, 'tools': TOOLS},
)
resp.raise_for_status()
data = resp.json()
model_content = data['candidates'][0]['content']
parts = model_content.get('parts', [])
calls = [p['functionCall'] for p in parts if 'functionCall' in p]
if not calls:
return ''.join(p.get('text', '') for p in parts)
# Append model's call turn
contents.append(model_content)
# Execute and build user turn
fn_parts = []
for fc in calls:
result = execute(fc['name'], fc.get('args', {}))
fn_parts.append({
'functionResponse': {
'name': fc['name'],
'id': fc.get('id', ''),
'response': {'result': result},
},
})
contents.append({'role': 'user', 'parts': fn_parts})
raise RuntimeError(f'Loop exhausted after {max_iter} iterations')
print(run_tool_loop("What's the weather in Seoul?"))
OAuth에서도 같은 개념 — 본문을 'request'로 감싸기·python
# OAuth version: prepend project, wrap original body in {model, project, request}
def run_tool_loop_oauth(prompt, max_iter=10):
token = get_access_token()
project = load_code_assist(token)
contents = [{'role': 'user', 'parts': [{'text': prompt}]}]
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
}
with httpx.Client(timeout=60) as client:
for _ in range(max_iter):
resp = client.post(
'https://cloudcode-pa.googleapis.com/v1internal:generateContent',
headers=headers,
json={
'model': 'gemini-2.5-flash',
'project': project,
'request': {'contents': contents, 'tools': TOOLS},
},
)
data = resp.json()['response']
model_content = data['candidates'][0]['content']
parts = model_content.get('parts', [])
calls = [p['functionCall'] for p in parts if 'functionCall' in p]
if not calls:
return ''.join(p.get('text', '') for p in parts)
# ... same loop as API key path
첫 코드 블록의 원시 HTTP 반복에 두 번째 도구 convert_temperature(value: number, from_unit: string, to_unit: string)를 추가해. Flash에게 "서울 날씨 어때? 그 온도를 화씨로 바꿔 줘."라고 물어 모델이 두 도구를 이어 호출하는지, 응답이 사용자 차례에서 functionResponse를 정확히 쓰는지 확인해.
Progress
Progress is local-only — sign in to sync across devices.