周六那个晚上,是我来江苏以来最快乐的一天晚上,那一天我们寝室三个人有一搭没一搭的聊着,从游戏、公司制度、出差聊到薪资、以后、发展,来到这边第一次这么放松的跟人聊天。很开心,周五练完胸之后,周六去做了有氧,周末就休息了一天。

不要去预想没有发生过的事,这个事会不会让我被裁?会不会让我感到尴尬?会不会挂科?会不会让我丢掉实习?会不会回不了家?会不会被坏人。。。。。。担心这些没有用,不如把精力放在那些我们能改变的事情上,比如现在Ai发展迅猛,Ai会不会把我替代掉?你不能把控Ai的趋势,能把控的只有自己,多学习AI技术。至于会不会挂科,每节课找好代课,至于挂不挂科,那是老师的事,我们能做的只有把到签了,把作业做了就行。

静姐没让我做标注工作之后我就可以专心在webai-lite上了,今天上午一直在排查lite与推理客户端的联调,主要是配置发送给客户端的json文件的格式,有些事错的,之前mentor在抄原webai的时候没抄对。现在把烂摊子丢给我让我来调。

调试过程总结

阶段一:加密狗无权限

用户打开程序时遇到 “加密狗无权限!” 弹窗。

排查过程:

阅读 main.cpp:112-137 的启动校验逻辑
程序启动时走两条路径:先尝试 VirBox(深思软许可),失败后降级到 HASP 物理加密狗,两条都失败才弹这个框
结论:需要插入 USB 加密狗或安装 VirBox 许可文件

阶段二:sample.zip 标注 JSON 字段为空导致训练崩溃

用户提供 webai-lite.json,训练报错:

1
2
config_utils.py:19 load_config
IndexError: list index out of range

排查过程:

分析 annotation JSON 结构,发现 tags: []、sampleImage_id: “”、uuid: “”、attributes 缺失等多处字段为空
用户修复后仍然报同样的 IndexError

阶段三:误判 config.zip 中的 null 值

用户把 config.zip 解压出来的 fsnet_detect_train.json 放到当前目录。

排查过程:

发现 parameters.inputs[2].value 是 null,推测修复它即可
用户将 null 改为 0 后重新训练,依然报同样的 IndexError

阶段四:追溯 pyd 源码找到真因

用户提供了新任务的完整目录 4491f9c7-…,其中 code/build_temp/ 下有 Cython 生成的 .c 中间文件。

关键突破:在 config_utils.c:3436 找到第 19 行的原始逻辑:

1
config_dict[p['param_name']] = p['realVaule'].split('|')[int(s['value'])]

代码把 s[‘value’] 当作数组索引,去取 realVaule.split(‘|’) 的元素。但服务端下发的 config 中传的是实际值而非索引:

参数 realVaule 传的 value 用作索引
image_channel [1, 3] 3 索引3 → ❌ 越界
image_shape [320,640,1024,1280,1536,2048] 640 索引640 → ❌ 越界,第 1 轮循环就崩溃了。

根因结论

服务端生成 config.zip 时的 bug:combox 类型参数应该传的是下拉选项的索引值(0/1/2…),但实际传了选项本身的真实值。

知识点

config_utils.pyd 是 Cython 编译产物,无法直接读源码
但 build_temp/ 目录下的 .c 中间文件保留了行号映射,可以用来反推 Python 逻辑,这是排查 pyd 内部问题的有效手段

中午吃饭的时候和mentor聊天说到我要同时打开三个vscode窗口,一个原webai,一个webai-lite,一个webai-device导致内存不够,电脑很卡。mentor和我说你可以在vscode中添加工作区。我现在才知道有这个东西,还是要和前辈交流啊,能学到很多东西的~

总之,很大一部分就是json格式的问题,解决完之后,还有一个问题就是样本太少,但是训练代码中规定样本数量必须大于3000张,这个都是训练时候的报错了,这个不归我管了,那么就是说,训练的整个最小流程是跑通了。

接下来就是跑推理的流程,杰哥先叫我去webai的测试环境那边去下了一个实例分割模型,放在lite本应该训练出来模型放置的地方,假装已经训练出一个模型了。然后开始跑推理流程。最开心的是,好像居然一次就跑通了,推理结果也回传了。但是客户端与服务器的训练和推理进度的端口没有打通,所以看不到进度,但是推理没有报错,结果全部回传。现在的任务就是把进度的端口再调一下。

