: 작성 → 검토 → 수정 → 검토
State 정의
Node 생성
Edge 연결
builder.add_edge("draft_resume", "review_resume")
# 조건부 엣지 -> 루프
builder.add_conditional_edges(
"review_resume", choose_next,
{"revise": "revise_resume", "end": END,})
builder.add_edge("revise_resume", "review_resume")
State 정의
Node 생성
Edge 연결
checkpointer 넣기
# 여러 invoke가 같은 체크포인터 객체에 저장된 대화를 사용
checkpointer = InMemorySaver() # 메모리에 저장하는 체크포인터
chat_graph = chat_builder.compile(checkpointer=checkpointer) # <-- 체크포인트 넣어줌!
답변
# 첫 호출에는 지원 자료와 질문을 함께 저장
config_a = {"configurable": {"thread_id": "career-A"}}
question_a1 = "공고와 연결해 제 경험 중 이력서에 강조할 것을 하나 골라 주세요."
first_turn = chat_graph.invoke({
"job_posting": job_posting,
"company_info": company_info,
"applicant_info": applicant_info,
"messages": [HumanMessage(content=question_a1)],
}, config_a)
# 같은 thread_id에 새 질문만 전달하면 저장된 자료와 이전 대화를 이어서 읽음
question_a2 = "그 프로젝트에서 제가 담당한 업무가 드러나도록 이력서에 넣을 한 문장으로 써 주세요."
second_turn = chat_graph.invoke({"messages": [HumanMessage(content=question_a2)]}, config_a)
현재 대화 A가 끝났다
체크포인터에서 Thread_id 기반으로 최신 State를 가져와서
→ 다시 이어서 대화 가능 (무조건 XX)
messages 누적 + 체크포인터 + 같은 thread_id + MessagesPlaceholder → 이전 대화를 이어가는 멀티턴 가능
사용자에게 질문, 사용자 검토, 권한 확인 요청
# 현재 작업 상태 묶음
snapshot = mail_graph.get_state(mail_config)
# 현재 state 데이터
snapshot.values["draft"]
# 현재 사람이 응답하길 기다리는 interrupt
snapshot.interrupts[0]
# 사람에게 요청을 보여주고 그래프를 멈춤
response = interrupt(request)
# 사람이 준 응답을 전달하면서 멈춘 그래프를 다시 실행
Command(resume={request.id: {"action": "approve"}})
# 수정 요청 전달
result = mail_graph.invoke(
Command(
resume={
request.id: {
"action": "edit",
"feedback": "더 짧게 수정해주세요."
}
}
),
mail_config
)
# 수정 후 다시 interrupt()에서 멈췄다면:
revised_snapshot = mail_graph.get_state(mail_config)
revised_request = revised_snapshot.interrupts[0]
State 정의
Node 생성
def human_review(state: NoticeState):
"""초안을 전달해 대기하고, 사람의 선택과 수정 내용을 반환합니다."""
print("검토 노드 시작") # 재개할 때도 이 줄부터 다시 실행되는지 확인
# graph의 실행이 멈추고, 사람의 입력을 받도록 대기 !!!
decision = interrupt(
# 아래의 딕셔너리는 정의된 형태의 딕셔너리 아님! 사용자에게 안내할 내용을 딕셔너리로 정리함 , 문자열 넣어도됨
{
"stage": "notice_review", # 화면에서 안내문 검토 단계임을 구분할 이름
"draft": state["draft"],
"actions": ["approve", "edit", "reject"],
"guide": "approve: 승인, edit: 수정 요청, reject: 반려",
}) # 초안과 선택지를 전달하고 중단. 재개 시 사람의 응답 딕셔너리를 반환
# command(resume) <-- 동작 재개
# decision 딕셔너리 key : action, feedback, edited_text
return {
"decision": decision["action"], # 응답 전체에서 선택한 경로만 State에 저장
"feedback": decision.get("feedback", ""),
# 사람이 최종 문장을 직접 고쳤다면 그 내용을 새 초안으로 반영
"draft": decision.get("edited_text", state["draft"]),
}
Edge 연결
# 스트리밍 응답
# (1) 중간 진행 상황 확인 하기 위한 용도
# (2) 최종 응답에 대해서 스트리밍으로 받기 위한 용도 -> 글자가 쭈르르륵 (다 완성해서 받는게 아니라 생성할 때마다 받기)
v2 vs v3 스트리밍
: LangGraph의 State와 실행 위치를 SQLite 파일에 저장 → 커널을 재시작해도 작업을 이어서 실행
# SQLite 연결 + 그래프 생성
with SqliteSaver.from_conn_string(str(chat_db)) as checkpointer:
graph = builder.compile(checkpointer=checkpointer)
# 저장된 State 조회
saved = graph.get_state(config)
# 실행 → State가 SQLite에 저장
graph.invoke(input, config)