Skip to content

AI 应用

首先,我们先理解下下面几个概念:

  1. lzc-cli 是懒猫微服中用来开发微服应用的脚手架,通过 lzc-cli 可以快速打包应用、安装应用、调试应用、发布应用等。
  2. 通过 lzc-cli 打包后产生的包为 lpk 格式,这是微服中的应用格式。
  3. 懒猫 AI 浏览器中可以安装插件,支持 PCAndroid 平台。

AI 应用 是指在微服应用的基础上,导入 AIApp resource 的应用。这一类应用的下载和安装都和正常的微服应用一样,区别在于:

  • 支持添加 AI 服务: 应用安装后,会自动将匹配设备型号的服务部署到算力舱上。
  • 支持添加 AI 浏览器插件: 应用安装后,AI 浏览器会自动加载对应插件。
  • 支持定义 AI 浏览器快捷方式和依赖关系: 快捷方式、上游服务、存储别名等配置由 resource 中的 config/aipod.yml 描述。

Resource 导入声明

微服应用通过 lzc-manifest.ymlimport_resources 声明导入 AIApp resource:

yml
import_resources:
  - kind: aipod

平台安装应用后,会把 AIApp resource 展开到下面的运行目录:

text
/lzcapp/run/resources/aipod/<package-id>/

其中 <package-id> 为应用包 ID。平台从该目录读取 AI Pod 配置、服务 compose、浏览器插件和兜底 metadata。

Resource 目录结构

推荐的 AIApp resource 结构如下:

text
aipod/
├── package.yml
├── lzc-manifest.yml
├── icon.png
├── config/
│   └── aipod.yml
├── agxorin/
│   └── docker-compose.yml
├── thor/
│   └── docker-compose.yml
└── extensions/
    ├── desktop.zip
    └── android.crx

