项目博客-AI旅行规划平台

从 0 到 1 打造 AI 旅行规划平台:LangGraph 多智能体工作流 + RAG 的完整工程实践

一个让用户输入目的地和偏好、自动生成逐日行程的 AI 平台。本文完整拆解它的架构设计:LangGraph 8 节点状态图、ReAct 规划智能体、三域隔离 RAG、Outbox 可靠消息、多层幂等与 AI 额度计费。全部方案均已在生产级代码中落地。

一、项目概览

AI 旅行规划平台是一个面向自由行用户的行程生成系统:用户输入目的地、日期和偏好,系统自动完成知识检索、行程规划、POI 核验,生成一份逐日行程预览,用户确认后定稿;另配一个可流式对话的 AI 旅行助手。

先给一组数据感受一下规模:

维度 数量
后端模块 23 个
API 接口 178 个
后端代码 约 2.1 万行
测试用例 507 项
数据库迁移 45 个
架构文档 75 篇

技术栈:FastAPI + Vue3 + LangGraph + Milvus + Elasticsearch + MySQL/PostgreSQL 双库 + Redis + RabbitMQ

二、整体架构

系统按"业务/AI/基础设施"分库存储:

  • MySQL:业务主库(用户、行程、生成任务、outbox 事件、额度周期)
  • PostgreSQL(AI 库):AI 专属数据(对话消息、LangGraph Checkpoint、长期记忆、助手运行记录)
  • Milvus:向量检索(RAG 稠密召回)
  • Elasticsearch:BM25 检索(RAG 稀疏召回 + 社区内容搜索)
  • RabbitMQ:异步任务管道(行程生成任务、outbox 事件分发)
  • Redis:缓存与分布式锁

#mermaid-svg-vQmvypZSFEfljZRu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vQmvypZSFEfljZRu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vQmvypZSFEfljZRu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vQmvypZSFEfljZRu .error-icon{fill:#552222;}#mermaid-svg-vQmvypZSFEfljZRu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vQmvypZSFEfljZRu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vQmvypZSFEfljZRu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vQmvypZSFEfljZRu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vQmvypZSFEfljZRu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vQmvypZSFEfljZRu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vQmvypZSFEfljZRu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vQmvypZSFEfljZRu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vQmvypZSFEfljZRu .marker.cross{stroke:#333333;}#mermaid-svg-vQmvypZSFEfljZRu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vQmvypZSFEfljZRu p{margin:0;}#mermaid-svg-vQmvypZSFEfljZRu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vQmvypZSFEfljZRu .cluster-label text{fill:#333;}#mermaid-svg-vQmvypZSFEfljZRu .cluster-label span{color:#333;}#mermaid-svg-vQmvypZSFEfljZRu .cluster-label span p{background-color:transparent;}#mermaid-svg-vQmvypZSFEfljZRu .label text,#mermaid-svg-vQmvypZSFEfljZRu span{fill:#333;color:#333;}#mermaid-svg-vQmvypZSFEfljZRu .node rect,#mermaid-svg-vQmvypZSFEfljZRu .node circle,#mermaid-svg-vQmvypZSFEfljZRu .node ellipse,#mermaid-svg-vQmvypZSFEfljZRu .node polygon,#mermaid-svg-vQmvypZSFEfljZRu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vQmvypZSFEfljZRu .rough-node .label text,#mermaid-svg-vQmvypZSFEfljZRu .node .label text,#mermaid-svg-vQmvypZSFEfljZRu .image-shape .label,#mermaid-svg-vQmvypZSFEfljZRu .icon-shape .label{text-anchor:middle;}#mermaid-svg-vQmvypZSFEfljZRu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vQmvypZSFEfljZRu .rough-node .label,#mermaid-svg-vQmvypZSFEfljZRu .node .label,#mermaid-svg-vQmvypZSFEfljZRu .image-shape .label,#mermaid-svg-vQmvypZSFEfljZRu .icon-shape .label{text-align:center;}#mermaid-svg-vQmvypZSFEfljZRu .node.clickable{cursor:pointer;}#mermaid-svg-vQmvypZSFEfljZRu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vQmvypZSFEfljZRu .arrowheadPath{fill:#333333;}#mermaid-svg-vQmvypZSFEfljZRu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vQmvypZSFEfljZRu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vQmvypZSFEfljZRu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vQmvypZSFEfljZRu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vQmvypZSFEfljZRu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vQmvypZSFEfljZRu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vQmvypZSFEfljZRu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vQmvypZSFEfljZRu .cluster text{fill:#333;}#mermaid-svg-vQmvypZSFEfljZRu .cluster span{color:#333;}#mermaid-svg-vQmvypZSFEfljZRu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vQmvypZSFEfljZRu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vQmvypZSFEfljZRu rect.text{fill:none;stroke-width:0;}#mermaid-svg-vQmvypZSFEfljZRu .icon-shape,#mermaid-svg-vQmvypZSFEfljZRu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vQmvypZSFEfljZRu .icon-shape p,#mermaid-svg-vQmvypZSFEfljZRu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vQmvypZSFEfljZRu .icon-shape .label rect,#mermaid-svg-vQmvypZSFEfljZRu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vQmvypZSFEfljZRu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vQmvypZSFEfljZRu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vQmvypZSFEfljZRu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存储
后端 FastAPI
前端
C端 Vue3
B端管理后台
API 层 178 接口
LangGraph 8节点工作流
旅行助手 ReAct Agent
RAG 检索服务
MySQL 业务库
PostgreSQL AI库
Milvus
Elasticsearch
Redis
RabbitMQ

