[Langchain] ReAct (2)

Hunie_07·2025년 4월 7일
0

Langchain

목록 보기
15/35

ReAct (1) 에 이어서 계속됩니다

📌 ReAct (Reasoning & Acting)

  • ReAct Agent는 Reasoning과 Acting을 결합한 가장 일반적인 에이전트 형태

  • 에이전트는 행동-관찰-추론 단계를 순환하며 작업을 수행

    • 행동 (act): 모델이 특정 도구(Tool)를 호출
    • 관찰 (observe): 도구의 출력(Tool Message)를 모델에 다시 전달
    • 추론 (reason): 모델이 도구 출력을 바탕으로 다음 행동을 결정 (예: 또 다른 도구를 호출하거나 직접 응답을 생성)
  • 도구 호출(act)과 결과 분석(observe)을 통해 다음 행동을 결정(reason)하는 체계적인 프로세스를 가짐


1️⃣ 도구 (Tool) 정의하기

  • ReAct 도구는 명확한 입출력 인터페이스를 통해 정의

  • 각 도구는 특정 기능을 수행하는 독립적인 컴포넌트로 구현

  • 도구의 입력과 출력 형식을 명확히 정의하여 에이전트와의 상호작용을 보장

1. RAG 체인 생성 (한국어, 영어 구분)

# RAG 체인 생성 (메타데이터를 포함해서 답변 생성)
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_openai import ChatOpenAI

# 질문 템플릿
template = """Answer the question based only on the following context.
Do not use any external information or knowledge. 
If the answer is not in the context, answer "I don't know".
Use the same language as the question.

[Context]
{context}

[Question] 
{question}

[Answer]
"""

# 프롬프트 생성
prompt = ChatPromptTemplate.from_template(template)

# LLM 모델 생성
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 문서 포맷터 함수
def format_docs_with_metadata(docs):
    formatted_docs = []
    for doc in docs:
        content = doc.page_content
        metadata = doc.metadata
        source = metadata.get('source', '출처 없음')
        formatted_docs.append(f"내용: {content}\n출처: {source}")
    return "\n\n".join(formatted_docs)


# RAG 체인 생성 (메타데이터 포함)
def create_rag_chain_with_metadata(vectorstore, top_k=2):
    """벡터 저장소에서 문서를 검색하여 메타데이터를 포함한 답변을 생성하는 RAG 체인 생성"""

    # 벡터 저장소에서 문서를 검색
    retriever = vectorstore.as_retriever(search_kwargs={'k': top_k})
    
    chain = RunnablePassthrough.assign(
        context=lambda x: format_docs_with_metadata(retriever.invoke(x["question"]))
    ) | RunnableParallel(
        context=lambda x: x["context"],
        answer=prompt | llm | StrOutputParser()
    )
    
    return chain

한국어 문서 RAG 체인

# 한국어 RAG 체인 생성
rag_chain_korean = create_rag_chain_with_metadata(db_korean, top_k=4)

# 한국어 RAG 체인 실행
response = rag_chain_korean.invoke({"question": "테슬라 창업자는 누구인가요?"})

pprint(response)

- 출력

{'answer': '테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다.',
 'context': '내용: ### Roadster (2005–2009)\n'
            '\n'
            'Elon Musk는 주류 차량으로 확장하기 전에 프리미엄 스포츠카로 시작하는 전략에 초점을 맞춰 적극적인 역할을 '
            '수행했습니다. 후속 자금 조달에는 Valor Equity Partners (2006)와 Sergey Brin, '
            'Larry Page, Jeff Skoll과 같은 기업가의 투자가 포함되었습니다.\n'
            ...
            }

영어 문서 RAG 체인

# 영어 RAG 체인 생성
rag_chain_english = create_rag_chain_with_metadata(db_english, top_k=4)

# 영어 RAG 체인 실행
response = rag_chain_english.invoke({"question": "Who is the founder of Tesla?"})

pprint(response)