端口差不多调好之后,mentor让我去会议室,说是leader要一起看一下mentor在做的agent和我在做的webai-lite进度。会上mentor的agent基本上都跑通了。可以多轮训练,评估,生成报告,很专业。我展示的时候就说现在整个训练推理流程基本跑通,但是因为样本数量问题所以客户端训练失败,但这都是训练内部问题了。接下来我本来还想展示推理流程,leader说他要看的是整个业务的流程,图像,mask标注,训练推理结果都保存在哪里?他不关心这些细节,还说推理失败这个问题可以往后放,我们先把多工作区做好,然后问我多久能完成,我说这个星期我抓紧。也就是说这周五必须完成多工作区。之后我就动用了禁忌力量:“Codex”帮我写PRD在最后。

leader还说样本不用3000张,随便多少都可以跑的,这个只能等多工作区搞好了以后再看了~

哟呵~今天来了一位新同事,看起来不像应届生,也不像实习,像是社招进来的大哥。


今日工作内容

  1. 跑通webai-lite整个训练,推理流程
  2. 整理webai-lite实现多工作区PRD

下阶段计划

  1. 梳理webai-lite文件结构,新增多工作区
  2. 规范webai-lite项目文件

多工作区数据隔离改造PRD

Goal

将 webai-lite 从“单工作区 _workspace/current”改造为支持多个工作区。每个工作区需要独立保存样本集、图片、标注、训练结果和推理结果,避免不同项目或场景之间的数据、job 和产物互相覆盖。同时必须兼容已经部署好的 D:\webai2.0-device 训练推理客户端,不能要求修改、重新编译或重新部署客户端。

What I Already Know

  • 当前后端的工作区服务是单例函数式访问:services/workspace.py:current_dir() 固定返回 settings.workspace_root / "current"
  • 当前数据目录集中在 _workspace/current/
    • images/ 保存图片。
    • annotations/ 保存标注 JSON。
    • masks/ 保存 sidecar mask PNG。
    • jobs/ 保存训练/推理 job JSON。
    • models_parameter/flows_parameter/ 保存当前现场覆盖配置。
    • dataset.yamldeployment.yaml 位于 current 根目录。
  • 当前产物目录是全局的 settings.artifacts_root / "latest",训练模型写到 latest/model,推理结果写到 latest/result
  • train.py / infer.py 提交 job 时没有写入 workspace_id,幂等检查也只按当前全局 job 列表扫描。
  • jobs.py 只读写 current/jobs
  • device_submit_bridge.pydevice_task_bundle.py 会用全局 current 工作区导出 COCO 样本包,并把完成后的模型/推理结果搬到全局 artifacts。
  • catalog.py 的现场覆盖、deployment.yaml 过滤规则也绑定全局 current。
  • 前端 Zustand 目前只保存 images/classes/currentImageId,没有 workspace_id
  • 前端训练/推理的 last job localStorage key 只按 training/inference 区分,未按工作区隔离。
  • 外部训练推理客户端位于 D:\webai2.0-device,是已部署的 Qt/C++ 服务,不能修改。
  • 该客户端不直接拉取 webai-lite 的 /api/jobs。webai-lite 内嵌 device_manager 通过现有 TCP 协议向客户端发送 start_training / start_inference
  • 设备客户端只识别 36 位设备任务 UUID,并按 <win_root_path>/training/<uuid><win_root_path>/inference/<uuid> 隔离本地任务目录。
  • 设备客户端推理完成后把 res/ 压缩为 result.zip,再通过 training_res / inference_res TCP 命令回传结果文件。
  • 设备客户端 HTTP 端口固定为 8001,服务端可用 GET /api/tasks/status?uuid=<device_task_uuid> 查询状态,用 GET /api/tasks/cancel?uuid=<device_task_uuid> 取消任务。

