资讯动态

用Qwen Code Skills构建数据分析智能体:从自然语言到报表自动生成

发布时间:2026/10/22 16:09:38 来源:尧图企业网站定制
数据分析岗干久了最烦的不是写代码而是反复回答业务方的“这个数怎么来的”“上周为什么跌了”“能不能再看一眼华东区的明细”。以前我都是手动查库、导Excel、再套一个模板图表碰上口径不一样的还得翻半天聊天记录解释。后来我换了个思路与其把人肉流程反复做不如把“问数”和“出报表”这套动作交给一个能自己写代码、自己执行、自己可视化的轻量智能体。基于 Qwen Code Skills 能力搭的这个数据分析智能体现在已经能处理大部分日常数据问答并且顺手生成带结论的报表。这篇就把我踩过的坑、改过的架构、还有关键代码一步步拆给你看适合被取数需求反复折磨的数据分析师、后端工程师以及想给团队搞个内部自助分析工具的开发者。先说清楚一个基础概念Qwen Code Skills指的是让大模型不只能生成自然语言回复还能把用户的提问拆解成一段可执行的 Python 代码放到受控的沙箱环境里去运行然后根据运行结果组织最终回答。它本质上是给模型配了一只“手”——模型不再靠记忆硬编数字而是通过真实的数据计算得出答案。这一下就解决了我之前做的数据问答系统里最头疼的两个问题口径乱、结果不可验证。1. 为什么用 Code Skills 来搭数据分析智能体1.1 传统数据问答的痛点和 Code Skills 的切入点先说说我最早用过的数据问答方案如果你也做过 NL2SQL 或者关键词模板匹配一定懂那种无力感。第一版我用的是“预设 SQL 模板 关键词抽取”。我在系统里写死了几十条 SQL比如“查销售额”“查订单量”“查库存”各写一条再通过简单的文本匹配把业务方的问题映射到模板上。看起来能用实际一上线就崩。业务方问法千奇百怪“哪个月卖得最好”“哪个品类的退货率高一点”“帮我看看最近七天和上周比是涨是跌”模板根本覆盖不过来。新问题来了就得加模板加模板就要改代码、重新部署维护成本高得离谱。第二版我尝试接 NL2SQL 模型让模型直接生成 SQL 再执行。这个方案在有明确表结构的静态场景里跑得不错但一旦遇到复杂业务口径就露馅。比如业务方说的“有效订单”和表里的status字段取值并不是一一对应模型生成的 SQL 查出来经常差一截。更麻烦的是SQL 生成错了之后整个流程就得重来而模型本身不知道自己错在哪你只能肉眼一行行去对。Code Skills 解决这个问题的方式很讨巧模型不是直接生成最终 SQL而是生成一段数据处理代码这段代码可以逐步执行、中途打日志、出错后读取报错信息自己修正。换句话说模型有了“试错”的能力。它第一次可能把订单状态过滤条件写错了但跑完发现结果为空看到报错或者返回的数据行数不对就会自己调整代码再跑一次。这种“写代码 → 执行 → 看结果 → 改代码”的循环比一次性生成 SQL 要健壮得多。1.2 整体架构与核心流程我搭的这套智能体核心分三层。理解层负责把用户的自然语言问题结合业务元数据转换成一段有明确目标的 Python 代码。这里说的元数据不是表结构那么简单还包括字段中文名、枚举值含义、常用统计口径、甚至示例数据。模型读到的上下文越结构化生成的代码就越贴近业务规则。执行层是一个隔离的 Python 运行环境负责跑模型生成的代码。我用的是本地沙箱限制了内存和 CPU 配额也禁了网络访问避免模型生成的代码做出危险操作。代码里可以自由使用 pandas、numpy、matplotlib 这些库因为它们本来就是数据分析标配。表达层拿到执行结果之后负责把 DataFrame 或者图表文件转换成用户能直接看懂的答案。如果用户问的是数值类问题系统就把统计结果组织成一句话回复如果是分析类问题系统会自动生成图表并附带简要的结论说明。整个流程跑起来是这样用户提问 → 组装上下文含元数据和历史查询信息 → 调用 Qwen 模型生成 Python 代码 → 沙箱执行 → 拿到结果 → 模型根据结果生成最终回答 → 展示。关键的细节是每次会话里智能体都会记录上一轮查询产生的数据形状和字段名。这样用户问完“上个月各品类销售额”之后再追问一句“那环比呢”智能体才知道该基于哪份结果继续做计算。1.3 相比传统 BI 工具这套方案强在哪传统 BI 工具比如各种报表平台它们解决的核心问题是“把固定的指标用固定的图表展示出来”。它们的强项是美观、稳定、权限管控完善弱项是灵活度差。业务方提一个新问题往往要在工具里重新建模少则半天多则一周。Code Skills 路线最大的优势是灵活。模型自动写代码意味着只要数据进了 pandas几乎所有分析问题都能解。昨天用户还在问“各品类销售额排行”今天突然问“把销售额分成高、中、低三档分别看订单量占比”这种临时需求在传统 BI 里得重新设计计算字段而在智能体这里就是一次自然语言问答的事。当然这条路线也不是万能的。复杂的权限体系、细粒度的行级数据隔离、正式的报表审批流程这些传统 BI 仍然有绝对优势。我的建议是把两者结合Code Skills 智能体负责快速探索和临时取数传统 BI 负责正式报表的发布和管理二者并不冲突。2. 环境准备与基础配置2.1 模型选型与调用方式模型我直接选了 Qwen 系列。原因有三中文理解能力强在中文数据分析这种场景下对语气和隐含语义的把握比较准上下文窗口够大能把元数据、历史对话、当前问题一次全塞进去函数调用和代码生成能力稳定生成代码的时候不容易出现语法低级错误。调用方式有两种可选。一种是直接使用官方接口通过 SDK 调用模型服务适合快速验证想法。另一种是本地部署模型服务用开源权重跑起来适合数据敏感、不允许出内网的团队。我的建议是前期先用官方接口把流程跑通等确认了效果再考虑本地化部署。不要把第一个版本就卡在部署环境上业务价值才是第一位的。2.2 沙箱执行环境搭建代码执行沙箱是这套系统的安全底线。模型生成的代码本质上是不受信任的虽然 Qwen 生成恶意代码的概率极低但你不能赌。我用 Docker 搭了一个独立的执行环境安装了 pandas、numpy、matplotlib、openpyxl 这几个库并把容器的网络模式设为none让它没法访问外网。沙箱的调用逻辑很简单把模型生成的代码写入一个临时文件后台运行该文件超时时间设置成 30 秒。如果超时或者执行报错就把最后几行日志返回给模型让模型根据日志修正代码。这一步是整个系统能“自动纠错”的物理基础。下面是我这边的核心调用片段简化掉了一些异常分支import subprocess import uuid def run_code_in_sandbox(code: str, timeout: int 30): file_id uuid.uuid4().hex file_path f/tmp/{file_id}.py with open(file_path, w, encodingutf-8) as f: f.write(code) cmd [ docker, run, --rm, --network, none, -m, 512m, -v, f{file_path}:/tmp/user_code.py, data-agent-sandbox, python, /tmp/user_code.py ] try: result subprocess.run( cmd, capture_outputTrue, textTrue, timeouttimeout ) return { stdout: result.stdout[-2000:], stderr: result.stderr[-2000:], exit_code: result.returncode } except subprocess.TimeoutExpired: return { stdout: , stderr: execution timeout, exit_code: -1 }能看出来这个函数返回给上层的是标准输出和标准错误。上层再把这些信息拼进下一次模型调用的上下文让模型看到报错然后自己改。这个“执行反馈”设计是整个智能体最关键的一环少了它模型就是在闭眼猜答案。2.3 数据源接入与预处理数据分析智能体不直接连业务库我建议中间至少隔一层。一是安全考虑不把核心库暴露给模型生成的代码二是性能考虑避免模型写出的全表扫描把线上库拖垮。我在实践里用的是“数据快照 离线同步”的方式每天早上把需要分析的表同步到分析库按天分区存储。智能体只查询当天的快照这样既保证了数据新鲜度又隔离了线上风险。同步过程顺便做了预处理——统一字段类型、补全缺失值、把时间字段转成标准格式。这部分看似基础却直接影响模型生成代码的正确率。比如一个日期字段在源表里是字符串2024/05/11如果不预处理成标准datetime模型生成的代码大概率会在这个字段上翻车。数据量不大的团队直接用 CSV 或者 SQLite 也行。我刚开始就是拿一个 SQLite 文件做的原型简单到只需要一个连接字符串。关键是先把流程跑通再去谈规模。提示如果你打算接入真实业务库一定要做“只读账号 强制 LIMIT”双重保护。代码执行沙箱里出现的 SQL 最好都包一层外层查询限制返回行数防止模型生成SELECT * FROM 订单表这种危险查询。3. 核心功能实现数据问答与报表生成3.1 让 AI 理解业务字段元数据注入构建智能体之前我严重低估了元数据的作用。第一次试跑我让模型“算一下各城市的订单量”出来的数据看着挺正常但仔细一核对发现有几个城市被拆成了不同行政区划比如“杭州市”和“杭州余杭区”算成了两行。模型并不知道city字段里既有市级也有区级口径乱成一锅粥。解决办法是把元数据表格化在每次调用时作为固定前缀给模型。元数据表长这样字段名中文含义类型枚举/示例值统计口径说明order_date下单日期datetime2024-01-01 起按下单支付时间统计非创建时间city城市string杭州、宁波等部分数据为区级粒度聚合时谨慎category品类string手机、电脑、配件一级品类不含子类sales_amount销售额float单位元实际支付金额不含退款order_status订单状态stringpaid / refunded统计有效订单时需过滤 refunded模型拿到这张表之后生成代码的依据就从一个模糊的表名变成了清晰的字段语义。这一步对回答准确率的提升非常明显体感上能从 60% 分直接拉高到 85% 以上。注意元数据不是越多越好我见过有人把几十张表的几百个字段全塞进去结果模型注意力被分散关键字段反而被忽略。控制在当前业务域最核心的 5 到 10 个字段最合适。3.2 数据问答的实现逻辑与关键代码问答模块的链路是拼接系统提示词 元数据表 历史执行结果 → 调用 Qwen 生成代码 → 沙箱执行 → 拿结果和图表文件路径。系统提示词我调试了很多版目前最稳定的一版核心要求是这样你是一名资深数据分析师请根据用户问题生成可直接运行的 Python 代码。 要求 1. 只允许使用 pandas、numpy、matplotlib如需其他库请先说明 2. 数据文件路径已经存在于 /data/ 目录变量名为 df 3. 代码中必须打印关键统计结果如果结果是表格使用 print(df.to_string()) 输出 4. 如果用户需要图表保存图片到 /output/chart.png并保证图片内容清晰 5. 如果数据无法支持用户的问题代码内直接输出“数据不足无法回答”不要编造结论。注意我要求模型把关键结果用print(df.to_string())打出来而不是让模型自己“看一眼数据”。因为沙箱环境里模型看不到结果只有代码输出文本能传回给模型。to_string()确保所有行都会输出不会被智能截断。调用模型生成代码后紧接着就是执行刚才那个run_code_in_sandbox并把执行输出拼回给模型让它组织成用户能直接读懂的回答。这里有个细节如果模型第一次生成的代码报错不要直接把原始报错丢给用户而是带着报错信息和历史代码再让模型重写一次。多轮纠错机制能大幅提高成功率。3.3 报表自动生成从结果到可视化报表生成我采用的是“两步走”第一步让模型生成统计数据并保存成结构化结果第二步专门让模型生成图表代码。拆分的目的很单纯——让图表参数可控。如果让模型一气呵成地既算数又画图经常出现统计口径对了但图例乱写、坐标轴标签缺失的情况。图表生成我固定用 matplotlib不用 plotly原因是沙箱环境里 plotly 生成的 HTML 文件在服务端展示不太方便matplotlib 输出 PNG 直接嵌到回复里最省事。颜色方案我预置了几套统一的风格模板模型只需要调用对应模板函数不用自己自由发挥配色。给几个我认为很实用的图表选择规则时间趋势分析折线图类别对比柱状图按数值排序构成占比饼图仅限类别少于 7 个时使用否则用横向柱状图两个变量相关性散点图。这些规则直接写进报表生成的提示词里能让输出的图表风格长期保持一致不会今天出一张红蓝配色、明天出一张黄绿拼盘。4. 实战复盘一个销售数据问答 Demo4.1 造一份模拟销售数据为了方便演示我自己造了一份模拟销售数据包含订单日期、城市、品类、销售额、订单状态五个字段一共 5000 条左右。数据覆盖 2024 年 1 月到 3 月城市选了杭州、宁波、上海、北京品类选了手机、电脑、配件。这份数据的核心特点是有几个“坑”约 5% 的订单状态是refunded统计有效销售额时必须过滤city字段里混了“杭州”和“杭州余杭区”两种粒度少量记录的sales_amount是空值。我故意把这些脏数据留进去就是为了验证智能体能不能像真人分析师一样处理它们。import pandas as pd import numpy as np np.random.seed(42) n 5000 dates pd.date_range(2024-01-01, 2024-03-31, freqD) df pd.DataFrame({ order_date: np.random.choice(dates, n), city: np.random.choice( [杭州, 宁波, 上海, 北京, 杭州余杭区], n, p[0.3, 0.2, 0.2, 0.2, 0.1] ), category: np.random.choice([手机, 电脑, 配件], n), sales_amount: np.round(np.random.uniform(100, 8000, n), 2), order_status: np.random.choice( [paid, refunded], n, p[0.95, 0.05] ) }) # 人为制造缺失值 df.loc[np.random.choice(n, 50), sales_amount] np.nan df.to_csv(sales_demo.csv, indexFalse)4.2 从提问到出报表的完整过程智能体上线后的第一个测试问题我是这么问的“1 月到 2 月各品类的有效销售额是多少按销售额倒序排并生成柱状图。”调用的完整链路在我这边是封装在一个/ask接口里的。为了讲清楚我把核心的流程拆成三步第一步组装对话上下文。除了用户问题我还把元数据表和上一次查询的输出字段结构附带上去。这一个版本里没有历史对话所以上下文主要由元数据构成。第二步调用 Qwen 生成代码。模型生成的代码大致思路是读取 CSV → 过滤order_status paid→ 过滤时间范围 → 按品类分组求和 → 排序 → 画图。模型很自然地把“有效销售额”理解成了需要过滤退款订单这一步和我的预判一致。第三步沙箱执行并返回结果。图表文件生成成功后模型在最终回答里写了一段带结论的文本大意是“1-2 月电脑品类有效销售额最高约 X 万元手机次之配件最低”。我看了一眼结果和手算的完全一致图表配色、标题、数据标签也都正常。这个回答里最让我满意的不是数据准确而是表述的口径很清楚——“有效销售额”这个词被准确翻译成了“排除退款订单后的销售额”说明元数据里的口径说明真正起到了作用。4.3 中间踩过的坑与调整这个 Demo 不是一次成功的。第一版跑起来后我发现一个频发问题当用户追问“占比呢”或者“那环比呢”的时候系统会直接报错因为模型不知道“占比”是相对哪个总体的占比也拿不到上一轮查询的结果数据。我当时的解决办法很笨但有效系统里维护一个会话级的状态记录上一轮查询输出的字段列表、行数和汇总结果摘要。每次组装模型调用上下文时除了用户当前的提问也把上一轮的状态摘要放进去。模型看到上一轮按品类分组了自然就知道“占比”是指各品类销售额占整体销售额的比例。另一个坑是图表中文乱码。沙箱环境里默认字体没有中文字体matplotlib 画出来的图全是方块。这个问题的修复是在 Docker 镜像里安装中文字体并设置rcParams的字体参数。别小看这个问题我第一次演示给团队看的时候图表上一片乱码观感非常拉胯当场就被嘲笑了。5. 常见问题排查与性能优化5.1 高频报错与解决方案速查表跑了几个月之后我把遇到的报错整理成了一张速查表分享出来应该能帮你少走不少弯路。现象根因解决方案模型生成代码中df变量未定义模型假设数据已经读取但脚本开头没有读文件在提示词里明确写出df pd.read_csv(/data/sales_demo.csv)这一行日期筛选后数据为空日期字符串和 datetime 类型比较出错预处理阶段统一转成 datetime并在元数据里标明格式图表中文显示为方块沙箱缺少中文字体安装 fonts-noto-cjk使用 rcParams 配置默认字体数据量太大执行超时全表计算耗时超过 30 秒预处理阶段做采样或聚合把数据量控制在单次可计算范围多轮追问答非所问上下文缺少上一轮结果状态维护会话状态把上一轮输出结构拼入上下文结果数值和业务对不上有效口径没被模型识别在元数据中给关键字段加“统计口径说明”5.2 回答不准、幻觉多的处理技巧数据分析智能体最怕的是“一本正经地胡说八道”。模型算出结果是 100它却说“大约增长了 50%”这种幻觉如果是给高层汇报用后果很严重。针对这个问题我给模型定了一条铁律所有结论必须引用代码输出中的具体数值禁止自己“推算”比例、增幅等衍生指标。如果代码输出里没有某条数据模型只能说“数据不足以支持该结论”不准自行推断。这条约束实际执行下来幻觉率降了不少。但我建议你也不要有太高期待自然语言生成模型始终存在不确定性。我在系统里加了一个“证据回显”的机制最终回答下方自动附上关键计算过程的代码片段和图表。用户如果对某个数有疑问可以顺着代码去核对。这个机制带来的信任感比单纯的准确率提升更管用。5.3 长表格与大数据量场景的优化随着数据量增长pandas 全量加载的方式很快就撑不住了。我试过直接让智能体分析一张两千万行的订单表模型生成代码后沙箱内存直接被打满进程被杀。后来我做了三层优化。第一层是预聚合。大多数业务问答并不需要明细数据只需要按维度聚合后的结果。我在数据同步阶段就按“日期 城市 品类”做了预聚合把两千万行压缩到几十万行分析速度立刻上来了。第二层是查询裁剪。在提示词里要求模型先查看数据的行数和列名再决定是否需要全表扫描。对于涉及长时间范围的问题鼓励模型先按月份切分逐段处理避免一次性载入所有数据。第三层是结果缓存。同一个用户在短时间内重复问类似问题时系统直接返回上一次的结果缓存。我实现了一个简单的时间戳缓存TTL 设为 5 分钟。这个优化虽然技术含量不高但对体感的提升非常明显业务方连续调整问题口径的时候几乎感觉不到等待。注意做预聚合的时候一定要保留一个“明细口径”字段比如is_refunded的计数列否则后续想算退货率的时候会发现分母已经丢了。我在这个坑里栽过一次后来所有聚合表都保留计数列宁可多占一点存储也不能丢失统计能力。写在最后这套基于 Qwen Code Skills 的数据分析智能体前前后后迭代了好几版从最开始只会回答“总额是多少”的玩具到现在能处理带口径、带追问、带图表的复杂场景。我个人最深的一个体会是智能体能不能用关键在于你有没有把业务知识注入进去。模型本身很聪明但如果你不告诉它“有效订单要排除退款”“城市字段有粒度混杂”它再聪明也只能靠猜。如果你也想搭一个类似的东西我的建议是从最小闭环开始一张表、一份模拟数据、一次核心问答流程跑通之后再加图表、再加多轮会话、再接真实数据源。别一上来就设计一个包含十个模块的宏大系统那只会让你陷入配置地狱。等核心流程稳定了后面的一切都是顺势而为。最后再分享一个小技巧模型生成的代码里我要求它每一步操作都打印一份简短的# 当前数据行数注释和实际输出比如print(len(df))。这看起来冗余但在排查问题是真有用——模型能通过行数变化判断出过滤条件有没有生效相当于给了它一双实时观察数据流动的眼睛。这个小改动让自动纠错的成功率明显上了一个台阶我建议你直接抄走。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