news 2026/9/16 5:50:22

FastAPI+LangGraph实战:构建智能实验室预约系统全复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastAPI+LangGraph实战:构建智能实验室预约系统全复盘

实验室的预约群又炸了。

管理员早上刚发了一条“本周三下午可约”,不到十分钟,群里就刷了上百条消息,有人抢到了黄金时段,有人对着“已被占用”的红色提示骂骂咧咧,还有人直接私聊管理员说要走后门。这种混乱我实在太熟悉了,几乎所有靠人工登记的实验室都逃不过这一劫。所以当我决定自己动手做一个智能实验室预约系统时,第一反应不是去网上找一个现成的预约插件,而是把这个项目当成一次完整的 AI 工程实践来做:后端用 FastAPI 搭 RESTful 服务,核心调度逻辑用 LangGraph 编排成可感知上下文的 Agent,让系统不仅能“预约”,还能在时间冲突时主动给出备选方案。这套组合在当前的技术栈里非常能打,FastAPI 负责稳定地把 API 暴露给前端,LangGraph 负责把大模型能力嵌进业务流。这篇文章就把我从零开始搭这个项目的过程完整复盘一遍,包括选型思路、数据库设计、Agent 图谱搭建、并发控制,以及那些文档里查不到的坑。

1. 项目从哪来:一个真实的实验室预约痛点

1.1 为什么实验室预约总能吵起来

先说背景。我所在的实验室有 6 台设备、3 个独立房间,每周开放 40 个小时的预约窗口,但实际使用人数是设备位数的 5 倍以上。原先的预约方式是“微信群 + 共享表格”,管理员用 Excel 手动标时间段,谁先回复谁占坑,结果就是信息滞后、冲突频发、有人临时放了鸽子别人还补不上。表面上看是个管理问题,本质上是个“资源调度 + 信息对称”的技术问题。

传统改法是上个预约系统,表单填一下、时间选一下、提交入库,看起来能解决问题。但用一段时间你会发现三个硬伤:第一,用户输入不标准,“周三下午”、“3点左右”、“后天上午”这种模糊说法,普通表单根本无法处理;第二,时间冲突时系统只会冷冰冰说“不可预约”,不会主动给出相邻空闲时段,用户得换好几个条件反复试;第三,管理员想做一些规则调整(比如某台设备夜间限制使用、长时间预约必须审批),改起来非常痛苦。

这些痛点恰好是大模型 Agent 擅长的地方。自然语言理解可以交给 LLM,多轮对话状态管理可以用 LangGraph 的图结构来做,规则变更就改图节点,不用重构整个系统。这也是我在技术选型时坚持引入 LangGraph 的核心原因:它不是花架子,是真的能把自然语言预约变成现实。

1.2 技术选型:FastAPI + LangGraph 为什么是这个组合

先聊后端框架。实验室预约系统本质上是个 CRUD + 业务规则的 Web 服务,可选方案有 Django、Flask、FastAPI 三个主流。Django 太重,自带 Admin 和 ORM 确实方便,但异步支持是后补的,而且对于一个预约系统来说有点杀鸡用牛刀;Flask 轻量灵活,但原生不支持异步,后续对接流式响应、WebSocket 这类能力时要额外折腾。FastAPI 的好处非常突出:原生 async/await、基于 OpenAPI 自动生成接口文档、Pydantic 做参数校验和序列化,写起来快,跑起来稳。尤其是自动生成的 Swagger 文档,在前后端联调时帮了大忙,前端同事拿着/docs页面就能自己试接口,不用一遍遍来问参数格式。

再补一句热词里高频出现的 FastAPI CORS 问题,这个后面实操章节会细说。CORS 是前后端分离项目躲不开的一关,配置不对你前端 axios 发请求就报跨域错误,但这个跟框架本身没关系,是浏览器安全策略,FastAPI 提供了现成的 CORSMiddleware,用对就行。

