본문 바로가기
C.W.K.
Stream
Lesson 04 of 04 · published

원시 HTTP 도구 호출 반복

~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

External links

Exercise

첫 코드 블록의 원시 HTTP 반복에 두 번째 도구 convert_temperature(value: number, from_unit: string, to_unit: string)를 추가해. Flash에게 "서울 날씨 어때? 그 온도를 화씨로 바꿔 줘."라고 물어 모델이 두 도구를 이어 호출하는지, 응답이 사용자 차례에서 functionResponse를 정확히 쓰는지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.