乐于分享
好东西不私藏

给AI请了个环保顾问:LangGraph构建排污许可申报Agent

给AI请了个环保顾问:LangGraph构建排污许可申报Agent

排污许可申报涉及法规查询、排放计算、表单填写、报告生成。一个LLM做决策、五个Tool函数干活、一个法规知识库兜底,LangGraph把这三层串起来,就是一个环保顾问Agent。分3篇文章讲清楚这个Agent的搭建,本文是架构篇,下篇讲对话设计,最后一篇讲知识库实现。

老张在佛山开印染厂,为了办排污许可证延期,要花时间了解目前政策。这场景不陌生,中小企业养不起专职环保专员,但申报材料错了轻则退件重则罚款。一个能一步步引导申报的 AI,正好解决这个问题。


一、一个 Agent 和一个聊天框的区别

市面上大部分 AI 客服,就是个"问答机",你问它答,答完就完了。

但排污许可申报这件事,需要的不是问答机,而是一个引导者,我们称之为 AI Agent。Agent不是简单的"你问我答"模型,而是一个能自主调用工具读取法规、代入公式算排放、根据已收集的信息决定下一步问什么的程序。

  • 什么时候该问什么问题(先问行业,再问规模,再问排放量)
  • 什么时候去查法规(用户问"COD 限值多少",它去翻标准)
  • 什么时候该算数(用户说"帮我算排放",它代入公式算)
  • 什么时候该填表(信息收齐了,它生成申报表草稿)

这套逻辑,用LangGraph的 create_react_agent 函数正好能搭起来。LangGraph是一个基于图结构编排AI Agent工作流的框架,每一步做什么、做完后跳转到哪一步,都可以显式定义。一个核心 Agent 加三层能力:

用户消息 → LangGraph Agent                ├── 🧠 LLM(DeepSeek):理解意图 + 决策                ├── 🛠 5 个 Tool 函数                │    ├─ 查法规(lookup_regulation)                │    ├─ 算废水排放(calculate_emission)                │    ├─ 算废气排放(calculate_air_emission)                │    ├─ 填表单(fill_form)                │    └─ 出报告(generate_report)                └── 📖 法规知识库(RAG → ChromaDB)

运行时,Agent 走的是 ReAct 循环:一种"推理(Reasoning)→ 行动(Acting)→ 观察(Observation)"的交替模式。LLM 输出如果包含 tool_calls 字段,LangGraph 自动路由到对应工具节点执行,工具结果返回后再喂回 LLM,直到 LLM 给出自然语言回复。这个循环由框架管理,不需要手写 while 或 if-else。LLM 根据对话上下文决定调用哪个工具以及如何拼接工具结果。

LLM根据用户消息决定调用哪个Tool,工具结果返回后再进入下一轮决策,直到给出回复


二、两个关键设计

一次只问一个问题。 第一版 prompt 写得洋洋洒洒:"请收集以下信息:行业、企业名称、联系人、废水排放量、废气排放量、有无处理设施……"结果 AI 非常听话,第一条回复就把所有问题全甩给用户,活生生把对话变成了调查问卷。后来在 prompt 里加了一条原则:"每轮回复只做一件事、只问一个问题",配合一个三步决策流程,效果才好转。这个过程单独在下一篇展开讲。

法规知识哪来的?AI 不能靠"记忆"来回答法规问题,容易产生幻觉。

办法是 RAG(检索增强生成):把法规原文拆块,用 BGE-small 模型编码成 384 维向量,存进 ChromaDB。ChromaDB 是一个轻量级开源向量数据库,专门用来存储向量和做相似检索。用户提问时,先用相同的模型把问题转为向量,在库中检索余弦相似度最高的 Top-3 条文,再把原文片段拼进 prompt,LLM 基于搜到的内容回答。

当前知识库里放了两份最核心的文件:

  • 《排污许可管理条例》(国务院令第 736 号),排污许可证制度的法律框架
  • 《排污许可管理办法》(生态环境部部令第 32 号),具体怎么申请、怎么审批的实操规则

