ใ……ใ…Žใ…‡
Add CodeWeaver Gradio app
515f392
"""
CodeWeaver LangGraph ์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌ์„ฑ.
LangGraph 6๊ฐ€์ง€ ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์™„๋ฒฝ ๊ตฌํ˜„:
โœ… Conditional Edges: ์งˆ๋ฌธ ์œ ํ˜•, ์บ์‹œ ์—ฌ๋ถ€์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ
โœ… Send API: 3๊ฐœ ๊ฒ€์ƒ‰ ๋…ธ๋“œ ๋ณ‘๋ ฌ ์‹คํ–‰ (fan-out/fan-in)
โœ… Subgraph: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ
โœ… Map-Reduce: Send API๋กœ ๋ณ‘๋ ฌ ๊ฒ€์ƒ‰ โ†’ ๊ฒฐ๊ณผ ๋จธ์ง€
โœ… Checkpointing: MemorySaver๋กœ ๋Œ€ํ™” ์ƒํƒœ ์ €์žฅ
โœ… Pydantic Typed State: ํƒ€์ž… ์•ˆ์ „์„ฑ ๋ณด์žฅ
"""
import logging
from typing import Literal
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import Send
from src.agent.state import AgentState
from src.agent.nodes import (
analyze_question_node,
check_cache_node,
create_plan_node,
classify_intent_node,
search_stackoverflow_node,
search_github_node,
search_official_docs_node,
collect_results_node,
evaluate_results_node,
refine_search_node,
filter_and_score_node,
summarize_results_node,
generate_answer_node,
return_cached_answer_node,
generate_with_history_node,
handle_too_many_questions_node,
initiate_dynamic_search_node,
combine_answers_node,
fanout_multi_questions,
run_single_question_worker_node,
)
logger = logging.getLogger(__name__)
def build_search_subgraph() -> StateGraph:
"""
๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ ์„œ๋ธŒ๊ทธ๋ž˜ํ”„๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
ํ๋ฆ„: filter_and_score โ†’ summarize_results
์ด ์„œ๋ธŒ๊ทธ๋ž˜ํ”„๋Š” ๋ฉ”์ธ ๊ทธ๋ž˜ํ”„์—์„œ ํ•˜๋‚˜์˜ ๋…ธ๋“œ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋ฉฐ,
๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์˜ ํ•„ํ„ฐ๋ง๊ณผ ์š”์•ฝ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
Returns:
์ปดํŒŒ์ผ๋œ ์„œ๋ธŒ๊ทธ๋ž˜ํ”„
"""
# ์„œ๋ธŒ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ (AgentState ์‚ฌ์šฉ)
subgraph = StateGraph(AgentState)
# ๋…ธ๋“œ ์ถ”๊ฐ€
subgraph.add_node("filter_and_score", filter_and_score_node)
subgraph.add_node("summarize_results", summarize_results_node)
# ์„œ๋ธŒ๊ทธ๋ž˜ํ”„ ๋‚ด๋ถ€ ํ๋ฆ„ ์ •์˜
# START โ†’ filter_and_score โ†’ summarize_results โ†’ END
subgraph.add_edge(START, "filter_and_score")
subgraph.add_edge("filter_and_score", "summarize_results")
subgraph.add_edge("summarize_results", END)
return subgraph.compile()
def route_after_analysis(state: AgentState) -> Literal["generate_with_history", "check_cache"]:
"""
์งˆ๋ฌธ ๋ถ„์„ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋‹ค์Œ ๋…ธ๋“œ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
Phase 2: New Routing Structure
Args:
state: ํ˜„์žฌ ์—์ด์ „ํŠธ ์ƒํƒœ
Returns:
- "generate_with_history": ํ›„์† ์งˆ๋ฌธ โ†’ ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€
- "check_cache": ๋…๋ฆฝ ์งˆ๋ฌธ โ†’ ์บ์‹œ ํ™•์ธ
"""
# NOTE: ๊ณผ๊ฑฐ ์ฒดํฌํฌ์ธํŠธ/๊ตฌ๋ฒ„์ „ ์ƒํƒœ๊ฐ’ ํ˜ธํ™˜์„ ์œ„ํ•ด ๊ตฌ๊ฐ’๋„ ๋งคํ•‘ ์ฒ˜๋ฆฌ
raw_qtype = state.question_type or "independent"
legacy_map = {
"followup": "clarification",
"cache_candidate": "independent",
"new_search": "independent",
}
question_type = legacy_map.get(raw_qtype, raw_qtype)
if question_type == "clarification":
return "generate_with_history"
# new_topic / independent ๋Š” ๋ชจ๋‘ ์บ์‹œ ํ™•์ธ(ํžˆํŠธ๋ฉด ๊ฒ€์ƒ‰ ์ƒ๋žต, ๋ฏธ์Šค๋ฉด ๊ฒ€์ƒ‰)
return "check_cache"
def route_after_plan(state: AgentState) -> Literal["analyze_question", "initiate_dynamic_search", "handle_too_many_questions"]:
"""
create_plan ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋‹ค์Œ ๋…ธ๋“œ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
Phase 4: Dynamic Parallel Search
Args:
state: ํ˜„์žฌ ์—์ด์ „ํŠธ ์ƒํƒœ
Returns:
- "analyze_question": ๋‹จ์ผ ์ฃผ์ œ โ†’ ๊ธฐ์กด ๊ทธ๋ž˜ํ”„ ์‹คํ–‰
- "initiate_dynamic_search": ๋‹ค์ค‘ ์งˆ๋ฌธ (2๊ฐœ) โ†’ Send API๋กœ ๊ทธ๋ž˜ํ”„ 2ํšŒ ์‹คํ–‰
- "handle_too_many_questions": ์งˆ๋ฌธ 3๊ฐœ ์ด์ƒ โ†’ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
"""
plan = state.plan or {}
case = plan.get("case", "single_topic")
if case == "too_many":
return "handle_too_many_questions"
elif case == "multiple_questions":
return "initiate_dynamic_search"
else:
return "analyze_question"
def route_after_cache(state: AgentState) -> Literal["return_cached_answer", "classify_intent"]:
"""
์บ์‹œ ํžˆํŠธ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋‹ค์Œ ๋…ธ๋“œ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
Phase 3 โ†’ Phase 4: create_plan ์ œ๊ฑฐ๋จ (์ด๋ฏธ START์—์„œ ์‹คํ–‰)
Args:
state: ํ˜„์žฌ ์—์ด์ „ํŠธ ์ƒํƒœ
Returns:
- "return_cached_answer": ์บ์‹œ ํžˆํŠธ ์‹œ ์ฆ‰์‹œ ๋‹ต๋ณ€ ๋ฐ˜ํ™˜
- "classify_intent": ์บ์‹œ ๋ฏธ์Šค ์‹œ ์˜๋„ ๋ถ„๋ฅ˜
"""
if state.cached_result:
return "return_cached_answer"
else:
return "classify_intent"
def route_after_generate(state: AgentState) -> Literal["combine_answers", END]:
"""
generate_answer ํ›„ ๋‹ค์Œ ๋…ธ๋“œ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
Phase 4: Dynamic Parallel Search
Args:
state: ํ˜„์žฌ ์—์ด์ „ํŠธ ์ƒํƒœ
Returns:
- "combine_answers": ๋‹ค์ค‘ ์งˆ๋ฌธ โ†’ ๋‹ต๋ณ€ ํ†ตํ•ฉ
- END: ๋‹จ์ผ ์งˆ๋ฌธ โ†’ ์ข…๋ฃŒ
"""
if state.is_multi_question:
return "combine_answers"
return END
def route_after_evaluation(state: AgentState) -> Literal["refine_search", "search_subgraph"]:
"""
๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํ‰๊ฐ€ ํ›„ ๋‹ค์Œ ๋…ธ๋“œ๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
Phase 3: Open Deep Research ํŒจํ„ด - ์ฟผ๋ฆฌ ๊ฐœ์„  ๋ฃจํ”„
Args:
state: ํ˜„์žฌ ์—์ด์ „ํŠธ ์ƒํƒœ
Returns:
- "refine_search": ๊ฒฐ๊ณผ ๋ถ€์กฑ & ๊ฐœ์„  ํšŸ์ˆ˜ 0ํšŒ โ†’ ์ฟผ๋ฆฌ ๊ฐœ์„ 
- "search_subgraph": ๊ฒฐ๊ณผ ์ถฉ๋ถ„ or ๊ฐœ์„  ํšŸ์ˆ˜ 1ํšŒ โ†’ ํ•„ํ„ฐ๋ง ์ง„ํ–‰
"""
needs_refinement = state.needs_refinement
refinement_count = state.refinement_count
# ์•ˆ์ „์žฅ์น˜: ์ตœ๋Œ€ 1ํšŒ๋งŒ ๊ฐœ์„ 
if needs_refinement and refinement_count < 1:
return "refine_search"
else:
return "search_subgraph"
def initiate_parallel_search(state: AgentState):
"""
Send API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ 3๊ฐœ์˜ ๊ฒ€์ƒ‰ ๋…ธ๋“œ๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
LangGraph Send API (Map-Reduce ํŒจํ„ด):
- ๊ฐ ๊ฒ€์ƒ‰ ๋…ธ๋“œ์— ๋™์ผํ•œ state๋ฅผ ์ „์†ก
- ๋ชจ๋“  ๋…ธ๋“œ๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋จ
- ๊ฒฐ๊ณผ๋Š” ์ž๋™์œผ๋กœ ๋จธ์ง€๋จ
Args:
state: ํ˜„์žฌ ์—์ด์ „ํŠธ ์ƒํƒœ
Returns:
Send ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ (fan-out)
"""
# Send API๋ฅผ ์‚ฌ์šฉํ•œ fan-out
# 3๊ฐœ์˜ ๊ฒ€์ƒ‰ ๋…ธ๋“œ๊ฐ€ ๋™์‹œ์— ์‹คํ–‰๋จ
return [
Send("search_stackoverflow", state),
Send("search_github", state),
Send("search_official_docs", state),
]
def build_agent_graph() -> StateGraph:
"""
CodeWeaver ์—์ด์ „ํŠธ์˜ ๋ฉ”์ธ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
Phase 4: Dynamic Parallel Search for Multiple Questions
์ „์ฒด ํ๋ฆ„:
1. START โ†’ create_plan (์งˆ๋ฌธ ์œ ํ˜• ๋ฐ ๊ฐœ์ˆ˜ ํŒ๋‹จ)
2. ์งˆ๋ฌธ ์œ ํ˜•์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ:
- single_topic: analyze_question โ†’ ๊ธฐ์กด ๊ทธ๋ž˜ํ”„
- multiple_questions: initiate_dynamic_search โ†’ Send API (๊ฐ ์งˆ๋ฌธ๋งˆ๋‹ค ๊ธฐ์กด ๊ทธ๋ž˜ํ”„ ๋…๋ฆฝ ์‹คํ–‰)
- too_many: handle_too_many_questions โ†’ END
3. analyze_question โ†’ ์งˆ๋ฌธ ๋ถ„์„
- clarification: generate_with_history โ†’ END
- new_topic/independent: check_cache
4. ์บ์‹œ ํ™•์ธ:
- ํžˆํŠธ: return_cached_answer โ†’ END
- ๋ฏธ์Šค: classify_intent
5. Send API (๋ณ‘๋ ฌ ๊ฒ€์ƒ‰ fan-out):
- classify_intent โ†’ 3๊ฐœ ๊ฒ€์ƒ‰ ๋…ธ๋“œ ๋ณ‘๋ ฌ ์‹คํ–‰
6. collect_results (fan-in) โ†’ evaluate_results
7. ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํ‰๊ฐ€:
- ๋ถ€์กฑ & refinement_count < 1: refine_search โ†’ classify_intent (๋ฃจํ”„)
- ์ถฉ๋ถ„ or refinement_count >= 1: search_subgraph
8. search_subgraph (filter โ†’ summarize)
9. search_subgraph โ†’ generate_answer
10. generate_answer ํ›„ ๋ถ„๊ธฐ:
- is_multi_question: combine_answers โ†’ END
- ๋‹จ์ผ ์งˆ๋ฌธ: END
ํ•ต์‹ฌ ๊ฐœ์„ ์‚ฌํ•ญ (Phase 4):
- โœ… create_plan์„ START๋กœ ์ด๋™ (์งˆ๋ฌธ ๊ฐœ์ˆ˜ ๋จผ์ € ๊ฐ์ง€)
- โœ… Send API๋กœ ๊ธฐ์กด ๊ทธ๋ž˜ํ”„ ์žฌ์‚ฌ์šฉ (์ฝ”๋“œ ์ค‘๋ณต ์—†์Œ)
- โœ… ์งˆ๋ฌธ 3๊ฐœ ์ด์ƒ ์‹œ ์นœ์ ˆํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
- โœ… Reducer ํŒจํ„ด์œผ๋กœ ์ž๋™ fan-in
Returns:
๊ตฌ์„ฑ๋œ StateGraph (์ปดํŒŒ์ผ ์ „)
"""
# ๋ฉ”์ธ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ
graph = StateGraph(AgentState)
# Phase 4: ๊ณ„ํš ์ˆ˜๋ฆฝ (START ์งํ›„)
graph.add_node("create_plan", create_plan_node)
graph.add_node("handle_too_many_questions", handle_too_many_questions_node)
graph.add_node("initiate_dynamic_search", initiate_dynamic_search_node)
# Phase 2: ์งˆ๋ฌธ ๋ถ„์„ & ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ ์ฒ˜๋ฆฌ
graph.add_node("analyze_question", analyze_question_node)
graph.add_node("generate_with_history", generate_with_history_node)
# ์บ์‹œ ๊ด€๋ จ
graph.add_node("check_cache", check_cache_node)
graph.add_node("return_cached_answer", return_cached_answer_node)
# ์˜๋„ ๋ถ„๋ฅ˜
graph.add_node("classify_intent", classify_intent_node)
# Send API๋ฅผ ์œ„ํ•œ ๋ณ‘๋ ฌ ๊ฒ€์ƒ‰ ๋…ธ๋“œ
graph.add_node("search_stackoverflow", search_stackoverflow_node)
graph.add_node("search_github", search_github_node)
graph.add_node("search_official_docs", search_official_docs_node)
# Phase 3: ๊ฒฐ๊ณผ ์ˆ˜์ง‘ ๋ฐ ํ‰๊ฐ€
graph.add_node("collect_results", collect_results_node)
graph.add_node("evaluate_results", evaluate_results_node)
graph.add_node("refine_search", refine_search_node)
# ์ตœ์ข… ๋‹ต๋ณ€ ์ƒ์„ฑ
graph.add_node("generate_answer", generate_answer_node)
# Phase 4: ๋‹ค์ค‘ ์งˆ๋ฌธ ๋‹ต๋ณ€ ํ†ตํ•ฉ
graph.add_node("combine_answers", combine_answers_node)
graph.add_node("run_single_question_worker", run_single_question_worker_node)
# ์„œ๋ธŒ๊ทธ๋ž˜ํ”„ (ํ•„ํ„ฐ๋ง & ์š”์•ฝ)
search_subgraph = build_search_subgraph()
graph.add_node("search_subgraph", search_subgraph)
# ===== ์—ฃ์ง€ ๊ตฌ์„ฑ =====
# 1. START โ†’ create_plan (Phase 4: ์ง„์ž…์  ๋ณ€๊ฒฝ)
graph.add_edge(START, "create_plan")
# 2. create_plan โ†’ ๋ถ„๊ธฐ (Phase 4: ์งˆ๋ฌธ ์œ ํ˜•๋ณ„ ๋ถ„๊ธฐ)
graph.add_conditional_edges(
"create_plan",
route_after_plan,
{
"analyze_question": "analyze_question",
"initiate_dynamic_search": "initiate_dynamic_search",
"handle_too_many_questions": "handle_too_many_questions",
}
)
# 3. handle_too_many_questions โ†’ END
graph.add_edge("handle_too_many_questions", END)
# 4. initiate_dynamic_search๋Š” Send ๋ฆฌํ„ด (๊ฐ Send๊ฐ€ analyze_question์œผ๋กœ)
# ์‹ค์ œ fan-out์€ conditional edge ํ•จ์ˆ˜์—์„œ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•จ
graph.add_conditional_edges(
"initiate_dynamic_search",
fanout_multi_questions,
)
# multi-question worker๋“ค์ด ๋๋‚˜๋ฉด reducer(multi_answers)์— ๋ชจ์ธ ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์นฉ๋‹ˆ๋‹ค.
# Fan-in: ๋‘ worker๊ฐ€ ๋ชจ๋‘ ์ด edge๋กœ ๋“ค์–ด์˜ค๋ฉด combine_answers๋Š” 1ํšŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
graph.add_edge("run_single_question_worker", "combine_answers")
# 5. ์งˆ๋ฌธ ๋ถ„์„ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ
graph.add_conditional_edges(
"analyze_question",
route_after_analysis,
{
"generate_with_history": "generate_with_history",
"check_cache": "check_cache",
}
)
# 6. ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€ โ†’ END
graph.add_edge("generate_with_history", END)
# 7. ์บ์‹œ ํ™•์ธ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ (Phase 4: create_plan ์ œ๊ฑฐ๋จ)
graph.add_conditional_edges(
"check_cache",
route_after_cache,
{
"return_cached_answer": "return_cached_answer",
"classify_intent": "classify_intent",
}
)
# 8. ์บ์‹œ ํžˆํŠธ ์‹œ ์ฆ‰์‹œ ์ข…๋ฃŒ
graph.add_edge("return_cached_answer", END)
# 9. Send API๋ฅผ ์‚ฌ์šฉํ•œ ๋ณ‘๋ ฌ ๊ฒ€์ƒ‰ (fan-out)
graph.add_conditional_edges(
"classify_intent",
initiate_parallel_search,
)
# 10. ๋ชจ๋“  ๊ฒ€์ƒ‰ ๋…ธ๋“œ โ†’ collect_results (fan-in)
graph.add_edge("search_stackoverflow", "collect_results")
graph.add_edge("search_github", "collect_results")
graph.add_edge("search_official_docs", "collect_results")
# 11. collect_results โ†’ evaluate_results
graph.add_edge("collect_results", "evaluate_results")
# 12. ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํ‰๊ฐ€์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ (Phase 3: refine_search ์ถ”๊ฐ€)
graph.add_conditional_edges(
"evaluate_results",
route_after_evaluation,
{
"refine_search": "refine_search",
"search_subgraph": "search_subgraph",
}
)
# 13. ์ฟผ๋ฆฌ ๊ฐœ์„  โ†’ ์˜๋„ ๋ถ„๋ฅ˜ (๋ฃจํ”„)
graph.add_edge("refine_search", "classify_intent")
# 14. ์„œ๋ธŒ๊ทธ๋ž˜ํ”„ โ†’ ์ตœ์ข… ๋‹ต๋ณ€ ์ƒ์„ฑ
graph.add_edge("search_subgraph", "generate_answer")
# 15. ์ตœ์ข… ๋‹ต๋ณ€ ํ›„ ๋ถ„๊ธฐ (Phase 4: ๋‹ค์ค‘ ์งˆ๋ฌธ ์ฒ˜๋ฆฌ)
graph.add_conditional_edges(
"generate_answer",
route_after_generate,
{
"combine_answers": "combine_answers",
END: END
}
)
# 16. combine_answers โ†’ ์ข…๋ฃŒ
graph.add_edge("combine_answers", END)
return graph
def create_agent(enable_checkpointing: bool = True):
"""
CodeWeaver ์—์ด์ „ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ปดํŒŒ์ผํ•ฉ๋‹ˆ๋‹ค.
Args:
enable_checkpointing: ์ฒดํฌํฌ์ธํŠธ ํ™œ์„ฑํ™” ์—ฌ๋ถ€
- True: MemorySaver ์‚ฌ์šฉ (๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ์šฉ)
- False: ์ฒดํฌํฌ์ธํŠธ ์—†์ด ์‹คํ–‰ (์ƒํƒœ ์ €์žฅ ๋ถˆ๊ฐ€)
Returns:
์ปดํŒŒ์ผ๋œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ๊ทธ๋ž˜ํ”„
Note:
ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” MemorySaver ๋Œ€์‹ 
PostgresSaver, SqliteSaver ๋“ฑ ์˜๊ตฌ ์ €์žฅ์†Œ ์‚ฌ์šฉ ๊ถŒ์žฅ
"""
graph = build_agent_graph()
if enable_checkpointing:
# ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ ์ฒดํฌํฌ์ธํ„ฐ (ํ”„๋กœ๋•์…˜์—์„œ๋Š” DB ์‚ฌ์šฉ ๊ถŒ์žฅ)
memory = MemorySaver()
return graph.compile(checkpointer=memory)
else:
return graph.compile()
# ์—์ด์ „ํŠธ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ (๋ชจ๋“ˆ ์ž„ํฌํŠธ ์‹œ ์ž๋™ ์ƒ์„ฑ)
agent = create_agent(enable_checkpointing=True)