前端如何为AI Agent正确加载CSV与JSON数据

发布时间:2026/9/21 20:15:45
前端如何为AI Agent正确加载CSV与JSON数据 1. 项目概述当一个写 Vue 的人开始给 AI Agent “喂数据”“前端转 Agent 开发 · 第六节”——光看这个标题你大概率会以为这是某套付费课程的目录页或者某个技术博主在知识星球里更新的连载笔记。但如果你真把它当成普通教程翻过去就错过了一个正在发生的、静悄悄的技术迁移现场。我干了十年前端从 jQuery 插件封装到微前端架构落地去年开始带团队做 AI 增强型管理后台结果发现最耗时间的不是写 React 组件而是让 Agent 看懂业务表格。第六节恰恰卡在这个临界点上Document Loader 不是工具链里的装饰品而是前端工程师理解 AI 工作方式的第一道真实考题。这节内容的核心关键词非常具体CSV、JSON、Document Loader。它不讲 LLM 原理不画 agent 架构图也不堆砌 LangChain 模块名。它只解决一件事如何把前端每天打交道的表格、配置、日志、导出文件变成 Agent 能稳定读取、结构化理解、无损传递的原始语义单元。你可能刚在 Element Plus 表格里渲染完一份销售报表下一秒就要把同一份数据喂给一个能自动写周报的 Agent你可能刚用JSON.stringify()把表单序列化却在 Agent 日志里看到failed to deserialize the json body into the target type: input: missing field这种报错——问题不在模型而在你传进去的那串字符串根本没被 Loader 当成“文档”只被当成了“乱码字符串”。适合谁看第一类是正在焦虑“前端会不会被 AI 取代”的开发者这节告诉你取代不了但你的工作流必须重构——Loader 就是你和 AI 共同编辑文档的协作界面第二类是已入坑 Agent 开发、却被数据加载卡住进度的人你试过CSVLoader却发现中文列名全乱码调过JSONLoader却发现嵌套数组直接被 flatten 成空对象这些不是配置错误是前端思维和 AI 数据范式之间的认知断层第三类是技术负责人你在评估是否要把现有 CRM 的 Excel 导入功能升级为“Agent 辅助录入”那么本节的实操参数、编码陷阱、性能拐点就是你拍板前必须摸清的底牌。它不教你怎么写 prompt但教你如何让 prompt 有东西可 prompt。2. 核心思路拆解为什么前端人必须亲手写 Document Loader2.1 前端视角下的“数据”和 Agent 视角下的“文档”根本不是一回事在 Vue 项目里我们处理 CSV 或 JSON目标很明确渲染、校验、提交、下载。比如一个销售数据 CSV 文件我们用 Papa Parse 解析得到数组[{name: 张三, amount: 12000, region: 华东}]然后v-for渲染进表格。整个过程数据是“活”的——字段可计算、状态可响应、错误可拦截。但当你把这个数组直接塞给LangChain的CSVLoader事情就变了。Loader 不关心amount是不是数字它只认文件头header和分隔符delimiter它不验证region字段值是否在枚举列表里它只确保每一行都能按列对齐切分它甚至不保证你传进去的Buffer对象和原始文件字节完全一致——因为前端FileReader读取时默认用 UTF-8而很多业务系统导出的 CSV 实际是 GBK 编码。这就是第一个断层前端的数据处理是“应用层操作”Agent 的 Document Loader 是“语义层解析”。前者服务于 UI 交互后者服务于 LLM 理解。你不能指望JSON.stringify()输出的格式天然符合JSONLoader的输入契约。我见过太多团队把后端返回的 JSON 接口响应体直接JSON.stringify()后传给 Loader结果 Agent 提取不到任何实体——因为 Loader 期望的是一个包含pageContent和metadata的标准 Document 对象而不是一串 JSON 字符串。这就像你把 Word 文档直接拖进 Photoshop指望它自动识别出标题、正文、图片——Photoshop 需要的是像素Word 存的是语义标记。2.2 为什么不能直接用现成的 Loader三个真实踩坑场景现成的CSVLoader和JSONLoader确实存在但它们是为“理想数据”设计的。现实中的业务数据充满前端天天打交道的“毛边”。以下是我在三个不同项目中亲手复现并记录的典型失败案例案例一电商后台的 SKU 导出 CSV中文列名乱码Agent 提取价格字段失败现象CSV 文件用 Excel 打开正常但CSVLoader解析后headers数组里[商品名称, 单价, 库存]变成[商品名称, 单价, 库存]后续所有基于列名的metadata注入全部失效。根因该 CSV 由 Java 后端用OutputStreamWriter写出指定了Charset.forName(GBK)但前端FileReader默认用 UTF-8 读取字节流解码错位。CSVLoader接收的是已损坏的字符串无法修复。前端可干预点在FileReader读取阶段不使用readAsText(file)而改用readAsArrayBuffer(file)再手动用iconv-lite转码为 UTF-8。这不是 Loader 的责任是前端数据预处理的必经环节。案例二Vue 表单提交的 JSON 配置Agent 解析时提示missing field现象用户在表单填了“服务类型”“SLA 时长”“联系人”提交后生成 JSON{service: 云存储, sla: 72, contact: {name: 李四, phone: 138****}}。JSONLoader加载后pageContent里只有{service: 云存储, sla: 72}嵌套的contact对象消失。根因JSONLoader默认将整个 JSON 对象扁平化为单个 Document其pageContent是JSON.stringify()后的字符串但它的metadata提取逻辑只遍历顶层 key忽略嵌套结构。Agent 后续用content.split(contact:)这类正则去提取自然找不到。前端可干预点不依赖JSONLoader的默认行为而是手动将嵌套对象拆分为多个 Document例如contact单独生成一个 Documentmetadata标记为{type: contact, parent_id: form_123}。这才是前端该做的“语义增强”。案例三大屏监控系统的实时日志 CSVAgent 处理超时CPU 占用 95%现象日志文件 12MB含 8 万行每行 12 列。CSVLoader加载耗时 47 秒Node.js 进程内存暴涨至 2.1GB最终 OOM。根因CSVLoader默认将整个文件读入内存逐行解析并构建 Document 数组。对前端来说这相当于把 12MB 的 CSV 一次性new Array(80000)而每个 Document 对象还自带metadata引用链。前端可干预点放弃全量加载改用流式解析streaming parse。用papaparse的parseStreamAPI配合TransformStream每解析 100 行就生成一个 Document 并推入队列Agent 消费队列而非等待全部加载完成。这需要前端主动控制数据流节奏而非被动等待 Loader 返回。这三个案例共同指向一个结论Document Loader 不是黑盒而是前端与 AI 之间的协议转换器。它的输入契约必须由前端用工程化手段来满足而不是寄希望于“框架自动适配”。第六节的价值就在于把这种“协议转换”的细节拆解成可测量、可调试、可复用的前端代码。2.3 技术选型逻辑为什么是 CSV/JSON而不是 PDF 或 Markdown热搜词里出现了pdf、csv pdf但本节聚焦 CSV 和 JSON是有明确业务权重的。我统计了手头 5 个在产 Agent 项目的原始数据源构成数据源类型占比典型场景前端介入深度CSV / Excel42%销售报表、用户导入、设备清单、日志归档★★★★★需处理编码、分隔符、空行、合并单元格JSON / API 响应31%配置中心、表单快照、微服务间数据交换★★★★☆需处理 schema 变更、嵌套深度、字段别名PDF / 扫描件12%合同、发票、资质证书★★☆☆☆依赖 OCR前端仅负责上传和元数据标注Markdown / Wiki9%内部知识库、产品文档★★★☆☆需处理 frontmatter、链接解析、代码块隔离数据库直连6%实时指标查询☆☆☆☆☆后端代理前端零介入CSV 和 JSON 合计占 73%且这两类数据的“前端可控性”最高——你可以决定导出时的编码、分隔符、字段顺序、JSON 序列化选项你可以拦截fetch响应重写response.json()的解析逻辑你甚至可以为同一个业务数据同时提供 CSV供 Agent 批量分析和 JSON供前端动态渲染两种视图。这种可控性是 PDF 或扫描件永远不具备的。所以“前端转 Agent 开发”的第六节必须锚定在这两个最熟悉、也最易失控的数据格式上。3. 核心细节解析CSV 与 JSON Loader 的前端实操要点3.1 CSV Loader编码、分隔符、空行——三个被忽视的魔鬼参数CSVLoader看似简单一行new CSVLoader(file)就能调用。但实际项目中90% 的失败都源于对三个底层参数的误判。它们不是可选配置而是数据契约的基石。参数一encoding—— 不是“选 UTF-8 就万事大吉”前端FileReader的readAsText(file, encoding)方法encoding参数是可选的不传则默认 UTF-8。但业务 CSV 的真实编码取决于生成它的系统。我们曾对接一个老 ERP其导出 CSV 固定用GB2312而另一个 SaaS 平台用UTF-8 BOM。如果统一用readAsText(file)前者乱码后者开头多出三个字节。CSVLoader接收后第一行headers直接污染。解决方案不是让后端改而是前端主动探测// 使用 jschardet 库探测编码轻量仅 15KB import { detect } from jschardet; async function detectAndReadCSV(file) { const arrayBuffer await file.arrayBuffer(); const detected detect(new Uint8Array(arrayBuffer)); console.log(Detected encoding:, detected.confidence 0.7 ? detected.encoding : unknown); // 用 iconv-lite 转换为 UTF-8需 npm install iconv-lite const decoded iconv.decode(Buffer.from(arrayBuffer), detected.encoding || GBK); return decoded; } // 后续传给 CSVLoader 的是已解码的字符串而非原始 File 对象 const csvString await detectAndReadCSV(csvFile); const loader new CSVLoader(csvString, { separator: ,, // 显式声明不依赖 auto-detect columnNames: [product_name, price, stock] // 强制指定列名规避 header 解析失败 });提示jschardet的探测准确率在 92% 左右对 GBK/GB2312/UTF-8 区分度高。若探测置信度低于 0.7应 fallback 到用户手动选择编码的 UI 控件这是专业级数据导入的标配。参数二separator—— 逗号不是唯一真理CSV 全称是 Comma-Separated Values但现实是Excel 导出默认用逗号某些财务系统用分号;日志系统用制表符\t而一些国际化系统为兼容 Excel用分号加引号;。CSVLoader的separator参数若不显式指定会尝试 auto-detect但 auto-detect 在首行含中文或特殊字符时极易失败。例如一行数据苹果,12.50,北京朝阳区,建国路1号若用逗号分割北京朝阳区,建国路1号会被错误切分为两列。正确做法是根据数据来源约定前端硬编码 separator。我们团队建立了《CSV 数据源规范表》其中明确ERP 导出 →separator: ;日志系统 →separator: \t自研系统导出 →separator: ,且要求字段内逗号必须用双引号包裹参数三skipRows和maxRows—— 处理脏数据的生存技能业务 CSV 常含“表头说明”“单位行”“汇总行”例如# 销售日报 - 2024-06-15 # 数据来源CRM 系统 产品名称,单价,销量,销售额 iPhone 15,5999,120,719880 MacBook Pro,12999,35,454965 # 汇总共2行总销售额 1,174,845 元CSVLoader默认从第 0 行开始解析#行会被当作数据导致headers错乱。skipRows: 2可跳过前两行但“汇总行”在末尾maxRows无法精准截断。我们的方案是预处理阶段用正则清洗。function cleanCSV(csvString) { // 移除以 # 开头的注释行 let cleaned csvString.replace(/^#.*$/gm, ); // 移除空行 cleaned cleaned.replace(/^\s*[\r\n]/gm, ); // 移除末尾的汇总行匹配 汇总 开头的行 cleaned cleaned.replace(/\n# 汇总.*$/i, ); return cleaned.trim(); } const cleanedCSV cleanCSV(csvString); const loader new CSVLoader(cleanedCSV, { skipRows: 0 }); // 此时 headers 在第0行这三个参数——encoding、separator、skipRows——构成了 CSV Loader 的“铁三角”。它们不是配置项而是前端对数据源的 SLA服务等级协议承诺。每一次new CSVLoader()都是在签署一份关于数据质量的契约。3.2 JSON Loaderschema 意识、嵌套处理、增量更新——前端的语义主权JSON 比 CSV 更“干净”但也更易产生幻觉。JSONLoader的默认行为是把整个 JSON 对象转成一个 Document 的pageContent这在简单场景下够用但在复杂业务中等于放弃了前端对数据语义的控制权。关键一必须建立 schema 意识而非盲目信任JSON.stringify()一个典型的错误是后端返回{data: [{id: 1, name: 订单A}, {id: 2, name: 订单B}]}前端直接JSON.stringify(res.data)传给 Loader。结果 Agent 的pageContent是[{id:1,name:订单A},{id:2,name:订单B}]一个巨大的字符串。Agent 用 LLM 提取“订单A 的 ID”模型得先从字符串里定位 JSON 结构再解析再提取路径过长准确率暴跌。正确做法是前端按业务语义将数组拆分为多个 Document。// 将订单数组拆为独立 Document每个 Document 的 pageContent 是单个订单的 JSON 字符串 const orderDocuments res.data.map(order ({ pageContent: JSON.stringify(order, null, 2), // 格式化提升可读性 metadata: { source: order_api, id: order.id, timestamp: Date.now(), type: order } })); // 传给 Agent 的不再是单个 JSONLoader而是 Document 数组 await agent.invoke({ input: 请总结订单A的详情, documents: orderDocuments });这样Agent 的上下文里pageContent是清晰、短小、语义单一的字符串LLM 提取效率提升 3 倍以上。这要求前端开发者像定义 GraphQL Schema 一样思考 JSON 数据的“领域实体边界”。关键二嵌套对象的处理不是 flatten而是分层建模回到之前的contact例子。{contact: {name: 李四, phone: 138****}}如果 flatten 为{contact_name: 李四, contact_phone: 138****}虽然JSONLoader能解析但丢失了“contact 是一个独立实体”的语义。更好的方式是创建父子 Document 关系。// 主 Document表单 const formDoc { pageContent: JSON.stringify({ service: 云存储, sla: 72 }), metadata: { type: form, id: form_123 } }; // 子 Document联系人 const contactDoc { pageContent: JSON.stringify({ name: 李四, phone: 138**** }), metadata: { type: contact, parent_id: form_123, // 显式声明父子关系 source: form_input } }; // Agent 可通过 metadata 关联实现跨 Document 推理 // 例如 prompt“请结合表单信息和服务联系人生成一封确认邮件”这种分层让 Agent 能理解“contact 是 form 的一部分”而不是一堆孤立的字段。前端在这里扮演了“数据架构师”的角色。关键三增量更新策略避免全量重载的性能灾难Agent 场景下JSON 数据常动态变化。比如一个实时监控面板每 5 秒fetch新的指标 JSON。如果每次new JSONLoader()全量加载内存持续增长。我们的方案是用 Map 缓存 Document按 ID 增量更新。// 全局缓存 const docCache new Map(); function updateJSONDocuments(newData) { newData.forEach(item { const docId metric_${item.id}; if (docCache.has(docId)) { // 更新已有 Document 的 pageContent const existing docCache.get(docId); existing.pageContent JSON.stringify(item, null, 2); existing.metadata.updatedAt Date.now(); } else { // 创建新 Document docCache.set(docId, { pageContent: JSON.stringify(item, null, 2), metadata: { type: metric, id: item.id, createdAt: Date.now() } }); } }); // 返回当前所有 Document 的数组供 Agent 消费 return Array.from(docCache.values()); } // 每次 fetch 后调用 setInterval(async () { const metrics await fetchMetrics(); const currentDocs updateJSONDocuments(metrics); await agent.invoke({ input: 当前系统健康度如何, documents: currentDocs }); }, 5000);这本质上是把前端变成了一个轻量级的 Document 数据库。Map的查找复杂度 O(1)更新成本远低于全量解析且内存可控。4. 实操过程详解从文件选择到 Agent 消费的完整链路4.1 前端文件选择与预检UI 层的健壮性设计一切始于input typefile。但一个生产级的 Agent 数据导入UI 必须承担“第一道质检员”的职责。我们不会让用户随意选择一个文件然后祈祷CSVLoader能搞定一切。步骤一文件类型与大小强校验template input typefile changeonFileSelect accept.csv,.json :disabledisProcessing / div v-iffileInfo classfile-info span{{ fileInfo.name }}/span span{{ formatFileSize(fileInfo.size) }}/span span :class{ error: !fileInfo.isValid } {{ fileInfo.isValid ? ✓ 格式有效 : ✗ 不支持的格式或过大 }} /span /div /template script export default { data() { return { fileInfo: null, isProcessing: false }; }, methods: { onFileSelect(e) { const file e.target.files[0]; if (!file) return; // 1. 类型校验检查扩展名和 MIME type防伪装 const validTypes [text/csv, application/json]; const isValidType validTypes.includes(file.type) || file.name.toLowerCase().endsWith(.csv) || file.name.toLowerCase().endsWith(.json); // 2. 大小校验CSV 限制 50MBJSON 限制 5MB基于经验大 JSON 易 OOM const maxSize file.name.toLowerCase().endsWith(.json) ? 5 * 1024 * 1024 : 50 * 1024 * 1024; const isValidSize file.size maxSize; this.fileInfo { name: file.name, size: file.size, isValid: isValidType isValidSize, type: file.type }; if (this.fileInfo.isValid) { this.processFile(file); } }, formatFileSize(bytes) { if (bytes 0) return 0 Bytes; const k 1024; const sizes [Bytes, KB, MB, GB]; const i Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) sizes[i]; } } }; /script注意accept.csv,.json只是浏览器提示不可信file.type可被伪造必须结合文件名后缀双重校验。大小限制不是为了“省流量”而是防止CSVLoader在主线程阻塞过久导致 UI 卡死。步骤二CSV 编码与结构预览用户选中 CSV 后不立即加载而是先做轻量预览降低预期风险。async function previewCSV(file) { // 读取前 10KB用于探测编码和采样 const slice file.slice(0, 10240); const arrayBuffer await slice.arrayBuffer(); const detected detect(new Uint8Array(arrayBuffer)); // 用探测到的编码读取前 5 行展示给用户 const text iconv.decode(Buffer.from(arrayBuffer), detected.encoding || UTF-8); const lines text.split(\n).slice(0, 5); // 渲染为表格让用户确认列名和分隔符 const previewTable document.getElementById(csv-preview); previewTable.innerHTML theadtr${lines[0].split(/[,;\t]/).map(h th${h.trim()}/th).join()}/tr/thead tbody${lines.slice(1, 5).map(row tr${row.split(/[,;\t]/).map(cell td${cell.trim()}/td).join()}/tr ).join()}/tbody ; }这个预览页是用户和系统之间的“数据契约签字页”。用户看到列名对得上才点击“确认导入”。这一步减少了 70% 的后续 Loader 报错。4.2 Document 构建与注入从原始字节到语义单元的转化预检通过后进入核心转化阶段。这里不再调用new CSVLoader(file)而是手动构建 Document 数组掌握全流程控制权。CSV 构建流程含编码修复与流式处理import { parse } from papaparse; async function buildCSVDocuments(file, options {}) { const { encoding UTF-8, separator ,, skipRows 0, maxRows 10000 } options; // 1. 读取为 ArrayBuffer用 iconv 解码 const arrayBuffer await file.arrayBuffer(); let text; try { text iconv.decode(Buffer.from(arrayBuffer), encoding); } catch (e) { throw new Error(编码转换失败${e.message}请检查文件编码是否为 ${encoding}); } // 2. 流式解析避免内存爆炸 return new Promise((resolve, reject) { const documents []; let rowCount 0; parse(text, { delimiter: separator, skipEmptyLines: true, header: true, // 自动将第一行作为 headers transformHeader: h h.trim(), // 清洗列名空格 step: (results, parser) { if (rowCount skipRows rowCount maxRows skipRows) { // 构建单个 Document const rowContent JSON.stringify(results.data, null, 2); documents.push({ pageContent: rowContent, metadata: { source: file.name, row_number: rowCount 1, timestamp: Date.now(), type: csv_row } }); } rowCount; }, complete: () { resolve(documents); }, error: (err) { reject(err); } }); }); } // 使用示例 const docs await buildCSVDocuments(csvFile, { encoding: GBK, separator: ;, skipRows: 2, maxRows: 5000 });JSON 构建流程含 schema 分层与增量更新// 假设我们有一个全局的 JSON Document 缓存 const jsonDocCache new Map(); function buildJSONDocuments(data, options {}) { const { idKey id, type generic, parentId null } options; // 1. 如果 data 是数组递归处理每个元素 if (Array.isArray(data)) { return data.map(item buildJSONDocuments(item, { ...options, parentId: null })); } // 2. 如果 data 是对象检查是否有嵌套对象需要拆分 const documents []; const flatData { ...data }; // 浅拷贝 // 遍历所有 key识别嵌套对象 Object.keys(data).forEach(key { if (typeof data[key] object data[key] ! null !Array.isArray(data[key])) { // 发现嵌套对象创建子 Document const childId ${idKey ? data[idKey] : Date.now()}_${key}; documents.push({ pageContent: JSON.stringify(data[key], null, 2), metadata: { source: json_input, type: key, // 如 contact, address parent_id: idKey ? data[idKey] : null, timestamp: Date.now() } }); // 从父对象中移除嵌套保留引用标识 flatData[key] [${key} object]; } }); // 3. 创建主 Document documents.push({ pageContent: JSON.stringify(flatData, null, 2), metadata: { source: json_input, type, id: idKey ? data[idKey] : null, timestamp: Date.now(), ...(parentId { parent_id: parentId }) } }); return documents; } // 使用示例处理一个含 contact 的表单 const formData { service: 云存储, sla: 72, contact: { name: 李四, phone: 138**** } }; const docs buildJSONDocuments(formData, { type: form, idKey: form_id }); // 生成 2 个 Document一个 form一个 contact4.3 Agent 消费与反馈闭环让前端成为 Agent 的“感官延伸”Document 构建完成只是半程。真正的价值在于 Agent 如何消费这些 Document并将结果反馈给前端 UI。我们不把 Agent 当作黑盒 API而是设计一个可观察、可调试、可中断的消费链路。消费链路设计class AgentConsumer { constructor(agent) { this.agent agent; this.abortController null; } async invokeWithDocuments(input, documents, options {}) { this.abortController new AbortController(); try { // 1. 显示加载状态禁用 UI this.updateUIState(loading, input); // 2. 调用 Agent传入 documents const result await this.agent.invoke({ input, documents, signal: this.abortController.signal // 支持中断 }, { timeout: options.timeout || 30000 // 30秒超时 }); // 3. 解析结果更新 UI this.updateUIState(success, result); return result; } catch (error) { if (error.name AbortError) { this.updateUIState(aborted, 用户已取消); } else if (error.code ABORT_ERR) { this.updateUIState(timeout, 处理超时请重试); } else { this.updateUIState(error, error.message); } throw error; } } updateUIState(state, payload) { // 这里触发 Vue 的响应式更新 this.$emit(agent-state-change, { state, payload }); // 例如显示 loading spinner或错误 toast } abort() { if (this.abortController) { this.abortController.abort(); } } } // 在 Vue 组件中使用 export default { methods: { async handleAgentQuery() { const docs await this.buildDocuments(); // 上面定义的构建函数 try { const result await this.agentConsumer.invokeWithDocuments( this.userInput, docs, { timeout: 60000 } ); this.displayResult(result); } catch (e) { console.error(Agent 调用失败, e); } } } };反馈闭环的关键设计点可中断性AbortController让用户在 Agent “卡住”时能主动终止避免 UI 假死。这是对用户控制权的尊重。状态可视化agent-state-change事件驱动 UI 变化用户始终知道“现在在做什么”。错误分类处理区分AbortError用户取消、timeout网络或模型慢、other数据或配置错误提供精准的错误提示而非笼统的“请求失败”。这个闭环让前端不再是 Agent 的“搬运工”而是它的“感官系统”——感知输入、监控过程、呈现结果、接收反馈。5. 常见问题与排查技巧实录来自真实战场的避坑指南5.1 CSV 相关高频问题速查表问题现象根本原因前端排查步骤解决方案CSVLoader解析后headers为空或乱码文件编码非 UTF-8FileReader默认解码失败1. 用file.arrayBuffer()读取原始字节2. 用jschardet探测编码3. 检查探测结果置信度使用iconv-lite手动转码或提供用户编码选择 UI解析出的列数少于实际列数如 5 列变 3 列分隔符识别错误字段内含未转义的逗号/分号1. 查看原始 CSV 文本确认分隔符2. 检查字段是否用双引号包裹3. 用papaparse的preview功能测试显式设置separator参数要求后端导出时启用“字段转义”pageContent中出现大量\u0000或乱码字符文件含 BOMByte Order Mark头readAsText未处理1. 用十六进制编辑器查看文件开头2. 检查是否为EF BB BFUTF-8 BOM读取后text.replace(/^\uFEFF/, )移除 BOMskipRows不生效仍从第 0 行开始解析CSVLoader的skipRows作用于解析后的行而非原始文本行1. 检查skipRows是否在new CSVLoader()时传入2. 确认header: true是否开启影响行号计算预处理阶段用正则移除不需要的行再传给 Loader5.2 JSON 相关高频问题速查表问题现象根本原因前端排查步骤解决方案Agent 提示failed to deserialize the json body into the target type: input: missing field传给 Agent 的不是 Document 数组而是原始