Agent 终于会“看”表格了——read_csv 和 read_excel 的故事
Agent 终于会“看”表格了——read_csv 和 read_excel 的故事
以前只能“写”,现在终于能“读”了
一、一个让我很无语的场景
前几天我在 REPL 里测试数据分析能力。
我手头有一个 CSV 文件,大概 2000 多行,里面有“ASR 识别文本”和“是否是 POI”两列。我想让 Agent 帮我看看这个文件长什么样。
我说:“读取一下这个 CSV,看看它有多少行,有哪些列。”
Agent 说好的,然后调用了 read_file。
read_file 返回了什么呢?一大坨原始的 CSV 文本。
2000 多行的内容,密密麻麻全是逗号和引号。模型面对这堆东西,完全没办法"理解"——它只能看到一堆文本,不知道哪行是表头、哪列是什么含义、有多少行、有多少空值。
结果 Agent 含糊地说:“这个文件看起来有一些数据……”
就这?
我当时的内心 OS:你倒是跟我说说有几行几列啊!
后来我才意识到,这不是模型的问题——是我的工具设计有问题。
二、先解释一个概念:什么是“数据预览”?
概念解释:数据预览(Dataset Preview)不是把整个文件的内容都倒出来,而是先看一眼它的“骨架”:
- 这个表格有几行几列?
- 列名叫什么?
- 每列是什么类型的数据(数字、文字、日期)?
- 有没有空值?
- 前几行长什么样?
就像你拿到一本厚厚的书,不会从第一页开始读——你会先看目录、看摘要、看第一章的前几段,判断这本书值不值得读。
数据预览就是这个道理:先看一眼“骨架”,再决定怎么用。
如果模型能先拿到这些信息,它就能做出更好的判断:
- “哦,这个表有 2473 行,两列,
ASR识别文本有 12 个空值”——那它就知道数据质量还可以,可以继续分析。 - “哦,这个表有 10 万行”——那它就知道不能用
read_file全量读取,得用采样或聚合方式处理。—
三、为什么之前没有这个能力?
回顾一下我当时手上的工具:
写侧(已经有):
| 工具 | 能力 |
|---|---|
write_csv |
写入 CSV |
create_excel |
创建 Excel |
读侧(之前没有):
| 工具 | 能力 |
|---|---|
| ? | 读取 CSV / Excel 的结构化信息 |
我只能“写”表格,不能“读”表格。
这就很尴尬了——Agent 可以生成一个 Excel 给你,但它打不开你发给它的 Excel。
就像一个作家只会写书、不会读书。
四、解决方案:read_csv 和 read_excel
今天新增了两个工具:
| 工具 | 作用 |
|---|---|
read_csv |
读取 CSV 文件,返回结构化预览 |
read_excel |
读取 Excel 文件,返回结构化预览 |
4.1 具体返回什么?
当 Agent 调用 read_csv 或 read_excel 时,返回的不是原始文本,而是一个结构化预览:
{
"path": "data/sample.csv",
"format": "csv",
"rows": 2473,
"columns": ["ASR识别文本", "是否是POI"],
"dtypes": {
"ASR识别文本": "string",
"是否是POI": "string"
},
"nullCounts": {
"ASR识别文本": 12,
"是否是POI": 0
},
"sample": [
{ "ASR识别文本": "今天天气不错", "是否是POI": "" },
{ "ASR识别文本": "去天安门广场", "是否是POI": "是" }
],
"sheets": ["Sheet1"],
"sheet": "Sheet1",
"scannedRows": 2473,
"bytes": 128400
}
模型拿到了这些信息,就可以很准确地告诉用户:
- “这个 CSV 有 2473 行、2 列。”
- “列名是
ASR识别文本和是否是POI。” - “
ASR识别文本列有 12 个空值,需要注意。” - “前两行数据是……(展示样例)”
4.2 两个关键参数
为了让模型能灵活控制预览的“深度”,我添加了两个参数:
| 参数 | 说明 | 默认 |
|---|---|---|
sample_size |
返回多少行样例 | 10(最多 50) |
scan_rows |
扫描多少行做统计 | 5000 |
也就是说,即使一个文件有 100 万行,read_csv 也不会全量加载——它只扫描前 5000 行来推断列类型和空值统计,然后返回 10 行样例。
控制 token 消耗,避免撑爆上下文。
4.3 环境变量调优
READ_DATA_MAX_BYTES=15000000 # 最大读取大小,15MB
READ_DATA_SAMPLE_SIZE=10 # 默认样例行数
READ_DATA_SCAN_ROWS=5000 # 统计扫描行数
五、对比:以前 vs. 现在
场景:用户说“帮我看看这个 CSV”
之前(用 read_file):
Agent: 调用了 read_file("data.csv")
read_file: 返回 2000 行原始 CSV 文本(全是逗号和引号)
Agent: “这个文件有一些数据……”(含糊其辞)
用户: ???
现在(用 read_csv):
Agent: 调用了 read_csv("data.csv")
read_csv: 返回结构化预览(行数、列名、类型、空值、样例)
Agent: “这个 CSV 有 2473 行、2 列。列名是 `ASR识别文本` 和 `是否是POI`。`ASR识别文本` 有 12 个空值,建议先处理空值再分析。”
用户: 清晰、有用!
同一个文件,不同的工具,天壤之别。
六、为什么这样设计?—— 三点思考
6.1 专用工具 > 通用工具
read_file 是“万能”的——什么文件都能读,但什么都不精。
read_csv 和 read_excel 是“专用”的——它们只做一件事,但做得好。
这让我想起一个设计原则:通用工具解决“能不能做”的问题,专用工具解决“做得好不好”的问题。
给 Agent 一堆万能工具,它能做很多事,但每件事都做得一般。给它一堆专用工具,它才能在特定领域做得专业。
6.2 预览 > 全量加载
以前我总觉得“要分析数据,就得把全部数据给模型”。
但这是错的——模型不是数据库,它的优势是“理解”和“推理”,不是“存储”和“检索”。
正确的做法是:先预览骨架,再按需读取细节。
就像数据分析师拿到一个数据集,不会先看全部数据——他会先 head() 看看前几行,info() 看看列类型,describe() 看看统计分布。
read_csv 和 read_excel 做的就是这个事。
6.3 读侧补全写侧,形成闭环
- 写侧:
write_csv→ 创建 CSV - 写侧:
create_excel→ 创建 Excel - 读侧:
read_csv→ 读取 CSV - 读侧:
read_excel→ 读取 Excel
现在 Agent 终于有了完整的数据操作闭环:能写、能读、能分析。
七、和之前的功能怎么配合?
| 功能 | 作用 | 和 read_csv/excel 的关系 |
|---|---|---|
write_csv / create_excel |
写数据 | 读侧和写侧对称,形成闭环 |
TaskStateTracker |
检查子任务完成度 | read_csv 可以作为“验证”子任务的完成条件 |
ContextCompressor |
压缩上下文 | read_csv 返回的是预览(小体积),天然友好 |
IntentPlanner |
拆子任务 | 数据分析场景下,会优先推荐 read_csv / read_excel |
举个例子:用户说“分析这个 Excel 的数据质量”。
IntentPlanner 拆出子任务:
- 读取 Excel 预览 →
read_excel - 检查空值占比 → 从
nullCounts中读取 - 总结数据质量 → 模型推理
TaskStateTracker 确保每一步都完成,才会允许 final_answer。环环相扣。
更多推荐




所有评论(0)