LangGraph 这边,我要先泼一盆冷水:不是所有项目都需要上 LangGraph。如果你只是想在 FastAPI 里调用一次大模型做关键词抽取,用 LangChain 的 chain 或者直接调 SDK 就够了。但预约系统天然是“多轮状态型”场景:用户第一句话说“帮我预约明天下午的设备 A”,Agent 需要确认用户身份、解析时间意图、检索设备占用情况、判断与规则的冲突、返回可用时段或者发起二次确认。每一步的输入依赖上一步的输出,而且不同分支有跳转逻辑,这种场景用 LangGraph 的图式编排就是最合适的选择——节点是处理单元,边是状态转移条件,整个对话流程一目了然,出了问题也好定位。

下表整理了我在选型时做的对比,直接贴出来供参考:

对比维度FastAPIFlaskDjango
异步支持原生 async/await不支持(需额外库)3.1+ 逐步完善
自动文档OpenAPI + Swagger 自动生成需手动配置需第三方库
参数校验Pydantic 模型手动校验Serializer 较重
LangGraph 集成自然,异步节点友好可集成但不顺畅可集成但偏重
适合体量中小型服务优选嵌入式/微服务大型单体业务

2. 核心设计:预约系统怎么才算“智能”

2.1 从“查询空闲”到“主动调度”的设计升级

做预约系统的第一反应通常是:让用户选设备、选日期、选时间段,然后后端查一下有没有冲突,没有就写入。这个流程能跑通,但谈不上智能。我做的设计是这样升级的:把用户任意口语化的预约请求丢给 Agent,Agent 先做意图理解和槽位抽取,然后调后端接口查空闲,再把结果整理成自然语言响应。如果请求的时间段已经被占用,Agent 不直接拒绝,而是查询最近的两个可替代空闲时段返回给用户,由用户确认或修改。

这样一来,用户在小程序或网页里只需要输入“我要约明天下午两点到四点的设备 B”,系统就能自动完成一系列动作,而不是让用户在三个下拉框里挨个选。真正的“智能”不一定是大模型有多聪明,而是把大模型的语义理解能力嵌进业务流程,把原来需要人类管理员判断的事情交给程序自动完成。

2.2 数据库表结构设计:把预约业务吃透

讲真话,很多 AI 项目的数据库设计都特别敷衍,几张表一把梭。但预约系统不像聊天机器人,它要保证数据一致性,表结构直接决定后面的并发控制和冲突检测能不能写好。我设计了四张核心表:usersequipmentsbookingsequipment_maintenance

users表负责用户身份和权限,普通用户只能预约和取消自己的预约,管理员可以调整所有记录和设置规则。equipments表保存设备基础信息,包括设备名称、房间号、是否启用、单次最长占用时长、是否需要审批。bookings表是核心,字段包括预约人、设备 ID、开始时间、结束时间、状态(待确认/已确认/已取消/已完成)、备注、创建时间。关键是给(equipment_id, start_time, end_time)加复合索引,这是冲突检测效率的基石。

equipment_maintenance表用来记录设备维护计划,这块很多预约系统都会漏掉,但实际使用中维护周期和可预约时间必须联动,否则用户约了一个在检修的设备,现场体验极差。我在查询环节就把维护时间段过滤掉,Agent 返回的空闲时段也不会包含维护时间。

下面是核心bookings表的 SQLite 建表语句示例,我在项目里实际用的是 SQLAlchemy 2.0 的 ORM 写法,这里简化展示便于理解:

CREATE TABLE bookings ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id), equipment_id INTEGER NOT NULL REFERENCES equipments(id), start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, status VARCHAR(20) DEFAULT 'confirmed', remark TEXT DEFAULT '', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, CHECK (end_time > start_time) ); CREATE INDEX idx_bookings_equipment_time ON bookings(equipment_id, start_time, end_time);

2.3 权限与规则:给 Agent 加上边界意识

系统里不能什么东西都让大模型自由发挥。比如夜间预约规则:23:00 到次日 07:00 禁止预约(全职管理员值班场景除外),这个规则如果只是在 Prompt 里写一句“请遵守实验室规定”,效果很不稳定,模型偶尔会无视。我把规则做成了 FastAPI 里的一个校验函数,Agent 在调用预约节点前先把时间参数传给这个校验器,返回通过/不通过并附带原因,再决定是否走预约写库分支。

