Modly 后端 API 深度指南:FastAPI 驱动的本地 AI 3D 生成服务

发布时间:2026/9/16 22:43:18
Modly 后端 API 深度指南:FastAPI 驱动的本地 AI 3D 生成服务
Modly 后端 API 深度指南FastAPI 驱动的本地 AI 3D 生成服务【免费下载链接】modlyDesktop app to generate 3D models from images or prompt using local AI — runs entirely on your GPU项目地址: https://gitcode.com/GitHub_Trending/mo/modly导读Modly 是一款在本地 GPU 上运行的 3D 模型生成桌面应用而 api/README.md 所描述的 FastAPI 后端正是整个应用的引擎舱它由 Electron 主进程拉起并托管对外提供健康检查、模型下载、图生 3D 推理、网格优化与导出等一整套 HTTP 接口。本文以该文档为主线结合仓库中 api/main.py、api/routers/、api/services/ 等源码完整还原后端的启动方式、核心端点用法、模型下载机制与生成任务的生命周期并说明如何将模型与扩展接入这一体系。读完本文你将能够独立搭建并调试 Modly 的本地后端理解其端到端的推理链路并具备向其中添加新 3D 生成模型的能力。1. 架构定位由 Electron 启动的本地 Python 服务后端是一个标准的 FastAPI 应用api/main.py 声明为titleModly API、version0.4.1它不对外暴露公网端口仅监听127.0.0.1:8765专门服务于同一台机器上的 Electron 桌面壳生命周期由 electron/main/python-bridge.ts 中的PythonBridge管理启动时spawn出python -m uvicorn main:app --host 127.0.0.1 --port 8765并在detached模式下将整个进程组托管退出时通过SIGKILL整组清理避免扩展子进程被孤儿化python-bridge.ts。Electron 通过轮询GET /health判断后端是否就绪python-bridge.ts后端崩溃时前端会收到python:crashed事件。后端启动时通过lifespan回调初始化generator_registry关闭时统一unload_all()释放显存api/main.py。因此对开发者而言既可以把后端当作独立服务手动启动来调试接口也可以把它看作 Electron 应用内部的本地推理微服务来理解其边界。2. 环境搭建与开发模式运行2.1 创建虚拟环境并安装依赖按 api/README.md 的 Setup 章节操作cd api python -m venv .venv # Windows .venv\Scripts\activate # macOS / Linux source .venv/bin/activate pip install -r requirements.txt依赖清单见 api/requirements.txt按职责可分为四组分组包用途Web 服务fastapi0.115.6、uvicorn[standard]0.34.0、python-multipart0.0.20HTTP 框架、ASGI 服务器、表单文件上传Agent 与 MCPhttpx0.27.0、mcp1.0.0Agent 聊天代理与外部 MCP 工具协议网格处理trimesh4.5.0、pymeshlab2023.12网格解码、减面、平滑、格式转换模型下载huggingface_hub0.27.0、hf_xet0.1.0从 HuggingFace Hub 拉取权重另有cryptography与certifi用于加密与 TLS 证书链。2.2 开发模式启动uvicorn main:app --host 127.0.0.1 --port 8765 --reload几点与源码相关的事实--host 127.0.0.1是硬性约束——后端只面向本机Electron 侧也写死了API_HOST 127.0.0.1、API_PORT 8765python-bridge.ts。CORS 中间件允许所有来源并显式暴露Content-Length响应头——这是为了让前端 three.js 生态的SplatLoader能跨域读到内容长度来预分配缓冲区api/main.py。启动后 Uvicorn 的 access log 会过滤掉所有/generate/status/路径的请求避免轮询噪音刷屏api/main.py。3. 核心端点总览api/README.md 给出了 5 个核心端点实际后端在 api/main.py 中注册了 9 组路由。下面先列文档中的核心表再补全完整路由表MethodPathDescriptionGET/healthHealth check (used by Electron to detect readiness)GET/model/statusModel download / load statusGET/model/downloadSSE stream of download progressPOST/generate/from-imageStart image-to-3D jobGET/generate/status/{job_id}Poll job status完整的路由注册api/main.py如下前缀文件提供的能力/api/routers/status.pyGET /health/settingsapi/routers/settings.py读写模型/工作区目录、更新 HF Token/modelapi/routers/model.py模型状态、切换、卸载、SSE 下载/generateapi/routers/generation.py图生 3D、任务轮询、取消/optimizeapi/routers/optimize.py减面、平滑、变换、导入、导出、高斯泼溅转.splat/extensionsapi/routers/extensions.py扩展热重载、扩展 setup、错误查询/exportapi/routers/export.pyGLB/STL/OBJ/PLY 格式导出/workflow-runsapi/routers/workflow_runs.py工作流运行任务的创建/查询/取消/agentapi/routers/agent.pyOllama 驱动的 Agent 对话与工具调用/workspace/{full_path:path}api/main.py动态托管工作区中的生成产物GLB/SPLAT 等说明README 中提到的GET /model/download在当前源码中对应的实际实现是GET /model/hf-downloadSSE 下载后文会详细讲解。4. 模型体系注册表、扩展与默认模型4.1 关于默认模型的过时记载README 的 Model 章节记载默认模型为TripoSRstabilityai/TripoSR约 2.4 GB并提示修改services/model_manager.py即可换模型。但经过对当前源码的核查这一记载已与实现脱节全仓库搜索不到services/model_manager.py文件model_manager字样仅出现在 README 本身。当前默认模型由环境变量SELECTED_MODEL_ID决定默认值为sf3dgenerator_registry.py而不是TripoSR。模型不再是硬编码的单一实现而是通过扩展extension机制动态发现在扩展目录放置manifest.jsongenerator.py即可注册新模型无需改动任何其他文件generator_registry.py。因此本节的换模型指南以当前源码为准。4.2 目录与环境变量后端的三个关键目录均由环境变量驱动generator_registry.pyElectron 在启动后端时通过PythonBridge注入环境变量默认值用途MODELS_DIR~/.modly/models模型权重存放目录WORKSPACE_DIR~/.modly/workspace生成产物输出目录EXTENSIONS_DIR未设置扩展模型适配器目录由 Electron 传入 userData 路径SELECTED_MODEL_IDsf3d启动时的默认活跃模型HUGGING_FACE_HUB_TOKEN/HF_TOKEN空访问 HF 受限仓库的令牌这三个目录会在后端启动时自动创建mkdir(parentsTrue, exist_okTrue)。4.3 扩展发现与加载流程GeneratorRegistry.initialize()在应用启动时调用api/main.py其发现逻辑generator_registry.py要点如下遍历EXTENSIONS_DIR下每个子目录跳过.开头的目录安装暂存目录和带有.modly-incomplete标记的目录安装未完成。目录必须同时包含manifest.json与generator.py否则跳过。manifest[type]非model的扩展不归注册表管理流程类扩展由 Electron 的 process runner 处理。若扩展带有nodes[]多节点扩展则以ext_id/node_id复合 ID 注册多个生成器否则回退为以ext_id注册单个生成器。加载分两种模式子进程模式新扩展自带venv/时用ExtensionProcess包装通过 api/runner.py 以换行分隔 JSON 协议与子进程通信extension_process.py。直接模式旧/兼容无 venv 时直接在 FastAPI 进程内import其generator.py。每个扩展的元数据名称、VRAM 需求、HF 仓库、参数 schema 等都来自manifest.json并通过_apply_manifest_metadata注入生成器实例。4.4 生成器契约BaseGenerator所有模型适配器都实现 api/services/generators/base.py 中的BaseGenerator抽象基类契约包括load()/unload()/is_loaded()模型生命周期管理。unload()会主动gc.collect()、清空 CUDA 缓存并在 Windows 上调用SetProcessWorkingSetSizeEx强制归还内存base.py。generate(image_bytes, params, progress_cb, cancel_event)接收图片字节与参数字典返回生成的.glb文件路径progress_cb(percent, step)上报进度cancel_event用于步骤间取消base.py。is_downloaded()按download_checkmanifest 中声明的相对路径检查权重是否已在磁盘未声明时检查目录非空。params_schema()返回 UI 渲染参数字段用的 schema默认读取 manifest 注入的_params_schema可被覆盖。_auto_download()提供标准的 HFsnapshot_download兜底下载base.py扩展可以自行覆盖以实现多仓库等定制下载逻辑。5. 模型下载带断点续传的 SSE 下载流README 将模型下载列为/model/download端点当前实现为GET /model/hf-downloadapi/routers/model.py返回text/event-stream。5.1 查询参数参数类型说明repo_idstring必填HuggingFace 仓库 ID如stabilityai/TripoSRmodel_idstring必填下载后落盘的本地模型 ID对应MODELS_DIR/model_idskip_prefixesstring可选JSON 编码的路径前缀黑名单如[*.onnx, config.json]include_prefixesstring可选JSON 编码的路径前缀白名单tokenstring可选HF 访问令牌优先级显式参数 环境变量HUGGING_FACE_HUB_TOKEN/HF_TOKEN当未显式传过滤参数时会回退读取扩展 manifest 中的hf_skip_prefixes/hf_include_prefixesmodel.py因此扩展作者只需在 manifest 里声明即可获得一致的过滤行为。5.2 SSE 消息格式每条事件为data: json\n\n进度字段如下{percent: 0, status: Listing repository files...} {percent: 12, file: model.safetensors, fileIndex: 2, totalFiles: 5, status: Downloading… 45%, bytesDownloaded: 209715200, totalBytes: 466033664} {percent: 100, status: done}进度分配策略0% 用于列出仓库文件1%–95% 用于逐文件下载按文件数均分95%–100% 留给收尾model.py。5.3 底层实现细节断点续传先写临时文件*.part下载完成后再replace为正式文件若正式文件已存在则直接跳过。续传时先 HEAD 请求解析出最终 CDN 地址再携带Range: bytes已有字节数-仅当响应为206 Partial Content时续写否则丢弃重下model.py。失败重试最多 3 次退避间隔从 2 秒起指数增长。暂停与取消通过POST /model/hf-download/pause与POST /model/hf-download/cancel设置threading.Event取消时只清理.part临时文件已完成的文件保留以便下次续传model.py。收尾确认/model/status返回活跃模型的downloaded/loaded布尔值/model/all则列出全部已知模型的元数据名称、版本、vram_gb、标签等见 generator_registry.py。6. 图生 3D生成任务的完整生命周期6.1 提交任务POST /generate/from-image按 api/routers/generation.py该接口接收 multipart/form-data字段类型默认值说明imagefile必填—输入图片content-type 必须以image/开头model_idstringsf3d使用的模型必须是注册表中存在的 IDcollectionstringDefault输出子目录名会经过清洗禁止/:*?|\等路径分隔符remeshstringquad重网格策略仅允许quad/triangle/noneenable_textureboolfalse是否启用纹理生成texture_resolutionint1024纹理分辨率paramsstring{}模型特有参数的 JSON 字符串与通用参数合并服务端校验通过后立即返回{job_id: uuid4}实际推理由 FastAPI 的BackgroundTasks异步执行不会阻塞请求。6.2 任务状态机GET /generate/status/{job_id}任务状态由 api/schemas/generation.py 中的JobStatus模型定义class JobStatus(BaseModel): job_id: str status: Literal[pending, running, done, error, cancelled] progress: int 0 # 0–100 step: Optional[str] None # Human-readable current step output_url: Optional[str] None error: Optional[str] None状态流转为pending → running → done | error | cancelled任务完成后progress100output_url被设置为/workspace/collection/文件名相对 WORKSPACE_DIR见 generation.py该 URL 由/workspace/{full_path:path}动态路由直接托管。任务对象驻留在内存字典中终态任务 30 分钟后被_purge_old_jobs()清理TTL 见 generation.py。6.3 执行链路后台任务的内部机制_run_generationgeneration.py的关键步骤预判模型加载先查active_status()纯字典查询判断模型是否已加载未加载时用smooth_progress在后台线程平滑推进 0–9% 的进度并显示Downloading… / Loading…标签base.py同时把真正的load()扔进线程池执行——避免阻塞事件循环。执行推理gen.generate(image_bytes, params, progress_cb, cancel_event)也在线程池中运行若生成器签名支持cancel_event则传入否则自动降级。结果落盘输出写入WORKSPACE_DIR/collection/子目录返回相对路径生成output_url。异常处理GenerationCancelled置为cancelled其他异常记录完整 traceback 到job.error并置为error。6.4 取消任务POST /generate/cancel/{job_id}取消有两层机制generation.py先通过cancel_event请求协作式取消若模型正被阻塞在推理中则直接 kill 当前活跃生成器的子进程gen._proc.kill()以立即停止 GPU 占用。/workflow-runs/{run_id}/cancel复用同一套内部状态workflow_runs.py。7. 网格后处理与导出7.1 减面与平滑POST /optimize/mesh与/optimize/smooth两个端点都依赖pymeshlab请求体分别为{path: collection/file, target_faces: N}与{path: ..., iterations: N}target_faces被夹取到[100, 500000]平滑迭代次数夹取到[1, 20]optimize.py、L182。路径解析做了严格防越界相对路径必须位于 WORKSPACE_DIR 内否则返回 400optimize.py。带纹理的模型走OBJ 中间格式以保留 UV 坐标自动改写 MTL 的map_Kd指向固定纹理名纯几何模型走PLY以换取速度核心算法是meshing_decimation_quadric_edge_collapse二次误差边折叠与apply_coord_laplacian_smoothing拉普拉斯平滑且都开启preservetexcoord/preservenormaloptimize.py。产物命名为原名_opt目标面数.glb/原名_smooth次数.glb写入原文件所在目录工作区外输入则写入Workflows/。7.2 变换烘焙与高斯泼溅支持POST /optimize/transform接收 4×4 行主序矩阵用 trimesh 将交互 gizmo 的变换烘焙进 GLB 场景层保证导出后变换持久optimize.py。高斯泼溅3DGS_is_gaussian_ply()通过检测f_dc_0、scale_0、rot_0属性识别 3DGS 点云_convert_gaussian_ply_to_splat()将球谐颜色与 sigmoid 不透明度转为标准二进制.splat32 字节/行pos/scale/rgba/rot并按 1–99 百分位框做居中归一化让模型始终落在相机前方optimize.py。转换结果按「源文件 mtime 转换版本」缓存GET /optimize/ply-to-splat与/optimize/import-by-path均走此路径。7.3 导出GET /export/{fmt}?path...api/routers/export.py 支持glb/stl/obj/ply四种格式GLB 直接以model/gltf-binary回传其余格式先经 trimesh 将场景扁平化为单一网格再转换输出STL 为model/stlPLY 为application/octet-streamOBJ 为text/plain。另有GET /optimize/serve-file可跨工作区直接伺服磁盘上的 GLB/SPLAT 文件用于导入到场景。8. Agent 与 MCP让外部模型操作 Modly8.1 Ollama 驱动的 Agent 对话POST /agent/chatapi/routers/agent.py在 FastAPI 内实现了完整的 tool-use 循环默认对接http://localhost:11434的 Ollama默认模型qwen2.5:3b最多 10 轮工具调用超限后提示Reached maximum tool iterations。内置 8 个工具agent.pylist_models、unload_models、get_mesh_info、decimate_mesh、smooth_mesh、get_generation_status、list_workflows、run_workflow、create_workflow。场景上下文当前网格路径、三角面数、可用扩展列表会以 system 消息注入供 LLM 决策create_workflow会调用_build_workflow_graph拼装出「输入节点 → 若干扩展节点 → Add-to-Scene 输出节点」的线性工作流图agent.py。执行完工作流后会立即以keep_alive: 0卸载 LLM把显存让给推理任务。8.2 MCP 服务器api/mcp_server.py 将 Modly 能力以 MCP 工具形式暴露给外部 AgentClaude Desktop、Codex CLI 等运行python mcp_server.py即可前提是 FastAPI 后端已在localhost:8765运行。工具包括modly_list_models、modly_switch_model、modly_generate_from_image、modly_get_generation_status等可直接在外部 Agent 配置中引用该脚本路径注册为 MCP server。9. 设置与运维接口9.1 路径与令牌管理/settingsapi/routers/settings.py 提供GET/POST /settings/paths读取或热更新MODELS_DIR与WORKSPACE_DIR运行时调用registry.update_paths()会先卸载全部模型再迁移目录。POST /settings/hf-token更新进程环境中的HUGGING_FACE_HUB_TOKEN/HF_TOKEN使之后派生的扩展子进程继承新令牌。9.2 扩展运维/extensionsapi/routers/extensions.py 提供POST /extensions/reload无需重启 FastAPI 即可重新扫描扩展目录并重建注册表返回当前模型 ID 列表与加载错误。POST /extensions/setup/{ext_id}为扩展创建独立 venv——执行其setup.py参数为嵌入式 Python 路径、扩展目录、GPU 计算能力 SM 值。无setup.py的旧式扩展直接返回skipped。GET /extensions/errors查询扩展加载错误详情如 venv 缺失、manifest 非法。GPU 计算能力通过torch.cuda.get_device_capability()探测如 SM 8.6 →86无 GPU 时返回 0。9.3 内存治理POST /model/unload-all会卸载全部模型、gc.collect()并在 Windows 上调用SetProcessWorkingSetSizeEx把工作集归还操作系统POST /model/unload/{model_id}用于卸载单个模型以便安全删除其文件。这在本地 GPU 资源受限的场景例如在 docs/running-on-jetson.md 描述的 Jetson 设备上非常关键。10. 扩展实践如何接入一个新模型综合 README 与源码在 Modly 中加入新模型的完整路径是放置扩展目录在EXTENSIONS_DIR下新建目录例如my-model/。编写manifest.json声明id、name、generator_class、hf_repo、download_check、hf_skip_prefixes、params_schema、vram_gb等字段多节点模型用nodes[]数组每个节点可有独立的hf_repo与参数 schema。实现generator.py继承 api/services/generators/base.py 的BaseGenerator实现load()与generate()如需自定义下载可覆盖_auto_download()。可选提供setup.py创建独立 venv内含依赖注册表会检测到 venv 并自动切换到子进程模式运行中可通过POST /extensions/setup/{ext_id}触发安装或在前端 Models 页点击 Repair。验证POST /extensions/reload后通过GET /model/all确认新模型已注册、GET /model/params确认参数 schema 生效再调用/generate/from-image端到端测试。与 README 中编辑services/model_manager.py的旧说明相比这套扩展机制让新增模型无需改动后端核心代码——这正是当前仓库 api/services/generator_registry.py 的实际设计。11. 测试与验证后端自带两套测试可作为接口契约与协议行为的权威参考api/tests/test_runner.py针对扩展子进程运行器 api/runner.py 的单元测试覆盖多节点选择_select_node、参数 schema 的优先级回退类方法 节点 manifest、manifest 元数据注入以及换行分隔 JSON 协议的send/recv行为——例如非法 JSON 行会被跳过并记录错误而不会崩溃。api/tests/test_extension_process.py针对ExtensionProcess子进程生命周期的测试。运行方式配合 scripts/run-pytests.mjs 可一键执行cd api python -m unittest discover -s tests12. 总结Modly 的 FastAPI 后端是一个典型的本地推理微服务设计由 Electron 托管生命周期通过注册表动态发现模型扩展以 SSE 流式报告下载与生成进度并以统一的任务状态机支撑图生 3D 的完整流程。README 所记载的 5 个核心端点是理解这套系统的入口而路由层api/routers、注册表层api/services/generator_registry.py、生成器契约api/services/generators/base.py与子进程协议api/runner.py共同构成了从 HTTP 请求到 GPU 推理再到产物落盘的完整闭环。掌握这套体系后无论是调试推理链路、新增模型扩展还是通过 Agent/MCP 将 Modly 接入更大规模的自动化流程都有清晰、可验证的路径可循。【免费下载链接】modlyDesktop app to generate 3D models from images or prompt using local AI — runs entirely on your GPU项目地址: https://gitcode.com/GitHub_Trending/mo/modly创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考