Onyx 技能开发实战:用 gslides_api.py 通过 Google Slides API 读取与编辑演示文稿
2026/9/10 13:35:53 网站建设 项目流程

Onyx 技能开发实战:用 gslides_api.py 通过 Google Slides API 读取与编辑演示文稿

【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer

Google Slides 是 Onyx(原 danswer)内置 Google Drive 技能中的三大办公文档能力之一。本指南以 slides.md 为核心,围绕配套的 gslides_api.py 命令行封装器,系统讲解如何读取演示文稿结构、按objectId精确定位页面元素、插入与替换文本、新建演示文稿,并通过原始batchUpdate请求实现任意高级编辑。读完本文,你将掌握这套面向 LLM/Agent 的 Slides 操作工具的完整命令集、参数语义与输出契约,能够在 Onyx Craft 沙箱中直接驱动真实演示文稿的读写。

一、定位:Slides API 与 Drive 导出的分工

在 Onyx 的 Google Drive 技能中,drive.md 提供的gdrive_api.py read <file_id>会把 Google 原生文档自动导出为文本:Docs 导出为 Markdown、Sheets 导出为 CSV、Slides 导出为纯文本。这种导出适合快速浏览内容,但无法满足编辑需求——纯文本中没有页(slide)与元素(shape、table、image)的结构信息。

gslides_api.py正是为此而生:它直接调用https://slides.googleapis.com/v1/(Slides REST API),暴露每个 slide 和 page element 的objectId,而这些objectId正是后续所有编辑操作(插文本、替换文本、batchUpdate)的寻址依据。这一点在 slides.md 开头有明确说明,也是它与gdrive_api.py read的核心分工:

  • 只想读内容gdrive_api.py read(导出纯文本,简单直接);
  • 需要结构、objectId 或任何编辑能力→ 本指南的gslides_api.py

配套的另外两个 per-API 指南提供了同类能力:Google Docs 的get-doc/insert-text/batch-update见 docs.md,Google Sheets 的读写封装见 sheets.md。三者遵循完全一致的调用约定与输出风格,掌握其一即可触类旁通。

注意:<presentation_id>就是演示文稿在 Drive 中的文件 id(Drive file id),与 Docs 的<document_id>、Sheets 的<spreadsheet_id>语义一致。id 参数同样接受完整的 Drive/URL 链接——封装器会为你自动提取 id(参见gdrive_api.py的约定)。

二、调用约定:路径、认证与写操作审批

2.1 运行方式

按 SKILL.md.template 的路径约定,在会话工作区中运行:

python .opencode/skills/google-drive/gslides_api.py <command> [args]

在仓库中的实际位置为backend/onyx/skills/builtin/google-drive/gslides_api.py,开发调试时可直接运行:

python backend/onyx/skills/builtin/google-drive/gslides_api.py <command> [args]

任意子命令都可用python gslides_api.py <command> -h查看该命令的具体参数(源码中通过argparseadd_subparsers(dest="cmd", required=True)构建了完整的子命令解析器,见 gslides_api.py)。

2.2 认证与审批机制

封装器自身不处理任何认证。脚本头部的模块 docstring 明确写道:沙箱 egress proxy 会向请求注入当前已连接用户的 bearer token("the sandbox egress proxy injects the connected user's bearer token")。也就是说,所有请求都以当前会话用户的身份发出,遵循该用户对目标演示文稿的访问权限。

读写权限有明显差异:读操作(get/text/page)自由执行;写操作(create、add-slide、insert-text、replace-text、batch-update)可能在 proxy 处暂停等待用户批准。因此在 Agent 工作流中,写操作应视为"可能触发人工确认"的步骤来设计。

2.3 底层请求实现

