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_infofull_scanCapabilityReportProposal 等属于项目自己的 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相关的代码,可自行参考。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