- 출력

{'answer': 'Tesla was founded by Martin Eberhard and Marc Tarpenning.',
 'context': '내용: Tesla, Inc. is an American multinational automotive and clean '
            'energy company. It designs, manufactures, and sells electric '
            'vehicles (BEVs), stationary
            ...
            }

2. RAG 체인을 Tool 객체로 변환

한국어 문서 RAG 도구

# 한국어 RAG 도구 생성 (한국어 문서 벡터 저장소 사용)
rag_tool_korean = rag_chain_korean.as_tool(
    name="rag_korean_db",
    description="한국어 질문에 대한 리비안, 테슬라 관련 문서를 벡터 저장소에서 검색하고, 그 결과와 함께 답변을 생성합니다."
)

print(f"Tool 이름: {rag_tool_korean.name}")
print(f"Tool 설명: {rag_tool_korean.description}")
print(f"Tool 입력 파라미터: ")
pprint(rag_tool_korean.args)

- 출력

Tool 이름: rag_korean_db
Tool 설명: 한국어 질문에 대한 리비안, 테슬라 관련 문서를 벡터 저장소에서 검색하고, 그 결과와 함께 답변을 생성합니다.
Tool 입력 파라미터: 
{'question': {'title': 'Question', 'type': 'string'}}

영어 문서 RAG 도구

# 영어 RAG 도구 생성 (영어 문서 벡터 저장소 사용)
rag_tool_english = rag_chain_english.as_tool(
    name="rag_english_db",
    description="Retrieve and generate answers from the vector store for English questions related to Rivian and Tesla."
)

print(f"Tool 이름: {rag_tool_english.name}")
print(f"Tool 설명: {rag_tool_english.description}")
print(f"Tool 입력 파라미터: ")
pprint(rag_tool_english.args)

- 출력

Tool 이름: rag_english_db
Tool 설명: Retrieve and generate answers from the vector store for English questions related to Rivian and Tesla.
Tool 입력 파라미터: 
{'question': {'title': 'Question', 'type': 'string'}}

2️⃣ 도구 (Tool) 호출하기

  • bind_tools 메서드로 LLM에 도구들을 연결하여 사용 가능하게 함

  • 도구 호출 결과는 ToolCall 객체를 통해 체계적으로 확인할 수 있음

from langchain_openai import ChatOpenAI

# 도구 목록
tools = [rag_tool_korean, rag_tool_english]

# LLM 모델 
llm = ChatOpenAI(model="gpt-4o-mini")

# 모델에 도구를 바인딩 (추가)
llm_with_tools = llm.bind_tools(tools=tools)

# 도구 사용하기 
query = "테슬라 창업자는 누구인가요?"
response = llm_with_tools.invoke(query)

pprint(response)

- 출력

AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_VbUQseK0pJhXvDJGxcKpQ5Au', 'function': {'arguments': '{"question":"테슬라 창업자"}', 'name': 'rag_korean_db'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': ... })

ToolCall 객체 확인

# ToolCall 객체 확인
response.tool_calls

- 출력

[{'name': 'rag_korean_db',
  'args': {'question': '테슬라 창업자'},
  'id': 'call_VbUQseK0pJhXvDJGxcKpQ5Au',
  'type': 'tool_call'}]

영어 쿼리로 실행

# 영어 도구에 대한 질문
query_en = "Who is the founder of Tesla?"
response_en = llm_with_tools.invoke(query_en)

pprint(response_en)

- 출력

AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_vZ6O4dIGNtF5REN2kvHzWkw4', 'function': {'arguments': '{"question":"Who is the founder of Tesla?"}', 'name': 'rag_english_db'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': ... })

ToolCall 객체 확인

# ToolCall 객체 확인
response_en.tool_calls

- 출력

[{'name': 'rag_english_db',
  'args': {'question': 'Who is the founder of Tesla?'},
  'id': 'call_vZ6O4dIGNtF5REN2kvHzWkw4',
  'type': 'tool_call'}]

