~14 min · raw-http, tool-loop, function-calling, no-sdk
Level 0Spark
0 XP0/35 lessons0/10 achievements
0/140 XP to next level140 XP to go0% complete
같은 loop, SDK 없이
Track 5 의 agentic tool loop 가 raw HTTP 와 동일하게 동작. Wire format 이 정확히 SDK 가 너 위해 만들고 있던 거. 제약된 환경에 박혀있을 때, wire-level 이슈 디버깅할 때, Gemini SDK 없는 언어로 loop 구현할 때 유용.
Wire 의 모양
Tool declaration 이 request body 의 tools field 에. 모델 response 가 functionCall part 포함; 다음 user turn 에 functionResponse part 로 답함. SDK 와 같음, spell out 만.
Code
Raw HTTP tool loop — ~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 통한 같은 아이디어 — body 를 'request' 로 wrap·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
첫 코드 블록의 raw HTTP loop 잡고 두 번째 tool 추가: convert_temperature(value: number, from_unit: string, to_unit: string). Flash 한테 물어: "서울 날씨 어때 + 그 온도 화씨로 변환?" 모델이 두 tool chain 하고 너 response 가 user turn 에 functionResponse 정확히 사용 확인.
Progress
Progress is local-only — sign in to sync across devices.