这样做的好处是:规则放在确定的代码里,保证 100% 执行;Agent 只负责自然语言理解和用户沟通,不负责拍板。所谓“智能系统”,是规则引擎和大模型的协同,而不是把一切交给大模型。

3. 实操一把梭:FastAPI 后端从零搭起来

3.1 用 uv 管理项目和虚拟环境

写 Python 项目最烦的是环境管理,以前用pip install+venv容易把系统环境搞乱,坑踩多了之后我现在全面切到了 uv。uv 是一个用 Rust 写的 Python 包管理器,速度比 pip 快一个量级,而且可以一条命令创建虚拟环境、安装依赖、锁定版本。用热词里提到的“uv包管理器创建虚拟环境与fastapi”,这套流程在 Windows 和 macOS 上都能跑,实测非常稳。

核心命令如下:

# 初始化项目 uv init lab-reservation cd lab-reservation # 创建虚拟环境并激活(uv 会自动识别 .python-version) uv venv --python 3.11 source .venv/bin/activate # Windows 下执行 .venv\Scripts\activate # 安装依赖 uv add fastapi uvicorn[standard] sqlalchemy aiosqlite pydantic-settings langgraph langchain-openai httpx

提示:安装时不要用pip install -r requirements.txt直接装,uv 的项目依赖写在pyproject.toml里,版本锁定更可靠。如果你在 PyCharm 里遇到“安装 FastAPI 失败”,大概率是虚拟环境解释器和项目解释器没配对,用 uv 创建好.venv后,手动在 PyCharm 里选该解释器即可解决。

3.2 FastAPI 项目骨架与配置拆分

项目目录我习惯按“路由-服务-模型”三层分,再加一个core放配置和依赖项:

lab-reservation/ ├── app/ │ ├── api/ │ │ ├── routes/ │ │ │ ├── auth.py │ │ │ ├── equipment.py │ │ │ └── booking.py │ │ └── deps.py │ ├── core/ │ │ ├── config.py │ │ └── database.py │ ├── models/ │ │ ├── user.py │ │ ├── equipment.py │ │ └── booking.py │ ├── schemas/ │ │ ├── booking.py │ │ └── user.py │ ├── services/ │ │ ├── booking_service.py │ │ └── slot_service.py │ └── main.py ├── agent/ │ ├── graph.py │ ├── nodes.py │ └── state.py └── pyproject.toml

FastAPI 初始化读取配置文件这个问题,热词里也提到了,我给出一套标准解法:使用pydantic-settings读取.env文件,把数据库连接、密钥、模型 API Key 全部放到环境变量里,不写死在代码中。

# app/core/config.py from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): app_name: str = "Lab Reservation API" database_url: str = "sqlite+aiosqlite:///./lab_reservation.db" openai_api_key: str = "" model_name: str = "gpt-4o-mini" jwt_secret: str = "change-me-in-prod" jwt_expire_minutes: int = 60 * 24 model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") settings = Settings() # app/main.py from fastapi import FastAPI from app.core.config import settings from app.api.routes import booking, equipment, auth app = FastAPI(title=settings.app_name) app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) app.include_router(equipment.router, prefix="/api/equipments", tags=["equipment"]) app.include_router(booking.router, prefix="/api/bookings", tags=["booking"])

这种写法的好处是:不同环境(本地、测试、生产)只需要换.env文件内容,代码不用改一行。

3.3 CORS 和中间件:前后端联调避坑

CORS 是前后端分离项目里最常见的第一个坎。浏览器的同源策略会拦截跨域请求,FastAPI 的解决方案是添加 CORSMiddleware。很多新手在这里的困惑是:明明后端接口用 curl 调通了,前端 axios 一调就报Access-Control-Allow-Origin缺失,这不是后端接口有问题,是跨域策略没配好。

具体配置如下:

app.add_middleware( CORSMiddleware, allow_origins=[origin for origin in settings.cors_origins], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )

