AI Agent是怎么操作电脑的?从零实现一个帮忙处理Excel的AI助手
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: string
range: 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_range
write_excel_range
而是电脑操作能力:
screenshot
click
type
scroll
keypress
同样的 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_info
read_range
write_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 不只保存显示值,还会保留:
values
formulas
formulaResultTrust
因为 Excel 中“看到的值”和“单元格实际公式”是两回事。
比如 G2 显示:
500
实际内容可能是:
=E2-F2
如果用户问“利润这一列为什么有几行算错了”,Agent 必须看到公式,而不只是显示值。
但 read_range 有一个明确边界:
它适合看局部数据,不适合证明全表结论。
例如用户要求:
检查 10 万行订单有没有重复。
模型只读了 A1:G200,即使这 200 行没有重复,也不能回答“没有重复订单”,因为剩下的数据根本没检查。
这类全表问题应该交给确定性程序。
项目里为此设计了 full_scan。它不会把十万行原始数据全部返回给模型,而是让本地程序完整扫描,只返回结果:
rowCount: 100000
duplicateRows: 37
订单号:
missing: 0
unique: 99963
销售额:
missing: 12
sum: 87362591.24
有了这份全量证据,模型才可以说:
整张表发现 37 条重复记录,
销售额存在 12 个空值。
同样,如果用户问:
销售额低于 1000 的订单有哪些?
即使表里有 30 万行,也没必要把全部数据发给模型一行行找,可以交给 find_rows 在本地筛选。
类似的确定性工具还可以包括:
aggregate
distinct_values
find_duplicate_rows
find_formula_inconsistencies
full_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_range
set_formula / fill_formula
add_column / delete_column
insert_rows / delete_rows
sort_rows
create_sheet / delete_sheet
conditional_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 / blocked
savePolicy:
allow / save-copy-only / read-only
canRead
canWrite
canCalculateFormulas
detectedFeatures
warnings
普通数据表可以 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_unique
sheet_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 Awareness
aggregate / find / full_scan
Capability Scanner
让程序负责全量扫描和确定性计算,让模型负责理解和决策。
然后,我们把 Excel 修改拆成明确的 WorkbookOperation:
add_column
fill_formula
delete_rows
create_sheet
conditional_format
...
再通过:
Schema
↓
权限策略
↓
Proposal
↓
Preview
↓
Approval
控制模型到底能改什么、准备改什么,以及什么时候真正执行。
最后,再加入 Verifier:
执行
↓
重新读取 / 重新计算
↓
Assertion
↓
通过 → 完成
失败 → Replanner
到这里,我们才真正从一个只会生成文本的 LLM,一步步搭出了一个能够处理 Excel 的 Agent:
LLM
│
判断下一步做什么
│
▼
Agent Runtime
│
┌──────────┼──────────┐
▼ ▼ ▼
Workbook Analysis Operations
Awareness Tools Tools
│ │ │
└──────────┼──────────┘
▼
Workbook Engine
│
▼
Excel
│
▼
Verifier
│
┌─────┴─────┐
▼ ▼
完成 Replanner
这套架构换一组 Tool,同样可以变成其他 Agent:
Excel Agent
LLM + Excel Tools + Runtime
Coding Agent
LLM + File / Shell / Test Tools + Runtime
Browser Agent
LLM + Navigate / DOM / Click / Type Tools + Runtime
Computer Agent
LLM + 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相关的代码,可自行参考。
更多推荐



所有评论(0)