三、核心设计一:LangGraph 8 节点行程生成流水线

这是整个项目最核心的设计。行程生成不是"一次大模型调用",而是一条确定性骨架 + 单点智能的流水线:
#mermaid-svg-1OC9GJzXgTurFvCP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1OC9GJzXgTurFvCP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1OC9GJzXgTurFvCP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1OC9GJzXgTurFvCP .error-icon{fill:#552222;}#mermaid-svg-1OC9GJzXgTurFvCP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1OC9GJzXgTurFvCP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1OC9GJzXgTurFvCP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1OC9GJzXgTurFvCP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1OC9GJzXgTurFvCP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1OC9GJzXgTurFvCP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1OC9GJzXgTurFvCP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1OC9GJzXgTurFvCP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1OC9GJzXgTurFvCP .marker.cross{stroke:#333333;}#mermaid-svg-1OC9GJzXgTurFvCP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1OC9GJzXgTurFvCP p{margin:0;}#mermaid-svg-1OC9GJzXgTurFvCP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1OC9GJzXgTurFvCP .cluster-label text{fill:#333;}#mermaid-svg-1OC9GJzXgTurFvCP .cluster-label span{color:#333;}#mermaid-svg-1OC9GJzXgTurFvCP .cluster-label span p{background-color:transparent;}#mermaid-svg-1OC9GJzXgTurFvCP .label text,#mermaid-svg-1OC9GJzXgTurFvCP span{fill:#333;color:#333;}#mermaid-svg-1OC9GJzXgTurFvCP .node rect,#mermaid-svg-1OC9GJzXgTurFvCP .node circle,#mermaid-svg-1OC9GJzXgTurFvCP .node ellipse,#mermaid-svg-1OC9GJzXgTurFvCP .node polygon,#mermaid-svg-1OC9GJzXgTurFvCP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1OC9GJzXgTurFvCP .rough-node .label text,#mermaid-svg-1OC9GJzXgTurFvCP .node .label text,#mermaid-svg-1OC9GJzXgTurFvCP .image-shape .label,#mermaid-svg-1OC9GJzXgTurFvCP .icon-shape .label{text-anchor:middle;}#mermaid-svg-1OC9GJzXgTurFvCP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1OC9GJzXgTurFvCP .rough-node .label,#mermaid-svg-1OC9GJzXgTurFvCP .node .label,#mermaid-svg-1OC9GJzXgTurFvCP .image-shape .label,#mermaid-svg-1OC9GJzXgTurFvCP .icon-shape .label{text-align:center;}#mermaid-svg-1OC9GJzXgTurFvCP .node.clickable{cursor:pointer;}#mermaid-svg-1OC9GJzXgTurFvCP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1OC9GJzXgTurFvCP .arrowheadPath{fill:#333333;}#mermaid-svg-1OC9GJzXgTurFvCP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1OC9GJzXgTurFvCP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1OC9GJzXgTurFvCP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1OC9GJzXgTurFvCP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1OC9GJzXgTurFvCP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1OC9GJzXgTurFvCP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1OC9GJzXgTurFvCP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1OC9GJzXgTurFvCP .cluster text{fill:#333;}#mermaid-svg-1OC9GJzXgTurFvCP .cluster span{color:#333;}#mermaid-svg-1OC9GJzXgTurFvCP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1OC9GJzXgTurFvCP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1OC9GJzXgTurFvCP rect.text{fill:none;stroke-width:0;}#mermaid-svg-1OC9GJzXgTurFvCP .icon-shape,#mermaid-svg-1OC9GJzXgTurFvCP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1OC9GJzXgTurFvCP .icon-shape p,#mermaid-svg-1OC9GJzXgTurFvCP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1OC9GJzXgTurFvCP .icon-shape .label rect,#mermaid-svg-1OC9GJzXgTurFvCP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1OC9GJzXgTurFvCP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1OC9GJzXgTurFvCP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1OC9GJzXgTurFvCP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不通过·回炉重规划
通过
1 validate_request 请求校验
2 retrieve_evidence 证据检索
3 planning_agent ReAct 规划智能体
4 validate_schema 结构校验
5 map_agent 高德 POI 核验
6 generation_review_agent 审核
7 预览落库·带引用
8 user_confirmation 人工确认
结束