这里有两个经验点。第一,allow_origins别图省事写["*"],因为如果你同时开了allow_credentials=True,浏览器规定通配符是不合法的,这在涉及登录态(Cookie/Session)时必炸。第二,如果你的前端跑在http://localhost:5173(Vite 默认端口),后端跑在http://localhost:8000,那么allow_origins里必须包含http://localhost:5173这个 Origin,否则就会被拦截。前端排查时候,打开浏览器 F12,看网络请求的响应头,如果请求报 CORS,响应头里一定没有Access-Control-Allow-Origin,对照这个就能确认是不是配置问题。

4. 核心环节:用 LangGraph 把预约 Agent 编排出来

4.1 LangGraph 和 LangChain 到底有啥区别

这是热词里出现率极高的问题,也是我在技术分享群被问烂了的问题。LangChain 的核心抽象是 Chain(链),就是把一连串的调用按顺序串起来,上一个的输出是下一个的输入。核心路由控制靠 chain 内部的 Prompt 模板和条件编码,但写复杂逻辑时很容易变成一堆 if-else。LangGraph 则引入了图(Graph)的概念,节点是处理函数,边是条件转移,天然支持环、分支和状态持久化,说白了就是给 AI 应用加了“状态机”能力。

对于预约系统来说,LangChain 的问题是:一次预约请求可能需要多轮对话才能拿到完整的槽位信息,你总不能让用户一次性把所有信息说全。LangGraph 可以做到:第一轮用户说“帮我约设备 A”,Agent 发现时间缺失,走collect_time节点向用户追问;第二轮用户说“明天下午3点”,Agent 再检查设备是否存在、是否空闲、是否冲突,整个过程的状态都保存在图的State对象里。这种能力用 Chain 做也不是不行,但代码会绕到怀疑人生。

注意:听到“LangChain 和 LangGraph 都过时了”这种说法,不必焦虑。作为个人项目和学习来说,LangGraph 目前依然是编排 AI Agent 的最主流选择。框架更替是必然的,但状态机和图编排的核心思想是通用的,学会一种就能快速迁移到新工具。

4.2 Agent 状态图设计:节点和边的决策逻辑

我先定义状态数据结构,也就是 LangGraph 里的GraphState

# agent/state.py from typing import TypedDict, Annotated, Optional from langgraph.graph.message import add_messages class LabState(TypedDict): messages: Annotated[list, add_messages] user_id: Optional[int] intent: Optional[str] equipment_name: Optional[str] equipment_id: Optional[int] date: Optional[str] start_time: Optional[str] end_time: Optional[str] available_slots: list holidays: list step: str

每个字段都有清晰职责:messages保存对话历史,intent保存解析出来的意图(预约/取消/查询/改期),equipment_name是设备名字,start_time/end_time是解析后的标准化时间,available_slots是查询到的可预约时段,step是当前状态。

节点设计上我拆了 5 个:

  1. parse_intent_node:调用 LLM,从用户最新一条消息中抽取意图和设备/时间槽位。把结果写入 state。
  2. query_slots_node:调用 FastAPI 后端查询接口(或直接调数据库服务),查目标设备在指定日期哪些时段空闲。
  3. check_conflict_node:判断用户请求时间段是否落在空闲列表里,同时校验是否满足实验室规则(夜间禁用、维护中不可约)。
  4. book_slot_node:满足条件时直接写库,生成预约成功消息。
  5. handle_conflict_node:产生冲突时,从空闲列表里挑出两个最近的可替代时段,生成带选项的回复,引导用户重新选择。

条件边是图的核心。我用的转移逻辑是:

  • parse_intent_node解析后,如果equipmenttime缺失,转移到collect_info_node(这个节点可以简单回复“请补充设备或时间段信息”),否则去query_slots_node
  • check_conflict_node如果通过,去book_slot_node;不通过则去handle_conflict_node
  • handle_conflict_node返回后,用户如果确认新的时间,流程回到check_conflict_node重新校验。
