独立开发项目ColdChain Guard冷盾:冷链温控合规智能分析系统项目复盘

文章目录

  • 一、项目背景与业务痛点
  • 二、整体业务流程
  • [三、技术架构总览:DAG 三层式 5-Agent 设计](#三、技术架构总览:DAG 三层式 5-Agent 设计)
  • 四、核心模块详解
    • [4.1 DAG Agent 协同:MasterAgent 是唯一的交通指挥员](#4.1 DAG Agent 协同:MasterAgent 是唯一的交通指挥员)
    • [4.2 双通道 RAG:Qdrant 向量检索 + Neo4j 图谱溯源](#4.2 双通道 RAG:Qdrant 向量检索 + Neo4j 图谱溯源)
    • [4.3 热传导温度预估模型:出发前的风险预判](#4.3 热传导温度预估模型:出发前的风险预判)
    • [4.4 GraphAgent:从"关键词搜索"到"关系溯源"](#4.4 GraphAgent:从"关键词搜索"到"关系溯源")
  • 五、工程架构与稳定性
    • [5.1 Celery 心跳模拟 + 实时告警调度](#5.1 Celery 心跳模拟 + 实时告警调度)
    • [5.2 熔断器 + 指数退避重试](#5.2 熔断器 + 指数退避重试)
    • [5.3 缓存](#5.3 缓存)
    • [5.4 可测试性设计](#5.4 可测试性设计)
  • 六、数据模型与工程细节
    • [6.1 Pydantic 数据模型:全链路强类型化的基础](#6.1 Pydantic 数据模型:全链路强类型化的基础)
    • [6.2 熔断器的滑动窗口与降级策略](#6.2 熔断器的滑动窗口与降级策略)
  • 七、总结与收获
    • [7.1 分层架构的可扩展性](#7.1 分层架构的可扩展性)
    • [7.2 降级策略是系统的一部分](#7.2 降级策略是系统的一部分)
    • [7.3 可测试性值得投资](#7.3 可测试性值得投资)

一、项目背景与业务痛点

冷链运输(药品、生鲜、冷冻、果蔬)最核心的风险就是温度超标。一次药品运输中温度超标 2℃,如果被药监部门查到,运输方可能面临数万到数十万的罚款;如果导致药品变质,后果更是不堪设想。

但传统的温度监控系统存在两个致命问题:

  1. 检测滞后:温度超标了才报警,但此时货物可能已经变质。能不能在出发前就预估出这条路线的温控风险?
  2. 靠人找法规:温度超标后,合规人员需要翻一堆 PDF 法规文件找依据、写处置报告,往往花大半天。能不能让 AI 自动检索法规、生成分析报告?

ColdChain Guard 就是为了解决这两个问题而设计的:

  • 出发前:接入高德路线与天气,基于一阶热传导模型推演 3 小时温度曲线,提前标记风险路段,用户可以选风险最低的路线。
  • 运输中:DAG 架构 5-Agent 协同工作,从温度检测 → 法规图谱双通道检索 → LLM 分析 → Markdown 报告生成,全链路自动化,3 秒内产出合规处置报告。

下面我基于项目代码,从业务流程到技术实现逐层拆解。


二、整体业务流程

整个系统的数据流从一个叫 TemperatureLog 的数据单元开始------它是一个 Pydantic 数据模型(不是依赖注入),定义了 6 个字段:sensor_id(传感器编号)、shipment_id(运输单号)、vehicle_id(车辆编号)、category(品类)、temperature(温度)、timestamp(时间戳)。

TemperatureLog 有两个来源:Celery 心跳模拟器 每 30 秒用热传导模型算出车厢温度并构造;前端 HTTP API 通过 Pydantic 把 JSON 请求体反序列化。两条路径汇合后进入 run_pipeline(),先写入 MongoDB 持久化,再用内存对象直接送检测。

下面这张图展示了 TemperatureLog 从创建到最终被 ReportAgent 消费的完整生命周期:

三、技术架构总览:DAG 三层式 5-Agent 设计

系统的核心是一个 有向无环图(DAG) 的 Agent 协同架构。MasterAgent 作为唯一的编排者(Agent 之间不直接通信,所有数据通过 MasterAgent 中转),把 5 个 Agent 按三层拓扑串起来:

  • Layer 0 检测层:DetectorAgent --- 纯规则引擎,零外部依赖。连续 3 个采样点超阈值才触发告警,带时间戳缺口检测,过滤传感器毛刺。
  • Layer 1 并行检索层:RetrievalAgent(Qdrant 向量检索 + MongoDB 历史案例匹配)和 GraphAgent(Neo4j 图谱溯源)并行执行。
  • Layer 2 分析报告层:AnalysisAgent(DeepSeek LLM 风险分析,带熔断器保护)→ ReportAgent(5 段式 Markdown 报告组装)。

四、核心模块详解

4.1 DAG Agent 协同:MasterAgent 是唯一的交通指挥员

这里有一个容易误解的点------5 个 Agent 之间是不直接通信的。所有数据传递都通过 MasterAgent 中转。

MasterAgent 有两个入口方法:scan_and_dispatch() 做检测阶段,dispatch_alert() 做处置阶段。在 dispatch_alert() 里,它先把 DetectorAgent 产生的 AlertRecord 的 3 个关键字段(alert_id/category/severity)拿出来,构造一个 ParallelAgentInput 对象。然后用 asyncio.gather() 把这个对象同时传给 RetrievalAgent 和 GraphAgent 并行执行------这样总耗时是两者的最大值,而不是相加。

两个并行 Agent 返回后,MasterAgent 把 AlertRecord + RetrievalResult + GraphTraces[] 汇总,构造 AnalysisAgentInput 调用 AnalysisAgent。再把所有前序输出汇总构造 ReportAgentInput,最后调用 ReportAgent。

这种模式叫编排者模式。好处是每个 Agent 只需要关心自己的 Input/Output,修改任何一个 Agent 的实现不影响其他 Agent。

4.2 双通道 RAG:Qdrant 向量检索 + Neo4j 图谱溯源

RAG 是项目的核心技术亮点。分为离线入库和在线检索两个阶段。

离线入库 的流程:5 份法规文件(2 份 PDF + 3 份 TXT)→ pdfplumber 提取文字(或直接读 TXT)→ 按条款锚点切出目标段落 → _truncate() 截断到 500 字 → BGE-M3 编码成 1024 维稠密向量 → upsert() 写入 Qdrant(覆盖 seed_qdrant.py 创建的占位数据)。

在线检索有两条路径:

  • 告警自动检索(RetrievalAgent) :代码拼出 "药品 运输温度偏离,需偏差处置与法规依据" 这段查询文本,BGE-M3 编码后走 Qdrant search(),带品类过滤(保证药品告警不搜食品法规),topK=5。同时第二路从 MongoDB 取同品类历史告警,文本化后 BGE-M3 编码,用余弦相似度计算 Top3 相似案例。两路结果合并成 RetrievalResult
  • 用户聊天检索(ChatService) :用户问题直接 BGE-M3 编码,品类过滤可选,同样 topK=5。检索到的法规列表被格式化成带编号([1] [2])的文字片段,和用户问题一起拼进 DeepSeek 的 messages。

关于重排(Reranker):项目目前没有使用 BGE-Reranker 做精排。因为 Qdrant 里只有 10 条数据,按品类过滤后只剩 3-4 条,全部返回都不超量,reranker 没有意义------它只改变顺序,不改变结果集。如果未来法规库扩大到几百条,再加上会有明显收益。

向量索引 是 HNSW,Qdrant 的默认配置。创建集合时只传了 size=1024distance=COSINE,没有传 hnsw_config 参数------走 Qdrant 服务端内置的 HNSW 默认值(m=16,ef_construct=100)。

4.3 热传导温度预估模型:出发前的风险预判

这是项目另一个核心功能。

复制代码
T(t+1) = T(t) + k1*(T_outside - T(t)) - cooling + K3*door

这叫一阶热传导差分方程。"一阶"表示只看当前和下一时刻的关系,"差分"表示用差值代替微分。

公式里的四项:

  1. prev:当前车厢温度,是变化的起点
  2. k1 * (t_outside - prev):外部热量通过隔热层渗入。温差越大,渗入越快。k1 按隔热等级分 3 档(normal=0.03 / medium=0.02 / high=0.01)
  3. - cooling:制冷压缩机降温。有个温控门限 ------只有温度超过 set_point + 0.5℃ 时制冷才启动(模拟真实压缩机的启停死区,2℃ 药品的门限就是 2.5℃)
  4. K3 * door:开门热冲击(K3=2.0,开门温度跳升 2℃)

12 组参数就是 4 个品类 × 3 个隔热等级的组合:药品/生鲜/冷冻/果蔬 × normal/medium/high。每个品类有自己的 set_point(药品 2℃、生鲜 4℃、冷冻 -18℃、果蔬 5℃),决定了制冷门限的高低。

接入高德路线与天气

  • _resolve_coords("上海") → 先查本地 8 城市坐标白名单,命中直接返回,省 API;没命中调高德地理编码
  • get_weather(city) → 调高德天气实时 API 拿当前气温。10 分钟 TTL 缓存 + in-flight 合并(同一城市并发请求合并,防止触发 QPS 限制)
  • _call_driving_api() → 调高德驾车路径 API,拿路线距离、时长、途径道路坐标折线
  • _generate_t_outside_from_weather() → 用起点和终点的真实气温做线性插值,生成沿途每 5 分钟一个点的气温序列

3 小时逐点推演 :拿到沿途气温序列后,用 simulate() 方法(和心跳 step() 用的完全相同的公式)循环 36 次(3h ÷ 5min = 36步),算出每一步的车厢温度,最后得到一整条温度曲线。

风险标记 :遍历温度曲线,算两个东西。一是 risk_score = ALPHA * overtemp_ratio + BETA * duration_bias(超温点占比 × 60 + 时长占比 × 20),三档显示(≤20绿、≤40黄、>40红)。二是找连续超过 crit_up(严重上限)的路段,标成 risk_segments,告诉用户"第45-75分钟会严重超温"。

4.4 GraphAgent:从"关键词搜索"到"关系溯源"

GraphAgent 是项目里和 RetrievalAgent 并行的另一条检索路径。RetrievalAgent 找的是"和告警相似的法规条文",GraphAgent 找的是"和告警相关的一切关联信息"------这是完全不一样的两件事。

项目里用 Neo4j 存了法规知识图谱。图谱是一组节点和边:节点类型有 ProductCategory(品类)、RiskType(风险类型)、Regulation(法规条款)、Punishment(处罚力度)、IndustryStandard(行业标准)、BestPractice(最佳实践)共 6 种标签。边类型就是它们之间的关系,比如"药品品类 → 属于医药类法规 → 引用GSP第105条 → 适用5级处罚"。

GraphAgent 做的事情叫多跳遍历 (Cypher 里写的是 [*1..3],意思是走 1 到 3 步的关系边)。比如一个"药品温度超标"告警进来,GraphAgent 先找到药品这个品类节点,然后从品类节点走一步找到相关风险类型,走两步找到关联法规条款,走三步找到处罚标准------全部一次性拉出来。

返回结果是一个 GraphTrace 列表,每个 GraphTrace 里有 path_id(轨迹ID)、path_summary(这条轨迹的人类可读摘要)、entities[](经过的节点)、relationships[](经过的边)、key_points[](关键结论点,带优先级),还有 source 字段统一标记为"neo4j知识图谱"。这些结果会和 RetrievalAgent 的结果一起,送进 AnalysisAgent 做决策依据。

五、工程架构与稳定性

5.1 Celery 心跳模拟 + 实时告警调度

告警链路是 Celery Beat 3 秒钟触发一次的定时任务:

  • heartbeat_simulation_task():按 2 小时真实行驶时长(240 分钟 = 1440 步)循环。每 3 秒一步,但时间推进量是 10 秒(step_dt=timedelta(seconds=10))。所以 1440 步跑完刚好 2 小时模拟时间。心跳里调用 heat_transfer.step()(和路线预估用的同一条公式),生成 TemperatureLog 写入 MongoDB。
  • detector_task():和心跳在同一个 worker,心跳每推 5 条数据就触发一次。读取 MongoDB 最近 N 条传感器记录,走 DetectorAgent 做规则检测,命中告警后 MasterAgent.dispatch_alert() 启动完整 Agent 流水线。

5.2 熔断器 + 指数退避重试

AnalysisAgent 是唯一调用外部 LLM 的环节,外部 API 不稳定是常态,所以加了两层保护:

  • 熔断器(Circuit Breaker)_call_with_circuit_breaker() 维护一个滑动窗口,连续 5 次失败就进入"熔断打开"状态,之后 30 秒内所有请求直接走降级,不去碰 LLM。30 秒后自动"半打开",放行一笔请求试探,成功就恢复,失败继续熔断 30 秒。
  • 降级生成 :熔断打开或 LLM 抛异常时,不用等超时,直接用本地模板生成一份分析报告,里面包含超温时间、平均温差等 _calc_stats() 预计算的硬数据,只是没有 LLM 的深度解读。报告里会标记 [LLM 熔断降级] 前缀,ReportAgent 识别到这个标记会在报告顶部加醒目警告。
  • 指数退避重试 :RetrievalAgent 和 GraphAgent 的 @retry 装饰器,每次失败等待时间是 2^attempt × base_delay,第一次失败等 1 秒,第二次等 2 秒,第三次等 4 秒------给 Qdrant/Neo4j 恢复留时间。

5.3 缓存

get_weather() 是高德 API 的高频调用点。一趟路线请求要查起点和终点两个城市的天气,多个并发请求可能查到同一个城市。所以缓存有两层:

  • 10 分钟 TTL 缓存:同一个城市的天气结果,10 分钟内不再调高德。
  • In-flight 合并 :如果两个请求几乎同时到、查同一个城市、缓存都失效了,第二个请求不会重新调 API,而是等待第一个请求的返回结果。用 asyncio.Lock 保证同一个城市同一时刻只有一个请求在飞。

5.4 可测试性设计

项目特别注重可单测性:

  • DetectorAgent 零外部依赖 :输入是 TemperatureLog 列表,输出是 AlertRecord | None。不需要 MongoDB/Redis/任何网络,直接在单元测试里构造数据就能跑。
  • TemperatureLog / AlertRecord 全部 Pydantic BaseModel :字段类型校验、缺失字段检测、自动序列化,测试数据构造一行 TemperatureLog(...) 搞定。
  • Agent 输入输出强类型化ParallelAgentInputAnalysisAgentInputRetrievalResult 都是 BaseModel。MasterAgent 里的组装过程就是"拼字段",IDE 会检查类型错误,运行时也有兜底。

六、数据模型与工程细节

6.1 Pydantic 数据模型:全链路强类型化的基础

项目里最关键的模型就是 TemperatureLog,它是从 Celery 心跳模拟器 → HTTP API → MongoDB → DetectorAgent 整条链路的流通货币:

python 复制代码
class TemperatureLog(BaseModel):
    sensor_id: str    # 传感器编号
    shipment_id: str  # 运单号
    vehicle_id: str   # 车牌号
    category: CategoryType   # 品类枚举
    temperature: float      # 温度值
    timestamp: datetime     # UTC 时间戳

6 个字段分成三组:sensor/shipment/vehicle_id 三 ID 用来溯源"哪辆车、哪批货、哪个传感器",category 是品类枚举(决定了阈值表),temperature + timestamp 是时序数据。

为什么用 Pydantic BaseModel? 不是依赖注入,而是数据契约------每个 Agent 的 Input/Output 都是 BaseModel。好处有三个:

  1. 字段类型自动校验,传参错误在运行时第一时间抛出,而不是静默变成脏数据
  2. model_dump_json() / model_validate_json() 直接和 JSON/HTTP/MongoDB BSON 互转
  3. IDE 能做类型补全,MasterAgent 组装参数时写错字段名直接报红

6.2 熔断器的滑动窗口与降级策略

熔断器(Circuit Breaker)的三态机是经典设计:

  • 关闭(Closed):正常转发请求,失败计数 +1。连续 5 次失败后变成打开。
  • 打开(Open):所有请求直接走本地模板,不碰 LLM API。持续 30 秒后变成半打开。
  • 半打开(Half-Open):只放行 1 笔请求。成功 → 关闭;失败 → 再打开 30 秒。

熔断器的价值在于"故障隔离"------DeepSeek API 挂了不会把整个 Agent 流水线拖死,系统还能生成报告(只是没有 LLM 深度解读那部分)。

七、总结与收获

整个项目从"冷链温控是冷启动业务场景"出发,逐步落地成了一个能跑、能测、能降级的完整系统。完成这个项目我有几点感悟:

7.1 分层架构的可扩展性

5-Agent DAG 不是一开始就设计好的。最开始只有 DetectorAgent 做规则检测,然后加了 RetrievalAgent 做法规检索,后来又加了 GraphAgent 做图谱溯源,再后来加了 AnalysisAgent 用 LLM 分析。MasterAgent 的三层拓扑(检测 → 并行检索 → 分析报告)是功能自然增长后慢慢梳理出来的,不是一开始就画出来的。

所以好的分层设计有个标志:加新功能不需要改现有代码的核心路径。GraphAgent 加上时,DetectorAgent 一行代码没改;AnalysisAgent 加熔断器时,ReportAgent 只要识别降级标记就行。

7.2 降级策略是系统的一部分

分布式系统里"某个环节会挂"不是故障,是常态。AnalysisAgent 的熔断器 + 本地模板降级,高德 API 失败时的 _plan_route_mock() 正弦波模拟,天气 API 不可用的 30℃ 基准插值------这些降级路径的代码量不比主路径少多少。

但它们值得。 真正跑起来时,你会发现 99% 的时间里这些降级逻辑都不触发,但只要触发一次,系统的观感就是从"挂了"变成"还能用"。这是生产系统和 Demo 的关键区别。

7.3 可测试性值得投资

DetectorAgent 纯规则、零外部依赖。heat_transfer.step() 函数纯数学计算。这两个模块的单元测试能覆盖 90%+ 的代码路径,而且跑起来零环境依赖。

相反,如果 DetectorAgent 直接读 MongoDB、直接连 Redis,那写测试的成本会翻 10 倍。所以把"纯计算"和"IO"拆开,从第一天开始就值得。

相关推荐
pqpo1 小时前
Agent Team 的上下文工程设计:如何组织和共享上下文
agent·ai编程
leeyi1 小时前
Agent 间 Transfer 交接:用户在不同 Agent 间无缝切换(第93篇-E79)
人工智能·aigc·agent
很楠爱上1 小时前
从“AI 看合同”到可举证的合同决策链:CounterClause(对薄) 的架构设计与工程实践
人工智能·经验分享·python·学习·agent
tachibana22 小时前
初识智能体
人工智能·ai·大模型·llm·agent
狂师2 小时前
AI 测试 | 把 UI 自动化测试执行固化成五步流程,这套AI Skill 思路可以直接抄
人工智能·agent·测试
苏灿烤鱼3 小时前
连庄+2,729,空降榜眼只+440
rust·openai·agent
SeaDhdhdhdhdh11 小时前
MCP Server 搭建与使用指南
java·ai·agent·mcp
李姆斯11 小时前
为啥Agent在coding表现这么好,但是在别的领域就是差的不少?
前端·agent·ai编程
nix.gnehc11 小时前
工具表之外 -- 派生、注入与两条原语
agent