Requirements

  • 后端必须引入明确的 workspace_id 概念,并提供工作区列表、创建、读取详情的 API。
  • 每个工作区必须独立保存:
    • 图片样本:images
    • 标注:annotations
    • mask sidecar:masks
    • 类别与数据集配置:dataset.yaml
    • 现场模型/流程覆盖:models_parameterflows_parameter
    • 部署过滤配置:deployment.yaml
    • 训练/推理 job:jobs
    • 训练产物:模型、指标、日志等
    • 推理产物:可视化结果、result.zip、报告、日志等
  • 前端必须提供当前工作区选择入口。切换工作区后,数据页、标注页、训练页、推理页均应重新加载当前工作区数据。
  • 前端提交训练/推理时必须携带当前 workspace_id,返回的 job 也必须带 workspace_id
  • job 查询、详情、状态回写、取消必须能按 workspace_id 定位,避免不同工作区中的 job 混淆。
  • 训练/推理后台桥接必须使用 job 里的 workspace_id 导出对应工作区样本,并把结果写回同一工作区的产物目录。
  • 必须保持 D:\webai2.0-device 当前 TCP/HTTP 协议完全兼容:
    • 不向客户端新增 workspace_id 字段。
    • 不改变 36 位设备任务 UUID。
    • 不改变文件发送顺序。
    • 不改变状态查询、取消和结果回传格式。
  • webai-lite 必须持久化 workspace_id + job_id + device_job_id 映射,并在客户端回传结果时通过该映射定位目标工作区。
  • artifacts 列表和下载必须限定在当前工作区产物目录内,继续使用路径穿越防护。
  • 保留对现有单工作区数据的兼容策略:未传 workspace_id 时默认使用 default 工作区,旧目录 _workspace/current 可被视为 default 数据源或在启动/首次访问时迁移。
  • 测试需要覆盖至少两个工作区之间的图片、标注、job、artifacts 隔离。

Acceptance Criteria

  • 可以创建至少两个工作区,并在前端切换。
  • 工作区 A 导入图片、保存类别和标注后,工作区 B 的图片/类别/标注列表不受影响。
  • 工作区 A 提交训练 job 后,工作区 B 的 job 列表默认不显示该 job。
  • 工作区 A 的训练结果写入 A 的产物目录,工作区 B 的 artifacts 列表不可见。
  • 工作区 A 的推理结果写入 A 的产物目录,工作区 B 的 artifacts 列表不可见。
  • 无需修改或升级 D:\webai2.0-device,现有设备客户端仍能接收训练/推理任务、查询/取消任务并回传结果。
  • 两个工作区提交的任务即使发往同一设备客户端,结果仍分别归档到各自工作区。
  • webai-lite 重启后仍可从磁盘 job 记录恢复 device_job_id 对应的业务 job 和工作区。
  • 旧版不传 workspace_id 的调用仍可落到 default 工作区。
  • safe_join 仍阻止 workspace file/artifact 下载路径穿越。
  • 后端 pytest -q 通过,前端 npm run build 至少通过类型与构建检查。

Technical Approach

采用“服务端路径上下文 + 前端全局 workspace_id + 设备协议兼容层”的增量改造。

  • 在后端 services/workspace.py 中引入 workspace-aware 路径解析,将当前全局 current_dir() 扩展为可接收 workspace_id 的版本。
  • 默认工作区 ID 使用 default,兼容旧 API。
  • 新目录建议采用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
_workspace/
workspaces/
default/
images/
annotations/
masks/
jobs/
models_parameter/
flows_parameter/
dataset.yaml
deployment.yaml
artifacts/
latest/
model/
result/
<workspace_id>/
...
_trash/
  • 为了兼容旧数据,default 可以先读取/迁移 _workspace/current_workspace/artifacts;最终服务逻辑统一通过 workspace path helper 访问。
  • API 兼容策略:
    • 现有路由继续保留,例如 /api/workspace/images/api/annotations/{image_id}
    • 新增或统一支持 workspace_id query/header/body,例如 ?workspace_id=xxx
    • 前端统一由 lib/api.ts 自动附加当前 workspace_id
  • job JSON 中固化 workspace_id,所有后续后台任务、回写、取消、产物归档均以 job 中的 workspace_id 为准。
  • 设备兼容层保持现有协议:
    • 对客户端仍只发送 device_job_id、任务文件与现有 TaskData 字段。
    • 不扩展客户端 TCP header,也不修改客户端 HTTP endpoint。
    • device_job_id 写回业务 job JSON,作为服务端关联键。
    • 客户端返回 training_res / inference_res 后,服务端通过业务 job 找到 workspace_id,再归档结果。

Alternative Considered

