☰
RocketRide `tool_word` 节点详解:用 Microsoft Graph 与 python-docx 为 Agent 打造 OneDrive docx 读写工具
2026/9/26 2:05:30 网站建设 项目流程

【免费下载链接】rocketride-server

High-performance AI pipeline engine with a C++ core and 50+ Python-extensible nodes. Build, debug, and scale LLM workflows with 13+ model providers, 8+ vector databases, and agent orchestration, all from your IDE. Includes VS Code extension, TypeScript/Python SDKs, and Docker deployment.

项目地址:https://gitcode.com/gh_mirrors/ro/rocketride-server
点击查看免费下载

本篇技术指南聚焦 RocketRide 仓库中的tool_word节点(nodes/src/nodes/tool_microsoft_365/word/README.md),它把 Microsoft Graph drive content API 与python-docx封装成一组 Agent 可调用的工具函数:读取文档文本、从段落列表新建.docx、追加文本、查找替换、导出 PDF。读完本文,你将掌握该节点的配置方式(Entra 应用 / 用户 OAuth 两种认证)、六个word_*工具的调用语义,以及它基于If-MatcheTag 的并发安全往返机制与单遍文本替换算法,可直接在自己的 RocketRide 管道中接线使用。

节点定位:无 lane 的 Agent 工具节点

tool_word是一个典型的tool 节点——数据不经过任何 lane(数据流通道)流动,每个操作都是注册给 Agent 的@tool_function,由 Agent 按需调用(word/IInstance.py 中的装饰器即注册入口)。它操作的.docx文件位于“acting user”(执行主体用户)的 OneDrive 中。

两个关键设计约束贯穿全部工具:

  • 操作目标是调用参数,不是节点配置:文件路径 / item id、新建文档的目标路径永远由 Agent 在每次调用时传入,节点配置中不存任何文件地址(IInstance.py)。
  • 输出是清洗后的形状,不是原始 Graph JSON:工具只返回id、name、webUrl、文本等字段,绝不把原始 docx/PDF 字节塞回工具通道(word/client.py 的clean_item实现)。

核心机制:无持久会话的 docx 往返(round-trip)

该节点没有持久的 Word Online 编辑会话。每一次写操作都是完整的“下载—本地编辑—回传”往返:

  1. GET获取文件元数据,拿到当前 eTag;
  2. GET .../content下载二进制 docx;
  3. 在进程内用python-docx编辑;
  4. PUT .../content整体回传,请求头携带If-Match: <eTag>。

client.py中的两个辅助函数精确实现了这一模式:

  • download_docx(auth, base, file) -> (bytes, etag):先取元数据拿 eTag,再取二进制内容(client.py);
  • upload_docx(auth, base, file, blob, etag):携带If-Match回传;eTag 为空时(例如全新文件)省略该头(client.py)。

并发冲突是显式失败而非静默覆盖:如果下载与回传之间文件被他人修改,Graph 会返回409/412,graph_client.request将其包装成GraphError,错误信息明确指出冲突,提示调用方“re-read and retry”,而不是 last-writer-wins 覆盖别人的编辑(graph_client.py)。冲突后的标准处理是先用word_read_text重新读取,再重试编辑。

值得注意的细节:word_replace_text在零匹配时会跳过上传(IInstance.py),这样一次无操作替换不会无谓地推进 item 的 eTag / mtime——这是源码层面的一个微妙优化。

配置与访问层级(Access Tier)

节点字段

字段说明
microsoft.authTypeservice(Entra 应用,客户端凭据流)或user(用户 OAuth),默认service
microsoft.tenantId/microsoft.clientId/microsoft.clientSecretservice认证用的 Entra 应用凭据
microsoft.userPrincipalNameservice认证下“acting user”的 UPN(应用专用调用以/users/{upn}为目标)
microsoft.oAuthButton/microsoft.userToken用户 OAuth:点击登录以填充访问令牌,由 broker 自动刷新
word.accessreadonly或write(默认),由core/microsoft_access.py中共享的WORD规范解析,scope 从不手工填写

字段模式定义见 services.word.json,其预置配置(preconfig)默认authType: "service"、access: "write"。

访问层级 → Graph Scopes

层级Scope能力
readonlyFiles.Read仅读取文档文本
writeFiles.ReadWrite完整读写(默认)

WORD规范定义在 nodes/src/nodes/core/microsoft_access.py:scopes={'readonly': ['Files.Read'], 'write': ['Files.ReadWrite']}, default='write'。节点的validateConfig、check_connection工具与build_auth三个检查点共用同一个token_scope_report判定,保证三处不会漂移(graph_client.py)。readonly层级的写工具调用会在运行时抛出MicrosoftAccessError(microsoft_access.py 的require_write)。

注意:本节点没有破坏性操作的门控开关(destructive gate flags)——所有写操作都由 Graph 自身的If-Match前置条件守护,而非节点侧的安全开关。