字段说明:

  • config/aipod.yml: AI Pod 配置文件,不需要外层 aipod:
  • agxorin/docker-compose.yml: AGX Orin 及默认 Jetson 设备使用的服务定义。
  • thor/docker-compose.yml: Thor 设备使用的服务定义。
  • extensions/*.zip|*.crx: AI 浏览器插件文件,可放置一个或多个。
  • package.ymllzc-manifest.ymlicon.png: metadata 和图标兜底来源。

兼容旧包时,平台仍会识别以下 legacy 路径:

  • ai-pod-service/docker-compose.yml: legacy 服务定义。仅当缺少设备型号目录下的 compose 时作为 fallback。
  • browser-extensions/extension.zip: legacy 浏览器插件。仅当缺少 extensions/ 下插件时作为 fallback。

config/aipod.yml

config/aipod.yml 只写 AI Pod 配置本身,不带外层 aipod:。例如:

yml
shortcut:
  disable: false
  title: ComfyUI
  url: /_lzc/aipod_backend/startup/gate/cloud.lazycat.aipod.comfyui?redirect=https%3A%2F%2Fcomfyui-ai
  favicon: /icon.png

locales:
  en:
    shortcut:
      title: ComfyUI

depends_on_others:
  - ollama-ai

depends_on_hosts:
  - comfyui-ai

storage_alias:
  models: /ssd/models

shortcut

安装到微服中的 AI 应用默认会显示在 AI 浏览器快捷方式上。可以通过 shortcut.disable 禁用,也可以覆盖标题、入口地址和图标:

  • shortcut.disable: 是否隐藏快捷方式,默认为 false
  • shortcut.title: 快捷方式标题。
  • shortcut.url: 快捷方式打开地址。长初始化服务推荐使用平台 startup gate。
  • shortcut.favicon: 快捷方式图标地址。

locales

locales 用于配置多语言 metadata。平台会按浏览器语言选择对应标题、描述或快捷方式字段,缺失时回退到默认配置。

depends_on_others

depends_on_others 声明当前 AI 应用依赖的其他 AI host。平台会在入口和启动检查中考虑这些依赖的可用性。

depends_on_hosts

depends_on_hosts 声明当前 AI 应用自身需要检查的 AI host。通常填写 compose 中通过 Traefik 暴露的 Host(...) 值。

storage_alias

storage_alias 声明服务中使用的存储别名,便于平台将模型、缓存或用户数据挂载到稳定路径。

添加 AI 服务

AI 服务通过 resource 中的 docker-compose.yml 描述。平台根据设备型号选择服务目录:

  1. Thor 设备优先使用 thor/docker-compose.yml
  2. 其他设备默认使用 agxorin/docker-compose.yml
  3. 如果型号目录缺失,再 fallback 到 legacy ai-pod-service/docker-compose.yml

agxorinthor 和 legacy 服务如果同时存在,暴露给 AI 浏览器的 AI host 必须一致,避免同一个应用在不同设备上生成不同访问地址。

服务访问规则

算力舱中的服务使用 traefik 结合 Host 规则转发。可被平台识别和部署的 compose 必须包含可解析的 Traefik Host(...) label,并且 host 值必须以 -ai 结尾。

yml
services:
  comfyui:
    image: registry.lazycat.cloud/demo/comfyui:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${LZC_SERVICE_ID}-comfyui.rule=Host(`comfyui-ai`)"

networks:
  default:
    external: true
    name: traefik-shared-network

规则说明:

  • Host(...) 中的值是微服和 AI 浏览器访问该服务的 AI host。
  • 只有加入 traefik-shared-network 的服务才能被 Traefik 接管。
  • 多个设备目录中的 compose 必须暴露相同的 AI host。
  • 长初始化服务必须配置 healthcheck,通过容器健康状态控制 ready 状态。平台的安装进度和 startup loading 会复用该状态。

关于 traefik 的更多使用方法可以查看 Host Rule

附加说明

算力舱中的 docker 默认使用 nvidia-runtime,容器中能直接使用 gpus,不需要显式指定 gpus 相关配置。更详细的说明,点击连接

服务环境变量

服务运行时,算力舱中会提供以下环境变量:

  1. 数据持久化路径

    • 定义: /ssd/lzc-ai-agent/data/<service_id>
    • 环境变量: LZC_AGENT_DATA_DIR
    • 示例: /ssd/lzc-ai-agent/data/cloud.lazycat.aipod.ai
    yml
    services:
      ollama:
        volumes:
          - ${LZC_AGENT_DATA_DIR}/data:/root/.ollama
  2. 数据缓存路径

    • 定义: /ssd/lzc-ai-agent/cache/<service_id>
    • 环境变量: LZC_AGENT_CACHE_DIR
    • 示例: /ssd/lzc-ai-agent/cache/cloud.lazycat.aipod.ai
    yml
    services:
      ollama:
        volumes:
          - ${LZC_AGENT_CACHE_DIR}/cache:/root/.cache
  3. 服务 ID

    • 定义: <appId> 去掉 . 后的值
    • 环境变量: LZC_SERVICE_ID
    • 示例: cloudlazycataipodfishspeech
    yml
    services:
      ollama:
        labels:
          - "traefik.http.routers.${LZC_SERVICE_ID}-ollama.rule=Host(`ollama-ai`)"

添加 AI 浏览器插件

AI 浏览器插件放在 resource 的 extensions/ 目录中,支持 zipcrx:

text
extensions/
├── desktop.zip
└── android.crx

zip 为浏览器兼容的插件格式。如果插件是 wxt 开发的,可以直接打包为 zip;也可以手动把插件目录打包成 zip。

兼容旧包时,平台仍会读取 browser-extensions/extension.zip 作为 legacy fallback。

Metadata 和图标兜底

平台优先读取 resource 中的 config/aipod.yml。当其中缺少标题、描述、图标等 metadata 时,会按下面顺序兜底:

  1. package.yml
  2. lzc-manifest.yml
  3. icon.png

如果 metadata 同时存在于微服应用 manifest 和 AIApp resource 中,AI 浏览器相关展示以 resource 配置为准。

启动入口和 loading

长初始化 AI 服务不再推荐每个 AI 应用内置前置 caddy 做健康判断。推荐复用平台提供的 startup gate:

text
/_lzc/aipod_backend/startup/gate/:appid?redirect=<url>

快捷方式直接进入 gate

如果应用入口就是一个 AI host,可以在快捷方式里直接进入 gate:

yml
shortcut:
  title: ComfyUI
  url: /_lzc/aipod_backend/startup/gate/cloud.lazycat.aipod.comfyui?redirect=https%3A%2F%2Fcomfyui-ai

规则说明:

  • :appid 为 AI 应用包 ID。
  • redirect 为服务 ready 后跳转的目标地址,需要 URL encode,通常是 compose 暴露的 AI host 或微服页面地址。
  • 静态前端可以直接走微服平台路由。
  • API 或模型服务访问走 Traefik upstream。
  • loading、健康检查、失败提示复用平台能力,不需要每个应用重复实现。

静态前端接入 startup gate

如果 AI 应用本身有微服静态前端,更推荐参考 cloud.lazycat.aipod.imgtrans 的做法,在 lzc-manifest.yml 中使用 application.injects 拦截页面请求。这样用户访问任意前端页面时,都会先进入平台 startup loading;服务 ready 后再回到原始页面,并且可以保留深链接和 query。

yml
application:
  subdomain: imgtrans
  injects:
    - id: aipod-startup-gate
      'on': request
      auth_required: false
      when:
        - /*
      unless:
        - /api/*
        - /debug/backend/*
        - /assets/*
        - /favicon.ico
        - /icon.png
        - /robots.txt
        - /manifest.json
        - /_lzc/*
      do:
        - src: >
            const appid = "cloud.lazycat.aipod.imgtrans";

            const cookieName = "aipod_startup_ready_" +
            appid.replace(/[^A-Za-z0-9]/g, "_");

            const cookies = String(ctx.headers.get("Cookie") || "");

            if (cookies.split(";").map((item) => item.trim()).some((item) =>
            item === cookieName + "=1")) {
              return;
            }

            const boxDomain = String(ctx.request.host || "").replace(/^[^.]+\./,
            "");

            const query = ctx.request.raw_query ? "?" + ctx.request.raw_query :
            "";

            const redirect = "https://" + ctx.request.host + ctx.request.path +
            query;

            const location = "https://" + boxDomain +
            "/_lzc/aipod_backend/startup/gate/" + encodeURIComponent(appid) +
            "?redirect=" + encodeURIComponent(redirect);

            ctx.response.send(307, "", { location });
  routes:
    - /=file:///lzcapp/pkg/content/dist
  upstreams:
    - location: /api/
      backend: http://backend:3000
      disable_trim_location: true
  public_path:
    - /debug/backend/
    - /api/

这个例子的关键点:

  • when: /* 拦截所有页面入口,unless 放行 API、调试接口、静态资源、图标和 /_lzc/* 平台内部路径,避免资源请求和 gate 自身被重复重定向。
  • appid 必须填写当前 AI 应用包 ID,startup gate 会用它查询安装和服务 ready 状态。
  • gate ready 后会写入 aipod_startup_ready_<appid> cookie,inject 先检查这个 cookie,已 ready 的请求直接放行,避免每次打开页面都进入 loading。
  • boxDomain 通过当前 host 去掉第一段 subdomain 得到,例如从 imgtrans.13gxg.heiyu.space 得到 13gxg.heiyu.space
  • redirect 保留用户原始访问地址和 query,并用 encodeURIComponent 传给 gate。服务 ready 后,平台会跳回这个地址。
  • 前端静态文件继续使用 routes 暴露,应用自己的 API 使用 upstreams 转发。startup gate 只负责 ready/loading,不替代业务路由。

对应的 AIApp resource 中可以声明依赖,平台会把这些依赖纳入 ready 判断:

yml
shortcut:
  disable: false
depends_on_others:
  - trans
  - ocr

指定某一个算力舱上的服务

在具有多个算力舱的情况下,可以通过 f-{算力舱的序列号}-{服务名称}-ai.{微服名称}.heiyu.space 来访问。例如:

text
https://f-1420225016421-dozzle-ai.catdog.heiyu.space