1. 从一台失败的吸蜂器说起这个项目到底在做什么先坦白一件事这个项目的起点是一台彻底失败的吸蜂器。我原本想做一个能安全把马蜂、胡蜂从屋檐下吸走的小装置思路很简单——用负压把蜂吸进容器再统一处理。结果实测下来吸力不够、密封不严、蜂群应激反应比预想剧烈得多最后这台机器就躺在角落里吃灰。但就在拆解它的过程中我盯着那个吸嘴和滤网突然意识到一个问题我真正需要的其实不是吸走蜂而是先搞清楚这是什么蜂、巢在哪、规模多大。因为绝大多数情况下你根本不知道墙缝里、树洞里、空调外机后面那团东西到底是什么是马蜂、蜜蜂还是别的什么巢有多大有没有攻击性。于是思路就转了与其做一个物理上很难做好的吸蜂器不如做一个能识别害虫巢穴的AI agent。用户拍一张照片或者描述一下位置和外观agent 就能判断这是什么类型的巢、风险等级如何、建议怎么处理。这就是标题里说的 A pest nest identification AI agent from a failed bee vacuum——一个从失败硬件项目里长出来的软件智能体。这个 agent 的核心能力说白了就是三件事看图识巢、对话追问、给出处置建议。它背后用到的是 VLM视觉语言模型做图像理解LLM大语言模型做推理和对话Ollama 做本地模型托管Flask 做服务封装。关键词里出现的 AI agent、VLM、LLM、Ollama、Flask基本就是这个项目的技术骨架。适合谁来参考如果你正在做AI agent 开发、想了解VLM LLM 怎么串成一个能解决实际问题的智能体、或者你手头正好有个半成品硬件项目想转软件方向这篇内容应该能给你不少可直接抄的细节。哪怕你只是想搞清楚Ollama 本地部署和Flask 部署怎么配合也能从里面找到能跑通的路径。我下面会按为什么这么设计—怎么搭起来—踩了哪些坑—怎么调优的顺序讲尽量把每个决策背后的理由说清楚而不是只丢一堆代码。2. 为什么选 VLM LLM 这条技术路线而不是纯图像分类2.1 纯分类模型在巢穴识别这件事上为什么不够用一开始我确实想过最省事的路子训一个图像分类模型把马蜂巢蜜蜂巢胡蜂巢不是巢分成几类softmax 一输出就完事。但真动手准备数据的时候就发现这条路走不通。第一巢穴的形态差异极大。同样是马蜂巢早期只有拳头大、挂在屋檐下成熟期可能长到脸盆那么大、层层叠叠有的建在树洞里只露出一个入口有的贴在玻璃上完全暴露。你要覆盖这些形态标注数据量会爆炸而且很多长尾形态根本收集不到。第二用户给的信息往往不只是一张图。真实场景里用户会补一句在二楼窗台外侧昨天开始有蜂飞进飞出或者在衣柜后面黑色的会嗡嗡响。这些文本信息对判断至关重要纯分类模型吃不下。第三分类模型只会给标签不会解释。用户真正想知道的是危不危险、要不要现在处理、找谁处理而不是一个冷冰冰的类别名。这就需要一个能推理、能对话的组件。所以结论很明确图像理解交给 VLM推理和对话交给 LLM两者串起来才是一个能用的 agent。2.2 VLM 和 LLM 在这个 agent 里各自扮演什么角色我把整个链路拆成三段每段职责很清晰感知层VLM接收用户上传的图片输出结构化的观察结果。比如画面中央有一个灰褐色、纸质纹理的球形结构直径约 20 厘米表面有多个孔洞周围有黑黄相间的昆虫活动。注意这里我刻意让 VLM 只做描述不做判断因为描述比判断更可靠也更不容易幻觉。推理层LLM拿到 VLM 的描述 用户的文字补充做综合判断。输出包括巢穴类型、置信度、风险等级、处置建议。这一层是真正的大脑。交互层Flask 前端负责接收图片和文字、调用后端、把结果渲染给用户同时维护多轮对话的上下文。这样分层的好处是每一层都可以单独替换和升级。哪天 VLM 换更强的模型只要输出格式不变上层完全不用动LLM 想换成本地小模型还是云端大模型也只改一个配置。2.3 为什么坚持用 Ollama 做本地模型托管关键词里Ollama出现频率极高这不是偶然。我选它主要基于三个现实考虑第一数据敏感性。用户上传的是自家房屋、庭院、周边的照片里面可能包含门牌号、车辆、家庭成员。把这些图传到云端 API隐私风险和心理负担都不小。本地跑模型图片不出机器这一点对这类应用几乎是刚需。第二成本可控。如果按调用量付费一个稍微有点用户量的识别服务账单会很难看。本地部署一次性投入硬件后续边际成本接近零。第三离线可用。关键词里有人搜ollama离线安装包ollama本地部署说明很多人有内网或弱网环境的需求。Ollama 装好模型后可以完全离线运行这对现场作业场景比如在郊区、农场很实用。当然Ollama 也有它的坑比如下载模型慢、默认装到 C 盘、显存调度不透明这些我在后面第 4 节会专门讲怎么处理。3. 把 agent 搭起来从 Ollama 到 Flask 的完整链路3.1 环境准备Ollama 安装与模型选择的取舍先说安装。Ollama 在 Windows、macOS、Linux 上都有对应安装包官网直接下就行。但有两个细节必须提前处理一是安装路径。默认它会装到系统盘模型文件也默认放系统盘的用户目录下。模型动辄几个 GB 到几十 GB系统盘很快会被撑爆。Linux 下可以通过设置环境变量OLLAMA_MODELS指定模型目录Windows 下则建议在安装前就把默认目录规划好或者装完后迁移并改环境变量。我自己的做法是单独挂一块盘专门放模型路径类似/data/ollama/models。二是模型选择。这个项目需要两类模型一个 VLM一个 LLM。VLM 我选的是支持图像输入的轻量多模态模型参数量控制在 7B 到 11B 之间理由是消费级显卡比如 12GB 显存能跑得动响应速度也能接受。LLM 则选 7B 到 14B 区间的通用对话模型中文能力要过关因为用户输入大概率是中文。选模型时有个经验不要一上来就追最大的。我试过直接上 30B 级别的模型结果单次推理要等十几秒交互体验直接崩掉。后来降到 7B 级别配合合理的 prompt识别准确率并没有明显下降但响应时间从十几秒压到两三秒体验完全不一样。拉模型的命令很简单ollama pull vlm-model-name ollama pull llm-model-name如果下载慢可以配置镜像源或者用离线包方式导入。关键词里ollama下载模型速度慢ollama离线安装包都是高频问题我的建议是优先找离线包一次导入省得反复等。3.2 用 Ollama 的 API 做 VLM 图像描述Ollama 启动后会暴露一个本地 HTTP 接口默认在11434端口。VLM 调用时把图片转成 base64 塞进请求体即可。核心逻辑大概是这样import base64 import requests def describe_image(image_path, modelyour-vlm-model): with open(image_path, rb) as f: img_b64 base64.b64encode(f.read()).decode(utf-8) payload { model: model, prompt: 请客观描述这张图片中是否存在昆虫巢穴结构包括位置、形状、颜色、纹理、大小估计、周围活动迹象。只描述你看到的不要下结论。, images: [img_b64], stream: False } resp requests.post(http://localhost:11434/api/generate, jsonpayload) return resp.json().get(response, )这里有个关键设计prompt 里明确要求只描述、不下结论。我踩过的坑是如果让 VLM 直接判断这是不是马蜂巢它会非常自信地胡说尤其是图片模糊的时候。改成只描述后输出稳定很多判断交给 LLM 去做整体准确率反而上去了。另外stream参数建议设成False做同步调用逻辑简单如果要做打字机效果再改成流式。3.3 LLM 推理层把描述和用户输入拼成判断VLM 给出描述后LLM 这一层负责综合判断。我用的 prompt 结构大致是def judge_nest(vlm_description, user_text): prompt f你是一个害虫巢穴识别助手。请根据以下信息判断巢穴类型和风险。 【图像描述】 {vlm_description} 【用户补充】 {user_text} 请按以下格式输出 1. 巢穴类型马蜂/蜜蜂/胡蜂/不确定/非巢穴 2. 置信度高/中/低 3. 风险等级高/中/低 4. 判断依据 5. 处置建议是否可自行处理、需要哪些防护、是否建议联系专业人员 payload { model: your-llm-model, prompt: prompt, stream: False } resp requests.post(http://localhost:11434/api/generate, jsonpayload) return resp.json().get(response, )强制结构化输出是这一层的核心技巧。早期我让模型自由发挥结果每次格式都不一样前端根本没法解析。后来固定成 5 个字段前端按字段渲染稳定多了。如果模型偶尔不守规矩可以在 prompt 里加一句必须严格按上述格式不要添加额外内容。3.4 Flask 服务封装接口设计与多轮对话上下文Flask 这一层做两件事暴露接口、维护会话。接口设计我尽量简单from flask import Flask, request, jsonify app Flask(__name__) sessions {} app.route(/identify, methods[POST]) def identify(): data request.json session_id data.get(session_id, default) user_text data.get(text, ) image_b64 data.get(image, ) vlm_desc if image_b64: vlm_desc describe_image_b64(image_b64) history sessions.get(session_id, []) result judge_nest(vlm_desc, user_text, history) history.append({user: user_text, assistant: result}) sessions[session_id] history[-10:] # 只保留最近10轮 return jsonify({result: result})这里有几个实战细节值得说会话上下文要截断。我一开始把全部历史都塞进 prompt结果 token 越滚越多响应越来越慢最后直接超上下文。改成只保留最近 10 轮后既保留了追问能力又控制了开销。图片和文字要分开处理。用户可能只发文字我家屋檐下有个灰色的东西也可能只发图也可能两者都有。接口要能兼容这三种情况不能强制要求图片。Flask 与 FastAPI 的选择。关键词里有人搜flask 与 fastapi 比较我选 Flask 的理由很直接这个项目是同步调用 Ollama没有高并发需求Flask 足够简单上手快调试方便。如果哪天要做异步、要上高并发再迁 FastAPI 也不迟。不要为了技术先进性选型要为了当前需求选型。4. 踩坑实录Ollama 部署和模型调用里那些没人告诉你的问题4.1 模型下载慢、装错盘、显存不生效的排查链路这部分是我花时间最多的地方按排查顺序讲。问题一下载慢到怀疑人生。第一次拉模型进度条几乎不动。排查下来是默认源在国外带宽受限。解决办法有两个配镜像源或者找离线包导入。离线包导入的命令是ollama create配合 Modelfile或者直接把模型文件放到OLLAMA_MODELS目录下再ollama list确认。我后来基本都用离线包一次搞定。问题二模型全堆在 C 盘。装完发现系统盘红了。原因是默认模型目录在用户目录下。解决方式是设置OLLAMA_MODELS环境变量指向大容量盘然后重启 Ollama 服务。注意改完环境变量一定要重启服务否则不生效我在这上面浪费了半小时。问题三明明有显卡却跑在 CPU 上。这个最隐蔽。表现是推理特别慢nvidia-smi看显存占用几乎为零。排查发现是 Ollama 没识别到显卡驱动或者驱动版本不匹配。解决办法是确认驱动装好、CUDA 版本兼容然后重启服务。关键词里ollama 怎么调用显卡ollama serve 段错误都是同类问题本质都是环境没配好。下面这张表是我整理的常见问题对照现象可能原因处理方式下载极慢默认源带宽受限配镜像源或离线包导入系统盘爆满模型默认装系统盘设置 OLLAMA_MODELS 到其他盘推理慢、显存不占未识别显卡检查驱动、重启服务服务启动报错端口占用或权限问题换端口、检查权限模型输出乱码编码或模型不兼容换模型、检查请求编码4.2 VLM 幻觉为什么我让它只描述不下结论VLM 幻觉是这个项目最大的质量风险。早期我让 VLM 直接输出这是马蜂巢风险高结果它经常把树上的鸟窝、墙上的污渍、甚至一团枯叶都判成马蜂巢而且语气极其肯定。后来我做了两个改动效果立竿见影第一职责分离。VLM 只描述客观特征判断交给 LLM。描述错了顶多是看走眼但判断错了会误导用户性质完全不同。第二加不确定性表达。在 prompt 里要求 VLM 如果看不清就说图像模糊无法确认而不是硬猜。这一条让误报率明显下降。第三多图交叉验证。如果用户上传多张图让 VLM 分别描述再让 LLM 综合。单图容易误判多图能互相印证。4.3 上下文膨胀与响应变慢会话管理的取舍前面提到会话历史要截断这里展开说。LLM 的上下文窗口是有限的而且上下文越长推理越慢、成本越高。我实测过把 20 轮历史全塞进去单次响应从 2 秒涨到 8 秒以上用户体验直接崩。我的处理策略是只保留最近 10 轮对话对历史做摘要压缩把早期对话浓缩成一两句话图片只在当轮使用不重复塞进历史。这样既保留了追问能力用户问那这个巢要处理多久agent 能记得上文又控制了开销。4.4 一个容易被忽略的坑模型版本与提示词的耦合这个坑很隐蔽。我换了 VLM 模型版本后发现原来调好的 prompt 突然不好用了输出格式变了。原因是不同模型对同一 prompt 的理解有差异。教训是prompt 要和模型版本绑定管理。我在代码里把 prompt 抽成配置文件换模型时同步调整 prompt并做回归测试。关键词里ollama 版本 0.35.1这类搜索说明版本管理确实是大家的痛点。5. 让 agent 真正好用从能跑到靠谱的调优经验5.1 风险分级把识别变成可执行的建议识别出巢穴类型只是第一步用户真正需要的是我该怎么办。所以我在 LLM 输出里加了风险分级和处置建议并且做了明确的分档高风险大型马蜂巢、靠近活动区域、有攻击迹象。建议立即远离、联系专业人员。中风险中小型巢、位置较偏、活动不频繁。建议做好防护后评估是否自行处理。低风险疑似旧巢、无活动迹象、非攻击性种类。建议观察无需紧急处理。这个分级不是拍脑袋定的而是参考了常见害虫处置的通用原则再结合用户反馈调整。关键是让建议可执行而不是模棱两可。5.2 多轮追问让 agent 主动补全信息好的 agent 不是被动等输入而是会主动追问。比如用户只发了一张模糊的图agent 应该问能再拍一张更近的吗或者告诉我它大概多大、在什么位置我在 LLM 的 prompt 里加了追问逻辑如果置信度低就生成一个追问问题返回给用户。这样交互从一问一答变成逐步收敛识别准确率明显提升。5.3 前端与部署Flask 之外还需要考虑什么Flask 跑起来只是第一步真正上线还要考虑反向代理用 Nginx 做前置处理静态文件和转发请求并发控制Ollama 单实例并发能力有限需要加队列或限流超时设置VLM 推理可能较慢Flask 和 Nginx 的超时都要调大日志与监控记录每次识别的输入输出方便复盘和优化。关键词里nginx 反向代理 ollamaflask部署都是这个环节的高频问题。我的经验是本地跑通和线上可用之间差的就是这些工程细节。5.4 我个人的几条实操心得最后分享几条踩坑换来的经验都是文档里不会写的第一先跑通最小链路再优化。我一开始就想把 VLM、LLM、前端、部署全做好结果卡在环境配置上一周没进展。后来改成先用命令行调通 Ollama再加 Flask最后加前端节奏就顺了。第二模型不是越大越好。7B 级别配合好 prompt在很多识别任务上已经够用响应还快。盲目上大模型体验反而更差。第三prompt 要当代码管理。版本化、可回滚、有测试否则换模型时你会很痛苦。第四隐私是这类应用的底线。本地部署不只是技术选择更是对用户的承诺。图片不出机器这一点值得坚持。第五留好扩展口。这个 agent 现在只做巢穴识别但同样的架构可以扩展到其他害虫识别、植物病害识别等场景。分层设计让扩展变得容易。这个项目从一台失败的吸蜂器开始最后长成了一个能实际帮到人的 AI agent。硬件没做成但软件方向反而更有价值——这大概就是做项目最有意思的地方失败的方向里往往藏着更好的答案。