六个 Agent 工具

工具对应 Graph 调用用途
word_read_textGET .../content读取文档正文段落与表格单元格文本,换行拼接
word_create_documentPUT /drive/root:/{path}:/content从段落文本列表新建.docx
word_append_textGET/PUT .../content(If-Match)在文档末尾追加段落
word_replace_textGET/PUT .../content(If-Match)跨正文段落与表格单元格查找替换,返回替换计数
word_export_pdfGET .../content?format=pdf、PUT .../content服务端转 PDF,上传到源文件旁
word_check_connectionGET /drive+ scope 报告诊断:连接与 scope 覆盖情况

各工具的实现要点

word_read_text:下载 docx →docx.Document(BytesIO(content))在内存解析 → 依次收集doc.paragraphs的正文段落,再遍历doc.tables中每个单元格的段落文本,最后以'\n'.join(parts)返回{text}(IInstance.py)。

word_create_document:用doc.add_paragraph(text)逐段建文档后直接PUT /drive/root:/{path}:/content,不带If-Match——新建文件尚无 eTag 可匹配,且本义就是创建(或有意覆盖)文件而非合并旧版本(IInstance.py)。

word_append_text:先download_docx取内容与 eTag → 追加段落 →upload_docx带If-Match回传(IInstance.py)。

word_export_pdf:GET {item}/content?format=pdf拿服务端转换的 PDF 字节 → 以“源文件名去掉.docx加.pdf”命名,上传到源文件所在目录,返回的是新 PDF 的元数据而非字节——避免把数 MB 内容塞回工具通道(IInstance.py)。

word_check_connection:以GET {base}/drive探测连接,并复用共享的 scope 报告逻辑确认已授予的 OAuth scope 覆盖配置的访问层级(IInstance.py)。

文件寻址:路径与 item id 的智能区分

工具接受两种文件参数:OneDrive 路径(如Docs/report.docx)或 drive item id。it()辅助函数用正则[A-Za-z0-9!]{15,}$判断:形如 item id(或root别名)的走/drive/items/{id},其余按root:/{path}:形式寻址,并对路径逐段做百分号编码(safe='/'保留分隔符)——未编码的空格会触发http.client.InvalidURL,未编码的#会截断路径导致寻址到错误文件(client.py)。

底层共享机制:graph_client.py

Word 与 Excel、OneDrive、Outlook Mail、Outlook Calendar 共用同一份 Graph 凭据与请求机制(graph_client.py,各服务client.py通过functools.partial绑定,word 服务在其第 38-41 行声明GraphService(product='Word', superset_scopes={'Files.ReadWrite.All'})):

  • 凭据安全:token 端点仅允许login.microsoftonline.com,broker 刷新 URL 仅允许https且 host 在信任名单(oauth2.rocketride.ai/oauth.rocketride.ai,自托管可用环境变量RR_OAUTH_BROKER_URL追加),篡改的存储 token 无法把凭据重定向到第三方主机(graph_client.py);
  • 两种认证实现:AppOnlyAuth(client_credentials,进程内缓存至过期前 60 秒)与BrokerUserAuth(broker 刷新,非 200 拒绝、200 无 access_token 视为契约违规、过期前 60 秒刷新)(graph_client.py);
  • 重试策略:429/5xx指数退避重试(退避序列 1s/2s/4s,最多 4 次尝试,Retry-After被钳制在 0–30s);429对所有方法重试,5xx仅对幂等方法(GET/HEAD/PUT/DELETE)重试,避免重放 POST 造成重复副作用(graph_client.py);
  • 错误语义:401/403快速失败并给出可读的授权修复提示(如AADSTS65001表示需要管理员同意);409/412快速失败并提示“re-read and retry”(graph_client.py)。

还有一个对 Word 至关重要的实现细节:Graph 对/content下载常返回 302 跳转到预授权的下载 CDN 主机,_AuthStrippingRedirectHandler会在跨主机跳转时剥离Authorization头——否则外域 CDN 会以 401 拒绝携带陌生 bearer token 的请求(graph_client.py,注释明确记录该问题“found live on word_read_text”)。

word_replace_text的单遍替换算法

替换发生在两个层面:正文段落与表格单元格。_iter_all_paragraphs先产出doc.paragraphs,再遍历每个表格的每行每单元格的段落(IInstance.py)。

