latentlearn-agent / graph.py
howcloudy's picture
Deploy LatentLearn Python Agent Backend to Hugging Face Space
89d813f
Raw
History Blame Contribute Delete
6.6 kB
"""
graph.py โ€” LangGraph ็Šถๆ€ๆœบ็ป„่ฃ…
ๅ›พ็ป“ๆž„๏ผš
[START]
โ”‚
intent_router
โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ mode==overview โ”‚ mode==followup/decompose
โ–ผ โ–ผ
tutor decomposer
โ”‚ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ”‚ needs_decomposition? โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ Yes โ†“ No โ†“
โ”‚ [END: ่ฟ”ๅ›ž tutor
โ”‚ decompose โ”‚
โ”‚ plan็ป™ๅ‰็ซฏ] โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ–ผ
offtopic_eval
โ”‚
[END]
thread_id ่ฏดๆ˜Ž๏ผš
- ๅฝ“ๅ‰ไฝœไธบ config["configurable"]["thread_id"] ไผ ๅ…ฅ graph.invoke()
- ๅ›พไฝฟ็”จ MemorySaver๏ผˆๅ†…ๅญ˜๏ผŒ่ฟ›็จ‹ๅ†… session ็บงๅˆซ็š„ checkpointing๏ผ‰
- ๆœชๆฅๆ›ฟๆขไธบ PostgresSaver / RedisSaver ๆ—ถ๏ผŒๅช้œ€ๆ›ดๆ”น checkpointer๏ผŒๅ…ถไฝ™ไธๅ˜
"""
from __future__ import annotations
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, StateGraph
from agent.nodes.decomposer import decomposer_node
from agent.nodes.offtopic_eval import offtopic_eval_node
from agent.nodes.tutor import tutor_node
from agent.nodes.tree_writer import tree_writer_node
from agent.nodes.anchor_locator import anchor_locator_node
from agent.state import AgentState
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Router functions๏ผˆๆกไปถ่พน๏ผ‰
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def route_after_intent(state: AgentState) -> str:
"""intent_router ๅŽ็š„่ทฏ็”ฑ๏ผšoverview ็›ดๆŽฅๅˆฐ tutor๏ผŒๅ…ถไฝ™ๅ…ˆ็ป decomposer"""
mode = state.get("mode", "followup")
if mode == "tree_writer":
return "tree_writer"
if mode == "overview" or state.get("skip_decomposition", False):
return "tutor"
return "decomposer"
def route_after_locator(state: AgentState) -> str:
"""anchor_locator ๅŽ็š„่ทฏ็”ฑ๏ผšๅค„ไบŽ็บฏๆ‹†่งฃๆจกๅผ(decompose)ๆˆ–้œ€่ฆๆ‹†่งฃ โ†’ END๏ผŒๅฆๅˆ™็ปง็ปญ tutor"""
if state.get("mode") == "decompose" or state.get("needs_decomposition", False):
return END
return "tutor"
def route_after_tutor(state: AgentState) -> str:
"""overview ไธ้œ€่ฆ่ท‘ๅๆฃ€ๆต‹๏ผ›followup ๆ‰่งฃๆž [OFFTOPIC] ๆŽงๅˆถๆ ‡่ฎฐใ€‚"""
if state.get("mode") == "overview":
return END
return "offtopic_eval"
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Intent Router Node๏ผˆ่ฝป้‡๏ผŒไป…ๅš mode ๆ ก้ชŒ๏ผŒไธ่ฐƒ็”จ LLM๏ผ‰
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def intent_router_node(state: AgentState) -> dict:
"""ๆ ก้ชŒๅนถ่ง„่ŒƒๅŒ– mode ๅญ—ๆฎต๏ผŒๆ— ้œ€ LLM ่ฐƒ็”จ"""
mode = state.get("mode", "followup")
if mode not in ("overview", "followup", "decompose", "tree_writer"):
mode = "followup"
return {"mode": mode}
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ๅ›พ็š„ๆž„ๅปบไธŽ็ผ–่ฏ‘
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def build_graph():
"""ๆž„ๅปบๅนถ่ฟ”ๅ›ž็ผ–่ฏ‘ๅฅฝ็š„ LangGraph ๅ›พ๏ผˆไฝฟ็”จ MemorySaver ไฝœไธบไธดๆ—ถ Checkpointer๏ผ‰"""
builder = StateGraph(AgentState)
# ๆณจๅ†Œ Nodes
builder.add_node("intent_router", intent_router_node)
builder.add_node("decomposer", decomposer_node)
builder.add_node("anchor_locator", anchor_locator_node)
builder.add_node("tutor", tutor_node)
builder.add_node("offtopic_eval", offtopic_eval_node)
builder.add_node("tree_writer", tree_writer_node)
# ๅ…ฅๅฃ
builder.add_edge(START, "intent_router")
# intent_router โ†’ tutor๏ผˆoverview๏ผ‰ๆˆ– decomposer๏ผˆfollowup/decompose๏ผ‰
builder.add_conditional_edges(
"intent_router",
route_after_intent,
{"tutor": "tutor", "decomposer": "decomposer", "tree_writer": "tree_writer"},
)
# tree_writer โ†’ END๏ผˆ็ป“ๆž„ๅŒ– mount plan๏ผ‰
builder.add_edge("tree_writer", END)
# decomposer โ†’ anchor_locator (ๅฎšไฝๅŽŸๆ–‡็‰‡ๆฎต)
builder.add_edge("decomposer", "anchor_locator")
# anchor_locator โ†’ END๏ผˆ้œ€ๆ‹†่งฃ๏ผŒ่ฟ”ๅ›ž plan๏ผ‰ๆˆ– tutor๏ผˆๅ•ไธ€้—ฎ้ข˜็ปง็ปญ๏ผ‰
builder.add_conditional_edges(
"anchor_locator",
route_after_locator,
{END: END, "tutor": "tutor"},
)
# tutor โ†’ overview ็›ดๆŽฅ็ป“ๆŸ๏ผ›followup ๅ†่ฟ›ๅ…ฅ off-topic ๆŽงๅˆถๆ ‡่ฎฐ่งฃๆž
builder.add_conditional_edges(
"tutor",
route_after_tutor,
{END: END, "offtopic_eval": "offtopic_eval"},
)
# offtopic_eval โ†’ END
builder.add_edge("offtopic_eval", END)
import os
# ๅฆ‚ๆžœๆฃ€ๆต‹ๅˆฐๆ˜ฏๅœจ LangGraph API / CLI / Studio ่ฟ่กŒ็Žฏๅขƒ๏ผŒๅˆ™ไธไผ ้€’่‡ชๅฎšไน‰ checkpointer
if any(k.startswith("LANGGRAPH_") for k in os.environ):
return builder.compile()
# MemorySaver๏ผš่ฟ›็จ‹ๅ†… checkpointing๏ผŒ็”จไบŽ thread_id ้š”็ฆปใ€‚
# ๆœชๆฅๆขๆˆ PostgresSaver ๅช้œ€ๆ”น่ฟ™ไธ€่กŒ๏ผš
# from langgraph.checkpoint.postgres import PostgresSaver
# checkpointer = PostgresSaver(conn_string=os.environ["DATABASE_URL"])
checkpointer = MemorySaver()
return builder.compile(checkpointer=checkpointer)
# ๆจกๅ—็บงๅ•ไพ‹๏ผŒ้ฟๅ…ๆฏๆฌก่ฏทๆฑ‚้‡ๆ–ฐ็ผ–่ฏ‘
graph = build_graph()