ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FitMall技术博客(AI重做)

FitMall技术博客(AI重做) FitMall 运动优选一个健身电商平台的三阶段 AI 架构演进全记录从规则引擎到 Dify 低代码编排再到 LangGraph 原生智能体——本文完整拆解一个全栈健身电商项目的技术选型、架构设计与 AI 工程化的踩坑实录。不只是接了个 AI而是一步步演进出企业级的 Agent 架构。目录项目概览技术栈一览架构设计AI 架构演进从 Dify 到 LangGraph为什么要重构Agent 与 Workflow两种形态的选型哲学混合检索底座Milvus ES 双路召回聊天 AgentReAct 循环 可跳转卡片训练建议与计划固定 Workflow 强校验 JSON慢任务异步化Celery RabbitMQ多级缓存让重复请求秒回降级链AI 永远不成为单点那些有意思的工程细节踩坑实录总结与思考项目概览FitMall 是一个面向运动健身人群的全栈 Web 应用承载了电商交易、课程学习、社区互动、训练管理、AI 智能助手五大业务线。前后端分离Docker Compose 一键部署。核心数据流浏览器 → Nginx (80) → Vue 3 SPA → /api/* → FastAPI (8000) ─┬→ MySQL / Redis / RabbitMQ ├→ Elasticsearch商品/课程/知识库检索 ├→ Milvus / Zilliz Cloud向量检索 ├→ PostgreSQLLangGraph checkpoint 持久化 ├→ MinIO头像/帖子图/课程视频S3 协议 └→ LLM APISiliconFlow 等LangGraph 编排AI 能力是本项目最有故事的部分它经历了三个阶段阶段形态问题V1BMI 规则引擎死板千人一面V2Dify 云端编排依赖外部平台意图识别靠提示词硬凑商品推荐链路绕一圈公网V3LangGraph 原生当前自主可控Agent Workflow 双形态混合检索异步化任务技术栈一览后端组件技术说明Web 框架FastAPI Uvicorn全异步支持 SSE 流式响应数据库MySQL 8.0 SQLAlchemy 2.0异步引擎 aiomysql 驱动缓存 / 会话Redis 7会话、验证码、分布式锁、AI 结果缓存、任务状态搜索引擎Elasticsearch IK 分词器商品/课程/帖子/用户 知识库 BM25向量数据库MilvusZilliz Cloud知识库稠密向量检索Embeddingbge-m3SiliconFlow API1024 维中文语义向量Agent 编排LangGraph LangChain聊天 Agent 固定 WorkflowCheckpointPostgreSQL AsyncPostgresSaverAgent 状态持久化服务重启记忆不丢消息队列RabbitMQ 4.xES 异步同步、订单超时、训练通知 Celery Broker异步任务Celery 5.6solo 池AI 训练计划后台生成认证JWT HTTP-Only Cookie双通道鉴权Redis Session 存储支付支付宝沙箱RSA2-SHA256自签名实现不依赖官方 SDK存储MinIOS3 协议 boto3自建对象存储匿名只读桶 预签名 URL短信阿里云 DypnsapiRedis 存验证码5 分钟过期ID 生成自研 Snowflake41 位毫秒时间戳 10 位机器 12 位序列前端组件技术说明框架Vue 3.5 Composition API TypeScriptscript setup语法构建Vite秒级热更新状态管理Piniauser / admin / cart 三个 Store样式TailwindCSS自定义 “Iron Chalk” 设计系统HTTPAxios 拦截器Cookie 自动携带 并发刷新拦截图表Chart.js体重趋势 / 训练统计WebSocket原生 WebSocket 自封装 Composable实时聊天 在线状态架构设计领域驱动模块划分后端采用按业务域垂直拆分的模块化结构每个模块内部遵循统一的四层范式module/ ├── models.py # SQLAlchemy 数据模型与数据库表一一对应 ├── schemas.py # Pydantic 请求/响应 SchemaAPI 契约 ├── router.py # FastAPI 路由定义薄层纯路由注册 └── service.py # 业务逻辑核心所有复杂逻辑在这里全局共12 个业务域auth goods course training community cart order ai admin user shared middleware其中ai域在 LangGraph 重构后内部又拆成了清晰的三层app/ai/ ├── agents/ # 聊天智能体ReAct 循环、工具集 │ ├── chat.py # LangGraph 图agent - tools 回环 │ └── tools.py # 商品/课程搜索、知识检索、用户画像 三个工具 ├── workflows/ # 固定流程训练建议、训练计划 │ ├── advice.py # 建议工作流 │ ├── plan.py # 计划工作流generate - repair 校验回环 │ └── schemas.py # 输出结构Pydantic 强校验 ├── rag/ # 检索底座 │ ├── retriever.py # 混合检索 RRF 融合 │ ├── milvus.py # 向量库适配 │ ├── es_bm25.py # BM25 适配 │ ├── embedding.py # bge-m3 封装 │ └── ingest.py # 语料切分入库 ├── llm/ # 模型供应商路由 ├── checkpoint.py # LangGraph 状态持久化Postgres checkpointer 管理 ├── chat/ advice/ plan/ search/ # 各业务 API路由服务 └── dify_client.py # Dify 客户端保留作降级通道 tasks/ ├── celery_app.py # Celery 实例brokerRabbitMQbackendRedis └── plan_tasks.py # 训练计划后台生成任务请求生命周期HTTP 请求 → CORS 中间件跨域白名单校验 → Request-ID 中间件注入 UUID 追踪链路 → 日志中间件记录 method/path/status/duration → 路由匹配 → 依赖注入DB Session / Redis / Auth User → Service 层业务逻辑 AI 编排 外部调用 → 统一响应格式 → {code: 1, data: {...}, message: success} → 异常处理器兜底6 种自定义异常 → HTTP 状态码映射AI 架构演进从 Dify 到 LangGraph4.1 为什么要重构Dify 阶段的 AI 助手是这样的用户想买蛋白粉 → Dify ChatflowLLM 意图识别 → {category: goods, keyword: 蛋白粉} → Dify HTTP 节点 → 公网回调自己的 API → ES 搜索 → LLM 格式化 → 推荐文案 product:// 链接能用但有三个企业级场景下的硬伤链路绕公网消息从自己的服务器出发绕到 Dify 云端再让 Dify 回调自己的公网 API 拿数据。一个搜商品的内部操作数据出网两次延迟和暴露面都不可控。编排黑盒意图识别的提示词、工具的调用时机全在 Dify 控制台里代码仓库里没有版本管理出问题只能登平台排查。形态单一聊天、建议、计划三种完全不同性质的任务被塞进同一种工作流形态里计划生成这种 2 分钟级慢任务在同步请求里直接把接口拖死。重构后的目标很明确Agent 管聊天、Workflow 管生成、检索自建、慢任务异步化、每一层都有降级。4.2 Agent 与 Workflow两种形态的选型哲学LangGraph 提供了两种建图方式本项目两种都用了各管一摊聊天助手Agent训练建议/计划Workflow形态agent ↔ tools回环LLM 自主决策start → 步骤 → END固定直线类比一个有工具箱的员工自己判断拿哪把工具一条流水线原料进去、成品出来适用输入不可预测需要灵活路由输入输出固定追求稳定可控失败代价高工具调错顶多答非所问低流程不会跑偏这个判断是整个重构的地基不要用 Agent 去做 Workflow 的事。用户点生成训练计划时不需要 AI 思考要不要先查一下用户资料——这些步骤是确定的写死就行确定性场景里自主性只会引入不稳定。4.3 混合检索底座Milvus ES 双路召回健身知识库深蹲要领、减脂原则、增肌营养等语料的检索单一手段都有短板纯向量检索Milvus懂语义“怎么练腿” 能命中 “深蹲”“臀桥”但对精确术语如专有名词不够敏感纯关键词ES BM25精确匹配强但 “怎么练腿” 和 “下肢训练” 永远匹配不上于是做了经典的双路召回 RRF 融合用户 query │ ┌───────────┴───────────┐ bge-m3 向量化 IK 分词 │ │ Milvus 近邻检索 ES BM25 检索 语义召回 TopK 关键词召回 TopK └───────────┬───────────┘ │ RRF 融合去重 score Σ 1 / (k rank) │ 归一化 → min_score 过滤 │ 最终 TopK 上下文几个关键实现细节1. 两条路径互相兜底任一失败不炸整体# retriever.py — 双路并行各自失败只降级自己tasks{dense:self.milvus.search(query_vector,top_k...,filtersfilters),bm25:self.elasticsearch.search(query,top_k...,filtersfilters),}completed:dict[str,list]{}forname,taskintasks.items():try:completed[name]awaittaskexceptExceptionaserror:logger.warning(知识检索路径 [%s] 失败跳过: %s,name,error)ES 挂了照样能出纯向量结果反之亦然。2. RRF 分数归一化让min_score阈值有可比性RRF 原始分数和有几条路径参与相关两路都命中时分数天然翻倍。除以理论最优分所有活跃路径都排第一的分数把结果归一到 0~1配置的置信度阈值才有稳定含义# 归一化除以所有活跃路径都排第一时的最优分数best_possibleactive/(self.config.rrf_k1)scorescore/best_possible3. 置信度兜底最高分低于阈值时不硬编答案而是返回置信度不足状态——宁可承认不知道也不拿不相关的知识糊弄用户。这在健身这种专业领域很重要一句错误的训练建议可能导致受伤。4. 入库侧语料按标题/段落切分bge-m3 向量化后同时写入 Milvus带knowledge_domain元数据过滤和 ES 知识索引BM25 用同一份内容双格式存储检索侧天然对齐。4.4 聊天 AgentReAct 循环 可跳转卡片聊天 Agent 用 LangGraph 的经典agent ↔ tools回环结构START → agentLLM 思考 ──有 tool_calls──→ tools执行工具 ↑ │ └────────── 结果回填 ←──────────────┘ 无 tool_calls 或达到步数上限 → END三个工具覆盖三类典型问题工具触发场景数据来源search_goods_or_courses“想买蛋白粉”“有减脂课吗”ES复用商城搜索服务search_fitness_knowledge“深蹲膝盖疼怎么回事”混合检索Milvus ESget_user_training_profile“按我的情况该练什么”MySQL画像 近 30 天统计卡片推荐是体验上的关键设计工具返回的每个商品/课程不只进 LLM 上下文还同时收进一个cards_holder容器。Agent 跑完后SSE 流里额外推一个cards事件前端渲染成可点击跳转的卡片# agents/chat.py — 流式产出两类事件asyncforchunk,_metadataingraph.astream({messages:input_messages,step:0},stream_modemessages):# 工具调用轮次不产生正文只产生 tool_call 参数流跳过避免回显 JSONifisinstance(chunk,AIMessageChunk)andchunk.tool_calls:continuetextgetattr(chunk,text,)oriftext:yield{content:text}ifcards_holder:yield{cards:cards_holder}# 最多 6 张去重限量为什么卡片不交给 LLM 输出系统提示词里写死了铁律“只使用工具返回的真实商品/课程绝不编造名称、价格或链接”。卡片数据从工具结果直通前端LLM 只负责组织推荐语言——这是从根源上杜绝 AI 幻觉出假商品的做法。模型哪怕再想编它也没有输出卡片的通道。防失控max_steps6步数上限防止模型陷入调工具→不满意→再调工具的死循环烧 Token。多轮记忆双模式Agent 的会话状态通过AsyncPostgresSaver写入 Postgres checkpointthread_id直接复用聊天会话 ID——服务重启、多 worker 部署后新一轮请求只传新增的那条消息历史由 checkpoint 恢复图从中断处无缝接力。Postgres 不可用时自动降级为「MySQL 历史全量重放」模式把AiMessage表的全部历史拼进初始messages功能不丢只是每次全量重放。删除会话时同步清理对应 checkpoint thread不留孤儿数据。4.5 训练建议与计划固定 Workflow 强校验 JSON训练计划生成是典型的确定场景输入目标/周期/水平/可用日/偏好和输出周×天嵌套的结构化计划都是固定的。它的 Workflow 长这样START → generate结构化输出 ──校验通过──→ END ↑ │ │ 校验失败且重试 2 └──── repair带错误信息重生成←┘核心技巧是with_structured_output——不是让 LLM “输出 JSON”那要靠祈祷而是用 Pydantic Schema 约束输出结构框架层保证解析# workflows/plan.py — generate 节点asyncdefgenerate(state:_PlanState)-dict:boundmodel.with_structured_output(PlanOutput)# 强校验resultawaitbound.ainvoke([(system,_PLAN_SYSTEM_PROMPT),(user,f请基于以下要求生成训练计划\n{request}),])return{output:payload,error:}# 成功# 失败时 return {error: str(e), repairs: 1} # 进 repair 环repair 环是亮点校验失败不是直接抛错而是把错误信息拼进提示词让模型自己修最多修 2 次。类比成代码编译报错后让 AI 自己改 bug——大部分格式问题一轮就修好用户体验从失败重试变成稍慢一点但成功。修复仍失败则返回None由上层降级到本地模板计划减脂/增肌/塑形/保持 四套模板保证接口永远有可用输出。4.6 慢任务异步化Celery RabbitMQ问题完整训练计划生成要 2~2.5 分钟LLM 生成长 JSON 校验。同步接口意味着用户盯着转圈 2 分钟、HTTP 超时风险、uvicorn worker 被占死。方案生成任务交给 Celery workerAPI 立即返回前端轮询。POST /ai/plan/generate │ ├─ Redis 查任务状态已 done → 直接返回计划缓存命中 ├─ 状态 pending_review → 返回 pending去走确认流程不重复生成 ├─ 状态 running → 返回 pending去重不重复派发 └─ 全新请求 → 标记 running → Celery 派发 → 返回 {status:pending, task_key} Celery worker独立进程 └─ LangGraph 生成草稿 → Redis 写 {status:pending_review, weekly_plan} 后端先不落库等用户确认 前端每 3 秒 GET /ai/plan/status?task_keyxxx └─ pending 继续等 / pending_review 弹「草稿确认框」→ 采纳/放弃 done 渲染计划 / failed 提示错误 POST /ai/plan/confirm {task_key, action} └─ actionaccept → 落库停用旧计划启用新计划→ status 变 done actionreject → status 变 rejected丢弃派发层还有一层降级RabbitMQ 不可用时自动回退到进程内asyncio.create_task单机开发环境不装 MQ 也能跑# plan/router.py — 派发优先级def_dispatch_plan(task_key,user_id,plan_request)-bool:try:fromtasks.plan_tasksimportgenerate_plan_task generate_plan_task.delay(task_key,user_id,plan_request)# CeleryreturnTrueexceptException:returnstart_background_generation(...)# 进程内 asyncio 兜底Celery 任务里的一个深坑任务是同步函数内部跑的是 asyncio 代码LangGraph 生成所以每个任务用asyncio.run()自建事件循环。落库动作不在任务里做——因为 Human-in-the-loop 需要先生成草稿、等用户确认再落库所以任务只负责生成并写 Redis真实库写发生在主进程的 confirm 接口里async 代码直接复用主进程 engine无 loop 冲突。搭配-P solo单并发池 任务内独立 loop 才稳。Worker 启动方式RabbitMQ 4.x 有兼容配置见踩坑实录python-mcelery-Atasks.celery_app worker-Psolo--loglevelinfo4.7 多级缓存让重复请求秒回三类缓存各司其职缓存KeyTTL失效逻辑训练建议用户画像 近 30 天统计 的 SHA16 小时画像/训练数据一变key 自然变化自动失效训练计划生成请求参数的 SHA17 天换目标/周期即新 key任务状态ai:plan:task:{key}1 小时轮询专用含 done/running/failed建议缓存的 key 设计值得一提不是简单的user:{id}而是把影响建议内容的所有因子目标、水平、身高体重、BMI、30 天训练量、时长、卡路里hash 进 key。好处是天然自失效——用户今天练了一节课、明早称了体重明天的请求 key 就变了自动重新生成而数据没变化时相同请求直接命中秒回。计划接口的三段式# 1. 已有完成结果缓存/任务 done直接返回taskawait_task_get(task_key)iftaskandtask.get(status)done:returncreated(PlanResponse(...))# 2. 已在生成中去重不重复创建任务iftaskandtask.get(status)running:returncreated({status:pending,task_key:task_key})# 3. 先标记 running再派发后台生成# 先标记后派发的顺序很关键避免任务瞬间完成被 running 覆盖await_task_set_running(task_key)_dispatch_plan(...)注意第 3 步的注释——先标记 running 再派发因为极端情况下 Celery 任务可能秒级完成缓存命中 LLM 响应后写 running 会把 done 结果覆盖回 running前端永远轮询不到结果。4.8 降级链AI 永远不成为单点每个 AI 功能都是三级火箭逐级点火聊天 LangGraph Agent → Dify 兜底 → 本地占位回复 Agent记忆 Postgres checkpoint → MySQL 历史全量重放 建议 LangGraph Workflow → Dify → BMI 规则引擎5 级分类 计划 LangGraph Workflow → 本地模板计划4 种目标 计划派发Celery(RabbitMQ) → 进程内 asyncio 知识检索Milvus ES 双路 → 单路降级 → 知识库暂不可用实测中最底层从不缺席即使 LLM API、Dify、Milvus、RabbitMQ全部同时挂掉用户依然能拿到一份 BMI 分级的文字建议和一份模板训练计划。AI 是锦上添花不是核心链路——这条原则贯穿了整个设计。那些有意思的工程细节1. HTTP-Only Cookie 会话认证用 Redis 替代 JWT传统 JWT 的痛点Token 存localStorageXSS 脚本可窃取泄露后在过期前无法撤销。FitMall 的方案登录 → 生成 Snowflake Session Token → 用户信息存 Rediskey: session:{token}, TTL: 24h → Token 写入 HTTP-Only Cookie前端 JS 不可读 → 后端解析 Cookie → Redis 查找 → 注入当前用户防 XSSHTTP-Only Cookie 对 JavaScript 完全不可见即时失效删除 Redis Key 即可让任意 Token 立即失效用户/管理端隔离两套独立 Cookie 名和 Redis 前缀杜绝越权自动续期每次活跃请求刷新 TTL同时保留 JWT 作为 WebSocket 的鉴权通道浏览器 WebSocket API 无法自定义 Cookie Header双通道并存。2. 手搓 Snowflake 分布式 ID 生成器┌─┬──────────────────────────┬──────────────┬──────────────┐ │0│ 41-bit 时间戳 (ms) │ 10-bit 机器码 │ 12-bit 序列号 │ └─┴──────────────────────────┴──────────────┴──────────────┘自定义纪元2024-01-01可用到 2094 年、时钟回拨容忍 5ms、threading.Lock保证线程安全、整数/Hex 双输出库表主键 / URL 各取所需。3. 双通道 ES 同步直写 消息队列兜底┌─ 通道 1: 直接写 ES用户立即搜到 CRUD 操作 ──────────┤ └─ 通道 2: RabbitMQ 消息 → 异步消费重试 ↑ ES 临时不可用时兜底重放直写保证管理员刚上架就能搜到消息队列保证 ES 恢复后数据最终一致。消费端指数退避重试5s→10s→20s→40s→60s最多 5 次。4. IK 分词器的双分析器策略{ik_index_analyzer:{tokenizer:ik_max_word},// 索引细粒度高召回ik_search_analyzer:{tokenizer:ik_smart}// 搜索粗粒度高精度}“增肌蛋白粉” 索引时切成尽可能多的词条任意组合可命中搜索时只做粗粒度切分避免苹果手机匹配到水果。这套策略同样服务于知识库 BM25 检索。5. 订单超时取消与 Redis 分布式锁下单后 30 分钟未支付自动取消并回滚库存。RabbitMQ 消息 消费端sleep剩余时间 RedisSET NX EX分布式锁防多实例并发处理。简单直接适合单实例场景大规模需换死信队列。6. WebSocket 实时聊天与在线状态内存字典存本进程连接 Redis 集合存跨进程在线用户双端同步注册/清理WebSocket 失败自动降级 HTTP POST 发消息未读计数实时推送。7. 对象存储从阿里云 OSS 迁到自建 MinIO早期用的阿里云 OSSSDKoss2后来主动迁到了自建 MinIO理由有三成本OSS 按存储 流量计费演示项目不值当MinIO 部署在自家服务器零边际成本。自家的井MySQL/Redis/ES/RabbitMQ/Postgres 全在自建服务器上再挂个云 OSS 属于自家有井还去买水还多一份外部依赖要维护。迁移成本几乎为零MinIO 完全兼容 S3 协议SDK 换成boto3S3 事实标准对外接口签名upload_file/delete_file保持不变三个调用点零改动。两个协议差异点值得记path-style 寻址MinIO 不支持 AWS 的 virtual-host 风格URL 是endpoint/bucket/key和公开读策略MinIO 用桶策略 JSON 授权匿名GetObject替代 OSS 的公共读桶。业务层保留指数退避重试和预签名 URL 能力语义完全对齐。踩坑实录重构过程真机联调修掉的坑每一个都值得记录1. RabbitMQ 4.3 拒绝 Celery 的 transient 队列Worker 一启动就抛541 INTERNAL_ERROR。根因RabbitMQ 4.3 起默认禁用transient 非排他队列而 Celery 的 control/mingle/gossip 广播队列正是这种类型。解法纯代码侧不动服务器# celery_app.pyworker_disable_mingleTrue,worker_disable_gossipTrue,worker_enable_remote_controlFalse,control_queue_durableTrue,event_queue_durableTrue,单 worker 场景这些广播本来就没用关掉反而干净。2. SQLAlchemy engine 与 event loop 的绑定Celery 任务里复用主进程的数据库 engine 会炸——连接池绑定已关闭的 loop。每个任务必须create_async_engine自建、用完dispose()。3. Embedding 服务的dimensions参数SiliconFlow 的 bge-m3 不接受dimensions参数加了直接 400。做成配置开关EMBEDDING_SEND_DIMENSIONS默认不发送。4. LangGraph 状态累积用普通TypedDict定义状态消息无法跨节点累积每个节点返回值直接覆盖。改继承MessagesState其内置的add_messagesreducer 自动做追加合并。5.tool装饰器漏加工具函数写了但没挂toolLangChain 不识别Agent 永远不调用它。工具存在和可被 Agent 发现是两回事。6. 工具调用流的正文回显stream_modemessages下工具调用轮次也会产出 chunk内容是 tool_call 参数 JSON。不过滤的话用户会看到聊天框里突然刷出一坨原始 JSON。按chunk.tool_calls跳过即可。7. 先标记 running 再派发顺序反了会出现任务毫秒级完成写 done → 主协程再写 running 把它覆盖 → 前端永远轮询不到结果。并发写同一个 key 时想清楚时序。8. 依赖版本冲突langchain-openai 1.6要求langchain-core1.6langgraph-checkpoint-postgres的正确版本区间是2.0,3.0。AI 生态迭代极快锁版本区间前先看清依赖树。9. 新版 uvicorn 在 Windows 硬编码 ProactorEventLoop接入 Postgres checkpoint 后本地启动一直降级——psycopg 的 async 模式只支持 SelectorEventLoop而这版 uvicorn 的asyncio_loop_factory在 Windows 上直接return asyncio.ProactorEventLoop且 uvicorn 先建事件循环、后导入 app 模块所以在 main.py 里设 event loop policy 永远太晚。解法是启动命令显式传自定义 looppython-muvicorn app.main:app--loopasyncio:SelectorEventLoop--loop支持任意module:attribute导入串SelectorEventLoop跨平台存在Linux 本来就是默认这条命令两边通用。典型教训event loop policy 只影响未来的循环框架自己建循环时 policy 就成了摆设。10. psycopg 连接池必须 autocommitAsyncPostgresSaver.setup()建表时会执行CREATE INDEX CONCURRENTLY这条 SQL不能跑在事务里而 psycopg 默认开事务。自建连接池时必须传kwargs{autocommit: True, prepare_threshold: 0, row_factory: dict_row}——这三个参数抄官方from_conn_string的实现就对了自作聪明省掉任何一个都会在奇怪的地方报错。总结与思考这轮重构做对了什么形态分离Agent 管不可预测的对话Workflow 管确定性的生成。用对工具比用好工具更重要。幻觉从架构层封死商品卡片数据从工具直通前端LLM 没有输出卡片的通道——不靠提示词祈祷靠数据通路设计。慢任务异步化2.5 分钟的生成从接口卡死变成立即反馈 进度轮询且带缓存和去重。状态真正持久化Agent 会话状态落 Postgres checkpoint服务重启记忆不丢——多轮记忆从每次全量重放变成只传增量、断点接力。每一层都降级AI 服务挂了系统照常运转只是变笨不变瘫。可以继续演进的方向意图分流前置先用小模型分类闲聊/咨询/导购闲聊直达 LLM 省去工具绑定开销计划生成提速分段生成 并发拼装每周一个子任务2.5 分钟压到 30 秒级订单超时的sleep方案换 RabbitMQ 死信队列支撑多实例部署更强 Human-in-the-loop当前是生成后确认草稿结合已有 checkpoint 可演进为生成前 interrupt 等用户确认参数——图跑到一半停下来用户点确认后从断点继续项目数据后端12 个业务模块 AI 三层架构agents/workflows/rag前端~40 个 Vue 组件3 个 Pinia Store基础设施Docker Compose 编排MySQL / Redis / ES / RabbitMQ / Postgres / MinIO / Milvus云端AI 链路1 个 ReAct Agent3 工具、2 个固定 Workflow、双路混合检索、Celery 异步任务、三级缓存、Postgres checkpoint 持久化从调一个 API到设计一套 AI 架构这个项目最大的收获是AI 工程化的核心不是模型是数据通路、失败路径和响应时间的设计。模型会越来越好但这些工程问题永远存在。
返回列表