对应的真实代码(workflow.py)非常短,因为复杂度被封装进了每个节点:

python 复制代码
graph = StateGraph(_LangGraphState)
graph.add_node("validate_request", self._graph_validate_request)
graph.add_node("retrieve_evidence", self._graph_retrieve_evidence)
graph.add_node("planning_agent", self._graph_generate_structured_draft)
graph.add_node("validate_schema", self._graph_validate_schema)
graph.add_node("map_agent", self._graph_verify_pois_with_amap)
graph.add_node("generation_review_agent", self._graph_check_date_budget_route_constraints)
graph.add_node("save_immutable_preview_with_audit", self._graph_save_preview_with_citations)
graph.add_node("user_confirmation", self._graph_user_confirmation)
# 审核节点带条件边:不通过则回到规划节点重试
graph.add_conditional_edges(
    "generation_review_agent", self._route_review,
    {"planning_agent": "planning_agent",
     "save_immutable_preview_with_audit": "save_immutable_preview_with_audit"},
)
graph = graph.compile(checkpointer=checkpointer)

为什么这样设计? 三个原因:

  1. 可控性 。8 个节点里 7 个是确定性的纯逻辑(校验、检索、Schema 检查、地图核验),只有 planning_agent 一个节点交给大模型。AI 的自由度被严格限制在"规划"这一件事上,输出还要经过 Schema 校验和地图核验两道闸门。
  2. 可追溯性。每个节点执行完都会产出一条审计记录(耗时、工具调用、降级标记)落库,行程出了问题可以精确到节点定位。
  3. 可恢复性 。图的状态通过 AsyncPostgresSaver 持久化,thread_id 绑定生成任务 ID,任何一步中断都能接续。

关于"软 HITL"的取舍 :第 8 个节点 user_confirmation 只标记"等待确认",不写正式行程数据。第 7 步落库的是一份不可变的行程预览(带引用),正式定稿走单独的 apply 接口。这比传统"人在环中阻塞等待"的 interrupt 模式更实用------图跑完就结束,用户什么时候确认都行,超时不占资源。

四、核心设计二:规划节点是 ReAct 智能体,但被"关在笼子里"

planning_agent 节点用 create_agent 构建成 ReAct 智能体,模型可以自主决定调用哪些只读工具(实时天气、官方知识库、社区帖子、网页搜索)来补齐证据,再产出结构化行程草稿。

关键约束有三层:

  1. 工具全部只读------智能体不可能污染业务数据;
  2. 输出强制约束在已验证的 POI 候选池内------检索阶段验证过哪些景点,规划就只能用哪些,从机制上杜绝"编造景点";
  3. 递归上限兜底------模型再怎么折腾也有步数上限,不会无限循环。