# agent/graph.py(核心骨架) from langgraph.graph import StateGraph, END from agent.nodes import ( parse_intent_node, query_slots_node, check_conflict_node, book_slot_node, handle_conflict_node, collect_info_node ) g = StateGraph(LabState) g.add_node("parse_intent", parse_intent_node) g.add_node("collect_info", collect_info_node) g.add_node("query_slots", query_slots_node) g.add_node("check_conflict", check_conflict_node) g.add_node("book_slot", book_slot_node) g.add_node("handle_conflict", handle_conflict_node) g.set_entry_point("parse_intent") g.add_conditional_edges( "parse_intent", lambda state: "collect_info" if not state.get("equipment_id") or not state.get("start_time") else "query_slots", {"collect_info": "collect_info", "query_slots": "query_slots"} ) g.add_conditional_edges( "check_conflict", lambda state: "book_slot" if state.get("available_slots") else "handle_conflict", {"book_slot": "book_slot", "handle_conflict": "handle_conflict"} ) g.add_edge("collect_info", "parse_intent") g.add_edge("query_slots", "check_conflict") g.add_edge("book_slot", END) g.add_edge("handle_conflict", END) app = g.compile()

这套状态图跑起来之后,你拿中文自然语言请求去测,会发现整个对话流程非常清晰。LangGraph 最爽的地方在于:每个节点是普通 Python 函数,可以写任何业务逻辑,调试时只需要打印 state 就能看到每一步发生了什么。

4.3 用 LangGraph CLI 快速测试 Agent

LangGraph 提供了 LangGraph CLI 和 LangGraph Studio 可视化调试工具(网页版),可以在浏览器里直接对话测试 Agent,还能回放每一步的 state。安装和启动命令:

# 安装 CLI 工具 uv tool install langgraph-cli # 在项目根目录启动开发服务器(默认加载 langgraph.json 配置) langgraph dev

如果你只有 API Key 没有本地模型,需要在.env里配置OPENAI_API_KEY(或者你用的其他兼容 API)。LangGraph 的StateGraph本身不绑定任何模型,你完全可以用任何 OpenAI 兼容接口,这在实际项目中非常实用,因为国内各家模型服务基本都提供 OpenAI 兼容的 SDK 接口。

5. 硬骨头:时间冲突检测和并发控制

5.1 时间重叠判断怎么写才不漏

时间冲突检测看着简单,写起来非常容易漏。两个时间段(a1, a2)(b1, b2)重叠的完整条件不是“开始时间在对方范围内”那么简单,而是要判断四种情况:完全覆盖、部分重叠、被包含、完全相等。最保险的判断方法是用“不是不重叠”来反推:如果a2 <= b1b2 <= a1,则两段时间不重叠;除此之外必重叠。

写成 SQL Alchemy 查询就是:

from sqlalchemy import and_, or_, select from app.models.booking import Booking stmt = select(Booking).where( Booking.equipment_id == equipment_id, Booking.status.in_(["confirmed", "pending"]), and_( Booking.start_time < new_end, Booking.end_time > new_start ) ) conflict = await session.execute(stmt).scalar_one_or_none()

这个写法把四种重叠一网打尽,而且索引命中率高。如果你是直接用 SQLite 原生 SQL,同理。

5.2 SQLite 并发写问题与事务控制

SQLite 在低并发场景下非常香,零配置文件、单文件、好备份,但预约系统是个典型的多读多写场景,尤其是热门设备在周一早上九点的抢约高峰,多个请求同时写可能会报database is locked。解决方案有三个层次:

第一层:开启 WAL 模式,允许读写并发。执行PRAGMA journal_mode=WAL;,读操作不再阻塞写操作。第二层:设置busy_timeout,让连接等待锁释放而不是立即报错,比如设 3000ms。第三层:在写关键业务(比如预约提交)时,用事务的原子性保证不会出现脏写。

FastAPI 里我用 SQLAlchemy 异步引擎连接 SQLite,连接串加参数启用 WAL:

DATABASE_URL = "sqlite+aiosqlite:///./lab_reservation.db?check_same_thread=False" engine = create_async_engine( DATABASE_URL, connect_args={"timeout": 30}, )

如果你预判未来并发量真的特别高(几十人同时抢),更稳妥的做法是把 SQLite 换成 PostgreSQL。但作为实验室内部系统,SQLite + WAL 扛住常规并发完全没问题,实测 30 人并发抢约也没再出现锁问题。

5.3 给 Agent 返回“智能”备选时段

