ARTICLE · 998064
AI_Agent是怎么操作电脑的_从零实现Excel助手_优化版
AI这两年发展很快,从最初以对话为主,到现在已经能写代码、改文件、操作网页。
AI Agent 看起来像是“大模型突然会操作电脑了”:它能打开网页、点击按钮、修改 Excel、执行代码。
但模型本身并没有因此获得电脑控制权。
真正发生的事情可以概括成:
用户提出目标↓模型判断下一步↓请求调用 Tool↓程序执行↓结果返回模型↓模型继续判断
模型负责判断“下一步做什么”,外部程序负责真正执行。这个闭环跑起来以后,一个原本主要负责生成内容的大模型,就开始表现得像一个能干活的 Agent。
OpenAI Agents SDK 里的 Agent 可以理解为配置了 instructions、model、tools 等能力的 LLM;真正运行时,还需要 Runner 或应用自己的执行循环。本文为了方便,后面说的 Agent 指整个运行中的系统,而不只是 SDK 里的 Agent 对象。
下面我们就从一个什么都不会的 LLM 开始,一步步把它变成一个真正能处理 Excel 的 AI 助手。
一开始,它只有模型,既看不到 Excel,也不能修改文件。接下来我们会逐步给它增加能力:
V0 只有 LLM↓V1 加入 Tool,能读取 Excel↓V2 加入 Agent Loop,能连续分析和调用工具↓V3 加入工作簿画像和确定性工具,能处理真实大表↓V4 加入 WorkbookOperation,能修改 Excel↓V5 加入 Schema、Capability 和 Approval,能够安全修改↓V6 加入 Verifier,执行后自动验证结果
最后得到的,才是一个真正敢让它处理工作文件的 Excel Agent。
在这个过程中,我们也会顺便回答一个更大的问题:AI Agent 到底是怎么操作电脑的?
一、第一步:先让模型能够读取 Excel
假设用户说:
帮我检查一下这份 Excel 的销售数据有没有问题。
模型能理解这句话,也知道可能要检查空值、重复订单、异常值,或者统计销售额。
问题是:它看不到你电脑里的 Excel。
普通的大模型调用基本是:
用户输入↓模型↓生成内容
但是,硬盘里有没有 sales.xlsx、文件有几个 Sheet、A1 写了什么,模型都不知道。
要让它真正处理 Excel,就要给它工具(Tool)。
这里先澄清一个容易误解的地方:Tool 不是把函数交给模型执行。
假设我们写了一个本地函数:
async function readExcelRange(sheet: string,range: string) {// 真正读取 Excel}
模型不会直接看到这段源码,也不会自己执行它。真正提供给模型的是一份工具定义,例如:
{type: ”function”,name: ”read_excel_range”,description: ”读取指定 Excel 工作表中的单元格区域”,parameters: {type: ”object”,properties: {sheet: {type: ”string”,description: ”工作表名称”},range: {type: ”string”,description: ”Excel A1 格式区域,例如 A1:F100”}},required: [”sheet”, ”range”]}}
这里的 parameters 用 JSON Schema 描述参数格式。简单理解,就是告诉模型:
工具名:read_excel_range用途:读取 Excel 区域参数:sheet: stringrange: string
模型拿到的是一张“工具说明书”。
现在用户说:
看看“销售数据”这个 Sheet 前 100 行有没有问题。
模型可以返回一个结构化的 Function Call:
{”type”: ”function_call”,”name”: ”read_excel_range”,”arguments”: {”sheet”: ”销售数据”,”range”: ”A1:F100”}}
这时候 Excel 仍然没有被读取。模型表达的只是:
下一步应该调用
read_excel_range,参数是“销售数据”和A1:F100。
程序收到请求后,根据 name 找到真正的 TypeScript 函数:
const result = await readExcelRange(”销售数据”,”A1:F100”);
函数执行后,假设返回:
[[”订单号”, ”地区”, ”商品”, ”数量”, ”销售额”],[”A001”, ”华东”, ”商品A”, 5, 2500],[”A002”, ”华南”, ”商品B”, 3, 1800]]
程序再把结果交回模型。到这里,在这个示例里,模型才真正获得 Excel 的实际内容。
所以 Function Calling 可以先记成一句话:
对于本文这种自定义 Function Tool,模型负责决定调用哪个工具、传什么参数;真正读写 Excel 的,是模型之外的执行环境。
到这里,我们已经做出了 Excel Agent 的第一个版本:
LLM + read_excel_range
它还谈不上“智能处理 Excel”,但至少已经从一个只能聊天的模型,变成了一个能够读取 Excel 内容的模型。
二、第二步:让它自己连续调用工具
一次 Tool Calling 已经可以完成简单任务:
用户↓模型↓Tool↓执行↓返回结果↓回答
但用户通常不会说“读取 A1:F100”,而会说:
帮我看看这个表有没有问题。
模型读完数据后,可能还要检查订单号是否重复、销售额有没有空值、公式是否异常。也就是说,工具执行以后不能直接结束,结果还要重新回到模型:
模型↓动作↓执行↓结果↓模型↓下一个动作
这就是 Agent Loop。
OpenAI Agents SDK 里的 Runner 做的也是类似的事情:调用模型,检查输出;如果模型产生 Tool Call,就执行工具,把结果放回上下文,再继续调用模型,直到得到最终结果或者达到最大轮数。
简化成伪代码就是:
while (turn < MAX_TURNS) {const response = await callModel(context, tools);if (response.toolCall) {const result = await executeTool(response.toolCall);context.push(result);continue;}return response.finalText;}
真实系统一轮也可能产生多个 Tool Call,这里只是为了说明原理做了简化。
那么,这个循环由谁负责?
本文把模型和真实工具之间的执行、编排层叫做 Agent Runtime:
LLM│▼┌─────────────────┐│ Agent Runtime ││ ││ 识别 Tool Call ││ 校验参数 ││ 调用工具 ││ 保存状态 ││ 处理错误 ││ 控制循环 │└────────┬────────┘│┌─────┼─────┐▼ ▼ ▼Excel Shell Browser
这里要分清三件事:
模型参与决定下一步动作; Function Calling 把工具调用表达成结构化请求; Runtime 组织调用、状态和执行流程。
现在这个助手已经从:
LLM + Excel Tool
变成了:
LLM + Excel Tools + Agent Loop
它不再只能执行一次读取,而是可以根据上一步的结果继续决定下一步该检查什么。
一个最小的 Excel Agent,到这里其实已经跑起来了。
三、先插一句:Agent 为什么也能操作电脑?
理解了上面的 Tool Calling 和 Agent Loop,Computer Use 就容易理解了。
如果 Runtime 给模型提供的不是:
read_excel_rangewrite_excel_range
而是电脑操作能力:
screenshotclicktypescrollkeypress
同样的 Agent Loop 就可以用来操作桌面。
比如用户说:
打开 Chrome。
一种直观的 Computer Use 流程可能是:
1. Runtime 截取当前屏幕2. 截图交给模型3. 模型识别 Chrome 图标的位置4. 模型请求执行点击操作5. Runtime 真正执行鼠标点击6. Windows 界面发生变化7. Runtime 再截图8. 新截图返回模型9. 模型判断 Chrome 是否已经打开
本质仍然是:
观察 → 判断 → 动作 → 执行 → 获得新状态 → 继续判断
Excel Agent 的观察可能是 read_range(),动作可能是 write_range();Computer Agent 的观察可能是 screenshot(),动作可能是 click()、type()。
所以所谓“AI 会操作电脑”,拆开以后其实是:
模型:下一步应该做什么?Runtime:真正把这个动作执行掉。
模型没有因此获得 Windows 的底层控制权。真正拥有文件、鼠标、键盘等权限的,仍然是本地程序或远程执行环境。
这也决定了安全边界不能只写在 Prompt 里。
例如:
可以读取文件,但不能删除文件可以修改 Excel,但删除 Sheet 必须确认可以操作网页,但付款不能自动执行
Prompt 可以告诉模型“不要删除 Sheet”,但执行层可以真正做到:
if (action.type === ”delete_sheet”) {requireUserConfirmation();}
OpenAI Agents SDK 也提供 Guardrails、人工审批等机制。不过对于真正有副作用的 Computer Use、Shell 等能力,应用仍然需要在执行层做权限和风险控制。
另外,“看截图 + 点坐标”并不是 Computer Agent 唯一的实现方式。真实产品还可能结合 DOM、浏览器自动化、Accessibility Tree、Shell、文件系统和 API。
但底层思路没有变化:
模型决定动作,工具接触真实环境,Runtime 把两者连接起来。
四、第三步:让它真正看懂并处理大型 Excel
到这里,我们已经可以做一个最小 Excel Agent:
get_workbook_inforead_rangewrite_range
Demo 能跑,但真正处理工作文件时很快就会遇到问题。
普通脚本往往提前知道数据在哪个 Sheet、订单号在哪一列、要处理多少行、修改哪些单元格。
Agent 面对的却可能只有一句:
帮我看看这个表有没有问题。
它连文件有几个 Sheet、数据有多少行、哪些列是公式都不知道。
因此在本文项目的 Excel Agent 设计中,下一步是先建立工作簿画像。下面的 get_workbook_info、full_scan、CapabilityReport、Proposal 等属于项目自己的 Excel 操作协议,并不是 OpenAI Agents SDK 的固定 API。
get_workbook_info 可以生成类似这样的信息:
Sheet 名称Used Range行数、列数字段类型缺失率唯一率公式比例数值范围日期范围候选主键重复行比例分析是全量还是抽样文件能力和兼容性警告
假设“销售明细”有十万行:
A:订单号B:商品C:地区D:数量E:销售额F:成本G:利润
没有必要一上来把十万行全部送给模型。程序可以先生成摘要:
{”sheet”: ”销售明细”,”usedRange”: ”A1:G100001”,”rowCount”: 100001,”columns”: [{”name”: ”订单号”,”type”: ”string”,”uniqueRate”: 0.998,”missingRate”: 0,”candidateKey”: true},{”name”: ”销售额”,”type”: ”number”,”missingRate”: 0.003}]}
模型已经可以判断订单号很可能是唯一标识、销售额是数值列、表大约有十万行,并且销售额存在少量缺失,然后再决定需要读取哪些真实数据。
这比默认把整个 Workbook 塞进上下文合理得多。
有了工作簿摘要以后,再按需读取真实数据。例如:
read_range(sheet = ”销售明细”,range = ”E1:G20”)
项目里的 RangeSnapshot 不只保存显示值,还会保留:
valuesformulasformulaResultTrust
因为 Excel 中“看到的值”和“单元格实际公式”是两回事。
比如 G2 显示:
500
实际内容可能是:
=E2-F2
如果用户问“利润这一列为什么有几行算错了”,Agent 必须看到公式,而不只是显示值。
但 read_range 有一个明确边界:
它适合看局部数据,不适合证明全表结论。
例如用户要求:
检查 10 万行订单有没有重复。
模型只读了 A1:G200,即使这 200 行没有重复,也不能回答“没有重复订单”,因为剩下的数据根本没检查。
这类全表问题应该交给确定性程序。
项目里为此设计了 full_scan。它不会把十万行原始数据全部返回给模型,而是让本地程序完整扫描,只返回结果:
rowCount: 100000duplicateRows: 37订单号:missing: 0unique: 99963销售额:missing: 12sum: 87362591.24
有了这份全量证据,模型才可以说:
整张表发现 37 条重复记录,销售额存在 12 个空值。
同样,如果用户问:
销售额低于 1000 的订单有哪些?
即使表里有 30 万行,也没必要把全部数据发给模型一行行找,可以交给 find_rows 在本地筛选。
类似的确定性工具还可以包括:
aggregatedistinct_valuesfind_duplicate_rowsfind_formula_inconsistenciesfull_scan
例如 aggregate 负责计算销售额合计,find_duplicate_rows 按订单号或组合键检查重复,find_formula_inconsistencies 检查公式模式是否突然异常。
这里有一条很实用的原则:
需要模型理解的,让模型看;能用确定性代码算出来的,就让程序算。
这样既省 Token,也减少模型直接处理大表时的计算错误。
到这里,这个 Excel Agent 又升级了一次:
最初:LLM + read_range现在:LLM+ Workbook Awareness+ read_range+ aggregate / find / full_scan+ Agent Loop
它已经不只是“能读 Excel”,而是开始具备处理真实工作簿和大型数据表的能力。
五、第四步:让它开始修改 Excel
会读 Excel 只是第一步,真正让 Agent 修改工作簿以后,问题会复杂很多。
最省事的设计是给模型一个:
modify_excel(prompt)
让它想怎么改就怎么改。
但这种工具几乎没有边界:程序很难提前知道模型准备改哪个 Sheet、影响多少单元格、会不会删数据,也很难做可靠的预览和审计。
所以项目里没有做万能的“修改 Excel”工具,而是把修改拆成明确的 WorkbookOperation:
write_cell / write_rangeset_formula / fill_formulaadd_column / delete_columninsert_rows / delete_rowssort_rowscreate_sheet / delete_sheetconditional_format...
比如用户说:
新增一列利润率,公式是利润除以销售额,低于 20% 的标红。
模型生成的不是一句“修改 Excel”,而是一组原子操作:
[{”type”: ”add_column”,”sheet”: ”销售明细”,”header”: ”利润率”},{”type”: ”fill_formula”,”sheet”: ”销售明细”,”range”: ”H2:H3563”,”formula”: ”=G2/E2”},{”type”: ”conditional_format”,”sheet”: ”销售明细”,”range”: ”H2:H3563”,”operator”: ”<”,”value”: 0.2}]
这样 Runtime 才能明确知道改哪个 Sheet、影响多少单元格、有没有删除操作、有没有公式以及影响范围有多大。
工具边界越明确,后面的校验、权限控制和结果验证就越容易。
到这里,我们已经从“会分析 Excel”走到了“能够用结构化操作修改 Excel”。
但能改,还不等于应该直接改。
六、第五步:让修改变得可控
操作拆细以后也不能直接执行。第一道防线是 Schema 校验。
模型可能生成非法 Excel 地址,或者一次要求写入几百万个单元格,所以 Tool Call 不能拿到就执行。
项目里的 Workbook Operation 会先经过 Zod Schema 校验,并限制 Excel 本身的合法范围。对于现代 .xlsx 工作表:
最大行数:1,048,576最大列数:16,384
项目还可以增加自己的限制:
一次最多 100 个 Operation单次请求最多影响 100,000 个单元格
这里要区分两种边界:
Excel 最大范围是文件格式和软件能力边界;Agent 自己规定的操作数量、影响单元格数量,则属于执行安全边界。
Schema 解决的是:
这个参数合法吗?
权限和风险策略解决的是:
这个操作虽然合法,但允许执行吗?
例如删除十万行完全可能符合 Schema,但 Runtime 仍然应该拒绝或要求用户确认。
因此真正的写入链路应该是:
LLM 输出↓Schema / 参数校验↓权限策略↓影响范围检查↓必要时用户确认↓执行
参数合法也不代表文件一定适合修改。
真实 .xlsx 不只是二维单元格,里面可能还有图表、PivotTable、Excel Table、外部链接、Power Query、Data Model、图片、Defined Name、数据验证、条件格式、宏、ActiveX、Custom XML 等对象。
如果当前 Workbook Engine 不能可靠保存这些高级对象,Agent 修改并保存一次,就可能导致原文件中的内容丢失。
所以项目打开文件时会先生成 CapabilityReport:
safetyLevel:safe / warning / blockedsavePolicy:allow / save-copy-only / read-onlycanReadcanWritecanCalculateFormulasdetectedFeatureswarnings
普通数据表可以 savePolicy = allow;遇到当前引擎无法可靠 round-trip 的高级对象,可以降成 save-copy-only,甚至 read-only。
这就是本文实现中 Capability Scanner 的作用:
先判断当前 Workbook Engine 能不能安全修改这个文件,再决定开放哪些写入能力。
其中是否能够重新计算公式,也取决于底层 Workbook Engine;能读写公式并不一定意味着能够完整复现 Excel 的公式计算能力。
即使参数合法、文件也允许修改,也不应该让模型说改就改。
项目里还有一个:
propose_operations
它只生成修改方案,不直接写入。
例如用户说:
把重复订单删掉,再创建一个重复订单统计 Sheet。
模型先生成:
摘要:删除 37 条重复订单,并创建统计表Operations:delete_rows ...create_sheet ...write_range ...Success Criteria:订单号必须唯一重复统计 Sheet 必须存在Assertions:range_uniquesheet_exists
此时 Excel 还没有变化。
界面可以先告诉用户:
预计删除 37 行新增 1 个工作表写入 38 个单元格
确认以后再执行:
WorkbookEngine.applyOperations()
完整链路变成:
用户自然语言↓LLM↓propose_operations↓Operation Schema↓参数 / 范围 / 权限校验↓Preview↓用户确认↓Workbook Engine↓Excel 修改
到这里,这个 Excel Agent 已经不仅“能修改”,而是开始具备一套受控执行协议。
七、第六步:让它自己验证修改结果
假设用户要求:
删除重复订单。
Agent 执行了一系列操作以后回答:
已经处理好了。
这句话本身没有证明力。
更可靠的方式是在 Proposal 里同时定义 assertions,执行结束以后重新检查。
例如:
range_unique(...)
返回 true,才能证明重复记录确实清掉了。
如果用户要求:
删除重复记录,但销售额合计必须保持不变。
执行前先记录销售额总和,执行后再验证:
aggregate_equals(operation = ”sum”,expected = 原销售额总和)
如果不相等:
验证失败↓任务不能宣布完成↓重新规划或交给用户处理
所以一个更完整的 Excel Agent 会形成这样的闭环:
Workbook Awareness↓get_workbook_info↓read_range / inspect↓aggregate / find / full_scan↓LLM 决定如何修改↓propose_operations↓Schema / 风险 / 权限校验↓Preview / 用户确认↓Workbook Engine↓Verifier↓成功 → 完成失败 → Replanner → 继续
也就是说,Excel Tool 最后已经不是几个零散函数,而是一套给模型使用的 Excel 操作协议:
先认识工作簿↓按需获取证据↓确定性计算交给程序↓模型决定怎么处理↓修改拆成原子操作↓执行前检查风险↓执行后重新验证
加上 Verifier 以后,这个从零搭起来的 Excel Agent 才真正形成完整闭环:
先理解 → 再分析 → 提出修改 → 安全执行 → 验证结果。
真正决定这个 Agent 靠不靠谱的,不只是模型能力,更是工具边界、执行限制和结果验证。
八、到这里,我们从零做出了什么?
回头看最开始,我们手里其实只有一个 LLM:
用户↓LLM↓文本
它能理解“帮我检查一下这个 Excel”,但既看不到文件,也不能真正修改任何东西。
第一步,我们给它加入 Excel Tool:
LLM+read_range
于是它第一次能够读取工作簿里的真实内容。
接着加入 Agent Loop:
LLM+Excel Tools+Runtime
工具执行结果不断返回模型,模型再决定下一步做什么。它开始能够自己完成“读取 → 判断 → 再读取 → 再判断”的连续任务。
但真正的工作簿可能有几十万行,也可能包含公式、图表、PivotTable 等复杂对象。所以我们继续加入:
Workbook Awarenessaggregate / find / full_scanCapability Scanner
让程序负责全量扫描和确定性计算,让模型负责理解和决策。
然后,我们把 Excel 修改拆成明确的 WorkbookOperation:
add_columnfill_formuladelete_rowscreate_sheetconditional_format...
再通过:
Schema↓权限策略↓Proposal↓Preview↓Approval
控制模型到底能改什么、准备改什么,以及什么时候真正执行。
最后,再加入 Verifier:
执行↓重新读取 / 重新计算↓Assertion↓通过 → 完成失败 → Replanner
到这里,我们才真正从一个只会生成文本的 LLM,一步步搭出了一个能够处理 Excel 的 Agent:
LLM│判断下一步做什么│▼Agent Runtime│┌──────────┼──────────┐▼ ▼ ▼Workbook Analysis OperationsAwareness Tools Tools│ │ │└──────────┼──────────┘▼Workbook Engine│▼Excel│▼Verifier│┌─────┴─────┐▼ ▼完成 Replanner
这套架构换一组 Tool,同样可以变成其他 Agent:
Excel AgentLLM + Excel Tools + RuntimeCoding AgentLLM + File / Shell / Test Tools + RuntimeBrowser AgentLLM + Navigate / DOM / Click / Type Tools + RuntimeComputer AgentLLM + Screenshot / Click / Type / Scroll Tools + Runtime
Planner、Replanner、Verifier、Proposal、Guardrail 并不是所有 Agent 都必须有。
在本文这种 Tool Calling 架构里,一个最小可工作的 Agent,可以近似理解成:
LLM + Tool + Loop
只是任务真正复杂起来以后,会陆续遇到新的问题:
任务太长,模型容易乱跑→ Planner环境变化,原计划走不通→ Replanner模型说完成了,但不知道真假→ Verifier修改有风险→ Proposal / Approval / Guardrail
于是,一个 Demo 才慢慢变成真正敢让它干活的系统。
回到最开始的问题:
AI Agent 到底是怎么操作电脑的?
不是给大模型“装上了鼠标”,也不是模型突然获得了 Windows 权限。
真正发生的是:
大模型参与判断下一步该做什么,Tool 负责接触真实环境,Runtime 负责把模型、工具、状态和执行流程组织起来。
而所谓“从零实现一个 Excel AI 助手”,本质上就是不断完善这三部分:让模型获得合适的工具,让 Runtime 可靠地执行这些工具,再用权限、审批和验证机制把这个循环约束在可信范围内。
真正让 Excel 被读取、文件被修改、鼠标被点击、代码被执行的,始终是模型之外的执行环境。
最后要补充一句:本文只是一个最简单的AI Agent实现,真正的商用agent远比这复杂的多,开源社区有很多agent相关的代码,可自行参考。