Chapter 7의 JSON 패턴을 API가 직접 지원한다.
tools 파라미터로 스펙 전달 → LLM은 별도 필드로 호출 결정.
그리고 그 반복이 ReAct.
tools = [
{
"type": "function",
"function": {
"name": "execute_sql",
"description": "DB에서 이력서 또는 채용공고 조회.
ID/부서명 등 조건이 명확할 때 사용",
"parameters": {
"type": "object",
"properties": {
"sql_query": {"type": "string", "description": "실행할 SQL"},
},
"required": ["sql_query"],
},
},
},
# ... retrieve_from_rag, get_confluence, save_excel, send_email
]
LLM은 description 만 보고 어떤 툴을 부를지 판단한다. 모호하면 잘못 부른다.
— 좋은 description이 곧 좋은 디스패치.
LLM의 내부 추론. message.reasoning으로 노출됨. reasoning 모델에서만 의미 있음.
툴 호출 결정. message.tool_calls에 함수 이름과 파라미터가 담겨 있다.
실행 결과를 role=tool로 history에. tool_call_id로 어떤 호출의 응답인지 매핑.
def handle_request(user_prompt, history, tools, max_steps=10): history.append({"role": "user", "content": user_prompt}) for step in range(max_steps): message = generate_response(history, tools) # 종료: 툴 호출 없으면 최종 답변 if not message.tool_calls: history.append({"role": "assistant", "content": message.content}) return history history.append({"role": "assistant", "content": message.content, "tool_calls": [tc.model_dump() for tc in message.tool_calls]}) # 툴 실행 + 결과 저장 for tc in message.tool_calls: params = json.loads(tc.function.arguments) try: result = TOOLS[tc.function.name](**params) except Exception as e: result = f"툴 실행 실패: {e}" history.append({"role": "tool", "tool_call_id": tc.id, "content": json.dumps(result)})
get_confluence_page_content("00000000")read_excel()
피드백 루프는 매 스텝 결정. 복잡한 목표일수록 LLM 호출이 늘어난다.
다음 챕터에선 '시작 전에 한 번 계획'.