从源码看,所有命令最终都收敛到同一个_req_json函数(gslides_api.py):

  • 基础地址固定为_BASE = "https://slides.googleapis.com/v1/"
  • 请求超时统一为_HTTP_TIMEOUT_SECONDS = 180秒;
  • 路径段通过_seg做 URL 编码(urllib.parse.quote(value, safe="")),因为演示文稿 id 可能包含特殊字符;
  • 参数中的None值会被过滤,GET 用查询串拼接,POST 用 JSON 序列化 body 并附带Content-Type: application/json; charset=utf-8

所有写命令最终都调用_batch_update(presentation_id, requests),即向presentations/{id}:batchUpdate发送{"requests": [...]}(gslides_api.py)。

三、读取演示文稿:get / text / page

读取命令有三个,覆盖"结构"、"纯文本"、"单页详情"三种粒度:

python gslides_api.py get <presentation_id> [--fields ...] python gslides_api.py text <presentation_id> python gslides_api.py page <presentation_id> <page_object_id>

3.1get:紧凑结构视图

返回演示文稿的紧凑结构视图:包含幻灯片列表、每个元素的objectId、形状文本等内容,足够支撑后续定位编辑,又不会携带庞大的样式/变换(transform)载荷。

默认字段选择器定义在源码的_DEFAULT_GET_FIELDS(gslides_api.py):

presentationId,title,revisionId, slides(objectId,pageElements(objectId,title, shape(shapeType,placeholder(type,index), text(textElements(textRun(content)))), table(rows,columns),image(contentUrl)))

可以看到默认视图刻意精选了:

  • 顶层:presentationIdtitlerevisionId
  • 每页 slide:自身的objectId
  • 每个 pageElement:objectIdtitle
  • 形状(shape):shapeType(如RECTANGLETEXT_BOX)、placeholdertype/index(区分标题、正文、页码等占位符)、text的文本运行内容;
  • 表格(table):rows/columns
  • 图片(image):contentUrl

--fields可覆盖默认值。传入空字符串''表示返回整个演示文稿的完整结构(不裁剪字段)。这种字段选择器语法是 Slides API 的原生能力,可以按需扩展到任意结构。

3.2text:逐页纯文本

返回每页的纯文本。实现上使用_TEXT_FIELDS = "title,slides(objectId,pageElements)"拉取完整的pageElements,再由_element_text递归提取文本(gslides_api.py):

  • 形状文本:shape.text下所有textRun.content拼接;
  • 表格单元格:遍历table.tableRows下每个tableCellstext
  • 组合元素:递归处理elementGroup.children

之所以这里要拉完整pageElements,是因为 Slides 的 fields 语法无法表达"任意深度递归"的分组元素;而较大的载荷在提取文本后被丢弃,不会进入 LLM 上下文(源码注释:"Only the extracted text is emitted, so the larger payload never reaches the LLM")。

3.3page:单页全量结构

page_object_id返回某一页的完整结构,默认不加字段裁剪(--fields可指定)。当需要精确定位某页内的元素、查看该页全部样式与变换信息时使用。

3.4 输出示例

get返回{"ok": true, "presentation": {...}}page返回{"ok": true, "page": {...}}text返回:

{"ok": true, "title": "...", "slides": [{"index": 0, "objectId": "...", "text": "..."}]}

四、创建与扩展演示文稿:create / add-slide

写操作从零开始建 deck 或追加新页:

python gslides_api.py create --title "Roadmap" python gslides_api.py add-slide <presentation_id> [--layout TITLE_AND_BODY]

4.1create:新建演示文稿

--title为必填参数。底层调用POST presentations,body 为{"title": a.title}(gslides_api.py)。返回{"ok": true, "presentation": {...}},其中包含新演示文稿的presentationId供后续命令引用。

4.2add-slide:按预设布局追加页面

--layout指定 Slides API 的预定义布局(predefinedLayout),默认值为BLANK。源码 help 中列出的常用取值(gslides_api.py):

布局取值说明
BLANK空白页(默认)
TITLE仅标题
TITLE_AND_BODY标题 + 正文
SECTION_HEADER章节标题页
...其余取值取决于所用模板

底层实现构造单个createSlide请求:

{"createSlide": {"slideLayoutReference": {"predefinedLayout": "TITLE_AND_BODY"}}}

并通过_batch_update发送。返回中会从replies[0].createSlide.objectId提取新页的objectId

{"ok": true, "objectId": "...", "data": {...}}

拿到新页objectId后,即可配合insert-textbatch-update填充该页内容。

五、文本编辑:insert-text / replace-text

python gslides_api.py insert-text <presentation_id> <shape_object_id> --text "..." python gslides_api.py replace-text <presentation_id> --find "{{name}}" --replace "Ada"

5.1insert-text:向指定形状插入文本

insert-text需要形状的objectId(从get的结果中获取)。参数与底层请求:

  • 位置参数:<presentation_id><object_id>(目标形状 objectId);
  • --text:必填,要插入的文本;
  • --index:可选,字符插入位置,默认 0(即从形状文本开头插入),type=int

底层构造的请求为(gslides_api.py):

{"insertText": {"objectId": "<shape_object_id>", "insertionIndex": 0, "text": "..."}}

注意与 Google Docs 的insert-text(按文档字符 index 定位)不同,Slides 的插入是"目标形状 objectId + 形状内字符索引"二维寻址——这正是get先取结构的原因。

5.2replace-text:全篇替换占位符

replace-text填充占位符文本的可靠方式:它使用 Slides API 的replaceAllText,一次性替换整个演示文稿中的每一处匹配,而不是只改一处。

  • --find/--replace:均为必填;
  • --match-case:可选开关,开启后大小写敏感匹配,默认大小写不敏感

底层请求为(gslides_api.py):

{"replaceAllText": { "containsText": {"text": "{{name}}", "matchCase": false}, "replaceText": "Ada" }}

典型的模板化场景:create建页 →insert-textadd-slide铺好带{{name}}{{date}}占位符的形状 → 多次replace-text一次性填充全部占位符,完成整份演示文稿的批量生成。

六、原始 batchUpdate:万能逃生舱

python gslides_api.py batch-update <presentation_id> '[<request>, ...]' python gslides_api.py batch-update <presentation_id> --file requests.json

6.1 请求载荷规则

batch-update接受一个JSON 数组(数组中的每个元素是一个 Slides API 请求对象),随{"requests": [...]}发送到presentations/{id}:batchUpdate端点。载荷可以内联传入,也可以放在文件中用--file指定——二者只能二选一,同时给出会报错(源码_load_json_arg明确校验,gslides_api.py);若都不是合法 JSON 数组,会返回requests must be a JSON array of request objects错误。

6.2 支持的请求类型

原文档列出的常用请求类型包括(slides.md 与源码 help 相互印证):

  • createShape—— 新建形状;
  • createTable—— 新建表格;
  • updateTextStyle—— 修改文本样式(字号、加粗、颜色等);
  • deleteObject—— 删除页面元素;
  • updatePageElementTransform—— 调整元素位置/缩放/旋转;
  • 此外还有createSlideinsertText(同前文)、replaceAllTextcreateSheets(新建幻灯片组)等完整 Slides 请求集。

由于batch-update直接透传原生请求对象,凡是 Slides API 支持的操作都可以在此表达,因此它是覆盖"其余一切"的逃生舱(escape hatch)。

6.3 组合示例

内联传多个请求,例如先建一个文本框再写入文字:

python gslides_api.py batch-update <presentation_id> '[ {"createShape": {"objectId": "TextBox1", "shapeType": "TEXT_BOX", "elementProperties": {"pageObjectId": "<page_object_id>", "size": {"height": {"magnitude": 3000000, "unit": "EMU"}, "width": {"magnitude": 3000000, "unit": "EMU"}}, "transform": {"scaleX": 1, "scaleY": 1, "translateX": 100000, "translateY": 100000, "unit": "EMU"}}}}, {"insertText": {"objectId": "TextBox1", "insertionIndex": 0, "text": "Hello from Onyx"}} ]'

更复杂的请求集合建议写入requests.json文件后用--file传入,便于复用与版本管理。

