AI 에이전트 설계 패턴 — ReAct, CoT, Tool Use를 Python으로 구현하기
·1966 단어수·10 분
작성자
Engineer
목차
목차
“LLM 앱"과 “AI 에이전트"는 다릅니다. LLM 앱은 입력을 받아 출력을 반환하는 단방향 파이프라인입니다. AI 에이전트는 목표를 달성하기 위해 스스로 계획을 세우고, 도구를 사용하고, 결과를 평가하며, 필요하면 방향을 바꾸는 자율적인 시스템입니다.
2026년 현재 에이전트는 개발 자동화, 데이터 분석, 리서치 어시스턴트 등 다양한 분야에서 실무 투입이 활발합니다. 이 글에서는 에이전트를 구성하는 세 가지 핵심 패턴 — ReAct, Chain of Thought, Tool Use — 을 Python으로 직접 구현하며 내부 동작 원리를 이해합니다.
fromopenaiimport OpenAI
client = OpenAI()defchain_of_thought(problem:str, model:str="gpt-4o")->str:"""Zero-shot Chain of Thought 프롬프팅""" response = client.chat.completions.create( model=model, messages=[{"role":"user","content":f"{problem}\n\n단계별로 생각해 보겠습니다:"}], temperature=0, max_tokens=1000,)return response.choices[0].message.content
# 예시: 복잡한 계산problem ="""
어느 창고에 물건이 있습니다.
월요일에 전체의 1/3을 출고했습니다.
화요일에 남은 것의 1/4을 입고받았습니다.
수요일에 현재 물건의 40%를 출고했습니다.
처음에 900개가 있었다면, 수요일 출고 후 몇 개가 남았나요?
"""result = chain_of_thought(problem)print(result)
FEW_SHOT_COT_PROMPT ="""다음 예시를 참고해 문제를 풀어주세요.
예시 1:
문제: 사탕이 15개 있습니다. 3명에게 똑같이 나누면 1명당 몇 개인가요?
풀이:
1. 전체 사탕: 15개
2. 나눌 사람 수: 3명
3. 1명당 사탕 = 15 ÷ 3 = 5개
답: 5개
예시 2:
문제: 시속 60km로 달리는 차가 2시간 30분 동안 이동한 거리는?
풀이:
1. 속도: 60 km/h
2. 시간: 2시간 30분 = 2.5시간
3. 거리 = 60 × 2.5 = 150 km
답: 150 km
이제 풀어주세요:
문제: {problem}풀이:"""deffew_shot_cot(problem:str)->str: response = client.chat.completions.create( model="gpt-4o", messages=[{"role":"user","content": FEW_SHOT_COT_PROMPT.format(problem=problem)}], temperature=0,)return response.choices[0].message.content
importjsonimportrequestsfromtypingimport Callable, Any
fromopenaiimport OpenAI
client = OpenAI()# 도구 레지스트리 — 이름으로 함수를 찾아 실행TOOL_REGISTRY:dict[str, Callable]={}defregister_tool(func: Callable)-> Callable:"""데코레이터로 도구 등록""" TOOL_REGISTRY[func.__name__]= func
return func
@register_tooldefsearch_web(query:str)->str:"""웹에서 정보를 검색합니다."""# 실제로는 Tavily, SerpAPI 등 사용returnf"'{query}'에 대한 검색 결과: [예시 결과]"@register_tooldefcalculate(expression:str)->str:"""수학 표현식을 계산합니다."""try:# eval은 보안 위험 — 실제로는 sympy 등 사용 result =eval(expression,{"__builtins__":{}})returnstr(result)except Exception as e:returnf"계산 오류: {e}"@register_tooldefget_current_time()->str:"""현재 날짜와 시간을 반환합니다."""fromdatetimeimport datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")@register_tooldefread_file(filepath:str)->str:"""파일 내용을 읽습니다."""try:withopen(filepath,"r", encoding="utf-8")as f:return f.read()[:2000]# 처음 2000자만except FileNotFoundError:returnf"파일 없음: {filepath}"# OpenAI tools 스키마 정의TOOLS =[{"type":"function","function":{"name":"search_web","description":"최신 정보나 모르는 사실을 웹에서 검색합니다.","parameters":{"type":"object","properties":{"query":{"type":"string","description":"검색 쿼리"}},"required":["query"]}}},{"type":"function","function":{"name":"calculate","description":"수학 계산을 수행합니다.","parameters":{"type":"object","properties":{"expression":{"type":"string","description":"계산할 수식 (예: 2 + 3 * 4)"}},"required":["expression"]}}},{"type":"function","function":{"name":"get_current_time","description":"현재 날짜와 시간을 조회합니다.","parameters":{"type":"object","properties":{}}}},]defexecute_tool(tool_name:str, tool_args:dict)->str:"""도구 이름과 인수로 실제 함수 실행"""if tool_name notin TOOL_REGISTRY:returnf"알 수 없는 도구: {tool_name}" func = TOOL_REGISTRY[tool_name]return func(**tool_args)
classSafeReActAgent(ReActAgent):def __init__(self,*args,**kwargs):super().__init__(*args,**kwargs)self.tool_call_history:list[str]=[]def_detect_loop(self, tool_name:str, tool_args:dict)->bool:"""동일한 도구를 같은 인수로 3번 이상 호출하면 루프로 판단""" call_signature =f"{tool_name}:{json.dumps(tool_args, sort_keys=True)}"self.tool_call_history.append(call_signature)returnself.tool_call_history.count(call_signature)>=3
classOrchestratorAgent:"""여러 전문 에이전트를 조율하는 오케스트레이터"""def __init__(self):self.research_agent = ReActAgent( tools=TOOLS, model="gpt-4o",)self.writer_agent = ReActAgent( tools=[],# 도구 없이 글쓰기만 model="gpt-4o",)self.reviewer_agent = ReActAgent( tools=[], model="gpt-4o-mini",# 검토는 저렴한 모델로)defrun(self, topic:str)->str:"""리서치 → 작성 → 검토 파이프라인"""# 1단계: 리서치 에이전트가 정보 수집 research_result =self.research_agent.run(f"'{topic}'에 대해 웹에서 최신 정보를 검색하고 핵심 내용을 정리해 주세요.")# 2단계: 작성 에이전트가 블로그 포스트 작성 draft =self.writer_agent.run(f"다음 리서치 내용을 바탕으로 기술 블로그 포스트를 작성해 주세요:\n\n{research_result}")# 3단계: 검토 에이전트가 품질 확인 final =self.reviewer_agent.run(f"다음 블로그 포스트를 검토하고 개선해 주세요:\n\n{draft}")return final
결정론적 도구 사용: 에이전트가 외부 API를 호출할 때는 멱등성(idempotent)이 있는 도구를 우선 사용합니다. 같은 작업을 두 번 실행해도 부작용이 없어야 합니다. 이메일 발송, 주문 처리 같은 부작용이 있는 도구는 반드시 사람의 확인을 거치는 Human-in-the-Loop 패턴을 적용합니다.
컨텍스트 길이 관리: 에이전트 루프를 반복할수록 메시지 히스토리가 쌓여 토큰을 소모합니다. 오래된 도구 결과를 요약해 압축하는 전략이 필요합니다.
에러 복구: 도구 실행 실패 시 에이전트가 대안을 찾도록 프롬프트를 설계합니다. “도구 호출에 실패하면 다른 방법을 시도하세요"라는 시스템 프롬프트가 도움이 됩니다.
ReAct, CoT, Tool Use는 서로 독립된 패턴이 아닙니다. 실제 에이전트는 세 패턴을 동시에 활용합니다. CoT로 계획을 세우고, Tool Use로 실행하고, ReAct 루프로 결과를 평가하며 반복합니다.
에이전트 개발의 핵심은 시스템 프롬프트 설계입니다. LLM이 언제 도구를 사용하고, 언제 멈추고, 어떻게 에러에 대응할지를 명확히 지시하는 것이 코드보다 중요합니다. 탄탄한 시스템 프롬프트 위에 단순한 루프 코드를 얹는 것이 과도하게 복잡한 프레임워크를 쓰는 것보다 대부분의 경우 더 잘 동작합니다.