新增全套嵌套路由,例如 /api/workspaces/{workspace_id}/images/api/workspaces/{workspace_id}/jobs。语义更清晰,但需要一次性改动前后端所有调用,对现有外部训练推理客户端和旧调用冲击更大。当前任务优先选择兼容式 query/header/body 携带方式,降低迁移风险。

Decision (ADR-lite)

Context: 已部署设备客户端只理解现有 TCP/HTTP 协议和设备任务 UUID,无法识别业务工作区,也不能修改代码。

Decision: 工作区信息只存在 webai-lite 服务端和 Web 前端。服务端使用业务 job 持久化 workspace_iddevice_job_id 的关联;设备客户端继续按原协议处理设备任务。

Consequences:

  • 客户端零改动,部署兼容风险最低。
  • 服务端 job 文件成为跨工作区、跨进程重启关联的事实来源。
  • 所有异步回写和产物搬运函数必须显式接收或从 job 解析 workspace_id,不能再读取全局 current/artifacts。

Expansion Notes

  • Future evolution:
    • 后续可增加工作区重命名、删除、归档、复制/克隆、容量统计。
    • 后续可支持把某个工作区导出为离线包。
  • Related scenarios:
    • Catalog 现场覆盖和 deployment.yaml 必须跟随工作区,否则不同项目的可用模型/流程会串。
    • last job、样本勾选、当前图片都要随工作区切换清空或分桶保存。
  • Failure and edge cases:
    • workspace_id 必须做白名单校验,不能允许路径片段、空白、..、斜杠。
    • 删除/重命名工作区若进入本任务,需要处理 active job;本任务先不做删除。
    • 旧客户端或旧 API 调用若不传工作区,应继续使用 default

Out of Scope

  • 工作区权限、用户登录、多租户安全模型。
  • 工作区删除、重命名、复制、导入导出整包。
  • 数据库化改造;本任务继续使用磁盘 JSON 和目录结构。
  • 同一个 job 跨工作区移动。
  • 修改真实训练/推理算法逻辑;本任务只改数据定位、打包、回写和产物归档边界。
  • 修改、编译、发布或部署 D:\webai2.0-device
  • 修改设备客户端 TCP header、HTTP endpoint、结果包格式和本地缓存数据库结构。

Research References

Definition of Done

  • 后端路径访问全部通过 workspace-aware helper,不再在业务逻辑中直接拼 settings.workspace_root / "current"
  • 前端 API 层统一携带当前工作区 ID,页面切换工作区后状态不会串。
  • 已部署设备客户端不需要任何代码修改。
  • 多工作区隔离测试覆盖关键链路。
  • 后端 lint/test 和前端 build 检查通过,无法运行的检查需记录原因。

Technical Notes

  • 关键后端文件:
    • api/webai_lite_api/config.py
    • api/webai_lite_api/services/workspace.py
    • api/webai_lite_api/services/workspace_ext.py
    • api/webai_lite_api/services/attach.py
    • api/webai_lite_api/services/jobs.py
    • api/webai_lite_api/services/catalog.py
    • api/webai_lite_api/services/cvat_coco_export.py
    • api/webai_lite_api/services/device_task_bundle.py
    • api/webai_lite_api/services/device_submit_bridge.py
    • api/webai_lite_api/routes/workspace.py
    • api/webai_lite_api/routes/annotations.py
    • api/webai_lite_api/routes/artifacts.py
    • api/webai_lite_api/routes/jobs.py
    • api/webai_lite_api/routes/train.py
    • api/webai_lite_api/routes/infer.py
  • 关键前端文件:
    • web/src/lib/api.ts
    • web/src/store/index.ts
    • web/src/App.tsx
    • web/src/pages/Data.tsx
    • web/src/pages/Annotate.tsx
    • web/src/pages/Train.tsx
    • web/src/pages/Infer.tsx
    • web/src/components/WorkspaceSamplePicker.tsx
    • web/src/components/JobResultPanel.tsx
    • web/src/lib/jobHelpers.ts
  • 外部只读参考:
    • D:\webai2.0-device
  • 相关规范:
    • .trellis/spec/backend/index.md
    • .trellis/spec/backend/directory-structure.md
    • .trellis/spec/backend/quality-guidelines.md
    • .trellis/spec/frontend/index.md
    • .trellis/spec/frontend/state-management.md
    • .trellis/spec/frontend/quality-guidelines.md