ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于CubeSandbox构建WeKnora Agent持久化运行环境

基于CubeSandbox构建WeKnora Agent持久化运行环境 我最近给团队做了一件事把 WeKnora 从“一个偶尔回答问题的知识库机器人”改造成“一个能连续干活的 Agent 服务员”。拆到最关键的一步就是标题里的这件事——基于 CubeSandbox 搭一套持久化运行环境。先说结论WeKnora 这类本地部署的知识 Agent真正难的不是模型选型也不是 RAG 流程而是“跑起来之后怎么活下来”。容器一重启对话就断索引文件散落在临时层工具调用到一半任务没了这些问题不解决Agent 就永远只能做演示。CubeSandbox 的价值在于把环境本身变成可管理、可恢复、可调度的产品而不只是给一个 shell。这篇文章是我在实际建设过程中的完整记录包含设计思路、目录规划、部署步骤、故障排查和一些踩坑经验适合正在做本地 Agent 部署、想把 WeKnora 接进现有编排平台的人参考。1. 先把需求拆开Agent 为什么需要“持久化运行环境”1.1 WeKnora 在 Agent 体系里到底是什么角色很多人把 WeKnora 当成一个简单问答接口其实不完全对。对我来说WeKnora 是一个自带知识库管理、语义检索、文档解析和工具调用能力的 Agent 服务。它可以独立运行也可以被外部的 Agent 编排系统当成一个工具节点来调用。我们团队内部文档很多散落在 Confluence、语雀、本地 Markdown 仓库里直接做大模型对话效果很差所以需要先解析、切片、向量化再通过 RAG 的方式回答。如果只是做一个问答 demo跑一个容器就够了。但一旦进入真实业务它要处理的东西就复杂了多轮对话里的临时状态、用户上传的附件、生成中的中间文件、工具调用的凭据、向量索引的增量更新。这些都不是模型参数而是运行时的“周边数据”。以前我习惯把所有东西都放在容器里后来发现每次升级或重启都要重新造一轮这时候才开始认真考虑持久化运行环境。1.2 无状态部署会让 Agent 变成“金鱼”无状态部署是 Web 服务的常识但放在 Agent 场景里它是一个隐性陷阱。我总结过几个最容易出现的问题对话上下文只存在内存里进程一重启用户刚才聊到哪了全部丢失长任务执行到一半沙箱被回收没有 checkpoint没有恢复入口临时下载的文件、生成的报表、中间结果散落在容器可写层镜像体积越滚越大工具调用凭据每次都要重新注入密钥管理全靠环境变量既不安全也不方便向量索引如果建在容器本地每次发布新版本都要重新灌库。不是说无状态不好而是 Agent 本身是有状态的。用户和 Agent 的交互天然是连续的任务有中间过程记忆有长期和短期之分。所以我们的目标不是把 Agent 做成完全无状态的服务而是把“状态”从“进程内存”里搬出来放到持久化介质上让进程本身可以随时被杀掉、重启、替换。1.3 CubeSandbox 解决的其实是“环境生命周期”CubeSandbox 这一类平台核心能力不是简单的隔离沙箱而是环境生命周期管理。它能创建环境、销毁环境、打快照、恢复快照、设置重启策略、限制资源和配置网络策略。你可以把整个运行环境看作一台可以随时克隆和恢复的“虚拟机”但它是通过容器和编排层实现的起停成本很低。我用几个维度做了比较对比项裸机部署普通 DockerCubeSandbox 这类沙箱平台环境隔离差中等强网络/资源/文件系统可隔离状态恢复依赖手工备份依赖数据卷管理支持快照和自动恢复资源限制手工配置需要自己写参数平台统一管理网络策略手工维护需要容器网络配置可视化/API 可配置环境复制成本高需要镜像和 Composer支持秒级复制和 clone所以在我的方案里CubeSandbox 不是替代 WeKnora而是给 WeKnora 提供了一个可以长期安家的运行环境。2. 环境方案设计与选型先订目录再谈架构2.1 三个必须持久化的层次在整个方案里我把需要持久化的东西分成三层每一层的存储方案都不一样。第一层是数据层。包括用户资料、知识库索引、文档元数据、会话归档。这些属于结构化或半结构化数据必须落到数据库里不能依赖容器文件系统。数据量小的时候SQLite 就够用数据量大了再迁到 PostgreSQL。向量索引单独放可以用本地向量库也可以接入独立向量数据库服务。第二层是状态层。包括短期会话、任务队列、运行中的上下文。这些数据要求读写快、有过期策略适合放在 Redis 或类似内存数据库中。有人问为什么不把所有对话历史都存 Redis因为长期历史会撑爆内存所以短期会话用 Redis长期记忆交给向量库和数据库。第三层是文件层。包括用户上传的文档、Agent 生成的报表、临时缓存、模型文件。这些是大文件不适合塞进数据库要放到持久化磁盘或对象存储里。这三层的对应关系直接影响 CubeSandbox 里的存储挂载方式。容器本身可以随时销毁但这三层数据必须留在外面。2.2 我给 WeKnora 划的目录结构和参数我在 CubeSandbox 里给 WeKnora 规划了下面这套目录结构/opt/weknora ├── app/ # 程序代码 ├── data/ │ ├── sqlite/ # 业务数据库 │ ├── vectors/ # 向量索引 │ ├── uploads/ # 用户上传文件 │ └── tmp/ # 临时中间文件 ├── models/ # 本地模型缓存 ├── logs/ # 运行日志 └── .secrets/ # 凭据文件权限 600代码目录/opt/weknora/app放在容器层不持久化因为代码应该通过镜像或发布流程更新。数据目录/opt/weknora/data、模型目录/opt/weknora/models、日志目录/opt/weknora/logs全部挂到 CubeSandbox 的持久卷上。这样做有一个明显好处容器重建后代码是最新版本但历史数据还在启动时只需要做一次索引检查和增量升级。2.3 选型为什么这么定我见过两种极端做法。一种是所有数据都丢 SQLite一个文件走天下适合几十兆的小项目但 Agent 跑久了会话一多查询会明显变慢。另一种是一上来就搞 PostgreSQL、Milvus、Redis、对象存储全家桶架构很豪华但本地部署维护成本直接爆炸。我这次选了中间路线小团队自用业务库先用 SQLite向量检索用本地文件型向量库先把整套流程跑通等并发用户多了再把 SQLite 平滑迁移到 PostgreSQL把向量库从本地目录切到独立服务。CubeSandbox 的好处是存储目录不变切后端存储时应用层改动很小环境不用重新建设。3. 从零搭建CubeSandbox 里的 WeKnora 运行环境3.1 准备沙箱和基础运行环境我这里用 CubeSandbox 的 API 风格做示例不同平台的控制台命令会有些差异但核心参数基本一致CPU、内存、磁盘、挂载目录、镜像、环境变量。# 创建一个名为 weknora-agent 的持久化沙箱 cube sandbox create \ --name weknora-agent \ --image python:3.11-slim \ --cpu 2 \ --memory 4Gi \ --disk 50Gi \ --volume /opt/weknora/data \ --volume /opt/weknora/models \ --volume /opt/weknora/logs这个配置对单机部署比较稳。我的经验是 Agent 场景尽量不要把内存卡得太死因为加载向量索引和模型推理都会临时吃内存2 核 4G 是目前底线。3.2 安装 WeKnora 服务进入沙箱后先把代码拉到/opt/weknora/app建虚拟环境装依赖。WeKnora 的依赖里通常包含文档解析、向量化、HTTP 服务三部分第一次安装会比较慢建议把 pip 缓存目录也持久化export PIP_CACHE_DIR/opt/weknora/data/pip-cache cd /opt/weknora/app python -m venv .venv source .venv/bin/activate pip install -r requirements-server.txt安装完成后初始化数据库和目录python cli.py init-db python cli.py init-dirs python cli.py import-docs --dir /opt/weknora/data/uploads第一次导入文档会触发向量化这一步建议放在后台跑不要占用交互终端。我通常会开一个logs/import.log记录进度方便排查。3.3 写一份环境配置文件持久化运行环境必须“配置化”不能靠人肉记住启动命令。我在 CubeSandbox 里维护了一份 YAML 配置runtime: name: weknora-agent image: python:3.11-slim replicas: 1 persistent_volumes: - name: data path: /opt/weknora/data - name: models path: /opt/weknora/models - name: logs path: /opt/weknora/logs env: WEKNORA_BIND: 0.0.0.0:8000 WEKNORA_DATA_DIR: /opt/weknora/data WEKNORA_MODEL_DIR: /opt/weknora/models WEKNORA_LOG_DIR: /opt/weknora/logs WEKNORA_LOG_LEVEL: INFO restart: policy: always liveness_probe: path: /healthz port: 8000 period_seconds: 10配置里最关键的是三个持久卷和restart.policy: always。如果没有明确的持久卷环境重建后数据还是会丢。liveness_probe也很重要Agent 服务有时候会假死没有健康检查的话平台不会自动拉起。3.4 把 WeKnora 接到 Agent 编排层如果你只是用 WeKnora 自带的聊天界面到这一步就结束了。但如果要接到 Dify、自研 Agent 编排器或者 CubeSandbox 内部的 Agent 流程里就需要把它暴露成一个可被调用的 Agent 工具。我的做法是在 CubeSandbox 内部网络里注册一个工具节点工具名weknora_knowledge_search 请求方式POST 请求地址http://weknora-agent:8000/api/v1/agent/run 请求头Authorization: Bearer ${WEKNORA_API_KEY}请求体大概长这样{ session_id: user_123, message: 帮我总结最近 30 天的项目周报, skills: [doc_search, file_generate, send_mail], top_k: 10 }这里最重要的字段是session_id。Agent 编排层每次调用 WeKnora都应该携带同一个用户会话标识这样 WeKnora 才能从持久化存储里恢复对话上下文而不是每次都当新用户处理。4. 持久化运行的关键快照、恢复与调度4.1 健康检查与自动重启环境配置好之后容器不是永远不挂的。模型推理时内存暴涨、磁盘写满、代码出现异常都可能导致进程退出。CubeSandbox 的自动重启只解决“进程起来了”的问题不解决“状态是否完整”的问题。所以我在 WeKnora 的启动命令里加了一步前置检查python cli.py check-db python cli.py check-index --incremental python cli.py start-server这样每次容器启动都会先确认数据库文件完整、索引文件存在再做增量检查。索引缺失的时候自动补建数据库有损坏就报错并触发告警而不是用空库启动服务不然用户会感觉“数据怎么全没了”。4.2 用快照解决升级和误操作持久化环境最大的价值是可恢复。我在升级 WeKnora 代码前一定会先在 CubeSandbox 里打一个快照cube snapshot create \ --runtime weknora-agent \ --tag pre-upgrade-20250607如果升级失败直接回滚cube snapshot restore \ --runtime weknora-agent \ --snapshot pre-upgrade-20250607我的建议是快照 tag 里带上时间和用途不要只写 v1、v2。我踩过坑一个叫 final 的快照三个月后根本想不起来里面是什么。4.3 多实例部署时的会话路由持久化之后很多人会觉得可以加副本水平扩展了。但这里有个坑如果同一个用户的多次请求被负载均衡到不同实例而两个实例共享数据库但各自的本地缓存不一样就会出现上下文错乱。我的做法是在 CubeSandbox 的路由规则里加“会话亲和性”路由判断 请求头 X-User-Session: user_123 转发目标所有带 user_123 的请求固定到 weknora-agent-1如果同时跑多个实例通常会把不同用户分散到不同实例上同一个用户的请求始终走同一个实例。数据层共享没问题但本地资源比如内存缓存、临时文件要保持一致。5. 上线后最容易踩的五个坑5.1 沙箱一重启对话全忘光第一次测试时我刚重启完沙箱就发现用户会话全部丢失。查了半天原因是 WeKnora 的默认数据库路径在/app/data而我挂载的是/opt/weknora/data两边对不上。数据虽然还在沙箱的某个角落但应用找不到。解决方式很粗暴把环境变量和挂载路径彻底统一程序里不写绝对路径全部通过环境变量读取。我在配置里指定WEKNORA_DATA_DIR/opt/weknora/data容器重建后同一份数据目录会被重新挂载。5.2 索引文件落在容器层镜像越跑越大Agent 的向量索引会持续变大。一开始我没把索引目录挂到持久卷结果每次导入文档容器文件系统就膨胀几 GB沙箱回收前镜像大得离谱。后来我把向量目录单独拆出来放到持久卷上再针对增量更新做定期检查问题才解决。这个坑的排查思路很简单du -sh /opt/weknora/app如果代码目录体积异常大说明有数据写进来了。5.3 健康检查路径不对导致容器无限重启我最初把健康检查配到了根路径/返回的却是 404。CubeSandbox 认为服务异常不断重启容器日志刷了好几屏才反应过来。后来在应用里暴露了/healthz接口专门做进程和依赖检查才算稳定。健康检查接口建议做成“只查关键依赖不查高成本逻辑”否则每次检查都触发一次向量检索反而拖垮服务。5.4 工具调用超时设置不合理Agent 编排层调用 WeKnora 做知识库检索有时候一次检索要 20 秒但编排层的默认超时只有 10 秒。结果是用户问问题WeKnora 已经在后台算完了前端却显示超时失败。我给不同的 skill 设置了不同超时时间Skill超时时间说明文档检索60 秒涉及向量检索和重排序网页抓取30 秒网络开销较大发送邮件10 秒简单 API 调用文件生成120 秒可能涉及模板渲染和导出核心原则是慢操作不要硬等同步结果能异步就异步先把任务 ID 返回给编排层再由回调更新状态。5.5 内存限制太低导致 OOM第一次跑 WeKnora我给了 4G 内存结果文档一多向量索引加载阶段直接 OOM。后来在 CubeSandbox 里做资源监控发现内存峰值主要发生在三个节点模型加载、批量向量化、检索重排序。我的调整方案是模型加载前先清理缓存批量导入任务单独放在低优先级队列里检索服务保持常驻但不加载全量模型。如果还紧张就把本地 embedding 模型换成更小规格的版本。6. 安全、成本与长期维护的个人经验6.1 沙箱不是保险箱权限要最小化很多人觉得容器隔离了就是安全的其实工具调用才是最大的漏洞。Agent 如果能执行命令能读文件能发网络请求那沙箱内部管理不当就相当于把一个有权限的机器人放进了公司内网。我的做法是给 WeKnora 配置一个专门的受限账号只允许读取指定的文档目录只开放白名单内的外网域名所有敏感操作都走审计日志。工具凭据放在.secrets/里权限设成 600并且由 CubeSandbox 的密钥服务注入不写死在代码里。6.2 按访问频率决定常驻还是按需启动Agent 持久化环境不代表必须 24 小时开着重型实例。如果你们每周只用几次可以配置成“按需启动 快照恢复”用户访问时从快照拉起环境空闲十分钟后自动休眠这样能省下大量机器成本。如果每天都有大量用户使用就保持一个常驻实例避免每次冷启动都要加载模型和索引。这套模式还有一个额外好处环境回滚方便。有人误改配置或者污染了数据直接恢复到上一个健康快照比手工清理干净得多。6.3 最后分享一个我自己常用的习惯我在 CubeSandbox 的启动脚本里加了一行source /opt/weknora/data/env.sh把容易变化的环境配置集中到一个持久化文件里。这样改端口、调模型路径、切数据目录都不需要重新构建镜像只要保证这个文件在持久卷上存在就行。当然这个文件里只放非敏感配置真正的密钥仍然走密钥管理服务。这套环境我已经跑了两个月最明显的感受是Agent 相关的部署问题大多数不是模型笨而是环境不持久。把 CubeSandbox 的持久卷、快照、健康检查和会话路由用起来之后WeKnora 才真正像是一个能长期工作的 Agent而不是一个跑完就忘的脚本。
返回列表