两份文件合计约 18,000 字,分成了 46 个语义块,覆盖从申请条件到违法处罚的全部条款。

用户问"无证排污罚多少"、"排污许可证有效期多久"、"什么情况需要变更"这类问题,AI 是从原文条款里找到答案再回答,而不是自己编。


三、一个真能用的对话

对话从头到尾走一遍是这样的:

用户:你好,我想办排污许可证助手:您好!我来帮您完成排污许可证的申报。      请问您的企业属于哪个行业?比如印染、化工、造纸、钢铁、污水处理厂等。用户:印染行业,废水 300 吨/天,有污水处理设施助手:好的,我帮您算一下印染行业的废水排放情况。      ──调用 calculate_emission──      COD 排放量:9.00 kg/天(已处理)      氨氮排放量:0.68 kg/天(已处理)      还想确认一下:您企业的名称和联系人信息是什么?用户:佛山市XX印染有限公司,联系人李工助手:谢谢。请问您是否已取得环评批复?      (这是申请排污许可证的前提条件)用户:有的助手:信息已齐全。需要我帮您生成排污许可证申报表草稿,还是出一份完整的申报报告?用户:先填个表看看助手:好的,正在生成申报表草稿。      ──调用 fill_form──      ═══════════════════════════════════════              排污许可证申报表(草稿)      ═══════════════════════════════════════      一、企业基本信息         企业名称:佛山市XX印染有限公司         联系人:李工         所属行业:印染         ...      二、排污信息         废水日排放量:300 吨/天         污水处理设施:有      三、主要污染物         - COD:200 mg/L         - 氨氮:15 mg/L         参考标准:《纺织染整工业水污染物排放标准》      ═══════════════════════════════════════      请核实后提交至当地生态环境局。

注意,这段对话里没有任何写死的if-else规则。Agent每一步该问什么、什么时候调用工具,是LLM根据已收集的信息自己判断的,这正是它跟传统规则引擎的本质区别。

换一家钢铁厂,Agent会自己去查钢铁的排放标准、算烧结废气的SO₂和NOₓ 排放量、生成适用钢铁行业限值的表单。行业不同,整个流程和结果都跟着变。这是Tool函数 + RAG知识库带来的灵活性。

各行业水污染物(左)和大气污染物(右)排放标准对比。每行业同时展示两类指标,钢铁在水和气上均有约束。


四、取舍与总结

省掉了MemorySaver。 LangGraph的MemorySaver用于持久化多轮状态,但这里的Gradio前端每次调用都把完整对话历史(messages 列表)传回 Agent,Agent每次看到全部上下文,不需要框架层做状态 persistence。减少一个依赖,启动逻辑也更简单。

不用ChromaDB的Python API查向量。 ChromaDB 0.5.5 的 collection.query() 在某些场景下返回空结果。绕开方案:通过SQLite直读 embedding_metadata 表拿到文档原文,再用BGE模型 + numpy 手算余弦相似度。46 个文档的线性扫描不到 1ms,完全够用,而且每一步都透明可控。

行业排放因子是写死的。 5个行业的COD、氨氮、SO₂、NOₓ 数据以字典形式硬编码,计算公式为 E = C × V / 1000 × (1 − η),其中 η 为处理设施去除率(有设施取 0.85,无设施取 0)。真实场景应接入排放因子数据库,但原型阶段用一个 dict 验证流程已经足够。

两个法规文件用HTML格式存着,脚本解析后导入向量库。 这是最通用的方式:换一批法规,改个路径重新跑脚本就行。

从构思到跑通,这个项目最有价值的不是写了多少代码,而是想清楚了一件事:AI Agent的能力边界不在模型大小,而在你怎么拆任务、怎么喂知识、怎么设边界。

本文只搭了框架,两个关键设计各写了一篇展开:Prompt 中的"一次只问一个问题"原则如何迭代出来,以及RAG知识库从HTML到向量检索的搭建细节。