基于 LangGraph 多 Agent 协同架构 的智能旅行预订系统,支持航班查改退、酒店预订、租车服务、旅行推荐,集成 四合一 RAG 检索管线 实现公司政策精准问答,内置 人机确认安全机制 保障敏感操作可控。
┌──────────────────────────┐
│ 👤 用户对话 │
└────────────┬─────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 🤖 Primary Assistant(主助理) │
│ 意图识别 → 任务分派 → 对话管理 │
│ │
│ 工具: Tavily 联网搜索 │ search_flights │ lookup_policy │
│ │
│ 路由: ToFlightBooking │ ToHotel │ ToCarRental │ ToExcursion │
└──────┬──────────┬──────────┬──────────┬──────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐┌──────────┐┌──────────┐┌──────────┐
│ ✈️ 航班 ││ 🏨 酒店 ││ 🚗 租车 ││ 🏝️ 旅行 │
│ 助理 ││ 助理 ││ 助理 ││ 助理 │
├──────────┤├──────────┤├──────────┤├──────────┤
│ 搜索航班 ││ 搜索酒店 ││ 搜索租车 ││ 搜索旅行 │
│ 改签 ││ 预订 ││ 预订 ││ 预订 │
│ 退票 ││ 修改 ││ 修改 ││ 修改 │
│ ││ 取消 ││ 取消 ││ 取消 │
└──────────┘└──────────┘└──────────┘└──────────┘
│ │ │ │
└──────────┴──────────┴──────────┘
│
┌────▼────┐
│ 主助理 │ ← 子助理完成/升级后自动返回
└─────────┘
🌐 点击查看完整系统架构图(建议自行生成最新版本:运行
graph_chat/draw_png.py)此处可放置系统架构全景图片,使用
graph_chat/draw_png.py生成后替换下方面链接
| 设计点 | 说明 |
|---|---|
| 1 主 + 4 子 Agent | 主助理负责意图识别与任务路由,4 个子助理各自掌握领域工具完成具体业务 |
| 安全/敏感工具分离 | 只读操作(搜索)直接执行;涉及修改的操作(改签、退票、取消)触发 interrupt_before,需用户确认 y/n |
| 对话状态栈 | 子助理完成任务或无法处理时通过 CompleteOrEscalate 自动回退到主助理,支持多轮上下文切换 |
| 四合一 RAG | 公司政策查询采用混合检索 + 父文档映射 + DeepSeek 重排序 + 长上下文重排 |
| 联网搜索增强 | 主助理配备 Tavily 联网搜索,可查询实时信息辅助回答 |
所有接口挂载在 /api 前缀下,分为两组:用户管理 和 工作流调用。
| 方法 | 路径 | 功能 | 认证 |
|---|---|---|---|
GET |
/api/users/getUsers/ |
获取所有用户列表 | 需要 Token |
GET |
/api/users/{pk}/ |
根据主键 ID 查询单个用户 | 需要 Token |
POST |
/api/register/ |
用户注册(内置用户名重复检查) | 白名单通行 |
POST |
/api/login/ |
用户登录,返回 JWT Token 和用户信息 | 白名单通行 |
POST |
/api/auth/ |
Swagger 文档中的 OAuth2 认证表单提交 | 白名单通行 |
PATCH |
/api/users/{pk}/ |
修改指定用户的信息 | 需要 Token |
POST |
/api/users/delete/ |
根据主键列表批量删除用户 | 需要 Token |
| 方法 | 路径 | 功能 | 认证 |
|---|---|---|---|
POST |
/api/graph/ |
调用多 Agent 工作流,传入 {user_input, config},返回 AI 回复 |
需要 Token |
请求示例:
POST /api/graph/
{
"user_input": "帮我查一下从北京飞上海的航班",
"config": {
"configurable": {
"passenger_id": "3442 587242",
"thread_id": "session-xxx"
}
}
}调用流程:
config中的passenger_id用于查询用户已有机票graph.stream()执行 LangGraph 工作流- 返回流式输出的最后一条 AI 消息
- 若触发
interrupt_before(敏感操作),返回确认提示,前端/客户端再发"y"继续
ctrip_assistant_backend/
├── api/ # API 路由层:接口定义 + 参数校验
├── config/ # 配置层:YAML 配置 + 日志配置
├── db/ # 数据库层:ORM 模型 + 通用 DAO
├── graph_chat/ # 核心引擎:多 Agent 图编排
├── tools/ # 工具层:Agent 可调用的函数
├── utils/ # 工具类:JWT / CORS / 中间件 / 密码哈希
├── main.py # FastAPI 启动入口
├── travel2.sqlite # 种子数据库(航班/酒店/租车/旅行)
├── travel_new.sqlite # 运行时数据库(自动生成)
└── order_faq.md # 公司政策 FAQ(RAG 知识库来源)
负责定义所有的 HTTP 接口和请求/响应数据校验(Pydantic Schema)。
api/
├── __init__.py
├── routers.py # 路由注册入口,组装所有分路由,挂载到 /api 前缀
├── schemas.py # 通用 Schema(泛型 CRUD 基类)
│
├── graph_api/ # 工作流接口
│ ├── __init__.py
│ ├── graph_schemas.py # 请求体 Schema:{user_input, config}
│ └── graph_views.py # POST /api/graph/ — 调用 graph.stream() 执行工作流
│
└── system_mgt/ # 用户管理接口
├── __init__.py
├── user_schemas.py # 用户相关 Schema:注册、登录、查询、修改
└── user_views.py # 7 个用户接口(注册/登录/认证/CRUD)
关键文件说明:
- routers.py:
router_v1()创建主路由,通过include_router加载用户管理和工作流两个分路由 - graph_views.py:核心接口,直接
from graph_chat.finally_graph import graph,调用graph.stream()执行多 Agent 工作流 - user_views.py:注册时有用户名查重(409 冲突)、登录返回 JWT、
/auth/是 Swagger 文档登录入口
config/
├── __init__.py # Dynaconf 配置加载器
├── development.yml # 开发环境配置
├── production.yml # 生产环境配置(备选)
└── log_config.py # 日志配置
关键配置项:
| 配置项 | 值 | 说明 |
|---|---|---|
HOST |
127.0.0.1 |
服务监听地址 |
PORT |
8000 |
服务端口 |
DATABASE.DRIVER |
mysql+pymysql |
用户数据用 MySQL |
DATABASE.NAME |
test_db4 |
数据库名 |
JWT_SECRET_KEY |
SHA256 密钥 | Token 加密密钥 |
ACCESS_TOKEN_EXPIRE_MINUTES |
30 | Token 有效期 |
WHITE_LIST |
登录/注册/docs 等 | 无需 Token 即可访问的路径 |
DEFAULT_PASSWORD |
123123 |
新用户默认密码 |
注意:.env 文件(与 ctrip_assistant_backend 同级)存放 LLM API Key 等敏感信息,由 llm_tavily.py 和 retriever_advanced.py 通过 load_dotenv 加载。
db/
├── __init__.py # SQLAlchemy 引擎 + 会话管理 + 公共模型基类
├── dao.py # 通用 BaseDAO(增删改查基类)
│
└── system_mgt/ # 用户模块
├── __init__.py
├── models.py # t_user 表的 ORM 模型
└── user_dao.py # UserDao(继承 BaseDAO,扩展按用户名查询)
说明:
- 两套数据库:用户认证数据存 MySQL(
test_db4),航班/酒店/租车等业务数据存 SQLite(travel_new.sqlite) - db/init.py:
- 创建 SQLAlchemy
engine和sessionmaker - 定义
DBModelBase公共基类:自动生成表名(t_+类名)、统一id/create_time/update_time字段
- 创建 SQLAlchemy
- db/dao.py:泛型
BaseDAO[Model, CreateSchema, UpdateSchema],提供get / get_by_id / create / update / delete / deletes通用方法
graph_chat/
├── finally_graph.py # 完整流程图组装与编译(项目入口图)
├── assistant.py # 主助理定义:提示词 + 工具 + 委派路由
├── agent_assistant.py # 4 个子助理定义:各自提示词 + 安全/敏感工具
├── build_child_graph.py # 4 个子工作流图构建
├── entry_node.py # 子助理入口节点的工厂函数
├── state.py # 对话状态 State 定义
├── base_data_model.py # 委派工具定义(ToFlight/ToHotel/ToCar/ToExcursion)
├── llm_tavily.py # LLM(DeepSeek)+ Tavily 搜索工具初始化
├── log_utils.py # 日志工具
├── draw_png.py # 流程图可视化导出
└── graph_gradio.py # Gradio 演示界面(备选)
finally_graph.py 是整个多 Agent 系统的组装入口,主要做以下几件事:
- 数据库自检:启动时检查
travel2.sqlite→ 自动解压travel2.zip→ 生成travel_new.sqlite - 节点注册:
fetch_user_info:启动时先查用户已有航班primary_assistant:主助理节点- 4 个子助理节点:
update_flight/book_hotel/book_car_rental/book_excursion - 各子助理的安全工具节点和敏感工具节点
- 路由逻辑:主助理根据工具调用结果,路由到对应子助理入口或工具节点
- 编译图:
builder.compile(checkpointer=memory, interrupt_before=[敏感工具节点]),在敏感操作前暂停等用户确认
assistant.py 定义:
CtripAssistant类:封装runnable.invoke()调用的节点类,带空输出重试机制- 主助理提示词:角色是"携程瑞士航空公司客服助理",负责搜索航班信息、查询公司政策、将预订等操作委派给子助理
- 主助理工具:
tavily_tool(联网搜索)+search_flights(航班查询)+lookup_policy(四合一 RAG 政策检索) - 委派路由:通过 Pydantic 工具
ToFlightBookingAssistant/ToHotelBookingAssistant/ToBookCarRental/ToBookExcursion将任务路由到对应子助理
agent_assistant.py 定义每个子助理的:
| 子助理 | 提示词角色 | 安全工具(只读) | 敏感工具(需确认) |
|---|---|---|---|
| 航班助理 | 航班查询、改签和预定 | search_flights |
update_ticket_to_new_flight, cancel_ticket |
| 酒店助理 | 酒店预订 | search_hotels |
book_hotel, update_hotel, cancel_hotel |
| 租车助理 | 租车预订 | search_car_rentals |
book_car_rental, update_car_rental, cancel_car_rental |
| 旅行助理 | 旅行推荐和预订 | search_trip_recommendations |
book_excursion, update_excursion, cancel_excursion |
每个助理的 runnable 由 ChatPromptTemplate | llm.bind_tools(tools + [CompleteOrEscalate]) 构建。
build_child_graph.py 为每个子助理构建独立的 StateGraph:
enter_xxx → xxx_assistant → [路由判断]
├── 纯安全工具 → xxx_safe_tools → 回到 assistant
├── 含敏感工具 → xxx_sensitive_tools → 回到 assistant
├── CompleteOrEscalate → leave_skill → 回到主助理
└── END
每个子图的 leave_skill 节点通过 pop_dialog_state 弹出对话栈,自动回到主助理继续对话。
entry_node.py 的 create_entry_node(assistant_name, new_dialog_state) 是一个工厂函数,生成子助理的入口节点。它创建一条 ToolMessage 告知子助理当前上下文,并设置 dialog_state 标记当前激活的子助理。
tools/
├── __init__.py # 数据库路径定义
├── tools_handler.py # 工具节点构建 + 错误回退 + 事件打印
├── flights_tools.py # 航班工具:查询用户航班 / 搜索 / 改签 / 退票
├── hotels_tools.py # 酒店工具:搜索 / 预订 / 修改 / 取消
├── car_tools.py # 租车工具:搜索 / 预订 / 修改 / 取消
├── trip_tools.py # 旅行推荐工具:搜索 / 预订 / 修改 / 取消
├── retriever_advanced.py # 四合一 RAG 检索:lookup_policy
├── retriever_vector.py # 旧版纯向量检索(已被 advanced 替代)
├── location_trans.py # 地理位置转换
├── init_db.py # 数据库初始化:从种子库生成运行库
└── llm_tavily.py # LLM 和 Tavily 初始化(graph_chat 中也有同名文件)
关键工具说明:
- flights_tools.py:所有航班相关工具,通过
@tool装饰器注册为 LangChain Tool,直接操作 SQLite - tools_handler.py:
create_tool_node_with_fallback(tools):创建带错误回退的ToolNode- 工具调用失败时自动捕获异常并生成友好的
ToolMessage反馈给 LLM
- retriever_advanced.py:四合一 RAG 管线:
- 混合检索:BM25 关键词 + BGE 向量 → RRF 融合
- 父文档映射:子段落检索 → 映射回完整章节
- DeepSeek 重排序:LLM 对候选文档逐条打分(1-10 分)
- 长上下文重排序:解决 "Lost in the Middle" 问题
utils/
├── __init__.py
├── cors.py # CORS 跨域中间件
├── dependencies.py # 数据库会话依赖注入 get_db()
├── docs_oauth2.py # Swagger 文档 OAuth2 认证适配
├── handler_error.py # 全局异常处理
├── jwt_utils.py # JWT Token 生成
├── middlewares.py # Token 验证中间件(核心)
└── password_hash.py # bcrypt 密码哈希
关键文件说明:
- middlewares.py:
- 全局 HTTP 中间件:每个请求到达时先执行
verify_token - 白名单路径(登录/注册/docs)直接放行
- 其他路径从
Authorization: Bearer {token}头提取 Token,用jose.jwt.decode()校验 - 校验成功后把
username绑定到request.state.username
- 全局 HTTP 中间件:每个请求到达时先执行
- jwt_utils.py:
create_token(subject)生成 JWT,payload 包含{exp, sub},默认 30 分钟过期 - docs_oauth2.py:重写
OAuth2PasswordBearer,让 Swagger 文档右上角的 "Authorize" 按钮可用,白名单路径不要求认证(避免死循环) - password_hash.py:
get_hashed_password()和verify_password(),使用 bcrypt 算法 - cors.py:允许前端
localhost:8080等来源的跨域请求 - dependencies.py:
get_db()为每个请求创建独立的数据库会话,请求结束自动关闭
- Python >= 3.10
- Conda 虚拟环境
test_env - MySQL 数据库
test_db4
conda activate test_env在 ctrip_assistant_backend 的上级目录创建 .env:
LLM_MODEL=deepseek-chat
LLM_API_KEY=your_deepseek_api_key
LLM_BASE_URL=https://api.deepseek.com/v1
TAVILY_API_KEY=your_tavily_api_key项目提供了脱敏模板 config/development.example.yml,复制后填入真实值:
cd config
copy development.example.yml development.yml编辑 development.yml,修改以下关键配置:
| 配置项 | 说明 |
|---|---|
DATABASE.PASSWORD |
MySQL 数据库密码 |
JWT_SECRET_KEY |
JWT 签名密钥(自行生成随机字符串) |
项目使用 MySQL 存储用户认证数据(需提前安装 MySQL 并启动):
-- 连接 MySQL 后执行
CREATE DATABASE IF NOT EXISTS test_db4 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;首次启动时,程序会自动创建 t_user 表(由 SQLAlchemy ORM 建表)。
cd ctrip_assistant_backend
python main.py后端运行在 http://127.0.0.1:8000,Swagger 文档在 http://127.0.0.1:8000/docs。
启动时自动完成数据库初始化(解压 travel2.zip → 生成 travel_new.sqlite)。
⚠️ 注意:前端仅为一个简易 Demo,主要方便参观者快速体验。以下文件可根据需要自行修改:
src/views/assistant/AssistantView.vue— 对话界面src/services/graph.ts— API 调用逻辑src/stores/chat.ts— 聊天状态管理
cd ctrip_assistant_fronted
npm install
npm run dev前端默认运行在 http://localhost:5173(由 Vite 启动)。
如果不启动前端,也可以直接用 Swagger 文档调试:
- 打开 Swagger 文档
http://127.0.0.1:8000/docs - 右上角 "Authorize" 用已注册账号登录
- 调用
POST /api/graph/与智能助手对话
| 层级 | 技术 |
|---|---|
| 多 Agent 编排 | LangGraph(StateGraph + MemorySaver + interrupt_before) |
| LLM | DeepSeek(deepseek-chat) |
| 后端框架 | FastAPI + JWT 认证 + Swagger / OAuth2 |
| RAG 检索 | 混合检索(BM25 + BGE 向量)→ RRF 融合 → DeepSeek 重排序 → 长上下文重排 |
| 用户数据库 | MySQL + SQLAlchemy ORM |
| 业务数据库 | SQLite(航班/酒店/租车/旅行) |
| 联网搜索 | Tavily Search API |
| 密码加密 | bcrypt(passlib) |
用户: 帮我查一下从北京飞上海的航班
主助理: [调用 search_flights] → 找到 3 个航班,展示给用户
用户: 我要退掉第三张票
主助理: [识别意图 → 委派给航班子助理]
航班子助理: [查询退票政策: lookup_policy] → ⚠ 中断,等待用户确认
系统: "AI助手马上根据你要求,执行相关操作。您是否批准上述操作?输入'y'继续"
用户: y
航班子助理: [执行 cancel_ticket] → "机票已成功取消。"
→ [CompleteOrEscalate] → 回到主助理
用户: 附近有什么推荐的酒店?
主助理: [识别意图 → 委派给酒店子助理]
酒店子助理: [调用 search_hotels] → 展示酒店列表...
