ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python电商订单数据可视化分析系统:Django与大模型Agent实战

Python电商订单数据可视化分析系统:Django与大模型Agent实战 每年到毕业季总有一堆人对着计算机毕业设计这几个字头疼。选题太老吧答辩老师看一眼就没兴趣选题太新吧又怕自己hold不住。今天分享一个我实际带过多次、也在线上跑通的选题方向Python电商订单数据可视化分析系统基于Django框架把数据分析、可视化、大模型和Agent串起来做。这套东西不是空中楼阁是真能跑、真能演示、也真能讲出东西的。先把这个系统是啥说清楚你有一个电商平台产生的订单数据可以是模拟数据也可以是爬虫抓的公开数据然后用Django写后端把订单数据做清洗、聚合、统计通过API给前端提供数据前端用ECharts等可视化库做成大屏看板展示销售额趋势、商品排行、用户画像这些东西再往上加一层接一个大模型接口让用户直接用中文问上个月卖得最好的三个商品是什么系统自动从数据库里查出结果并返回答案。这个设计的好处是既有传统的数据处理和可视化展示又有当前热门的大模型应用场景技术深度够答辩时能讲的东西非常多。这篇文章面向的读者我觉得有三类人一是正在选题的计算机专业学生想把毕设做得既有工作量又有亮点二是想快速上手Django数据分析项目的开发者需要一个完整的参考路径三是对大模型落地应用感兴趣的同行想看看除了聊天机器人之外大模型还能怎么接到业务系统里。不论你是哪类下面这套从设计到落地的完整拆解都值得收藏。1. 项目概述与整体设计思路1.1 这个系统到底解决什么问题电商平台的订单数据是典型的高价值密度低的数据。一个平台每天产生几千几万条订单但真正对运营决策有用的信息比如哪些商品卖得好、哪个时段下单最集中、哪些地区的用户购买力强需要经过清洗、聚合、统计才能真正看出来。这个系统的核心目标就是把这串原始订单数据变成一眼能看懂的可视化图表再用自然语言问答的方式降低看数据门槛。换句话讲系统解决的是三个层面的问题。第一个层面是数据管理订单数据散落在Excel或数据库里查询和统计都要写SQL或者手动操作系统把这些集中起来并提供统一接口第二个层面是分析展示光有数据不够得画出来销售额趋势用折线图、商品排行用柱状图、品类占比用饼图让决策者扫一眼大屏就知道当前经营状况第三个层面是智能交互非技术用户不会写SQL但他们想知道江浙沪地区上周的客单价是多少这时候大模型语言接口就派上用场把用户的问题翻译成数据查询动作并组织成自然语言回答。这些需求叠在一起就决定了系统的整体形态一个B/S架构的数据应用后端负责数据存取和业务逻辑前端负责可视化展示和人机交互中间用API通信。Django在这个场景下非常合适它的ORM让数据模型定义变得简洁自带Admin后台可以直接管理数据模板和DRFDjango REST Framework又能快速搭出接口层对整个开发周期来说非常友好。1.2 技术选型背后的逻辑Django、MySQL、ECharts、DeepSeek这套组合是我反复校验过的每个选择都有明确的理由。先说Django框架。网上经常有人争论Flask轻量、FastAPI高性能为什么选Django核心原因是毕设或者中小型数据项目要的不是极致性能而是完善的配套和整体的开发效率。Django自带Admin后台数据模型一定义后台管理页面就出来了演示的时候可以直接进去增删改查这个效果在答辩现场非常加分ORM抽象层让你不需要手写大量SQL定义好模型类就能做复杂查询模板引擎加上Django的静态文件管理部署起来也比前后端彻底分离的方案简单。如果你的项目经验里没有大型前端工程的需求用Django经典的服务端渲染配合部分Ajax动态刷新完全够用而且架构清晰逻辑好讲。再说可视化技术选型。目前主流有ECharts、AntV G2Plot、Chart.js、D3.js这么几个方向。我的建议是ECharts理由很直接文档最全、例子最多、对中文场景的支持最好而且地图组件可以直接用中国地图GeoJSON做地域分布分析的时候特别方便。ECharts是纯前端库通过npm或者CDN引入即可后端只需要提供规范的JSON数据结构两端通过接口对好字段名就能画出图来。然后是大模型的选型。标题里提到的DeepSeek是目前国内开源开放做得比较成熟的模型它的API兼容OpenAI的调用格式用Python的openai库或者直接requests都能调非常轻量。更重要的是它支持function calling函数调用这让Agent的实现路径变得非常顺你给模型定义好有什么工具可以用模型收到用户问题后自己决定调用哪个工具、传什么参数然后你执行工具返回结果模型再把结果整理成人话。整个过程不用微调模型纯靠提示词和工具定义就能完成一个可用的数据分析Agent。关于这部分后面有一整节专门讲。最后补充一句关于数据库的思考。毕设级别的数据量通常是几万到几十万条MySQL完全够用。但如果你想在系统设计里体现大数据的要素可以在数据导入环节引入Pandas做批量处理在架构说明里预留一个数据量增大后可迁移到ClickHouse/StarRocks的扩展点即可。你要清楚毕设的核心是让老师看到你掌握了方案选型的判断力而不是真的在单机环境里跑一个分布式集群。2. 电商数据建模与预处理2.1 订单核心表结构设计任何数据分析系统的地基都是数据模型。电商订单领域最核心的几张表我建议这样设计订单主表、订单明细表、商品表、用户表、支付流水表再加一个地区维度表辅助地域分析。订单主表存的是每一笔订单的概要信息字段包括订单号order_no、用户IDuser_id、订单状态status取值如已支付/已发货/已完成/已取消、下单时间create_time、支付时间pay_time、订单金额total_amount、实付金额pay_amount、优惠金额discount_amount、收货省份province、收货城市city、支付方式payment_method。这里面需要注意订单金额和实付金额必须分开存因为电商场景里促销满减太常见统计的时候两套口径各有用途。订单明细表呢一个订单可能包含多个商品所以明细表要记录订单IDorder_id、商品IDproduct_id、商品数量quantity、商品单价price、小计金额subtotal。为什么要单独建明细表因为商品维度的分析哪个商品卖得多、哪个SKU贡献了多少营收必须靠明细表聚合才能算出来如果你把所有商品信息瘫在主表里一个多商品订单的数据就会非常冗余而且很难做商品级拆分。商品表用来存商品本身的属性商品名、类目category、品牌brand、上架时间、销售状态、成本价可选、标签。商品表的意义在于给聚合结果关联商品信息比如算出销售额TOP10的商品之后你得回到商品表把这些商品的类目、品牌拿出来才能进一步做类目占比这种分析。用户表存用户的基础信息用户名、注册时间、性别如果有、年龄区间、会员等级。用户表的加入让用户画像和复购分析成为可能。不过实际做的时候要注意电商数据里的用户信息往往不全性别年龄大把空值这种情况要么用随机值填充模拟要么干脆不做用户属性分析只做用户交易行为分析比如下单频次、消费金额分层。地区维度的设计有一点技巧直接在主表里存省份城市字段是最省事的尤其在数据量几万条这个级别没必要单独拆维度表。但如果你想在架构上更像数仓范儿可以建一张region表存省市区编码和名称订单表里只存代码查询通过外键关联。这个选择了看你的数据量级如果只是几万条直接存文本字段查询还更快。以下是一份参考的Django模型骨架可以直接照搬或者改造成自己的from django.db import models class Order(models.Model): STATUS_CHOICES ( (paid, 已支付), (shipped, 已发货), (completed, 已完成), (cancelled, 已取消), ) order_no models.CharField(max_length64, uniqueTrue, verbose_name订单号) user_id models.CharField(max_length32, db_indexTrue, verbose_name用户ID) status models.CharField(max_length20, choicesSTATUS_CHOICES, verbose_name订单状态) create_time models.DateTimeField(db_indexTrue, verbose_name下单时间) pay_time models.DateTimeField(nullTrue, blankTrue, verbose_name支付时间) total_amount models.DecimalField(max_digits10, decimal_places2, verbose_name订单金额) pay_amount models.DecimalField(max_digits10, decimal_places2, verbose_name实付金额) discount_amount models.DecimalField(max_digits10, decimal_places2, default0, verbose_name优惠金额) province models.CharField(max_length32, verbose_name收货省份) city models.CharField(max_length32, verbose_name收货城市) payment_method models.CharField(max_length20, verbose_name支付方式) class Meta: db_table order_main indexes [ models.Index(fields[create_time, status]), ] class OrderItem(models.Model): order models.ForeignKey(Order, related_nameitems, on_deletemodels.CASCADE) product_id models.CharField(max_length32, db_indexTrue) product_name models.CharField(max_length128) quantity models.IntegerField(default1) price models.DecimalField(max_digits10, decimal_places2) subtotal models.DecimalField(max_digits10, decimal_places2) class Meta: db_table order_item class Product(models.Model): product_id models.CharField(max_length32, uniqueTrue) product_name models.CharField(max_length128) category models.CharField(max_length64, db_indexTrue) brand models.CharField(max_length64, blankTrue) create_time models.DateTimeField(auto_now_addTrue) class Meta: db_table dim_product class User(models.Model): user_id models.CharField(max_length32, uniqueTrue) nickname models.CharField(max_length64, blankTrue) register_time models.DateTimeField(nullTrue, blankTrue) level models.CharField(max_length16, default普通会员) class Meta: db_table dim_user建表时的几个约定俗成经验时间字段统一用DateTimeField且db_indexTrue加上因为后面所有按时间聚合的查询都靠这个索引订单号加唯一约束防止重复数据金额字段一律用DecimalField而不是FloatField浮点数做金额计算会出现0.10.2不等于0.3的精度问题这在答辩的时候是能拿出来讲的细节。数据量大了以后Django的ORM自动生成的查询如果有慢查询风险可以用connection.queries查看实际执行的SQL语句或者用django-debug-toolbar这个工具在调试模式下看每条请求的SQL耗时。2.2 数据清洗与聚合计算模型建好了接下来是把数据灌进去。如果数据是自己造的那直接写脚本用Django ORM批量插入如果数据是从公开数据集或爬虫来的就得先做清洗。我见过太多做数据分析项目的人上来就直接往库里灌数据结果做可视化的时候发现各种诡异问题销售额趋势图出现个离谱的峰值一查原来是某天导入了一笔金额1000万的测试订单用户分布图显示某省的用户占了90%仔细看是爬虫数据里默认值没处理干净。这些都是清洗不到的坑。清洗的常规步骤应该是去重订单号是唯一键重复的订单直接丢弃或者保留第一条。写脚本时判断Django ORM的get_or_create或者先查询再插入。空值处理支付时间为空的订单一般是未支付或支付失败这类数据有两种处理直接过滤掉分析维度只关注已支付订单或者把状态设置成cancelled后保留。我的建议是做销售分析时只保留pay_time不为空的数据因为这才是真实的交易。异常值过滤订单金额为0或负数、数量为0或负数、单价远超正常范围这些数据要么是测试生成的要么是异常订单做个阈值过滤或标记。时间对齐把日期格式统一成YYYY-MM-DD或标准时间戳时区也要统一不然后面按天统计的时候会发现部分数据归到了错误的日子。编码处理CSV文件要用encodingutf-8-sig读取不然Excel导出的文件在Windows下读进来中文会乱码。清洗完之后就是聚合计算。Django的ORM提供了非常方便的聚合工具annotate和aggregate这俩函数就是为数据分析场景设计的。比如你要算每日销售额趋势用一行查询就行from django.db.models.functions import TruncDate from django.db.models import Sum daily_sales (Order.objects .filter(statuscompleted) .annotate(dayTruncDate(pay_time)) .values(day) .annotate(totalSum(pay_amount)) .order_by(day))这段代码的含义是取所有已完成订单按支付时间截断到天TruncDate再按天分组求和。返回的QuerySet是[{day: date对象, total: Decimal(1234.56)}, ...]这样的结构直接可以转成JSON给前端。类似的你想按月份、按周统计把TruncDate换成TruncMonth或TruncWeek就行这是Django从3.2开始就内置的数据库函数非常好用。再比如商品TOP10的排行核心是一条top_products (OrderItem.objects .values(product_id, product_name) .annotate(total_quantitySum(quantity), total_salesSum(subtotal)) .order_by(-total_sales)[:10])这里利用了values()加annotate()的组合values先指定分组字段annotate为每组计算聚合值最后order_by排序取前10。这种写法能替代大部分手写SQL的GROUP BY场景而且可读性非常好。在真实的项目中有些客户会在需求里指定看累计销售额、订单量、客单价、退款率这些核心指标。客单价的算法是总销售额除以总订单数退款率就是退款订单数除以总订单数。这些指标建议在系统里做一个概览仪表盘接口一次性返回一组数字前端配一个数字翻牌器的效果展示视觉冲击力很强答辩映象分直接拉满。3. Django后端核心实现3.1 Django项目框架搭建与分层Django项目的目录结构我不建议把所有代码塞到默认的单一app里。数据分析系统涉及的模块比较清晰建议按业务边界拆成多个app一个app管一个领域这样后面写接口、写权限、写测试都清爽很多。一个参考的项目结构是这样的project/ ├── manage.py ├── config/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── orders/ # 订单数据模型与订单服务 │ ├── goods/ # 商品数据模型 │ ├── users_app/ # 用户数据模型 │ ├── analysis/ # 聚合查询、报表接口 │ ├── dashboard/ # 可视化大屏相关视图 │ └── agent/ # 大模型Agent接口 ├── static/ │ ├── css/ │ ├── js/ │ └── vendor/echarts/ ├── templates/ │ ├── dashboard.html │ ├── agent_chat.html │ └── admin.html ├── scripts/ │ ├── generate_mock_data.py │ ├── import_csv.py │ └── clean_data.py └── requirements.txt这个结构的好处一目了然orders、goods、users_app是基础数据层analysis是核心分析逻辑层dashboard是展示层agent是大模型集成层。每一层的职责清晰写代码的时候心理有数答辩画架构图也能画得非常规范。用Django命令python manage.py startapp orders挨个创建即可别忘了把app加到INSTALLED_APPS里在config/settings.py中配置好。Settings层面的几个关键配置我直接给出来参考。数据库配置指定MySQL注意字符集要加utf8mb4否则存不了emoji和一些生僻字DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: ecommerce_analysis, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }这个是PyMySQL驱动的配置方式。记得先pip install pymysql然后在config/__init__.py里写一行import pymysql; pymysql.install_as_MySQLdb()Django就能通过MySQLdb接口用上PyMySQL了。还有一个必须提的是DEBUGTrue的时候Django会打印所有SQL日志开发调试很好用但部署上线一定要把DEBUGFalse同时配置ALLOWED_HOSTS这是防信息泄露的基本操作。静态文件的处理也要注意DEBUGFalse之后Django不再自动提供静态文件服务你需要用whitenoise或者Nginx把static目录配好否则页面CSS、JS全部加载不出来。这个小坑几乎每届都有学生踩后面用专门一节讲排错。3.2 API接口设计与关键实现前后端交互的数据格式我建议统一用JSON。接口的URL设计要RESTful一点让答辩老师听着专业接口路径方法功能说明/api/dashboard/overview/GET核心KPI概览总销售额、订单量、客单价、退款率/api/dashboard/sales_trend/GET销售额/订单量时间趋势/api/dashboard/category_ratio/GET商品类目销售额占比/api/dashboard/top_products/GET商品销量/销售额TOP10/api/dashboard/geo_distribution/GET各省份订单量/销售额分布/api/dashboard/payment_method/GET支付方式占比/api/agent/chat/POST大模型数据分析问答以销售趋势接口为例实现非常直接import json from django.http import JsonResponse from django.views.decorators.http import require_GET from django.db.models.functions import TruncMonth from django.db.models import Sum, Count from apps.orders.models import Order require_GET def sales_trend(request): metric request.GET.get(metric, sales) # sales / orders group_by request.GET.get(group_by, month) # day / week / month trunc_func { day: TruncDay, week: TruncWeek, month: TruncMonth, }.get(group_by, TruncMonth) qs (Order.objects .filter(statuscompleted) .annotate(periodtrunc_func(pay_time)) .values(period) .annotate( total_salesSum(pay_amount), order_countCount(id) ) .order_by(period)) result [{ period: item[period].strftime(%Y-%m-%d) if item[period] else None, total_sales: float(item[total_sales] or 0), order_count: item[order_count], } for item in qs] return JsonResponse({code: 0, data: result})这里有个非常关键的细节item[total_sales]是Decimal类型不是json序列化不了的必须用float()转成浮点数否则JsonResponse直接报错。同理日期对象也要strftime转字符串。我建议在项目里封一个通用的小函数把ORM返回的QuerySet结果统一转成可JSON序列化的dict这个函数你后面写几十个接口都会用到。还有一个要说的就是QuerySet的惰性求值特性。Django的ORM查询在真正迭代之前不会执行SQL这导致一个常见bug你在视图函数里先定义了一个qs然后修改了某个条件最后循环使用qs得到的结果和你预期不一致。理解这一点后写出来的代码就会更注意及时执行或者注意作用域不然在循环里二次过滤QuerySet会造成数据库多次查询性能上也会有问题。3.3 查询性能优化的实战经验数据分析系统的查询模式是典型的少量大查询一个接口可能要对整张表做聚合。数据量在10万条以内MySQL索引优化得好Django ORM的聚合查询通常都在几百毫秒内完成问题不大。但有几个地方要注意否则数据量一到几十万条页面就会卡得没法看。第一日期字段必须有索引。所有按时间分组的查询比如销售额趋势、按月统计、按周统计底层SQL都是GROUP BY date(pay_time)这类带范围条件的查询如果pay_time没有索引全表扫描是必然的。建模型的时候给pay_time加db_indexTrue或者建表后手动加索引ALTER TABLE order_main ADD INDEX idx_pay_time (pay_time);。第二避免在循环中查询数据库。新手最常见的写法是查了商品列表之后在for循环里对每个商品再查一次订单表这就是N1问题。比如你想算每个商品卖了多少正确做法是一次聚合查询拿到全部分组计数而不是循环里一个个查。Django的select_related和prefetch_related也要学会用它们能把外键关联的查询合并成少数几条SQL。第三用Redis做缓存。数据分析的图表数据通常是分钟级不变的完全没必要每次请求都去算一遍。引入Redis之后把聚合结果缓存起来设置过期时间例如5分钟或10分钟。Django里有现成的缓存框架只需要在settings里配置CACHES { default: { BACKEND: django.core.cache.backends.redis.RedisCache, LOCATION: redis://127.0.0.1:6379/1, TIMEOUT: 300, } }然后在视图里用cache.get和cache.set包一层命中缓存时直接返回JSON未命中时再算并写入缓存。注意缓存key的设计要包含查询参数比如sales_trend_2024_06_month不然不同参数的请求会串数据。第四把非核心指标做成异步预计算。有些指标耗时较长又不需要实时比如用户复购率、RFM分层这类计算可以每晚或者每小时跑一次定时任务把结果存到一张独立的统计表里页面直接查这张表。Django有django-crontab或者django-celery-beat可以用用最简单的django-crontab就能扛住大部分场景。4. 可视化大屏与图表实现4.1 ECharts选型与数据对接格式可视化部分我强烈推荐ECharts理由前面说过现在讲讲具体怎么用、怎么和数据接口对接。ECharts是百度开源现在Apache孵化的JavaScript图表库支持折线图、柱状图、饼图、散点图、雷达图、地图等几十种图表而且图表交互成熟有缩放、tooltip、图例开关这些内置功能你不需要自己造轮子。引入方式很简单直接下个echarts.min.js放到静态目录或者在HTML里用CDNscript srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script但要注意毕设如果用CDN答辩现场可能会遇到没网的情况。稳妥起见把echarts.min.js下载到本地static目录做成离线也能跑。这一点在毕业设计里非常加好感——现场演示不怕断网。图表初始化的套路都一样三步走先准备一个DOM容器设置宽度高度然后echarts.init(dom)创建实例最后chart.setOption(option)配置数据和样式。核心的工作都在option里我们需要让后端的JSON数据和ECharts的data结构对得上。举个销售额趋势图的例子。后端返回的数据形如[{period: 2024-01, total_sales: 12345.67}, {period: 2024-02, ...}]前端拿到后要把它转换成ECharts需要的两个数组x轴类目数据和y轴数值数据。fetch(/api/dashboard/sales_trend/?group_bymonth) .then(res res.json()) .then(res { const data res.data; const xData data.map(item item.period); const yData data.map(item item.total_sales); const chart echarts.init(document.getElementById(salesChart)); chart.setOption({ title: { text: 月度销售额趋势 }, tooltip: { trigger: axis }, xAxis: { type: category, data: xData }, yAxis: { type: value, name: 销售额 }, series: [{ name: 销售额, type: line, smooth: true, areaStyle: {}, data: yData }] }); });这里有个经验点ECharts的data数组顺序必须和后端返回的顺序一致。如果查询结果里有些月份没有数据后端要么补零要么前端做处理不然图表会多出空档或者对不齐。更稳的方式是后端把完整的月份列表生成出来缺数据月份自动补0。4.2 大屏布局与数据联动可视化大屏的布局简单来说就是中间突出核心、四周分布辅助。一个标准的电商大屏可以这样规划顶部系统标题 当前时间 日期切换左上核心KPI数字卡片总销售额、总订单量、客单价、退款率右上销售额趋势折线图面积图效果最直观中间商品类目占比饼图/环形图左下省份销售分布地图右下商品销售TOP10横向柱状图布局上我用的是CSS Grid把大屏分成若干区域每个区域放一个div容器页面加载后分别初始化各个图表。大屏的背景色一般是深色系比如#0f1c3a这种深蓝配上高亮的卡片和数据视觉冲击力强。ECharts主题可以直接用dark内置主题echarts.init(dom, dark)。数据联动是另一个值得讲的设计点。比如你在商品TOP10柱状图上点击某个商品其他图表比如趋势图和品类占比图同步切换到该商品的数据。实现思路是给柱状图绑定click事件拿到商品名参数后重新请求后端接口再更新其他图表的数据。这个交互效果本身不复杂但演示起来非常唬人能体现出系统不是静态图表堆砌而是有交互逻辑在里面的。另一个建议是定时刷新。用setInterval每60秒重新拉一次数据接口更新所有图表配合一个最后更新于XX:XX的提示让大屏看起来像实时监控系统。当然如果后端数据本来就是静态的定时刷新纯属形式但演示效果确实好答辩现场老师会觉得系统是活的。还有一点关于前端框架的选择提醒如果你熟悉前端工程化用Vue或React拆分组件会更好维护但毕设如果时间紧就直接用原生HTMLJSECharts不引入构建工具部署简单不容易出环境问题。两种方案没有绝对的对错关键是你能讲清楚自己的技术选型。5. 大模型Agent智能分析模块5.1 DeepSeek接入与Function Calling实现这个模块是整套系统最亮的部分也是很多同学觉得听起来高大上但不知道从哪下手的部分。其实思路理清楚之后实现非常直接。核心思路是用户在前端输入自然语言问题比如今年6月和5月相比销售额增长了多少系统把这个问题连同可用的分析工具定义一起发给大模型大模型理解问题后返回一个调用某个工具的动作比如调用get_sales_trend参数是start_date2024-05-01, end_date2024-06-30系统执行这个函数拿到真实数据再把数据返回给大模型大模型把数据组织成自然语言回答最后回答展示给用户。这个链路里的关键概念就是Function Calling函数调用DeepSeek的API是支持这个能力的。你需要做两件事一是定义一个函数清单JSON Schema格式告诉模型你有哪些工具可以用每个工具的入参是什么二是在对话循环里处理模型返回的tool_calls真正去执行工具。下面给一个简化的实现参考。假设我们封装了一个call_deepseek(messages, tools)函数import requests import json DEEPSEEK_API_KEY sk-xxxx DEEPSEEK_API_URL https://api.deepseek.com/v1/chat/completions def call_deepseek(messages, toolsNone): headers { Content-Type: application/json, Authorization: fBearer {DEEPSEEK_API_KEY}, } payload { model: deepseek-chat, messages: messages, tools: tools or [], tool_choice: auto, temperature: 0.2, } resp requests.post(DEEPSEEK_API_URL, headersheaders, jsonpayload, timeout60) resp.raise_for_status() return resp.json()[choices][0][message]然后定义可用工具。这里定义两个分析函数的schemaanalysis_tools [ { type: function, function: { name: get_sales_trend, description: 获取指定时间范围内的销售趋势数据金额和订单量, parameters: { type: object, properties: { start_date: {type: string, description: 起始日期格式YYYY-MM-DD}, end_date: {type: string, description: 结束日期格式YYYY-MM-DD}, group_by: {type: string, enum: [day, week, month], default: day} }, required: [start_date, end_date] } } }, { type: function, function: { name: get_top_products, description: 获取指定时间范围内销量或销售额排名前N的商品, parameters: { type: object, properties: { start_date: {type: string}, end_date: {type: string}, limit: {type: integer, default: 10}, order_by: {type: string, enum: [sales, quantity], default: sales} }, required: [start_date, end_date] } } } ]接下来在/api/agent/chat/的视图里把上面的串起来from apps.analysis.services import get_sales_trend, get_top_products require_POST def agent_chat(request): data json.loads(request.body) user_question data.get(message, ) system_prompt 你是电商数据分析助手。用户会输入关于订单数据的问题。 你会根据可用工具获取真实数据然后用中文友好地回复用户。 如果问题无法用工具回答告知用户你能回答哪些类的问题。 messages [ {role: system, content: system_prompt}, {role: user, content: user_question}, ] # 第一轮调用模型可能返回需要调用函数的结果 ai_message call_deepseek(messages, toolsanalysis_tools) if ai_message.get(tool_calls): # 把模型的function call结果追加到对话中 messages.append(ai_message) for tool_call in ai_message[tool_calls]: func_name tool_call[function][name] func_args json.loads(tool_call[function][arguments]) if func_name get_sales_trend: func_result get_sales_trend(**func_args) elif func_name get_top_products: func_result get_top_products(**func_args) else: func_result {error: unknown tool} messages.append({ role: tool, tool_call_id: tool_call[id], content: json.dumps(func_result, ensure_asciiFalse) }) # 第二轮调用模型根据真实数据生成最终回答 final_message call_deepseek(messages, toolsanalysis_tools) answer final_message[content] else: answer ai_message[content] return JsonResponse({code: 0, message: answer})这个流程踩过坑的同学都知道最容易出错的地方是messages的格式。做Function Calling时tool_calls在assistant消息里对应每个tool的role: tool消息必须带正确的tool_call_id并且顺序跟上文一致。漏掉tool_call_id或者顺序错乱API就会报错。另外调get_sales_trend这类工具函数时参数可能来自用户自然语言比如用户说上个月模型不一定能正确把日期算出来。解决方法是你可以多定义一个工具get_current_date让模型先获知当前日期再去推算上个月是哪一天。这是Agent开发里很经典的一招我称之为给Agent提供感知时间的能力。5.2 Agent的设计边界与降级兜底大模型Agent模块虽然炫但要让它稳定可用必须做好边界控制和降级兜底。边界控制的第一件事是只允许Agent调用白名单内的函数。上面例子里的analysis_tools就是白名单模型只能在这几个函数里选择不能随便调用其他系统功能。这样即使模型被恶意提示词注入也拿不到系统权限。你还可以在函数内部加上参数校验比如limit限制在1~100date必须符合日期格式防止模型生成异常参数把系统拖垮。第二件事是超时和异常处理。大模型API调用肯定有网络延迟如果用户问了个复杂问题模型要两轮对话、要执行函数整个流程可能耗时10~20秒。前端要加正在分析的loading状态后端要设置合理的超时时间比如30秒。如果第一轮调用就超时或者报错要返回友好的提示信息而不是让用户看到500错误页。我的做法是捕获异常后返回固定文案智能分析服务暂时不可用请稍后再试或直接查看可视化图表。第三件事是回答降级。如果模型无法理解用户的问题或者没有调用任何工具不要硬编一个答案诚实告诉用户系统能回答哪些范围的问题并给出几个示例问题比如上个月销售额多少销量最高的商品是什么各省份订单量分布。这其实也是一种提示词策略在system prompt里明确说明边界模型的答非所问率会大幅下降。还有一种增强方案是把历史图表数据作为上下文。比如用户在对话中问上个月的销售额趋势如何Agent返回文字描述的同时可以附带一组JSON图表数据前端拿到后直接渲染一个迷你趋势图。这需要你在工具函数里同时返回聚合数据和描述文本实现上也不复杂但用户体验会提升一个档次。6. 常见问题与排查技巧实录6.1 数据、编码与静态文件的坑先说一个每个新手都会踩的坑读取CSV文件中文乱码。这个问题基本是文件编码引起的Excel在Windows下默认保存的是GBK/GB2312编码而Python读取时默认用UTF-8所以读出来全是乱码。解决办法是pd.read_csv(data.csv, encodinggbk)或者读取时自动检测编码import chardet with open(data.csv, rb) as f: result chardet.detect(f.read(10000)) encoding result[encoding] df pd.read_csv(data.csv, encodingencoding)如果数据是从网上下载的用chardet检测一下通常都能正确识别。另一个容易踩的是用Pandas处理完DataFrame后直接把数据写入MySQL时字段类型不一致比如DataFrame里的日期字段是object类型写入MySQL datetime字段时报错。解决方法是入库前显式做pd.to_datetime()转换。再一个高发问题是Django静态文件404。开发模式下DEBUGTrueDjango自带静态文件服务一旦你为了展示效果把DEBUGFalse所有CSS、JS全部404。这是我每年都能遇到的问题。解决方案是安装whitenoisepip install whitenoise在settings.py的MIDDLEWARE里加上whitenoise.middleware.WhiteNoiseMiddleware放在SecurityMiddleware之后然后运行python manage.py collectstatic把静态文件收集到一个目录Whitenoise就能帮你把静态文件服务起来。如果是部署到云服务器更推荐用Nginx单独配静态文件但Whitenoise的方案对学生来说是性价比最高的。6.2 数据库连接与查询超时MySQL默认的max_connections是151连接数打满就会报Too many connections。Django的数据库连接池本身是每线程一个连接并发上来之后很容易把连接数打满。毕设场景下最简单的方案就是确保用完后在settings.py里设置CONN_MAX_AGEDATABASES { default: { # ... CONN_MAX_AGE: 60, # 连接复用60秒 OPTIONS: { # ... pool_size: 10, } } }注意pool_size这个参数需要配合django-db-connection-pool或者SQLAlchemy的池化方案才生效如果只是原生DjangoPyMySQL看连接数变化即可。真出现连接数爆了可以先在MySQL命令行执行SHOW PROCESSLIST;看是谁占着连接再用SET GLOBAL max_connections 500;临时调高。查询超时的问题前面的性能优化部分讲过。补充一个常见场景你写了一个跨多个条件的聚合查询包含时间范围、商品类目、省份等多个过滤条件如果没有组合索引查询可能要走几次全表扫描。建议在MySQL里对最常用的组合加联合索引比如(create_time, status, province)。索引不是越多越好但分析系统的查询模式比较固定针对性的联合索引收益非常明显。6.3 大模型接口对接的典型报错大模型Agent模块的报错我整理一个速查表都是实际运行中最常遇到的错误现象原因解决办法AuthenticationErrorAPI Key错误或过期检查环境变量中的key确认账户余额RateLimitError请求频率超限在调用端加time.sleep(1)或做指数退避重试InvalidRequestErrormessages格式问题检查tool_call_id是否缺失、role字段是否合法模型返回空content模型选择只调用了工具但工具结果未正确回传检查tool结果是否append到messages中文字符被转义没有指定ensure_asciiFalseJSON输出时设置json.dumps(..., ensure_asciiFalse)用户问题答非所问提示词没有明确边界加强system prompt给出示例问题和回答格式其中我特别想强调RateLimitError。现在很多大模型API免费版的调用频率限制是每分钟几十次如果你的大屏页面每60秒刷一次再加上Agent接口的调用很容易触发限流。代码里务必加一个统一的调用封装里面带重试逻辑和退避策略别让一个限流报错直接把页面搞崩。还有一点关于模型选择DeepSeek有deepseek-chat和deepseek-reasoner两个模型可选。Function Calling场景下我建议用deepseek-chat响应更快、成本更低deepseek-reasoner的推理能力强但延迟更高不太适合对话式交互。如果你要展示模型能分析复杂问题可以预置几个复杂分析case比如对比上个月和上上个月各品类的销售变化这类问题用deepseek-chat也能跑得很好。6.4 答辩演示时的环境预案最后讲一点可能和代码无关但至关重要的东西答辩演示环境预案。每年答辩现场总有人的系统现场跑不起来原因不外乎几类数据库连不上、静态文件加载不出来、大模型接口没网、Python环境版本不一致。我的建议是答辩前一星期做一次从零搭建演练在一台干净电脑上从安装Python、创建虚拟环境、pip安装依赖、初始化数据库、导入数据到最后打开页面完成整个流程。然后把所有依赖固定版本导出requirements.txt。现场演示时优先用本机环境不要依赖外部网络。大模型接口如果现场网络不好要提前准备降级方案——要么在Agent模块里做一个模拟模式不真正调用外部API用预设规则返回答案要么准备好录制的演示视频作为备胎。这不是投机取巧而是工程人员应有的风险预案意识。从数据模型设计到API接口实现从可视化大屏到大模型Agent这套跨境电商订单分析系统的每一步都是可以在答辩时展开讲半天的。尤其是大模型接入的部分你只要讲清楚Function Calling的链路、边界控制策略和降级方案老师基本就能确认这不是一个为了蹭热点挂个API的假亮点而是真正理解了大模型在业务系统里怎么落地。我个人在实际带项目的过程中最深的体会是这个系统最难的不是某个单一技术点而是把Django、ECharts、大模型这几个不同层次的东西串成一个整体。每层之间通过标准的JSON接口通信每层内部做好自己的职责整个系统就非常稳固。最后再分享一个小技巧如果你想让整个项目看起来更完整加一个数据导入口——支持上传CSV文件并自动解析入库这样演示的时候就能现场导入一份新数据给老师看交互感和完成度瞬间拉满。这个模块在技术上其实就是复用前面讲的数据清洗逻辑工作量不大但给人留下的印象非常深刻。
返回列表