Tool 과 관련 없는 쿼리로 실행

  • tool_calls 없이 content 답변을 생성하는 모습
# 도구와 관련 없는 질문 테스트
query_test = "오늘 날씨는 어떤가요?"
response_test = llm_with_tools.invoke(query_test)

pprint(response_test)

- 출력

AIMessage(content='죄송하지만, 현재 날씨 정보를 제공할 수 없습니다. 하지만 날씨 관련 정보를 원하시면, 지역이나 특정 날짜에 대해 검색해보실 수 있는 웹사이트를 이용해 보시는 것을 추천드립니다.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': ... })

3️⃣ 도구 (Tool) 실행하기

  • 도구 호출 함수는 AIMessage의 tool_calls를 실행하고 결과를 반환하는 헬퍼 함수로 구현

  • tool_map을 통해 각 도구별 호출을 처리하며 invoke 메소드로 실행

  • 최종 체인은 llm_with_tools와 call_tools를 파이프라인으로 연결하여 구성


1. 도구 이름을 기준으로 매핑 정의

# 도구 맵 생성
tool_map = {
    "rag_korean_db": rag_tool_korean,
    "rag_english_db": rag_tool_english
}

# 도구 맵을 사용하여 도구 이름을 도구 객체로 변환 (도구 이름을 키로 사용)
tool_map["rag_korean_db"].invoke({"question": "테슬라 창업자는 누구인가요?"})

- 출력

{'context': "내용: ### Roadster (2005–2009)\n\nElon Musk는 주류 차량으로 확장하기 전에 프리미엄 스포츠카로 시작하는 전략에 초점을 맞춰 적극적인 역할을 수행했습니다. ... \n출처: data\\테슬라_KR.md",
 'answer': '테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다.'}

영어 RAG 도구

# 도구 맵을 사용하여 도구 이름을 도구 객체로 변환 (영어)
tool_map["rag_english_db"].invoke({"question": "Who is the founder of Tesla?"})

- 출력

{'context': '내용: Tesla, Inc. is an American multinational automotive and clean energy company. It designs, manufactures, and sells electric vehicles (BEVs) ... \n출처: data\\Tesla_EN.md',
 'answer': 'Tesla was founded by Martin Eberhard and Marc Tarpenning.'}

2. 도구 호출 함수 정의

  • 상단에서 도구 호출로 출력된 ToolCall 객체와 동일한 결과
from langchain_core.messages import AIMessage
from langchain_core.runnables import Runnable

# 도구 호출 함수 정의
def call_tools(msg: AIMessage) -> Runnable:
    """
    tool calling helper 함수: AIMessage에 있는 tool_calls를 실행하고 결과를 반환
    """
    tool_calls = msg.tool_calls.copy()
    for tool_call in tool_calls:
        tool_call["output"] = tool_map[tool_call["name"]].invoke(tool_call["args"])
    return tool_calls


# 도구 호출 함수를 사용하여 도구 호출 실행 
print("ToolCall 객체: ")
pprint(response.tool_calls[0])
print("-"*150)

tool_calls = call_tools(response)  # 도구 호출 실행 (AIMessage 객체를 입력으로 사용)
pprint(tool_calls)

- 출력

ToolCall 객체: 
{'args': {'question': '테슬라 창업자'},
 'id': 'call_VbUQseK0pJhXvDJGxcKpQ5Au',
 'name': 'rag_korean_db',
 'output': {'answer': 'Martin Eberhard와 Marc Tarpenning입니다.',
            'context': '내용: ### Roadster (2005–2009)\n'
                       '\n'
                       'Elon Musk는 ...'
                       '출처: data\\테슬라_KR.md'},
 'type': 'tool_call'}