每个段落的处理由_replace_in_paragraph完成(IInstance.py),核心原则是单遍扫描、基于原始文本计算:

  • original = paragraph.text(该段落所有 run 文本的拼接)只扫描一次,替换计数与替换结果都从这个永不改变的原串计算;
  • 这正是为了避免“先 run 后段落”双遍扫描的缺陷:若replace值本身包含find(如find='foo'、replace='foobar'作用于'foo is here'),双遍扫描第一遍会替换成'foobar is here',第二遍又在新写入的'foobar'里再找到'foo',最终错误计数为 2 且文本被破坏成'foobarbar is here';
  • 零匹配的段落完全不动——任何 run 与格式都不受扰动;
  • 至少一个匹配的段落:新文本写入第一个 run,该段落其余 run 清空。第一个 run 的格式对整个合并文本生效——多 run 段落一旦命中替换,段内的格式边界(粗体/斜体跨度等)会丢失。这是朴素 read-modify-write 文本替换的已知取舍,并非真实 Word 编辑会话的替代品。

接线方式

这是tool节点:通过control(classType: "tool")接到 Agent 旁边,同时 Agent 需要自己的memory节点:

{ "id": "tool_word_1", "provider": "tool_word", "config": { "type": "tool_word" }, "control": [{ "classType": "tool", "from": "agent_rocketride_1" }] }

Agent 会自行发现word_*工具并按指令调用。在数据流层面,services.word.json的lanes为空对象,确认了该节点纯工具属性。

认证设置:两种模式与所需 Graph 权限

认证方式二选一:

  • Entra 应用(service):填microsoft.tenantId/microsoft.clientId/microsoft.clientSecret/microsoft.userPrincipalName,走 client-credentials 流;
  • 用户 OAuth(user):点击登录按钮填充microsoft.userToken,由 broker 自动刷新。

完整的 Entra 应用 / 授权设置见 docs/docusaurus/apps/vscode/microsoft-oauth.md(所有 Microsoft 365 工具服务共用)。应用所需 Graph 权限取决于认证模式:

认证模式层级Graph 权限
用户 OAuth(委派)readonlyFiles.Read
用户 OAuth(委派)writeFiles.ReadWrite
Entra 应用(application,需管理员同意)readonlyFiles.Read.All
Entra 应用(application,需管理员同意)writeFiles.ReadWrite.All

应用权限(application permissions)一律需要管理员同意。与 Excel workbook API 不同(其 API 不接受 application-only token),Word 的 drive-item 下载/上传/转换调用支持 app-only 模式,因此两种认证模式都被完整支持。service模式下所有 Graph 调用以/users/{userPrincipalName}为目标(user_base的实现见 graph_client.py),UPN 即选定凭据作用的数据归属用户。

限制与边界

  • 并发:word_append_text、word_replace_text是往返编辑,依赖If-Match;eTag 过期(下载期间文件被他人改动)会以命名冲突的GraphError形式暴露。工具不会自动重试——先用word_read_text重读,再重试编辑。
  • PDF 导出:word_export_pdf依赖 Graph 服务端格式转换(?format=pdf);超大或非常规文档可能转换更久,甚至上游转换失败。
  • 格式坍缩:word_replace_text将匹配段落的文本写入首 run 并清空其余 run,段内格式坍缩为首 run 样式——适合纯文本编辑,不能替代真实 Word 编辑会话;未匹配段落不受影响。
  • 限流:限流按 Entra 应用 / 租户维度计数,节点对429/5xx响应以指数退避重试。

端到端示例

一个 Agent 创建报告,随后查找替换占位符:

word_create_document { "path": "Docs/report.docx", "paragraphs": ["Q3 Report", "Status: DRAFT"] } word_replace_text { "file": "Docs/report.docx", "find": "DRAFT", "replace": "FINAL" }

word_create_document在Docs/report.docx生成两段文本的新文档并返回{id, name, webUrl};word_replace_text将DRAFT替换为FINAL(带If-Match回传),返回{replacements: 1}。若期间文件被他人修改,第二次调用会收到 409/412 冲突的GraphError,此时先调用word_read_text获取最新内容再重试即可。

故障排查速查表

  • Scope / 403 错误:调用word_check_connection;若 scope 缺失——用户认证下断开并重新连接 Microsoft 账户,服务认证下按所需层级授予 / 同意 Entra 应用权限。
  • access为只读:写工具抛出MicrosoftAccessError;把word.access提升到write。
  • 冲突 / 409 / 412 错误:文件在下载后发生了变化(他人编辑,或前一次调用的上传已推进 eTag)。调用word_read_text获取当前内容后重试编辑。
  • word_replace_text返回 0:确认目标文本精确匹配——find是字面量(区分大小写)子串匹配,不是正则。

【免费下载链接】rocketride-server

High-performance AI pipeline engine with a C++ core and 50+ Python-extensible nodes. Build, debug, and scale LLM workflows with 13+ model providers, 8+ vector databases, and agent orchestration, all from your IDE. Includes VS Code extension, TypeScript/Python SDKs, and Docker deployment.

项目地址:https://gitcode.com/gh_mirrors/ro/rocketride-server
点击查看免费下载
上一篇:NettyRPC 项目使用教程
下一篇:Vite 8 解析:Rolldown 驱动的新一代前端构建工具链如何落地

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

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

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

立即咨询