一句话总结:自主性和可控性不是对立的,关键在于把自主权放在哪一层。让模型决定"查什么、怎么查",但"能用什么、能产出什么"由工程侧焊死。

五、核心设计三:三域隔离的 RAG 数据面

RAG 检索分三个互不渗透的域:

内容 可见性
官方知识域 目的地攻略、POI 权威信息 全员可见
社区内容域 用户发布的田野笔记、路线 全员可见
用户私人记忆域 个人出行画像、偏好沉淀 仅本人可见(绑定 user_id)

每个域独立 collection 与索引,检索时按域过滤可见性。跨用户数据泄漏在 schema 层被拦截------不是靠应用层代码小心,而是数据结构上就查不到别人的记忆。用户私人记忆还会随对话沉淀(出发城市、兴趣标签、出行节奏、旅行者类型),越用越懂你。

检索本身是混合检索:Milvus 向量(语义)+ ES BM25(关键词)双路召回,RRF 融合排序:

python 复制代码
def _rrf(self, dense_results, sparse_results):
    merged = {}
    for results in (dense_results, sparse_results):
        for rank, result in enumerate(results, start=1):
            key = (result.document_id, result.chunk_id)
            previous = merged.get(key)
            score = (previous[1] if previous else 0.0) + 1 / (self.config.rrf_k + rank)
            merged[key] = (result, score)
    # 对"两路都排第一"的理论最优分归一化,让 min_score 有意义
    ...

融合后取 top8 上下文,且带 citation(引用来源)------行程里每个建议都能溯源到知识库文档或网页。

抑制幻觉的机制 :设置 0.35 置信度门槛(rag_min_score),低于阈值时系统不编造行程,而是转入澄清提问,让用户补充信息。宁可多问一句,不可瞎编一段。

六、核心设计四:旅行助手------create_agent + 7 工具 + SSE 流式

行程生成之外还有一个独立的对话式旅行助手。它同样是 create_agent 构建的 ReAct 智能体,挂载 7 个工具:

工具 说明
search_official_knowledge 检索官方知识域
search_community_posts 检索社区内容域
search_personal_memory 检索用户私人记忆域
get_weather 实时天气
get_weather_forecast 多日天气预报
web_search 网页搜索(时效信息)
fetch_web_page 抓取网页全文(搜索结果不够细时跟进)

对话通过 astream_events 实现 SSE 流式输出,用户能实时看到模型的思考和工具调用过程,不用对着空白等待。

两个工程细节:

  • 幂等去重 :每条消息以 client_message_idai_messages 表做唯一索引,前端重试、网络重发都不会产生重复应答、重复扣费;
  • 消息级计费:助手对话按消息扣 AI 额度,行程生成按任务扣------两种能力两种计费粒度(下文详述)。

七、核心设计五:Outbox + 三层幂等,事件不丢不重

异步链路(生成任务从创建到执行)的可靠性靠三层防线:

第一层:接口幂等。 创建生成任务时以(user_id + idempotency_key)组合校验:同 key 同内容直接返回原任务、不重复扣费;同 key 不同内容抛 IDEMPOTENCY_CONFLICT。

第二层:Outbox + Publisher Confirms。 业务事务内写 outbox_events 表(与业务数据同库同事务,天然原子);独立发布器只投递 published_at IS NULL 的事件,RabbitMQ 确认送达后回写时间戳------没确认就不回写,进程崩了重启会重新投递,配合消费端幂等实现"至少一次送达 + 恰好一次效果"。失败消息进死信队列兜底。

第三层:消费端幂等认领。 Worker 收到消息先 FOR UPDATEgeneration_jobs 行,只有 queued 状态才执行 ------MQ 重复投递、延迟重投全部直接跳过。进度回写还要校验 trace_id 且阶段只进不退,防止一条延迟的旧投递把新状态覆盖回去。

这套设计的本质:把"不丢"交给 Outbox 的事务原子性,把"不重"交给数据库行锁 + 状态机的唯一性判断,中间的 MQ 只是运输管道,坏了重投也没关系。

写路径同样有保障:行程定稿接口要求 If-Match-Version(乐观锁防协作覆盖)+ X-Operation-ID(防重试重复定稿),配合第一层的 Idempotency-Key,形成多层幂等矩阵。

