"""LangGraph 最小脚手架(作业 1)。 图结构:START → normalize_input → reply → END 骨架已装配好:状态、节点注册、边和编译(默认 InMemorySaver)。 两个节点的具体逻辑留待实现(见函数体内的 TODO)。 离线运行示例: python examples/langgraph_smoke.py 对应测试:test/agent/test_scaffold.py """ from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import END, START, StateGraph from .state import AgentState # 脚手架阶段固定返回的阶段码;作业 3 起由转换表驱动。 DEFAULT_STAGE_CODE = "1002" def normalize_input(state: AgentState) -> dict: """归一化本轮输入,为回复节点准备上下文。 TODO(作业 1): 实现该节点。建议职责: - 校验/清理 ``state["input_text"]``(例如去除首尾空白); - 返回需要写入状态的字段增量,例如 ``{"input_text": cleaned}``; - 保持纯函数:不读全局状态、不做 IO。 """ cleaned = state["input_text"].strip() return {"input_text": cleaned} def reply(state: AgentState) -> dict: """生成固定模板回复,并把轮数加一。 TODO(作业 1): 实现该节点。验收要求: - 每次调用使 ``turn_count`` 恰好 +1;首轮会话没有历史 checkpoint,用 ``state.get("turn_count") or 0`` 起步; - ``response_text`` 使用固定模板,**包含本轮输入文本** (``state["input_text"]``); - 响应文本需带公共协议前缀,例如 ``1002...``(``stage_code`` 用 ``DEFAULT_STAGE_CODE``,API 层负责解析该前缀); - ``stage_code`` 写入 ``DEFAULT_STAGE_CODE``。 参考实现:: turn_count = (state.get("turn_count") or 0) + 1 response_text = ( f"{DEFAULT_STAGE_CODE}" f"已收到:{state['input_text']}" ) return { "turn_count": turn_count, "response_text": response_text, "stage_code": DEFAULT_STAGE_CODE, } """ turn_count = (state.get("turn_count") or 0) + 1 response_text = ( f"{DEFAULT_STAGE_CODE}" f"已收到:{state['input_text']}" ) return { "turn_count": turn_count, "response_text": response_text, "stage_code": DEFAULT_STAGE_CODE, } def build_graph(checkpointer=None): """装配并编译最小图。 参数: checkpointer: 自定义 checkpointer;缺省使用 ``InMemorySaver``。 作业 7 会替换为 ``AsyncPostgresSaver``。 返回: 编译后的图。调用方通过 ``config={"configurable": {"thread_id": <会话 ID>}}`` 区分会话。 """ builder = StateGraph(AgentState) builder.add_node("normalize_input", normalize_input) builder.add_node("reply", reply) builder.add_edge(START, "normalize_input") builder.add_edge("normalize_input", "reply") builder.add_edge("reply", END) return builder.compile(checkpointer=checkpointer or InMemorySaver())