일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 추천시스템
- REACT
- update_state
- humannode
- langgrpah
- subgraph
- removemessage
- langgraph
- rl
- tool_calls
- human-in-the-loop
- 밑바닥부터 시작하는 딥러닝
- RecSys
- Ai
- tool_binding
- pinecone
- summarize_chat_history
- lcel
- 강화학습
- rag
- LangChain
- conditional_edge
- add_subgraph
- 밑바닥부터시작하는딥러닝 #딥러닝 #머신러닝 #신경망
- toolnode
- chat_history
- 강화학습의 수학적 기초와 알고리듬의 이해
- Python
- conditional_edges
- 강화학습의 수학적 기초와 알고리듬 이해
- Today
- Total
타임트리
[LangGraph] Manually Updating the State 본문
LangGraph는 기존의 Naive RAG 나 Advance RAG와는 달리 노드 단위로 흐름을 조절할 수 있다.
특히 노드를 기준으로 중간에 interrupt
를 걸 수 있다. (interrupt_before
, interrupt_after
)
이러한 기능을 활용해서 중간에 사람이 개입하여 판단할 수 있도록 만들 수 있다!
왜 human-in-the-loop 이 필요하지?
- Agent를 전적으로 신뢰할 수 없을 때, 사람이 개입해야 할 필요가 있다.
- 일부 작업에 대해서는 의도대로 실행되고 있는지 확인하기 위해 실행하기 전 사람이 직접 개입하여 "승인" 절차를 도입하고 싶을 수 있다.
이번에는 이전까지 살펴봤던 검색 Tool을 가진 그래프를 활용하고 LLM이 생성한 검색 쿼리가 적절한지 판단하도록 만들어보자.
(LangGraph의 iterrupt_before
기능을 사용해 Tool Node를 호출하기 전 중단해보자.)
전체적인 그래프는 기존과 동일하니까 여기서는 이미 아래와 같이 그래프가 컴파일 된 상태라고 가정하자.
memory = MemorySaver() # 인메모리 객체
graph = flow.compile(checkpointer=memory)
RunnableConfig 정의
우선, 그래프를 실행할 때 설정값을 전달하기 위한 RunnableConfig를 먼저 정의하자.
from langchain_core.runnables import RunnableConfig
init_input = State(messages=[("user", "Modular RAG가 뭔가요?")])
config = RunnableConfig(
configurable={"thread_id": "sample1"}
)
Stream 호출
설정값을 전달하고, stream_mode="values"
로 설정하여 각 노드마다 출력하는 State의 값으로 받아오자. (stream_mode="updates"
의 경우, 각 노드마다 업데이트된 값이 출력됨)
이때, interrupt_before
파라미터로 해당 노드를 호출하기 전 멈출 노드를 지정해주자. (여기서는 tool_node
)
events = graph.stream(
input=init_input,
config=config,
stream_mode="values",
interrupt_before=["tool_node"]
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print() # 마지막 메세지 출력
실행 결과를 보면 아래처럼 chatbot
노드에서 Tool Calls가 포함된 AIMessage
를 반환하고 중단된 것을 확인할 수 있다.
================================ Human Message =================================
Modular RAG가 뭔가요?
================================== Ai Message ==================================
Tool Calls:
tavliy_web_search (call_VZHlK4eRZKu7SA5a7jrRJyMB)
Call ID: call_VZHlK4eRZKu7SA5a7jrRJyMB
Args:
query: Modular RAG
마찬가지로 스냅샷 객체를 만들고, next
메서드로 다음에 방문할 노드를 출력해보면 tool_node
임을 알 수 있다.
snapshot = graph.get_state(config)
snapshot.next
('tool_node',)
1. 검색 결과를 변경해보기
이번에는 다음 노드를 진행하기 전에 중단하고 다음 노드의 결과를 임의로 추가한 뒤 진행하는 방법을 알아보자.
즉, tool_node
를 호출하기 전, 검색 쿼리를 변경하는 것이 조금 더 사실적인 시나리오겠지만, 연습삼아 검색 결과를 임의로 수정해 진행해보자.
Modular RAG에 대한 검색 결과를 아래처럼 말도 안되는 내용으로 검색이 되었다고 가정해보자.
search_result = """
[검색결과임]
Modular RAG는 Sean이 만든 개념으로, 기존의 단방향적 흐름을 가지는 Naive RAG 혹은 Advanced RAG의 단점을 극복하고 모듈 단위로 RAG 요소를 구성함으로써 유연성과 확장성 측면에서 여러 장점을 지닌다. 시스템 구성 요소를 독립적으로 변경하거나 조정 가능하고, 특정 모듈만 확장하거나 교체하여 성능을 개선할 수 있다.
자세한 내용은 [Sean의 블로그](https://sean-j.tistory.com/)를 참고하세요.
"""
그리고, 그래프의 스냅샷(snapshot)에서 마지막 메세지의 tool_call_id
와 일치하는 ToolMessage
를 생성하자.
from langchain_core.messages import ToolMessage
# 스냅샷으로부터 마지막 메세지의 tool_call_id 가져오기
snapshot = graph.get_state(config)
tool_call_id = snapshot.values["messages"][-1].tool_calls[0]["id"] # 'call_VZHlK4eRZKu7SA5a7jrRJyMB'
# ToolMessages 생성
tool_messages = [ToolMessage(content=search_result, tool_call_id=tool_call_id)]
이제 남은 것은 Graph의 상태를 업데이트해주는 것이다. Graph의 상태를 업데이트하기 위해 update_state
메서드를 사용한다.
(참고로 reducer를 사용해 messages
의 value는 list
에 append
되므로 동일하게 적용된다!)
graph.update_state(
config=config,
values={"messages": tool_messages},
as_node="tool_node"
)
update_state: 그래프의 노드 중 하나처럼 작동
params:
- config: RunnableConfig
- values: 업데이트할 값
- as_node: 값의 출처인 노드 지정
State가 잘 업데이트 되었는지 확인해보면, 마지막 메세지의 값이 임의로 집어넣은 ToolMessage
인 것을 확인할 수 있다.
print(graph.get_state(config).values["messages"][-1].pretty_print())
================================= Tool Message =================================
출처: https://sean-j.tistory.com/
Modular RAG는 Sean이 만든 개념으로, 기존의 단방향적 흐름을 가지는 Naive RAG 혹은 Advanced RAG의 단점을 극복하고 모듈 단위로 RAG 요소를 구성함으로써 유연성과 확장성 측면에서 여러 장점을 지닌다. 시스템 구성 요소를 독립적으로 변경하거나 조정 가능하고, 특정 모듈만 확장하거나 교체하여 성능을 개선할 수 있다.
자세한 내용은 [Sean의 블로그](https://sean-j.tistory.com/) 를 참고하세요.
또한, 스냅샷의 next
메서드 호출 결과가 이전에는 tool_node
였지만 이제는 chatbot
으로 변경되었다!
snapshot = graph.get_state(config)
snapshot.next
('chatbot',)
이제 남은 그래프의 과정을 수행해보자. 이를 위해 config와 함께, input으로는 None을 넘겨주면 된다.
remain_events = graph.stream(None, config, stream_mode="values")
for event in remain_events:
if "messages" in event:
event["messages"][-1].pretty_print()
결과를 확인해보면, 의도한 대로 임의로 집어넣은 검색 결과를 바탕으로 LLM이 응답을 생성한 것을 확인할 수 있다.
================================= Tool Message =================================
출처: [https://sean-j.tistory.com/](https://sean-j.tistory.com/)
Modular RAG는 Sean이 만든 개념으로, 기존의 단방향적 흐름을 가지는 Naive RAG 혹은 Advanced RAG의 단점을 극복하고 모듈 단위로 RAG 요소를 구성함으로써 유연성과 확장성 측면에서 여러 장점을 지닌다. 시스템 구성 요소를 독립적으로 변경하거나 조정 가능하고, 특정 모듈만 확장하거나 교체하여 성능을 개선할 수 있다.
자세한 내용은 [Sean의 블로그]([https://sean-j.tistory.com/](https://sean-j.tistory.com/)) 를 참고하세요.
================================== Ai Message ==================================
Modular RAG는 기존의 단방향적 흐름을 가지는 Naive RAG 또는 Advanced RAG의 단점을 극복하기 위해 Sean이 만든 개념입니다. 이 방식은 RAG( retrieval-augmented generation) 요소를 모듈 단위로 구성하여 유연성과 확장성을 제공합니다.
주요 특징은 다음과 같습니다:
- 시스템 구성 요소를 독립적으로 변경하거나 조정할 수 있음.
- 특정 모듈만 확장하거나 교체하여 성능을 개선할 수 있음.
-
- 이러한 특성 덕분에 Modular RAG는 더 많은 유연성과 개선된 성능을 제공할 수 있습니다. 더 자세한 내용은 [Sean의 블로그](https://sean-j.tistory.com/)를 참고하실 수 있습니다.
2. 검색 쿼리를 변경해보기
이번에는 다음 노드를 호출하기 전에 그래프 진행을 중단하고, 이전 노드의 출력을 변경하고 진행하는 방법을 알아보자. 즉, tool_node
를 호출하기 전, tool_calls의 검색 query를 변경해보자.
그래프 진행이 중단된 후 마지막 메세지의 id와 동일한 AIMessage를 생성한 뒤 messages에 갈아끼우면 될 것이라고 쉽게 예상 가능하다!
이전과 동일한 그래프지만, thread_id
를 변경해서 새로 진행해보자.
from langchain_core.runnables import RunnableConfig
init_input = State(messages=[("user", "키보드의 키 위치 변경하는 방법을 알려주세요.")])
config = RunnableConfig(
configurable={"thread_id": "sample2"}
)
# 그래프 스트림 호출
events = graph.stream(
input=init_input,
config=config,
stream_mode="values",
interrupt_before=["tool_node"]
)
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
마찬가지로 chatbot
노드에서 도구 호출을 반환했고, interrupt_before
인자값으로 인해 tool_node
가 호출되기 전 중단되었다.
================================ Human Message =================================
Modular RAG가 뭔가요?
================================== Ai Message ==================================
Tool Calls:
tavliy_web_search (call_Cip0iL0Ajtv5Yqgbn0dZZvbL)
Call ID: call_Cip0iL0Ajtv5Yqgbn0dZZvbL
Args:
query: 키보드 키 위치 변경 방법
실제 해당 쿼리로 tavily 웹 검색을 수행하기 전에, 사용자에게 해당 검색 쿼리를 보여주고 혹시 수정하고 싶다면 수정할 수 있도록 UI를 제공한다든가 하는 방향으로 활용할 수 있다.
이때, 사용자가 키보드 키 위치 변경 방법
라는 검색어를 특정 사이트(여기서는 내 블로그; https://sean-j.tistory.com
)에서만 검색하고 싶다고 해보자. 그럼 현재 add_messages
의 reducer
특성을 활용하여, chatbot
노드에서 반환한 AIMessage
와 동일한 id
를 갖는 메세지를 생성하고, 이를 그래프의 update_state
메서드를 사용하여 업데이트 해주면 된다.
from langchain_core.messages import AIMessage
# 스냅샷
snapshot = graph.get_state(config)
last_message = snapshot.values["messages"][-1]
# 기존 tool_call의 args 변경
update_tool_calls = last_message.tool_calls[0]
update_tool_calls['args'] = {"query": "키보드 키 위치 변경 방법 site:https://sean-j.tistory.com"}
# 기존 id와 동일한 AIMessage 생성
update_message = AIMessage(
content=last_message.content,
tool_calls=[update_tool_calls],
id=last_message.id
)
print(update_tool_calls)
content='' additional_kwargs={} response_metadata={} id='run-317e65da-4604-49f0-85e3-68b5be7fb04d-0' tool_calls=[{'name': 'tavliy_web_search', 'args': {'query': '키보드 키 위치 변경 방법 site:[https://sean-j.tistory.com](https://sean-j.tistory.com/)'}, 'id': ...}]
기존 id와 동일한 AIMessage까지 생성했으니, 그래프의 update_state
메서드로 기존 메세지를 업데이트해주자!
graph.update_state(
config,
values={"messages": [update_message]},
as_node="chatbot"
)
이제 남은 과정을 진행해보면 다음처럼 블로그로부터 검색해와서 답변하는 것을 확인할 수 있다.
events = graph.stream(None, config, stream_mode="values")
for event in events:
if "messages" in event:
event["messages"][-1].pretty_print()
================================== Ai Message ==================================
Tool Calls:
tavliy_web_search (call_HhWzrwfoofs21vM1GJbcAOnZ)
Call ID: call_HhWzrwfoofs21vM1GJbcAOnZ
Args:
query: 키보드 키 위치 변경 방법 site:[https://sean-j.tistory.com](https://sean-j.tistory.com/)
================================= Tool Message =================================
Name: tavliy_web_search
[{"url": "https://sean-j.tistory.com/?page=2", "content": "{\"title\": \"타임트리 - 티스토리\", \"content\": \"[키보드] 키 위치 변경하기. 직장과 집, 휴대용 이런저런 키보드를 바꿔가며 코딩하는 편인데, ...
================================== Ai Message ==================================
키보드의 키 위치를 변경하는 방법은 다음과 같습니다.
1. **Windows 레지스트리 편집기 사용하기**:
- `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout` 경로로 이동합니다.
- `Scancode Map`이라는 값을 변경하여 원하는 키매핑을 hex 코드로 입력합니다.
- 아래의 예시를 참고하여 메모장에 입력한 후, `.reg` 파일로 저장하여 실행하면 됩니다.
**예시**:
```plaintext
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00, \
00,00,00,00, \
03,00,00,00, \
38,00,5b,e0, \ ; 좌측 ALT키를 좌측 Window 키로 할당
5b,e0,38,00, \ ; Window 키를 좌측 ALT키로 할당
00,00,00,00
```
2. **메모장에 입력 후 저장**:
- 위와 같은 내용을 메모장에 입력하고, 파일 형식을 `.reg`로 변경하여 저장합니다.
- 저장한 파일을 실행하여 레지스트리에 적용합니다.
이 과정을 통해 키보드의 키 위치를 원하는 대로 변경할 수 있습니다.
자세한 내용은 [이 링크]([https://sean-j.tistory.com/28)에서](https://sean-j.tistory.com/28) 확인할 수 있습니다.
---
출처:
LangGraph. "LangGraph Quick Start". https://langchain-ai.github.io/langgraph/tutorials/introduction/#requirements
위키독스 - <랭체인LangChain 노트> - LangChain 한국어 튜토리얼🇰🇷 (https://wikidocs.net/book/14314)
'LLM > LangGraph' 카테고리의 다른 글
[LangGraph] Branches for parallel node execution(병렬 노드 실행) (0) | 2024.12.31 |
---|---|
[LangGraph] ToolNode (0) | 2024.12.31 |
[LangGraph] Delete Messages (0) | 2024.12.31 |
[LangGraph] Human Node (LLM이 판단) (0) | 2024.12.30 |
[LangGrpah] Tool Binding (1) | 2024.12.22 |