------------------------------------------------------------------------------------------------------------------------------------------------------
[{'args': {'question': '테슬라 창업자'},
  'id': 'call_VbUQseK0pJhXvDJGxcKpQ5Au',
  'name': 'rag_korean_db',
  'output': {'answer': 'Martin Eberhard와 Marc Tarpenning이 테슬라를 창립했습니다.',
             'context': '내용: ### Roadster (2005–2009)\n'
                        '\n'
                        'Elon Musk는 ...'
                        '출처: data\\테슬라_KR.md'},
  'type': 'tool_call'}]

3. 도구 호출 및 실행 체인 정의

  • 역시 동일한 결과
# 도구 호출 체인 생성
search_tool_chain = llm_with_tools | call_tools

# 도구 호출 실행 (한국어 쿼리)
query = "테슬라 창업자는 누구인가요?"
search_response = search_tool_chain.invoke(query)

pprint(search_response)

- 출력

[{'args': {'question': '테슬라 창업자는 누구인가요?'},
  'id': 'call_jd6CWOP7p9p8qQld94SxrKIl',
  'name': 'rag_korean_db',
  'output': {'answer': '테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다.',
             'context': '내용: ### Roadster (2005–2009)\n'
                        '\n'
                        'Elon Musk는 ...'
                        '출처: data\\테슬라_KR.md'},
  'type': 'tool_call'}]

영어 RAG 도구

# 도구 호출 실행 (영어 쿼리)
query = "Who is the founder of Tesla?"
search_response = search_tool_chain.invoke(query)

pprint(search_response)

- 출력

[{'args': {'question': 'Who is the founder of Tesla?'},
  'id': 'call_MLipsR2MBs7aTLzu3tSunZNE',
  'name': 'rag_english_db',
  'output': {'answer': 'Tesla was founded by Martin Eberhard and Marc '
                       'Tarpenning.',
             'context': '내용: Tesla, Inc. is an American multinational '
             			...
                        '출처: data\\Tesla_EN.md'},
  'type': 'tool_call'}]

Tool 과 관련 없는 쿼리로 실행

  • tool_calls 객체가 없어 빈 리스트 반환
# 도구 호출 실행 (한국어 쿼리) - 도구와 관련 없는 질문
query = "오늘 날씨는 어떤가요?"
search_response = search_tool_chain.invoke(query)

pprint(search_response)

- 출력

[]

4️⃣ AgentExecutor

  • AgentExecutor는 LangChain의 기본 에이전트 실행 시스템

  • 에이전트의 계획-실행-관찰 사이클을 자동으로 관리

  • 에이전트의 행동을 모니터링하고 결과를 반환


1. Agent (에이전트) 생성

  • 에이전트는 LLM과 도구를 통합하여 복잡한 작업을 수행하는 시스템

  • 프롬프트 템플릿을 기반으로 사용자 요청을 해석하고 적절한 도구 선택

  • 도구 실행 결과를 분석하여 최종 응답을 생성하는 워크플로우 구현

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent

# 프롬프트 템플릿 정의
prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 사용자의 요청을 처리하는 AI Assistant입니다."),
    ("user", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad")
])

# LLM 모델 생성
llm = ChatOpenAI(model="gpt-4o-mini",temperature=0)

# 도구 목록 생성 
tools = [rag_tool_korean, rag_tool_english]

# 에이전트 생성 (도구 호출)
agent = create_tool_calling_agent(llm, tools, prompt)

2. AgentExecutor (에이전트 실행기) 생성

  • AgentExecutor는 에이전트의 작업 흐름을 관리하고 결과를 처리하는 컴포넌트

  • 사용자 입력부터 최종 출력까지의 전체 프로세스를 조율하고 제어

  • 에러 처리, 로깅, 결과 포맷팅 등 시스템 운영에 필요한 기능 제공

from langchain.agents import AgentExecutor

# 에이전트 실행기 생성
agent_executor = AgentExecutor(
    agent=agent,      # 도구 호출 에이전트
    tools=tools,      # 도구 목록
    verbose=True,     # 상세 로그 출력
    )
