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查看该命令的具体参数(源码中通过argparse的add_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)))可以看到默认视图刻意精选了:
- 顶层:
presentationId、title、revisionId; - 每页 slide:自身的
objectId; - 每个 pageElement:
objectId、title; - 形状(shape):
shapeType(如RECTANGLE、TEXT_BOX)、placeholder的type/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下每个tableCells的text; - 组合元素:递归处理
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-text或batch-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-text或add-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.json6.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—— 调整元素位置/缩放/旋转;- 此外还有
createSlide、insertText(同前文)、replaceAllText、createSheets(新建幻灯片组)等完整 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.py的search/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),仅供参考