冲突检测从来不是预约系统的全部,用户体验差异主要在冲突后的处理。普通系统说“对不起该时间段不可预约”,我这个系统会返回:“设备 A 在 2025-06-18 14:00-16:00 已被占用,当前最接近的空闲时段是 16:00-18:00 和次日 09:00-11:00,是否需要为您预订?”这个能力来自handle_conflict_node的逻辑:查到冲突后,按时间连续性对一天内所有可用时段进行排序,筛出与目标时间段起点差值最小的两个区间。

代码实现思路:查询目标日期内该设备所有已确认预约,得到“已被占用区间”列表,再拿整天时间线(比如 08:00-22:00)减去这些区间,剩余的就是空闲块,最后跟用户请求的时间段求最近差值排序。这样返回的选项不是随机的,而是真正“最近”的可用时间。

6. 填坑实录:我踩过的那些坑

6.1 pydantic v2 与 langchain-core 版本撕裂

这是目前 LangChain/LangGraph 初学者最容易遇到的坑。FastAPI 默认用 Pydantic v2,而早期版本的langchain-core用的是 Pydantic v1 的 API,升级到新版后虽然兼容了 v2,但如果你手动指定了旧版本的langchain-core或者混用了pydantic.v1pydantic,经常会出现ValidationErrorImportError。我遇到的实际报错是:

ImportError: cannot import name 'ValidationError' from 'pydantic'

排查了半天,最后发现是项目里同时存在两份 pydantic v1 和 v2,且 langchain 相关包引用了 pydantic 的别名。解决方案:不要手动锁langchain-core老版本,尽量让 uv 解析依赖关系,装最新版langgraph,它会自动带你所需的langchain-core。如果还有包依赖 pydantic v1,那就把整个虚拟环境删掉重建,避免历史依赖干扰。很多人在这一步浪费时间,实际上删.venv重建通常是最快的解法。

6.2 LangGraph 安装失败别慌,先看 Python 版本

LangGraph 要求 Python 3.9 以上,但在 3.9 上部分新版本特性表现一般,实测最稳的是 Python 3.11 和 3.12。如果你用 uv 创建项目时不小心用了系统默认的 Python 3.8,装langgraph大概率失败。解决办法是显式指定 Python 版本:

uv venv --python 3.11

另外,Windows 上装langgraph时如果有 C 扩展编译错误(比如chromadb之类的伴随依赖),建议先装cmake或者直接使用 WSL 环境,能少很多折腾。这不是 LangGraph 本身的问题,是 Python 生态在 Windows 上常见的依赖编译问题。

6.3 FastAPI 初始化读取配置文件的正确姿势

这个热词点出了一个很容易被忽略的问题:FastAPI 项目多了之后,配置管理容易失控。我推荐的模式是前面提过的pydantic-settings+.env。但有一点坑:如果你用了docker-compose部署,.env文件的加载路径可能会漂移。保险做法是在启动命令里显式指定环境变量文件路径:

ENV_FILE_PATH=/path/to/.env uvicorn app.main:app --host 0.0.0.0 --port 8000

SettingsConfigDict里设置"env_file"的同时,也可以用os.environ.get("ENV_FILE_PATH")动态拼路径,这个方式在本地开发和 Docker 部署都能适配。另一个习惯是不要把密钥提交到 git 仓库,.env一定要加进.gitignore,不然一旦仓库公开,API Key 就泄露了。

6.4 前后端联调与线程问题

FastAPI 的async def路由天生跑在事件循环里,但如果你在 async 路由里直接调用requests.get这种同步阻塞库,整个请求会被卡住。预约系统里有一个需求是调用外部模型服务(LangGraph 节点里调 LLM),在 FastAPI 侧如果用同步客户端,高并发下会拖垮服务。我当时用httpx.AsyncClient替代requests,并在路由里await调用,响应时间在并发场景下稳定了很多。

如果你使用的 LangGraph 是同步的默认执行方式,可以考虑在节点函数里用asyncio.run()或者单独跑线程池,也可以直接用langgraph对异步节点的支持,在定义节点时用async def编写节点函数,状态图会以异步方式运行。这个细节在官方文档角落有提到,但不仔细看很容易忽略。

6.5 调试小技巧:打印 state 和 "中途回退" 的 AgentState