七、输出契约与错误处理

7.1 统一的 JSON 输出

所有命令都在 stdout 输出 JSON:

命令返回结构
create{"ok": true, "presentation": {...}}
get{"ok": true, "presentation": {...}}
page{"ok": true, "page": {...}}
text{"ok": true, "title": "...", "slides": [{"index", "objectId", "text"}]}
add-slide{"ok": true, "objectId": "...", "data": {...}}
insert-text/replace-text/batch-update{"ok": true, "data": {...}}

7.2 空字段裁剪与--raw

默认情况下,输出会经过_prune递归删除None/""/[]/{}等空值(gslides_api.py),让 LLM 面对的输出尽量精简——注意布尔值与 0 会被保留(源码注释:"Booleans and 0 are kept — they carry signal")。传入--raw可跳过裁剪,拿到完整原始响应。

7.3 退出码与错误信息

错误统一输出到 stderr 并返回非零退出码(main函数中的异常处理,gslides_api.py):

  • 退出码2--file指向的文件不存在(FileNotFoundError);
  • 退出码1:参数校验错误(ValueError)、JSON 解析失败、HTTPError(打印HTTP <code> calling Google Slides: <detail>,包含 Google 返回的错误详情)、网络错误(URLError,打印network error calling Google Slides: ...)。

一次成功的调用返回{"ok": true, ...}并退出0。Agent 编排时可根据退出码与 stderr 内容区分"参数错误 / 文件缺失 / HTTP 服务端错误 / 网络错误",从而决定重试还是向用户求助。

7.4 一个常见的 404 陷阱

结合 SKILL.md.template 的云部署说明:当部署的 Google Drive 授权为每文件粒度(drive.file)时,gdrive_api.pysearch/read只能看到 Onyx 自己创建的文件;对权限外的文件,Drive API 会返回HTTP 404 "File not found"(而非 403)。但Docs、Sheets、Slides API 不受 per-file 授权限制——它们可以访问用户能打开的任何同类型文件。因此:拿到一个 Slides 链接或 id 时,即使gdrive_api.py read报 404,也应直接尝试gslides_api.py get/text,很可能依然可用;而空search结果也不代表文件不存在,可能只是超出授权范围。这正是"per-API 指南"存在的意义。

八、端到端实战流程

把以上命令串起来,一个"由 Agent 自动生成演示文稿"的典型工作流如下:

Step 1 —— 新建演示文稿

python gslides_api.py create --title "Q3 Roadmap"

记录返回中的presentationId

Step 2 —— 追加标题页与正文页

python gslides_api.py add-slide <presentation_id> --layout TITLE_AND_BODY python gslides_api.py add-slide <presentation_id> --layout TITLE_AND_BODY

记录每次返回的objectId(分别为 slide A、slide B 的 id)。

Step 3 —— 查看结构,取得形状 objectId

python gslides_api.py get <presentation_id>

在返回的slides[].pageElements[].objectId中挑出标题 shape 与正文 shape(可通过placeholder.type区分,如TITLE/BODY)。

Step 4 —— 填充占位符文本

python gslides_api.py replace-text <presentation_id> --find "{{title}}" --replace "Q3 产品路线图" python gslides_api.py replace-text <presentation_id> --find "{{milestone}}" --replace "发布 2.0"

Step 5 —— 复杂元素用 batchUpdate 兜底

需要插入表格、调整元素位置或删除某个占位元素时,构造请求数组交给batch-update

Step 6 —— 校验结果

text快速核验各页文本是否符合预期,必要时用get复查结构。

九、相关资源

  • 技能总览与路径/权限约定:SKILL.md.template
  • Slides 封装器源码:gslides_api.py
  • 本文档(原始版):slides.md
  • Drive 文件读写与纯文本导出(gdrive_api.py read):drive.md
  • Google Docs 结构编辑(按字符索引操作):docs.md
  • Google Sheets 单元格读写:sheets.md(gsheets_api.py)

【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询