大神经的花园 · 系统架构
对外站点 https://jobrag.com.cn。本页只展开花卉站:检索、花神经客服、花友图鉴、花园建档。
同机还有票据管家与商务标书 Agent,不画进主图;两边只共用匿名反馈库。
1. 整体架构
浏览器只打花卉站。Nginx 终结 HTTPS,Gunicorn 跑 Flask。查询与客服共用 query_pipeline,再按层访问本地卡片、花友库或 Dify。
flowchart TB
user["用户浏览器"]
nginx["Nginx 1.18.0 HTTPS :443"]
gunicorn["Gunicorn 26.0.0 2 workers :5000"]
flask["Flask 3.1.3 · Python 3.10.12"]
subgraph pages["页面"]
home["首页"]
searchPage["结果页 search"]
community["花友图鉴 community"]
garden["花迹花园 garden"]
feedback["意见反馈 feedback"]
end
subgraph apis["核心接口"]
searchApi["POST api-search"]
chatApi["POST api-flower-chat"]
uploadApi["POST community"]
fbApi["POST api-feedback"]
end
qp["query_pipeline 清洗 · 认植物 · 意图 · 改写 · 分库"]
local["local_plants L1养护卡 L2秋海棠名录"]
contrib["user_contributions 花友库 + CLIP"]
bloom["bloomtrace 花园档案 + 识图"]
mem["bt_chat_sessions 会话记忆"]
fbstore["feedback_store 匿名意见"]
subgraph ext["外部与知识层"]
dash["DashScope qwen-vl-plus"]
weather["和风天气 QWeather"]
clip["本地 CLIP ViT-B-32"]
dify["Dify 1.16.1"]
weaviate["Weaviate 1.27.0"]
pg["PostgreSQL 15 Dify库"]
end
user --> nginx --> gunicorn --> flask
flask --> pages
flask --> apis
searchApi --> qp
chatApi --> qp
chatApi --> mem
qp --> local
qp --> contrib
qp --> dify
searchApi --> clip
chatApi --> clip
searchApi --> dash
chatApi --> dash
uploadApi --> contrib
contrib --> clip
contrib --> dify
garden --> bloom
bloom --> mem
bloom --> dash
bloom --> weather
fbApi --> fbstore
dify --> weaviate
dify --> pg
箭头是调用方向。Dify 的 Postgres / Weaviate 属于知识平台,不是花卉站自己的业务库。
2. 组件与版本
以下为 2026-08-15 在云主机 Ubuntu 22.04.5 上核对的运行版本。
| 层级 | 组件 | 版本 | 作用 |
|---|---|---|---|
| 系统 | Ubuntu | 22.04.5 LTS | 云主机操作系统 |
| 接入 | Nginx | 1.18.0 | HTTPS 终结,反代 127.0.0.1:5000 |
| 运行时 | Python | 3.10.12 | 花卉站解释器 |
| 运行时 | Gunicorn | 26.0.0 | 2 worker,超时 180 秒,systemd job-rag.service |
| 应用 | Flask / Werkzeug / Jinja2 | Flask 3.1.3 | 页面与 API |
| HTTP | requests | 2.34.2 | 调用 Dify、审核接口 |
| 图像 | Pillow | 12.3.0 | 上传校验、感知哈希、CLIP 预处理 |
| 模型运行 | PyTorch | 2.13.0+cu130 | 本地 CLIP 推理 |
| 模型运行 | torchvision | 0.28.0+cu130 | CLIP 依赖 |
| 模型运行 | transformers | 5.14.1 | 加载 CLIPModel / CLIPProcessor |
| 模型运行 | safetensors | 0.8.0 | 本地权重格式 |
| 安全 | cryptography | 49.0.0 | 上传元数据可选加密、JWT 相关 |
| 知识平台 | Dify | 1.16.1 | 知识库 retrieve、花神经 Chat |
| 知识平台 | dify-plugin-daemon | 0.6.10-local | Dify 插件进程 |
| 知识平台 | dify-sandbox | 0.2.15 | Dify 沙箱 |
| 缓存 | Redis | 6-alpine | Dify 队列与缓存,花卉站不直连 |
| 天气 | 和风天气 QWeather | Geo v2 + 3 日预报 | 花园日志天气缓存 |
3. 数据库与版本
花卉站业务数据全部是 SQLite 3.37.2。Dify 自己另有 PostgreSQL 15 和 Weaviate 向量库。
| 库 | 引擎 / 版本 | 路径 | 主要内容 |
|---|---|---|---|
| bloomtrace.db | SQLite 3.37.2 | /opt/job-rag-system/app/bloomtrace.db | bt_plants 花园档案、bt_logs 养护日志、bt_users、bt_weather_cache、bt_conversations 客服日志、bt_chat_sessions 会话记忆 |
| community_uploads.db | SQLite 3.37.2 | /opt/job-rag-system/app/community_uploads.db | 花友图鉴:标题、说明、图片路径、感知哈希、CLIP 向量、审核状态、同步 Dify 文档 ID |
| feedback.db | SQLite 3.37.2 | /opt/job-rag-system/data/feedback.db | 匿名意见。花卉站与票据管家共用。字段:应用、类型、正文、页面路径、时间、已读状态。无姓名/手机/邮箱 |
| jobs.db | SQLite 3.37.2 | /opt/job-rag-system/app/jobs.db | 历史职位爬取表,当前花卉站主流程不再使用 |
| Dify 业务库 | PostgreSQL 15-alpine | docker-db_postgres-1 | Dify 应用、知识库元数据、对话。花卉站通过 HTTPS API 访问,不直连 |
| Dify 向量库 | Weaviate 1.27.0 | docker-weaviate-1 | 三个花卉知识库的 text-embedding-v4 向量 |
三个 Dify 数据集:花卉知识库、秋海棠中文知识库、用户上传花卉图鉴。索引方式 high_quality,doc_form=text_model,不是多模态向量库。
4. 模型与使用节点
本地视觉检索用 CLIP;识图用通义视觉;知识库向量用通义文本嵌入;客服生成走 Dify 应用「花神经」。
| 模型 | 提供方 / 规格 | 用在哪个节点 | 说明 |
|---|---|---|---|
| CLIP ViT-B/32 | 本地 openai/clip-vit-base-patch32 transformers CLIPModel |
花友上传入库后的向量化 以图搜图 search_similar_uploads 上传图片 NSFW 审核 |
路径 /opt/job-rag-system/models/openai-clip-vit-base-patch32。相似度阈值 0.78。不经过 Dify。 |
| qwen-vl-plus | 阿里云 DashScope 兼容模式 | 花园建档识图 identify_plant_from_image 检索/客服有图时把植物名补进问句 |
唯一视觉模型。用于「这是什么植物」的命名,不直接生成养护长文。不再回退到本机 Ollama。 |
| text-embedding-v4 | 通义 Tongyi / langgenius/tongyi | Dify 三个花卉知识库的向量化与 hybrid retrieve | L4 检索节点。text_model,不用来做图片 embedding。 |
| 花神经 Chat 应用 | Dify 1.16.1 应用「花神经」mode=chat | /api/flower-chat 在本地卡片未命中之后 | 绑定三库,对话内检索 + 生成。检索页不调用此应用。 |
| qwen-plus | 阿里云 DashScope 兼容模式 | 花友上传文本 Layer 2 语义审核 | 关键词层始终启用。LLM 层走阿里云;未配密钥则跳过语义层,仍拦关键词。 |
| 感知哈希 aHash 8x8 | Pillow 本地 | CLIP 不可用时的相似图回退 上传去重辅助 |
不是神经网络,列在这里便于对照节点。 |
5. 工作模式
花卉站不是每一步都让模型自己选工具。首页查询、花神经、花友上传都是固定流水线:先拦异常输入,再认植物和意图,最后按层取资料。颜色:绿=感知,蓝=规划,金=执行,红=反思。点图上的框看右侧说明,也可自动演示。
flowchart LR perc["1 感知
读字 · 看图 · CLIP"] --> plan["2 规划
清洗、认植物、意图"] plan --> act["3 执行
养护卡 · 知识库 · 花神经"] act --> refl["4 反思
澄清、未命中、敏感词拒绝"] refl -->|字太少或意图不清| perc classDef cPerc fill:#e7efe4,stroke:#315f43,color:#1c2a1e classDef cPlan fill:#e8eef8,stroke:#3d4f7c,color:#1c2a1e classDef cAct fill:#fff6df,stroke:#c4a15a,color:#1c2a1e classDef cRefl fill:#fdecec,stroke:#b45353,color:#1c2a1e class perc cPerc class plan cPlan class act cAct class refl cRefl
检索页只返回资料,不调用花神经生成。花神经在本地卡片未命中后才生成。本机 Ollama / qwen2.5 已停用,视觉和语义审核走阿里云。
6. 用户查询流程
首页搜索框 POST /api/search。先做冷却、字数和敏感词;有图先比花友相似图,没中再识图得名。检索只返回资料,不调用花神经。
flowchart TD
qStart["首页输入"] --> qApi["提交检索接口"]
qApi --> qPace["冷却与频次"]
qPace --> qKw["关键词审核"]
qKw --> qKwOk{"通过?"}
qKwOk -->|否| qBlock["拒绝查询"]
qKwOk -->|是| qShort{"字太少且无图?"}
qShort -->|是| qAsk["请补充花名或照片"]
qShort -->|否| qHas{"有图片?"}
qHas -->|是| qSim["CLIP 花友图"]
qSim --> qSimOk{"命中?"}
qSimOk -->|是| qHit["返回花友图"]
qSimOk -->|否| qVl["通义视觉识名"]
qVl --> qMerge["拼进问句"]
qMerge --> qRoute["检索路由"]
qHas -->|否| qRoute
qRoute --> qCard{"本地卡?"}
qCard -->|是| qLocal["返回养护卡或名录"]
qCard -->|否| qL4["知识库 retrieve"]
qL4 --> qScore{"分数大于 0.35?"}
qScore -->|是| qDocs["返回片段"]
qScore -->|否| qMiss["未命中"]
classDef perc fill:#e7efe4,stroke:#315f43,color:#1c2a1e
classDef plan fill:#e8eef8,stroke:#3d4f7c,color:#1c2a1e
classDef act fill:#fff6df,stroke:#c4a15a,color:#1c2a1e
classDef refl fill:#fdecec,stroke:#b45353,color:#1c2a1e
class qStart perc
class qHas perc
class qSim perc
class qApi plan
class qPace plan
class qKw plan
class qKwOk plan
class qShort plan
class qRoute plan
class qCard plan
class qL4 plan
class qScore plan
class qVl act
class qMerge act
class qHit act
class qLocal act
class qDocs act
class qBlock refl
class qAsk refl
class qMiss refl
class qSimOk refl
7. 花卉智能客服流程
浮窗「花神经」走 POST /api/flower-chat。先做冷却、最短二字和敏感词;装载会话与花园档案后,本地卡片或花友图能答就直接答。需要生成时才把档案拼进问句,交给 Dify。
flowchart TD
cWid["花神经浮窗"] --> cApi["提交客服接口"]
cApi --> cPace["冷却两秒"]
cPace --> cKw["关键词审核"]
cKw --> cKwOk{"通过?"}
cKwOk -->|否| cBlock["拒绝"]
cKwOk -->|是| cShort{"过短且无图?"}
cShort -->|是| cAsk["请补充"]
cShort -->|否| cValid["校验图片格式"]
cValid --> cMem["装载会话与档案"]
cMem --> cImg{"有图片?"}
cImg -->|是| cSim["CLIP 花友图"]
cSim --> cSimOk{"命中?"}
cSimOk -->|是| cRet["返回花友图"]
cSimOk -->|否| cVl["通义视觉识名"]
cVl --> cRoute["客服路由"]
cImg -->|否| cRoute
cRoute --> cOut{"结果"}
cOut -->|澄清| cClarify["追问不调模型"]
cOut -->|本地卡| cCard["返回养护卡"]
cOut -->|需生成| cBind["拼入花园档案"]
cBind --> cDify["交给花神经"]
cDify --> cSave["回写会话ID"]
classDef perc fill:#e7efe4,stroke:#315f43,color:#1c2a1e
classDef plan fill:#e8eef8,stroke:#3d4f7c,color:#1c2a1e
classDef act fill:#fff6df,stroke:#c4a15a,color:#1c2a1e
classDef refl fill:#fdecec,stroke:#b45353,color:#1c2a1e
class cWid perc
class cImg perc
class cSim perc
class cApi plan
class cPace plan
class cKw plan
class cKwOk plan
class cShort plan
class cValid plan
class cMem plan
class cRoute plan
class cOut plan
class cBind plan
class cVl act
class cRet act
class cCard act
class cDify act
class cSave act
class cBlock refl
class cAsk refl
class cClarify refl
class cSimOk refl
闲聊在检索页只给短自我介绍;在花神经里会进入 Dify 多轮对话。登录用户会带上花园档案;匿名用户只有 30 分钟内的会话记忆。发送按钮在上一轮未完成时禁用,答完后仍有 2 秒冷却。
8. 用户记忆
记忆分三层,互不混用。首页检索仍然一轮一问,不读花园档案。客服只在需要花神经生成时注入档案,本地养护卡保持通用。
| 层 | 存什么 | 谁能用 | 多久失效 | 不存什么 |
|---|---|---|---|---|
| 短期会话 | Dify conversation_id,写在 bt_chat_sessions | 同一浏览器会话;登录用户换页仍能续上 | 空闲 30 分钟后新开一轮 | 不把别人的会话 ID 接到当前用户 |
| 花园档案 | 昵称、默认城市、最多 8 盆花的位置·朝向·生长阶段·最近一条日志 | 已登录且调用花神经生成时 | 以当前花园数据为准,每次现查 | 手机、邮箱、密码、IP、他人植株 |
| 对话日志 | bt_conversations 原文问答,给后台统计 | 管理员查看 | 长期保存 | 不会再喂回模型当记忆;注入前缀也不写入此表 |
flowchart TD
mWho["识别登录态"] --> mKind{"花园用户?"}
mKind -->|是| mProf["读取花园档案"]
mProf --> mSum["压缩成档案摘要"]
mKind -->|否| mAnon["匿名浏览器"]
mAnon --> mSid["分配匿名会话号"]
mSum --> mIdle{"空闲超过 30 分钟?"}
mSid --> mIdle
mIdle -->|是| mNew["新开 Dify 会话"]
mIdle -->|否| mKeep["沿用 conversation_id"]
mNew --> mAsk["用户提问"]
mKeep --> mAsk
mAsk --> mNeed{"需要花神经?"}
mNeed -->|否| mLocal["本地卡或花友图"]
mNeed -->|是| mJoin["档案加用户问题"]
mJoin --> mDify["Dify 多轮生成"]
mDify --> mWrite["回写会话ID"]
classDef perc fill:#e7efe4,stroke:#315f43,color:#1c2a1e
classDef plan fill:#e8eef8,stroke:#3d4f7c,color:#1c2a1e
classDef act fill:#fff6df,stroke:#c4a15a,color:#1c2a1e
classDef refl fill:#fdecec,stroke:#b45353,color:#1c2a1e
class mWho perc
class mKind perc
class mAsk perc
class mProf plan
class mSum plan
class mAnon plan
class mSid plan
class mIdle plan
class mNeed plan
class mJoin plan
class mKeep act
class mNew act
class mDify act
class mWrite act
class mLocal refl
匿名用户各自有独立的 Dify user,不再共用 flower-chat-anonymous。刷新页面会向 GET api-flower-chat-session 取回未过期的会话 ID。
9. 用户上传流程
花友图鉴先审核再入库。文本关键词始终拦;语义审核改走阿里云 qwen-plus,不再调用本机 Ollama。
flowchart TD
uForm["填写标题说明和照片"] --> uPost["提交图鉴接口"]
uPost --> uRate["IP 频次限制"]
uRate --> uKw["关键词审核"]
uKw --> uKwOk{"通过?"}
uKwOk -->|否| uRej["拒绝 不入库"]
uKwOk -->|是| uLlm["qwen-plus 语义审核"]
uLlm --> uImg["CLIP 图片审核"]
uImg --> uImgOk{"通过?"}
uImgOk -->|否| uRej
uImgOk -->|是| uStore["校验并落盘"]
uStore --> uDup{"疑似重复?"}
uDup -->|是| uRej
uDup -->|否| uSql["写入 SQLite"]
uSql --> uDify["同步 Dify 花卉图鉴"]
uDify --> uClip["异步 CLIP 向量"]
uClip --> uDone["前台展示"]
classDef perc fill:#e7efe4,stroke:#315f43,color:#1c2a1e
classDef plan fill:#e8eef8,stroke:#3d4f7c,color:#1c2a1e
classDef act fill:#fff6df,stroke:#c4a15a,color:#1c2a1e
classDef refl fill:#fdecec,stroke:#b45353,color:#1c2a1e
class uForm perc
class uPost plan
class uRate plan
class uKw plan
class uKwOk plan
class uLlm plan
class uImg plan
class uStore act
class uSql act
class uDify act
class uClip act
class uDone act
class uRej refl
class uImgOk refl
class uDup refl
10. 输入预处理流程
搜索和客服共用 query_guard 与 query_pipeline.preprocess。先截断、拦敏感词和过短输入,再认植物、认意图、改写分库。
flowchart TD
pRaw["原始问句"] --> pClip["截到 500 字"]
pClip --> pPace["冷却与频次"]
pPace --> pKw["关键词审核"]
pKw --> pKwOk{"通过?"}
pKwOk -->|否| pBlock["拒绝"]
pKwOk -->|是| pShort{"有效字不足 2?"}
pShort -->|是| pAsk["请补充"]
pShort -->|否| pZw["去零宽字符和 HTML"]
pZw --> pInj["去掉注入句"]
pInj --> pPlant["认植物名"]
pPlant --> pIntent["认意图"]
pIntent --> pRew["改写问句"]
pRew --> pRoute["按层分库"]
classDef perc fill:#e7efe4,stroke:#315f43,color:#1c2a1e
classDef plan fill:#e8eef8,stroke:#3d4f7c,color:#1c2a1e
classDef act fill:#fff6df,stroke:#c4a15a,color:#1c2a1e
classDef refl fill:#fdecec,stroke:#b45353,color:#1c2a1e
class pRaw perc
class pClip plan
class pPace plan
class pKw plan
class pKwOk plan
class pShort plan
class pZw plan
class pInj plan
class pPlant plan
class pIntent plan
class pRew plan
class pRoute act
class pBlock refl
class pAsk refl
| 步骤 | 函数 | 做什么 |
|---|---|---|
| G1 截断 | query_guard.clip_query | 最多 500 字;输入框 maxlength 同步限制 |
| G2 节奏 | enforce_query_pace | 搜索 1 秒、客服 2 秒冷却;每 IP 每分钟 20 次 |
| G3 敏感词 | moderate_text | 与花友上传同一套关键词;不把命中词回显给用户 |
| G4 最短字 | effective_length | 无图时至少 2 个汉字或字母 |
| N1 清洗 | preprocess | 去零宽字符、HTML、注入句 |
| N2 认植物 | extract_plant | 常见名/别名/学名,秋海棠中文名;最长匹配优先 |
| N3 认意图 | classify_intent | care / pest / identify / chitchat / unclear / general |
| N4 改写 | rewrite_query | 补标准中文名和意图关键词 |
| N5 分库 | route_query | search 只检索;chat 本地未中则花神经 |
11. RAG 路由矩阵
| 植物库 × 意图 | /api/search 检索页 | /api/flower-chat 花神经 |
|---|---|---|
| 常见花卉 × 养护/鉴定 | L1 养护卡 | 同一张养护卡 |
| 常见花卉 × 病虫害 | L4 花卉知识库片段 | 改写句 → 花神经 |
| 秋海棠 × 鉴定 | L2 名录卡片 | 同一张名录卡片 |
| 秋海棠 × 养护/病虫害 | L4 秋海棠库 + 用户图鉴 | 改写句 → 花神经(不用 L2 空字段当养护) |
| 未识别植物 | L1 → L4 → 花友补充 → miss | 同左;仍无则花神经生成 |
| 意图不清 / 一字乱敲 | 澄清或请补充 | 澄清或请补充,不调 Dify |
| 闲聊 | 短自我介绍 | 花神经对话 |
| 敏感词 | 拒绝查询 | 拒绝发送 |
| 有图 | 花友相似图;否则识图名进入同一矩阵 | 同左;需生成时把图一并交给花神经 |
| 层 | 数据 | 模型/查法 | 可以回答 | 禁止 |
|---|---|---|---|---|
| L1 | 常见花卉卡约 250 种 | 规则匹配,无向量模型 | 光照、浇水、养护要点 | 拿它鉴定秋海棠名录 |
| L2 | iBegonias 秋海棠名录 | 规则匹配,无向量模型 | 品种名、形态字段 | 空 description 当养护百科 |
| L3 | 花友图鉴 SQLite | 关键词;CLIP 相似图 | 补充实例和照片 | 覆盖 L1/L2 权威答案 |
| L4 | Dify 三个数据集 | text-embedding-v4 hybrid retrieve,阈值 0.35 | 病虫害、深度养护 | 把空记录整库向量化 |
| L5 | 花神经 Chat | Dify 对话内检索 + 生成;登录时注入花园档案摘要 | 多轮、闲聊、按用户在养花给建议 | 无依据时编造品种或药方;把档案当全站知识 |
12. 安全基线
搜索和客服现在与花友上传共用关键词审核。语义审核和识图都走阿里云,不再依赖你本机上的 Ollama / qwen2.5。
| 项 | 现网做法 |
|---|---|
| 长度 | 输入框与后端都限制 500 字 |
| 最短有效输入 | 无图时至少 2 个汉字或字母;客服一个字一个字回车会被拦住 |
| 冷却 | 搜索 1 秒、客服 2 秒;每 IP 每通道每分钟 20 次 |
| 敏感词 | 色情 / 政治 / 暴恐关键词;命中只提示换题,不回显词表 |
| 上传语义审核 | 阿里云 qwen-plus;未配密钥则只走关键词 |
| 识图 | 仅 DashScope qwen-vl-plus,失败即报错,不再回退 Ollama |
| 客服图片 | 仅 JPG/PNG,单张 5MB,一次最多 5 张 |
| 用户记忆 | 会话 ID 按登录用户或匿名浏览器隔离;花园档案不含手机邮箱;注入前缀只给 Dify,不写入 bt_conversations |