LobeHub CLIlh generate详解:文本、图像、视频与语音生成命令及服务端异步任务机制
【免费下载链接】lobehub🤯 LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.项目地址: https://gitcode.com/GitHub_Trending/lo/lobehub
本文基于 LobeHub 仓库中的 CLI 参考文档.agents/skills/cli/references/generate.md及对应源码编写,系统讲解lh generate(别名lh gen)的 7 个子命令——文本补全、图像/视频生成、TTS 合成、ASR 转写、异步任务查询与下载。读完后你将掌握完整的命令行参数、典型的“提交—轮询—下载”异步工作流,以及服务端“batch + generation + asyncTask”事务模型与超时判定机制,能够在脚本和自动化流程中可靠地驱动 LobeHub 的生成能力。
命令总览
lh generate是 LobeHub CLI(lh)中统一的内容生成入口,支持文本、图像、视频、语音与转写五类能力。命令结构如下(来源:apps/cli/src/commands/generate/):
lh generate (alias: gen) ├── text <prompt> # 文本生成 ├── image <prompt> # 图像生成 ├── video <prompt> # 视频生成 ├── tts <text> # 文本转语音 ├── asr <audioFile> # 语音转文本(ASR) ├── download <generationId> <asyncTaskId> # 等待并下载生成结果 ├── status <generationId> <asyncTaskId> # 查询异步任务状态 └── list # 列出生成主题(topics)重要:
status和download需要asyncTaskId(UUID 格式,例如7ad0eb13-e9a5-4403-8070-1f7fe95b2f95),而不是generation ID(gen_xxx)。asyncTaskId打印在video/image命令输出的→ Task之后。传错 ID 会在服务端触发 NOT_FOUND 或 SQL 报错,CLI 会解析这类错误并给出带正确示例的提示信息(见 index.ts 中的 parseGenStatusError)。
从源码看,registerGenerateCommand在 apps/cli/src/commands/generate/index.ts 中注册generate命令与gen别名,并依次挂载text、image、video、tts、asr五个子命令,另外还在同一文件中直接定义了status、download、delete、list四个子命令。命令注册代码配有测试 generate.test.ts。
lh generate text <prompt>:文本生成
生成一次 LLM 文本补全(单轮 completion,不带工具调用)。源码:text.ts。
lh gen text "Explain quantum computing" [options] echo "context" | lh gen text "summarize" --pipe| 选项 | 说明 | 默认值 |
|---|---|---|
-m, --model <model> | 模型 ID | openai/gpt-4o-mini |
-p, --provider <provider> | Provider 名称 | 从 model 前缀推导 |
-s, --system <prompt> | 系统提示词 | - |
--temperature <n> | 温度(0-2) | - |
--max-tokens <n> | 最大输出 token 数 | - |
--stream | 启用流式输出 | false |
--json | 输出完整 JSON 响应 | false |
--pipe | 从 stdin 读取附加上下文 | false |
Pipe 模式
使用--pipe时,命令会读取 stdin 内容并拼接到 prompt 之后(prompt 在前,stdin 内容在后,以空行分隔)。适合把文件内容直接喂给模型:
cat README.md | lh gen text "summarize this" --pipe源码层面的实现细节
- Provider 推导:从源码看,若未显式传
-p,CLI 会将 model 按/拆分,取第一段作为 provider(如openai/gpt-4o-mini→ provideropenai、modelgpt-4o-mini);若 model 不含/则回退到openai。 - 请求通道:
text不走 tRPC,而是通过 getAuthInfo 拿到服务端地址与鉴权头后,直接 POST 到/webapi/chat/{provider}。非流式请求会显式携带responseMode: 'json',因为后端默认把非流式请求转成 SSE,这里要求返回纯 JSON 响应。 - 响应解析:非流式输出兼容两种格式——OpenAI 的
choices[0].message.content与 Anthropic 的content[0].text,取不到时回退为打印原始 JSON。 - 流式输出:
--stream时逐行解析 SSE,识别data:行与[DONE]结束标记;既处理 LobeHub 以 JSON 字符串形式下发的内容块(如"Hello"),也处理标准 OpenAIchoices[0].delta.content格式,--json模式下每行输出一个解析后的 JSON 事件。
lh generate image <prompt>:图像生成
文本生图。这是一个异步操作——命令只负责提交任务并返回 generation ID 与 async task ID,供后续追踪。源码:image.ts。
lh gen image "A sunset over mountains" [options] lh gen image "A cute cat" --model dall-e-3 --provider openai --json| 选项 | 说明 | 默认值 |
|---|---|---|
-m, --model <model> | 模型 ID | dall-e-3 |
-p, --provider <provider> | Provider 名称 | openai |
-n, --num <n> | 生成图片数量 | 1 |
--width <px> | 宽度(像素) | - |
--height <px> | 高度(像素) | - |
--steps <n> | 采样步数 | - |
--seed <n> | 随机种子 | - |
--json | 输出原始 JSON | false |
非 JSON 输出示例:
✓ Image generation started Batch ID: gb_xxx 1 image(s) queued Generation gen_xxx → Task 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 这就是 asyncTaskId —— 后续 status/download 用它 Use "lh generate status <generationId> <asyncTaskId>" to check progress.典型工作流:
# 1. 提交生成 —— 记下输出中的两个 ID lh gen image "A cute cat" # Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 # 2. 用 generationId + asyncTaskId(UUID)等待并下载 lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o cat.png源码佐证
从 image.ts 的执行顺序看,CLI 先调用generationTopic.createTopic(type 为image)创建生成主题,再调用image.createImage,把width/height/steps/seed解析为数字后放入params透传给服务端。imageNum决定了输出中image(s) queued的数量——每张图对应一条 generation 记录和一条 asyncTask。
lh generate video <prompt>:视频生成
文本(或图像)生成视频,同样是异步操作。源码:video.ts。
lh gen video "A cat playing piano" -m <model> -p <provider> [options]| 选项 | 说明 | 是否必需 |
|---|---|---|
-m, --model <model> | 模型 ID | 是 |
-p, --provider <provider> | Provider 名称 | 是 |
--aspect-ratio <ratio> | 宽高比(如 16:9) | 否 |
--duration <sec> | 时长(秒) | 否 |
--resolution <res> | 分辨率(如 720p) | 否 |
--seed <n> | 随机种子 | 否 |
--json | 输出原始 JSON | 否 |
注意:与 image 不同,video 没有默认模型,-m和-p为requiredOption(源码中通过.requiredOption()声明,缺失时 commander 直接报错)。可用lh model list <provider> --type video查询该 provider 的可用视频模型。
非 JSON 输出示例:
✓ Video generation started Batch ID: gb_xxx Generation gen_xxx → Task 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 这就是 asyncTaskId —— 后续 status/download 用它 Use "lh generate status <generationId> <asyncTaskId>" to check progress.典型工作流:
# 1. 查询 provider 可用的视频模型 lh model list volcengine --json | grep -i seedance # 2. 提交生成 —— 记下输出中的两个 ID lh gen video "A cat on a runway" -m doubao-seedance-2-0-260128 -p volcengine \ --aspect-ratio 9:16 --duration 5 --resolution 1080p # Generation gen_abc123 → Task 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 # 3. 用 generationId + asyncTaskId(UUID)等待并下载 lh gen download gen_abc123 7ad0eb13-e9a5-4403-8070-1f7fe95b2f95 -o result.mp4 --timeout 600文档之外:源码中的图生视频选项
参考文档只列出了文生视频选项,但从 video.ts 的源码可以看到,CLI 实际还支持以下图像参考选项,服务端入参 schema(video/index.ts)也接受这些字段:
| 选项 | 说明 | 映射到 params 的字段 |
|---|---|---|
--image <url> | 首帧图像 URL(图生视频) | imageUrl |
--images <urls...> | 多个参考图 URL | imageUrls |
--end-image <url> | 尾帧图像 URL | endImageUrl |
lh generate tts <text>:文本转语音
TTS 合成。源码:tts.ts。
lh gen tts "Hello, world!" [options]参考文档未展开 TTS 选项,以下是从 tts.ts 源码整理的完整选项表:
| 选项 | 说明 | 默认值 |
|---|---|---|
-o, --output <file> | 输出音频文件路径 | output.mp3 |
--voice <voice> | 声音名称 | alloy |
--speed <n> | 语速倍率(0.25-4.0) | 1 |
--model <model> | TTS 模型 | tts-1 |
实现上,tts与text类似,不走 tRPC,而是 POST 到/webapi/tts/openai,payload 中同时携带顶层model/voice/speed与嵌套的options: { model, voice }(兼容服务端两种读取方式)。响应体直接是音频字节流,CLI 写入本地文件并打印保存路径与大小(KB)。
lh generate asr <audioFile>:语音转文本
ASR(Automatic Speech Recognition)转写。源码:asr.ts。
lh gen asr recording.wav [options]参考文档未展开 ASR 选项,以下是从 asr.ts 源码整理的完整说明:
| 选项 | 说明 | 默认值 |
|---|---|---|
--model <model> | STT 模型 | whisper-1 |
--provider <provider> | AI provider | openai |
--language <lang> | 语言代码(如 en、zh) | - |
--json | 输出原始 JSON | false |
源码中有几个值得注意的实现细节:
- 输入既可以是本地文件路径,也可以是
http(s)://URL。URL 输入会先下载到临时文件(lh-asr-<pid>-<ts>-<name>),转写完成后在finally中清理。 - 3MB 内联阈值:小于等于 3MB(
MAX_INLINE_AUDIO_BYTES = 3 * 1024 * 1024)的音频以 base64 内联方式(audioBase64 + fileName + mimeType)通过 tRPCasr.transcribe提交;更大的音频会先通过uploadLocalFile上传到文件存储,再以fileId提交转写,避免大字节流穿过 tRPC。注释说明该阈值与服务端内联上限(apps/server/src/routers/lambda/asr.ts)保持一致。 - URL 下载的 MIME 处理:
fetchAudioFromUrl会从Content-Disposition(兼容 RFC 5987 扩展形式)或 URL 路径推断文件名;当签名 URL 路径中没有扩展名时,会按Content-Type映射表补上转写服务能识别的扩展名(audio/mp4 → m4a、audio/opus → ogg等)。 - 默认输出为纯文本(
result.text),--json时输出完整 JSON。
lh generate download <generationId> <asyncTaskId>:等待并下载
等待异步生成任务完成后下载结果文件。源码:index.ts。
<asyncTaskId>是 image/video 输出中→ Task之后的 UUID。不要把 generation ID(gen_xxx)传到这里——会触发服务端错误。
lh gen download <generationId> <asyncTaskId> [-o output.png] lh gen download gen_xxx 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx -o ~/Desktop/result.mp4 --timeout 600| 选项 | 说明 | 默认值 |
|---|---|---|
-o, --output <path> | 输出文件路径(自动识别扩展名) | <generationId>.<ext> |
--interval <sec> | 轮询间隔(秒) | 5 |
--timeout <sec> | 超时时间(秒,0 = 不超时) | 300 |
行为(与 index.ts 中的轮询循环逐条对应):
- 按指定间隔轮询
generation.getGenerationStatus; - 显示实时进度:
⋯ Status: processing... (42s); - 成功后从
generation.asset.url下载文件到本地,并打印保存路径与大小;若 asset 含缩略图 URL 也会一并打印; - 出错或 ID 传错时,通过
parseGenStatusError打印指向正确 ID 格式的具体提示(例如“第二个参数必须是 asyncTaskId —— 即 video/image 输出中→ Task后面的 UUID”),并以非零码退出; - 超时后提示稍后使用
lh gen status继续检查。
默认输出文件名从 asset URL 的路径部分推断扩展名(去掉?查询串后取最后一段),无法识别时回退为bin。
lh generate status <generationId> <asyncTaskId>:查询状态
查询异步生成任务的状态。
同样注意:第二个参数是
→ Task后的 UUID,不是gen_xxx。
lh gen status <generationId> <asyncTaskId> [--json] lh gen status gen_xxx 7ad0eb13-xxxx-xxxx-xxxx-xxxxxxxxxxxx| 选项 | 说明 |
|---|---|
--json | 输出原始 JSON 响应 |
展示内容(由colorStatus函数渲染颜色):
- 状态(带颜色):
success(绿)、error(红)、processing(黄)、pending(青); - 失败时的错误信息(
error.message或原始 JSON); - 成功时的资产 URL 与缩略图 URL(
asset.url/asset.thumbnailUrl)。
错误处理与download共用parseGenStatusError:它区分两种 NOT_FOUND——Async task not found(asyncTaskId 传错,常见于误传gen_xxx)与Generation not found(generationId 传错),以及含async_tasks字样的 INTERNAL_SERVER_ERROR(非 UUID 导致服务端 SQL 查询失败),分别给出修正用法示例。
lh generate list:列出生成主题
lh gen list [--json [fields]]表格列:ID、TITLE、TYPE、UPDATED。
从源码看,list调用generationTopic.getAllGenerationTopics,表格输出对标题截断到 40 字符,UPDATED列用相对时间(timeAgo)渲染;--json支持可选的逗号分隔字段过滤(outputJson(items, fields))。
补充:源码中还提供了delete子命令。参考文档的命令树未列出它,但 index.ts 中实现了lh gen delete <generationId>(支持--yes跳过确认),对应服务端generation.deleteGeneration。从服务端实现看(generation.ts),删除是幂等的(记录不存在时直接返回),并只删除缩略图文件、保留主文件记录。
后端架构:异步任务模型
图像与视频生成使用统一的异步任务模式,结合服务端源码(image/index.ts、video/index.ts、generation.ts)可以看到完整链路:
创建主题→
generationTopic.createTopic。CLI 在每次 image/video 提交前都会先建一个 type 为image/video的 generation topic,作为生成记录的归组容器(即lh gen list列出的对象)。提交生成→
image.createImage/video.createVideo。从 image/index.ts 源码看,服务端在一个数据库事务内完成三件事:- 插入
generationBatches记录(含 prompt、model、provider、width/height 与完整 config); - 按
imageNum批量插入generations记录,并为每条 generation 分配 seed(若请求携带seed,通过generateUniqueSeeds生成对应数量的唯一种子序列); - 为每条 generation 插入一条
asyncTasks记录(初始状态Pending),并把返回的asyncTaskId回写到 generation 上。
事务提交后,服务端 fire-and-forget 地触发后台任务:image 路由通过
createAsyncCaller创建统一的异步调用器后逐个调用asyncCaller.image.createImage(不 await);video 路由则通过initModelRuntimeFromDB初始化模型运行环境并走后台轮询(processBackgroundVideoPolling)。返回结构为{ data: { batch, generations }, success: true },每条 generation 携带自己的asyncTaskId——这正是 CLI 输出中Generation gen_xxx → Task <uuid>两列的数据来源。- 参考文档提到 image/video 路由不经过
keyVaults中间件,API key 由数据库侧读取(initModelRuntimeFromDB或createAsyncCaller内部处理)。源码中两个路由的 procedure 链均为wsCompatProcedure.use(serverDatabase)再加withScopedPermission('file:upload'),即带工作区兼容鉴权、服务端数据库与文件上传权限检查;image 路由还叠加了chargeBeforeGenerate/chargeAfterGenerate的预扣费/结算逻辑(商业计费特性)。
- 插入
轮询状态→
generation.getGenerationStatus,入参{ generationId, asyncTaskId }两者都必填,且asyncTaskId必须是async_tasks表中的 UUID 而非gen_xxx;返回{ status, error, generation },成功时generation携带资产 URL。- 查询前会先调用
AsyncTaskModel.checkTimeoutTasks(asyncTask.ts):把pending/processing超过约 5 分钟的任务标记为error。超时常量定义在 packages/business/config/src/server/route.ts:ASYNC_TASK_TIMEOUT = (60 * 5 - 2) * 1000,即298 秒(略小于 5 分钟,留 2 秒余量)。 - 服务端只返回 asyncTask 的状态;仅当状态为
success时才加载 generation 的资产信息,为error时返回结构化AsyncTaskError。这与 CLIstatus命令打印的三类信息(状态/错误/资产 URL)一一对应。
- 查询前会先调用
关键服务端路由文件:
| 文件 | 职责 |
|---|---|
| apps/server/src/routers/lambda/image/index.ts | 图像创建(事务建 batch/generations/asyncTasks + 触发异步任务) |
| apps/server/src/routers/lambda/video/index.ts | 视频创建(含参考图 URL 归一化与后台轮询) |
| apps/server/src/routers/lambda/generation.ts | 状态查询与删除 |
| packages/database/src/models/asyncTask.ts | AsyncTaskModel(含checkTimeoutTasks) |
参考文档与源码索引
| 内容 | 路径 |
|---|---|
| CLI 参考文档(本文基础) | .agents/skills/cli/references/generate.md |
| 命令注册(status/download/delete/list) | apps/cli/src/commands/generate/index.ts |
| text 子命令 | apps/cli/src/commands/generate/text.ts |
| image 子命令 | apps/cli/src/commands/generate/image.ts |
| video 子命令 | apps/cli/src/commands/generate/video.ts |
| tts 子命令 | apps/cli/src/commands/generate/tts.ts |
| asr 子命令 | apps/cli/src/commands/generate/asr.ts |
| 命令注册测试 | apps/cli/src/commands/generate.test.ts |
| 图像路由 | apps/server/src/routers/lambda/image/index.ts |
| 视频路由 | apps/server/src/routers/lambda/video/index.ts |
| 生成状态/删除路由 | apps/server/src/routers/lambda/generation.ts |
| 异步任务模型与超时判定 | packages/database/src/models/asyncTask.ts |
| 超时阈值(298s)定义 | packages/business/config/src/server/route.ts |
适用前提小结:所有生成类命令要求 CLI 已登录可访问的 LobeHub 服务端;text/tts直接走/webapi/HTTP 接口,image/video/asr/status/download走 tRPC 客户端,因此都受服务端 RBAC 权限(如file:upload)与模型 bank 可用性的约束(服务端会对不在模型 bank 中的品牌模型 ID 抛出明确的 BAD_REQUEST 错误)。异步任务的--timeout默认 300 秒、服务端 298 秒超时标记,意味着长视频任务建议显式调大--timeout(如 600),超时后仍可用lh gen status在任务真正超时标记前继续查询。
【免费下载链接】lobehub🤯 LobeHub is your Chief Agent Operator, organizing your agents into 7×24 operations by hiring, scheduling, and reporting on your entire AI team.项目地址: https://gitcode.com/GitHub_Trending/lo/lobehub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考