# 에이전트 실행 (한국어 쿼리)
response = agent_executor.invoke(
    {"input": "테슬라 창업자는 누구인가요?"}
)

- 출력

> Entering new AgentExecutor chain...

Invoking: `rag_korean_db` with `{'question': '테슬라 창업자는 누구인가요?'}`

{'context': "내용: ### Roadster (2005–2009)\n\nElon Musk는 ... \n출처: data\\테슬라_KR.md", 'answer': '테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다.'}테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다. 이들은 200371일에 테슬라 모터스를 설립하였으며, 각각 CEO와 CFO를 역임했습니다. 이후 Elon Musk가 2004년에 투자하여 회장 겸 최대 주주가 되었습니다.

> Finished chain.

에이전트 실행 결과 출력

# 에이전트 실행 결과 출력
pprint(response)

- 출력

{'input': '테슬라 창업자는 누구인가요?',
 'output': '테슬라 창업자는 Martin Eberhard와 Marc Tarpenning입니다. 이들은 2003년 7월 1일에 테슬라 '
           '모터스를 설립하였으며, 각각 CEO와 CFO를 역임했습니다. 이후 Elon Musk가 2004년에 투자하여 회장 겸 '
           '최대 주주가 되었습니다.'}

에이전트 실행 중간 단계 반환 설정 추가

return_intermediate_steps=True

# 에이전트 실행기 생성 (중간 단계 반환)
agent_executor = AgentExecutor(
    agent=agent,      # 도구 호출 에이전트
    tools=tools,      # 도구 목록
    return_intermediate_steps=True  # 중간 단계 반환 (기본값 False)
    )

# 에이전트 실행 (영어 쿼리)
response = agent_executor.invoke(
    {"input": "Who is the founder of Tesla?"}
)
# 에이전트 실행 결과 출력
pprint(response)

- 출력

{'input': 'Who is the founder of Tesla?',
 'intermediate_steps': [(ToolAgentAction(tool='rag_english_db', tool_input={'question': 'Who is the founder of Tesla?'}, log="\nInvoking: `rag_english_db` with `{'question': 'Who is the founder of Tesla?'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_9im7hyRiUjkxMfpTpg30G8QM', 'function': {'arguments': '{"question":"Who is the founder of Tesla?"}', 'name': 'rag_english_db'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_86d0290411'}, id='run-5fa46d19-3a86-4d43-a25f-cd390c5a5f1b', tool_calls=[{'name': 'rag_english_db', 'args': {'question': 'Who is the founder of Tesla?'}, 'id': 'call_9im7hyRiUjkxMfpTpg30G8QM', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'rag_english_db', 'args': '{"question":"Who is the founder of Tesla?"}', 'id': 'call_9im7hyRiUjkxMfpTpg30G8QM', 'index': 0, 'type': 'tool_call_chunk'}])], tool_call_id='call_9im7hyRiUjkxMfpTpg30G8QM'),
                         {'answer': 'Tesla was founded by Martin Eberhard and '
                                    'Marc Tarpenning.',
                          'context': '내용: Tesla, Inc. is an American '
                          ... '출처: data\\Tesla_EN.md'})],
 'output': 'Tesla was founded by Martin Eberhard and Marc Tarpenning in July '
           '2003. Elon Musk joined the company shortly after, leading its '
           'initial funding and becoming chairman.'}

Tool 과 관련 없는 쿼리로 실행

# 에이전트 실행 (한국어 쿼리) - 도구와 관련 없는 질문
response = agent_executor.invoke(
    {"input": "오늘 날씨는 어떤가요?"}
)

# 에이전트 실행 결과 출력
pprint(response)

- 출력

{'input': '오늘 날씨는 어떤가요?',
 'intermediate_steps': [],
 'output': '죄송하지만, 현재 날씨 정보를 제공할 수 없습니다. 하지만 다른 질문이나 도움이 필요하신 부분이 있다면 말씀해 주세요!'}

0개의 댓글