八、核心设计六:AI 额度计费------把 AI 能力做成可计费产品

这是很多 AI Demo 缺失的一环:AI 能力必须有商业闭环。系统实现了双粒度计费:

  • 消息级:旅行助手每产生一条 AI 回复扣一次额度;
  • 任务级:行程生成每个任务扣一次额度(不成功不扣)。

额度挂在会员权益上:会员购买立即生效,已有排队周期自动顺延到新周期之后。所有额度校验、扣减、会员周期计算都在服务端完成,前端只做展示。

九、社区与工程沉淀

除了 AI 主线,平台还有完整的社区闭环:行程定稿后可发布"田野笔记"沉淀为社区内容(反哺 RAG 的社区域),支持路线"沿用"计数。

工程侧的沉淀:

  • 507 项测试 :核心链路(幂等、额度扣减、RAG 融合)全覆盖,数据库迁移用 alembic upgrade head → downgrade -1 → upgrade head 验证可回滚;
  • 75 篇架构文档:8 节点工作流逐节点讲解、RAG 切片算法走查、错误复盘记录等,做到"每个关键决策都有文档可查";
  • 45 个数据库迁移:全部通过 Alembic 管理,schema 变更从不手改库。

十、踩过的坑与思考

1. 短信通道的坑。 198 开头号段被运营商屏蔽导致验证码收不到,排查了很久才定位到是通道问题而不是代码问题。教训:外部依赖的故障要第一时间换通道验证,而不是反复查自己的代码。

2. LangGraph checkpoint 的取舍。 早期纠结要不要做"节点级断点续跑",后来算了一笔账:整张图跑完只要几十秒,除规划节点外全部是确定性、只读、可重入的,整图重跑比断点续跑简单一个数量级。最终采用 RabbitMQ 重投 + DB 幂等认领 + trace_id 对账实现任务级恢复。启示:分布式恢复的粒度不是越细越好,要匹配任务的耗时特征。

3. 降级优先于报错。 Dify、ES、天气 API、网页搜索,所有外部依赖都有降级路径:检索降级走单路召回、ES 降级走 MySQL LIKE、网页搜索不可用就跳过。AI 平台的价值恰恰体现在"坏一半还能用"。

十一、核心页面展示

十二、总结

这个项目给我的最大收获是:AI 应用的难点不在调通模型,而在工程化------怎么让大模型的输出可信(候选池约束 + 双重校验)、怎么让异步链路可靠(Outbox + 三层幂等)、怎么让 AI 能力可运营(双粒度计费 + 会员权益)。LangGraph 编排、RAG 混合检索这些"AI 味"的方案,最终都要落在数据库行锁、唯一索引、版本号这些朴素的工程手段上才真正可靠。

如果你也在做 AI 应用工程化落地,欢迎评论区交流。


文中截图为平台实际运行页面;架构图与流程图使用 Mermaid 绘制,代码片段摘自项目真实实现。

相关推荐
小马9261 小时前
智能体时代的两块基石:DeepSeek Harness 开源与“认知基础设施“数据库
数据库·人工智能·开源·agent
Lumistory1 小时前
照明工程落地效果评判与改造的实践参考
大数据·人工智能·光照贴图
一只肥瘫瘫1 小时前
编码器 PLL 角度与速度观测器原理
人工智能·算法
AI风控技术指南1 小时前
大模型安全围栏厂商评估:以数美科技为例拆解能力、架构和 POC 指标
大数据·网络·人工智能
weixin_531670891 小时前
Interlude起来:久坐提醒软件为什么需要登录?有没有完全本地运行的Mac休息提醒软件?
人工智能·macos·swift
熊猫钓鱼>_>2 小时前
从“串数据“焦虑到 Space 自由,我用 Agent Bucket 智能体桶管游戏素材
开发语言·人工智能·游戏·agent·bucket·workbuddy·space
机器人猎头David2 小时前
机器视觉在机器人行业中有哪些实际应用?
人工智能·机器人·招聘·机器人猎头·猎头公司
2501_936960362 小时前
1.3 RAG检索增强生成实战:手把手教你落地AI技术
人工智能