LLM 엔지니어링9장 · 도구를 쓰는 LLM 과 Agentic Workflow8 / 21← 이전목차다음 →MetaContents

9장. 도구를 쓰는 LLM 과 Agentic Workflow

ReAct

수학 문제를 풀 때 "먼저 넓이를 구하자(생각) → 공식을 쓴다(행동) → 24 가 나왔다(관찰) → 이제 둘레를 구하자(생각)"처럼 머릿속 생각을 적어 가며 풀면 실수가 줄고, 남이 봐도 어디서 틀렸는지 압니다. ReAct 는 LLM 이 이렇게 생각과 행동을 번갈아 적게 하는 방법입니다.

생각, 행동, 관찰을 되풀이하다 답으로 끝나는 ReAct 의 흐름을 카드로 놓은 그림

세 카드가 ReAct 의 한 바퀴입니다. 생각(Thought)에서 다음 할 일을 글로 적고, 행동(Action)에서 도구 이름과 입력을 정해진 꼴로 적고, 관찰(Observation)에는 우리 코드가 도구를 실행한 결과를 붙여 줍니다. 답을 알면 "Final Answer:"로 끝냅니다. 여기서 풀이 노트가 ReAct 의 글입니다.

도구 호출과 ReAct

견줄 것도구 호출(function calling)ReAct(글로 적기)
도구를 부르는 법API 의 tools, tool_calls정해진 꼴의 글(Action: 도구)
필요한 모델도구를 지원하는 모델아무 대화 모델
꼴이 깨질 위험적다(API 가 맞춘다)크다(우리가 글을 해석한다)
생각이 보이나보통 안 보인다생각이 글로 남는다

ReAct 의 글 해석하기

모델이 쓴 글에서 행동 부분을 정규식으로 뽑아냅니다.

Python
import re

reply = """Thought: 회식비 한도를 먼저 찾아야 한다.
Action: search_rules
Action Input: 회식비"""

m = re.search(r"Action:\s*(\w+)\s*Action Input:\s*(.+)", reply)
if m:
    print("도구:", m.group(1), "· 입력:", m.group(2).strip())
final = re.search(r"Final Answer:\s*(.+)", reply)
print("최종 답:", final.group(1) if final else "아직 없음")

도구: search_rules · 입력: 회식비 와 최종 답: 아직 없음 이 나옵니다. 우리 코드가 search_rules 를 실행해 "Observation: …"을 붙여 다시 보내면 모델이 다음 생각을 이어 씁니다.

코드에 나오는 함수

코드쉬운 뜻
import rere 는 regular expression(정규식)의 줄임말이다. 글자의 무늬를 찾는 표준 라이브러리다
re.search(무늬, 글)글에서 무늬에 맞는 첫 곳을 찾는다. 없으면 None 을 돌려준다
r"..."raw(날것) 글자다. \s 같은 역슬래시를 파이썬이 바꾸지 않고 그대로 정규식에 넘긴다
\s*빈칸이나 줄바꿈이 0 개 이상 있다는 뜻이다. s 는 space(빈칸)다
(\w+)글자·숫자·밑줄이 1 개 이상 이어진 것을 묶어 잡는다. w 는 word(낱말)다
(.+)줄 끝까지 아무 글자나 1 개 이상 묶어 잡는다
m.group(1), m.group(2)괄호로 묶어 잡은 첫째, 둘째 조각을 꺼낸다
.strip()앞뒤의 빈칸과 줄바꿈을 떼어 낸다

이 코드가 뜻하는 것

식당 주문서에서 "메뉴:" 칸과 "수량:" 칸만 오려 내 주방에 넘기는 일입니다. 손님이 여백에 무엇을 적었든, 정해진 칸 이름 뒤의 글자만 읽습니다. 여기서 주문서가 reply, "메뉴:" 칸이 Action:, "수량:" 칸이 Action Input: 입니다.

줄하는 일쉽게 말하면
reply = """Thought: ..."""모델이 썼다고 치는 ReAct 글손님이 쓴 주문서
m = re.search(r"Action:\s*(\w+)\s*Action Input:\s*(.+)", reply)행동과 입력을 찾아 잡는다"메뉴:"와 "수량:" 칸을 찾아 오린다
m.group(1), m.group(2).strip()도구 이름과 입력을 꺼낸다오린 칸의 글자를 읽는다
final = re.search(r"Final Answer:\s*(.+)", reply)최종 답이 있는지 찾는다"주문 끝" 표시가 있나 본다
final.group(1) if final else "아직 없음"있으면 답을, 없으면 "아직 없음"을 고른다끝 표시가 없으면 주문을 더 받는다

출력은 "search_rules 도구를 회식비로 불러 달라"는 요청이 있고 아직 답은 없다는 뜻입니다. 비유와 다른 점은, 모델은 주문서 칸 이름을 틀리게 적을 때가 있어 무늬를 못 찾는 일이 생긴다는 것입니다.

최종 답이 왔을 때

관찰을 받은 뒤 모델이 "Final Answer:"로 끝냈다고 해 봅니다. 아래 글은 설명을 위해 지어낸 것이고, 답의 내용은 7장 company_rules.txt 의 제15조와 같게 적었습니다.

Python
import re

# 관찰을 받은 뒤 모델이 썼다고 치는 글(설명용으로 지어낸 글)
reply = """Thought: 제15조에 한도가 있다. 이제 답할 수 있다.
Final Answer: 팀 회식비는 한 사람에 한 번 5만원까지입니다."""

m = re.search(r"Action:\s*(\w+)\s*Action Input:\s*(.+)", reply)
print("행동:", m)
final = re.search(r"Final Answer:\s*(.+)", reply)
print("최종 답:", final.group(1) if final else "아직 없음")
출력
행동: None
최종 답: 팀 회식비는 한 사람에 한 번 5만원까지입니다.
결과읽는 법
행동: NoneAction: 무늬가 없다. 부를 도구가 없다
최종 답: 팀 회식비는 ..."Final Answer:" 뒤의 글을 잡았다. 루프를 끝내고 이 글을 사용자에게 보여 준다

ReAct 루프는 행동이 있으면 도구를 실행해 "Observation:"을 붙이고, 최종 답이 있으면 멈춥니다. 둘 다 없으면 꼴이 깨진 것이므로 다시 묻거나 실패로 셉니다.

주의할 점

  • 작은 모델은 꼴을 자주 어깁니다(Action 없이 끝내기, 관찰을 스스로 지어 쓰기). 모델이 "Observation:"까지 지어 쓰지 않도록 그 글자가 나오면 생성을 멈추게 합니다(stop 인자).
  • 생각 글은 모델의 설명일 뿐 실제 판단의 기록이 아닐 수 있습니다. 생각을 믿기보다 행동과 결과를 확인합니다.
← 이전 절9장 · 자율 루프다음 절 →9장 · 워크플로와 에이전트