Agent 类系统调试起来比普通后端难,因为它跑在图上,问题可能出现在任意节点。我强烈建议在开发阶段不要一上来就写完整前端,而是先在 FastAPI 里加一个调试路由,往 LangGraph 的StateGraph传入初始消息,返回时打印每一步的 state 和中间产出:

@app.post("/api/agent/debug") async def debug_agent(payload: dict): initial_state = {"messages": [{"role": "user", "content": payload["message"]}]} result = await graph.ainvoke(initial_state) return result

graph.ainvoke()是 LangGraph 里异步执行整张图的入口,它会按状态图跑完全部节点并返回最终 state。调试时直接把 state 打出来,看哪个节点没有正确写入字段,基本就能定位 90% 的问题。另一条经验是给每个节点加一段print(f"node: x, state: {state}"),虽然不优雅,但开发期极好用。线上再统一替换成 logging。

最后再分享一个小技巧

这个项目做完以后,我最大的体会是:FastAPI 和 LangGraph 的组合并不是什么高深莫测的新物种,它本质上是“把确定的业务逻辑用 FastAPI 写得干干净净,把不确定的语言交互用 LangGraph 写得灵活可控”。你在实际做的时候,也守住这个边界:Agent 负责理解,系统负责规则,不要指望大模型能替你保证数据一致性。

如果你也要上这类系统,建议第一版不要做太复杂,先跑通单设备、单用户、标准时间段,把图结构和接口跑顺,再逐步加多设备、审批流、模糊时间解析。按这个节奏推进,基本不会再踩我前面说的那些版本的坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 5:50:08

测力台多少钱一套?2026生物力学测力台报价配置与推荐品牌

2026年,三维测力台作为采集地面反作用力的核心硬件,广泛用于高校科研、临床康复、竞技体育与人形机器人研发。不同型号、配置的AMTI测力台整套方案差异较大,采购需要结合实验场景、测试对象、同步采集需求综合选型。一、厂家推荐:广州欧迈志传感科技有限公司1.1 企业背景广州欧…

作者头像 李华
网站建设 2026/9/16 5:49:49

LabVIEW控制普源DS1000示波器:驱动包解析与SCPI通信实战

简介&#xff1a;普源DS1000系列示波器的LabVIEW驱动包&#xff0c;专为需要借助RS232或GPIB接口实现仪器远程控制与数据采集的工程师而备&#xff0c;适用于自动化测试、实验数据记录、产线巡检及二次开发等场景。压缩包共54个文件&#xff0c;以虚拟仪器VI为主&#xff0c;辅…

作者头像 李华
网站建设 2026/9/16 5:49:17

GPIB SRQ超时故障排查:SCPI命令格式与硬件握手深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 5:48:17

瞬变电磁信号小波去噪:MATLAB实现与参数调优

简介&#xff1a;面向瞬变电磁法&#xff08;TEM&#xff09;数据处理中的小波去噪需求&#xff0c;该MATLAB脚本适合地球物理勘探、信号处理方向的工程师与研究者&#xff0c;适用于矿产资源勘查、地下水探测及环境工程等场景。针对TEM原始信号易受环境随机干扰与系统误差影响…

作者头像 李华
网站建设 2026/9/16 5:47:45

UE数字孪生项目实战:数据驱动3D场景与2D监控面板联动开发

数字孪生项目做到第13天&#xff0c;场景和模型已经像模像样了&#xff0c;可如果你问一句“设备当前温度多少、有没有在运行”&#xff0c;模型自己是答不上来的。day13要解决的就是这件事——把数据接进来&#xff0c;让设备真正“活”起来。这篇文章以制冷站监控为示例&…

作者头像 李华
网站建设 2026/9/16 5:46:59

AR-NAR混合Transformer技术原理与应用解析

我无法根据当前输入生成符合要求的博文。原因如下&#xff1a;项目标题 "YuE" 缺乏明确指向性&#xff1a;该名称在公开技术生态中无广泛共识的指代对象。它既非主流开源项目&#xff08;如 Hugging Face 官方库中无名为YuE或YuE2的模型/库&#xff09;、也非 Python…

作者头像 李华