피드백 루프는 매 스텝 결정.
Planning은 시작 전에 한 번 결정.
복잡한 목표일수록 후자가 길을 잃지 않는다.
planning_system_prompt = """당신은 채용 업무 실행 계획을 수립합니다. 규칙: 1. 실행 전에 전체 계획을 먼저 완성. 2. 평가기준표는 하나만 조회. 3. 이력서 비교가 필요하면 모든 이력서를 먼저 조회 후 평가. 출력 형식 (JSON만): { "goal": "목표 요약", "plan": [ {"step": 1, "tool": "툴이름", "desc": "이 단계에서 할 일"}, {"step": 2, "tool": "툴이름", "desc": "..."} ] }""" def make_plan(goal): messages = [{"role": "system", "content": planning_system_prompt}, {"role": "user", "content": goal}] message = generate_response(messages, tools) return json.loads(message.content)
def execute_plan(plan, goal): history = [{"role": "user", "content": f"목표: {goal}\n계획: {plan['plan']}"}] for step_info in plan["plan"]: step_num = step_info["step"] desc = step_info["desc"] for inner in range(5): messages = [{"role": "system", "content": execution_system_prompt}, *history, {"role": "user", "content": f"Step {step_num}만: {desc}"}] message = generate_response(messages, tools, "auto") if not message.tool_calls: # 이 step 완료 history.append({"role": "assistant", "content": message.content}) break for tc in message.tool_calls: result = TOOLS[tc.function.name](**json.loads(tc.function.arguments)) history.append({"role": "tool", "tool_call_id": tc.id, ...})
외곽 for = 계획대로 step 순회. 내부 for = step 안에서 tool_calls 반복.
step 종료 조건 = tool_calls 없음.
goal = """소프트웨어 엔지니어링 채용공고를 분석하고 내용에 맞는 후보자를 RAG로 찾아서 이력서를 조회해줘. 그리고 평가결과를 엑셀에 저장해줘.""" # 1. 계획 plan = make_plan(goal) # → { # "goal": "...", # "plan": [ # {"step":1, "tool":"execute_sql", "desc":"채용공고 조회"}, # {"step":2, "tool":"retrieve_from_rag", "desc":"후보자 검색"}, # {"step":3, "tool":"execute_sql", "desc":"이력서 조회"}, # {"step":4, "tool":"save_excel", "desc":"평가결과 저장"} # ] # } # 2. 실행 history = execute_plan(plan, goal)
평가·저장·보고서·엑셀 컬럼명까지 모든 규칙이 한 시스템 프롬프트에 쌓인다.
컨텍스트가 길어질수록 LLM이 헷갈리기 시작한다.