在INERIT博客中接入文章生成工作流时,我也希望能同步生成代表图片。如果正文完成后,还需单独制作并上传缩略图,那么最后一步仍离不开人工操作。由于博客文章以多种语言提供,图片中的文字也需要对应调整语言。
此前已在使用的Codex已具备图像生成功能。于是我产生了一个疑问:该功能是否也能在博客服务器端调用?

ChatGPT Pro商品说明界面。本文中实现的调用路径为Codex CLI。
最终,我将通过ChatGPT账户登录的Codex CLI与HTTP服务器部署于同一容器中。当博客发起图像生成请求时,由worker启动CLI并接收生成的文件,再上传至博客存储库。从博客端来看,形式上是在调用图像生成API,但实际在其内部执行的是已登录状态的CLI。**此图像生成任务不消耗独立的Image API额度,而是计入Codex订阅用量。**核心在于实现了让此前已可用的功能通过博客进行调用。
本文围绕建立上述连接时所确认的行为,以及为避免因缩略图而重做整篇文本所进行的改造过程展开说明。所验证的运行版本为 Codex CLI 0.164.0、Node.js 24,验证日期为2026年11月5日。
首先确认的是登录方式与实际生成的图像文件
Codex支持两种认证方式:通过ChatGPT订阅登录,或使用API密钥认证。本次实现采用前者。传递给博客的密钥是用于访问自行搭建的HTTP服务器的应用密钥,而Codex的登录凭证则在服务器内部单独管理。即两种认证机制被分离使用。官方认证文档
官方文档说明,内置图像生成功能包含在通用Codex使用额度之内。此外,codex exec可复用已保存的CLI认证状态,--json则用于导出执行事件。将这两项功能结合,我们判断可通过服务器进行处理。图像生成文档,非交互式执行文档
但仅确认命令可执行,并不足以认定图像生成功能可用。还需验证在服务器的非交互式执行模式下,是否确实能调用内置图像工具,并生成实际的结果文件。
在当前运行的版本中,通过该路径可获得PNG文件。worker从codex exec --json的thread.started事件中提取执行ID,再从该执行对应的CODEX_HOME/generated_images/<thread-id>目录中收集结果。
在此过程中,我们未将模型最后回答内容作为成功判定条件。因为仅凭“已生成图像”这样的文本说明,或进程退出代码,无法保证存在可供博客使用的图像文件。**只有当对应执行的PNG文件存在,且文件类型与大小校验均通过时,才视为图像生成任务成功。**若需转换为指定尺寸,还需实际执行图像解码操作。
generated_images中的路径与事件处理方式,是基于当前运行版本的实现所验证的行为。这也是锁定CLI版本的原因之一。
在CLI前添加了轻量级任务API
我们未让博客直接处理CLI输出或登录文件,而是在Node.js服务器中仅定义所需请求,并将CLI调用逻辑封装在服务器内部。
调用流程如下:
블로그 워커 → 내부 HTTP API → Codex CLI → PNG 검증 → 블로그의 WebP 저장
图像API与CLI运行于同一容器中。原因在于便于回收生成的文件,同时也可一并固定CLI版本与执行环境。向博客仅暴露必要字段,如提示词、分辨率、任务状态等。
请求格式按以下规范确定。下列路径与参数均属于自建封装器的规格。
POST /images HTTP/1.1
Authorization: Bearer <internal-app-key>
Idempotency-Key: <saved-request-id>
Content-Type: application/json
{
"prompt": "블로그 썸네일. 터미널에서 이미지 작업이 실행되는 장면. 큰 한글 제목은 'Codex로 썸네일 자동화'.",
"size": "1920x1080",
"effort": "medium"
}服务器不在图像生成完成前维持该连接,而是返回HTTP 202状态码及任务ID。博客通过GET /images/{id}轮询任务状态,成功后再通过GET /images/{id}/file获取PNG文件。状态查询与下载均需使用相同的应用认证。
我们也实现了返回同步响应的兼容路径,但博客端仍采用获取任务ID的方式。原因在于,即便生成耗时较长或博客进程重启,也必须能够识别此前正在等待的任务。
在CLI执行方面,我们使用了Node.js的spawn()。命令与选项通过参数数组传递,并通过shell: false执行;图像描述内容则经由标准输入传入。并未将提示词直接拼接到shell命令字符串中。
同时,我们将worker的环境变量限制为必要项。不将博客的应用密钥或其他独立的OpenAI API密钥直接继承至CLI子进程,并禁用了图像生成所不需要的shell、应用与插件工具。该worker的唯一任务,是通过内置工具生成一张图像。
缩略图失败不应导致全文重写
相较于生成单张图像,我们更关注失败后的处理。
若文章与正文图片已就绪,但在缩略图生成或存储阶段出现错误,从头重新执行会导致已完成任务被重复。新生成的正文内容可能与之前不同,图像生成也可能产生重复。
因此,我们在博客缩略图阶段保留了以下信息:
- 已确定的图像提示词与参考素材
- 发送至图像服务器的请求ID
- 图像服务器返回的任务ID
- 生成后尚未上传至存储库的图像字节数据
根据失败位置不同,后续接续任务也不同。若图像服务器已接收任务,但在收到响应前连接中断,则使用相同请求ID查找已接收的任务。若已获取PNG,仅存储库上传失败,则使用已缓存的字节数据重试上传。
此时使用的是Idempotency-Key。同一应用以相同密钥、相同请求内容再次发送时,返回已有任务;若以相同密钥发送不同内容,则返回409状态码。
但仅凭此尚不足以完成恢复。因为在服务器重启期间,若图像尺寸或提示词配置发生变化,博客重新构建的请求可能与最初发送的内容不同。即使已有已接收的任务,重新请求仍可能引发冲突。
因此,我们又新增了通过请求键查询任务的端点。
GET /images?request_id=<saved-request-id>
Authorization: Bearer <internal-app-key>博客即便没有已保存的任务ID,也会先调用此端点进行检查。若已有对应任务,则继续获取其结果;仅当无对应任务时才发起新请求。这相当于将“基于当前配置重建并重新发送旧请求”与“查找原请求本身”区分开来。
当然,图像worker进程自身终止的情况则不同。处于等待状态的任务会被恢复,但执行中中断的任务则以interrupted状态终止。我们并不保证已终止的CLI执行能够无缝续接生成。即使需要新的图像生成任务,博客正文及此前阶段的结果仍得以保留。
分辨率与参考素材是自建API的约定
除提示词外,还需确定哪些参数应纳入请求体。博客中无需为单篇文章批量生成多张图像,因此限制一次请求仅生成一张图像。
| 参数 | 在本封装器中的含义 |
|---|---|
prompt | 图像内容与构图 |
size | 最终返回文件的宽度与高度 |
effort | Codex的推理强度设置 |
reference_images | 必要时附带的参考图像 |
transparent_background | 透明背景请求 |
特别是size与effort,仅看名称容易误解其含义。
size: "1920x1080"表示返回PNG文件的尺寸。我们首先要求按该比例生成,但内置工具可能返回不同尺寸,因此由服务器对最终尺寸进行调整。若原始比例不符,则以中心为基准裁剪并缩放,边缘处的文字或主体可能被裁切。因此,生成提示词中需强调安全边距,而在检查缩略图时,除尺寸外还须关注构图。此设置并不保证模型以原生分辨率生成。
effort对接了Codex的model_reasoning_effort。我们未将其等同于图像API中的质量等级参数。
参考素材为可选项。当实际产品或界面形态至关重要时,它能起到辅助作用;但对于抽象的开发主题,无需强行附加无关照片。博客端需查看候选图像的实际内容后决定是否采用,并仅将选定的文件传递给worker。
服务器不接收远程图像URL或任意文件路径,而是接收大小受限的PNG、JPEG、WebP字节数据并进行验证。随后将其存入任务目录,并作为CLI的--image输入及内置工具的参考路径进行传递。当前实现限制最多5张。该数值是根据当前运行的Codex内置工具设定的限制,并未假设其与官方Image API的输入上限一致。
最终生成的PNG由博客转换为WebP格式并存储至对象存储。当前转换质量为85,若长边超过3840像素则进行缩放。读者浏览器加载的是此存储后的图像,因此每次打开页面时无需调用Codex。
在Kubernetes环境中保留登录信息与任务状态
仅在容器中安装CLI,无法维持订阅登录状态。若Pod重建时认证信息丢失,图像生成也将暂停直至重新登录。
我们在服务器上按账号部署CODEX_HOME,并存放于持久卷中。在无浏览器的服务器上进行登录时,采用设备码认证方式。
codex login --device-auth用户通过本地浏览器打开服务器显示的URL并输入代码后,登录结果将保存至对应的CLI环境。由于登录文件包含凭证信息,因此不纳入容器镜像或Git仓库。官方认证文档也对此方式及文件处理标准进行了说明。无头环境下的登录
提供给应用的密钥与该登录文件的生命周期不同。更换博客调用密钥无需重新登录Codex账户。我们在图像服务器上存储应用密钥的哈希值,并基于请求所有者对任务与文件访问进行隔离。
当前图像服务器为使用基于文件队列的单一实例。Kubernetes部署同样采用单副本及Recreate策略,并通过文件锁防止两个进程同时操作同一数据目录。此部署在更新过程中存在短暂中断。我们并未认为仅增加HTTP服务器副本即可安全扩展。
对并发执行数与等待队列也设置了上限。若触达使用限制,则进入等待状态,并限制生成超时时间与重试次数。当安全系统阻止构图时,会与常规临时错误区分返回。
接入博客后发生的变化
目前,博客根据文章标题与正文生成缩略图指令,必要时选择参考素材,再调用此内部API。将生成的图像转换为WebP并保存后,即与文章关联;若出现错误,则保留缩略图阶段的状态信息。至此,无需重写正文即可继续图像生成任务。
在验证实现过程中,我们还运行了封装器的14项自动化测试。测试覆盖重复请求处理、重启后队列恢复、执行中断状态、按请求所有者隔离的文件访问、参考素材输入限制、真实PNG采集与尺寸转换等环节。这些测试通过将生成器替换为模拟对象来验证服务器行为,并非所有测试都会实际向OpenAI发起图像生成。
订阅额度仍是重要的运维条件。官方文档说明,内置图像生成可能比不生成图像的类似任务平均快3至5倍消耗额度。因此,提升吞吐量需同时关注生成时间与剩余额度。官方推荐的程序化图像生成路径为Image API;本文所述内容是为博客自建配置的CLI封装。图像生成与使用额度
实现之后才了解到的API额度权益
在整理本文过程中,我得知满足条件的Pro订阅用户可关联自有API组织,从而获得API额度权益。该额度也可用于官方Images API。使用此权益需满足API组织关联与激活等条件。订阅用户的API额度指南
新发现的这一选项与本次实现所消耗的额度类型不同。若使用API额度调用官方Image API,则从该额度中扣减。而我所构建的封装器,调用的是已登录Codex的内置工具,因此不消耗独立的Image API额度。
借助此次配置,**在API额度余额之外,我们得以将已拥有的Codex额度用于博客图像生成。**明确官方API与订阅登录方式消耗的是不同额度后,我们便可根据任务特性选择另一条路径。
同样可用于翻译这类可等待的任务
INERIT博客的缩略图无需在读者打开页面的瞬间生成。只需在发布准备阶段提交任务并保存结果即可。相较于需要即时响应的聊天服务,此类任务对生成顺序具备一定容错空间。
多语言翻译也可按相同思路扩展。将原文与目标语言加入任务队列,对CLI返回的标题与正文进行校验后,再纳入译文。需检查代码块、链接、引文及数值是否完整保留,并仅对失败语言重新处理,从而避免重做整篇原文。将本路径应用于翻译,仍是尚未实施的后续验证任务。
不过,将任务迁移至CLI,并不意味着翻译所需的token总量减少。若此前是通过付费API处理,将其中一部分迁移至订阅额度使用,则存在降低独立API侧计费用量与成本的可能性。但相应地,需承担Codex额度消耗、等待时间、结果校验及重试运维负担。目前尚未测量博客翻译在token数量或成本上的实际节省效果。
本次实际接入的是缩略图功能。博客通过内部API发起图像任务,Codex在通过订阅登录的环境中执行生成,结果文件由博客负责存储。所需并非一次快速响应,而是可等待后使用的图像,以及失败后仍能保留的任务状态。正因如此,我们得以在博客中将Codex的订阅功能以API方式调用。




