做教学智能体最尴尬的时刻,就是学生问"能不能画一下y=x²-2x-3的函数图",你叭叭讲了一堆顶点坐标、对称轴、单调区间,学生还是懵的。去年我搭教学智能体时就栽在这儿,折腾了将近两天才把"让智能体画函数图"这件事彻底跑通。这篇就聊聊这个过程里走的路、选的方案、踩的坑,给正在做教育类Agent、助教机器人、知识类智能体的朋友一点参考。
先说清楚我做的教学智能体是什么场景:学生在对话框里问数学题,智能体负责答疑、讲概念、给步骤。大多数情况下文字就够了,但凡是涉及函数性质、图像变换、零点交点的问题,光靠文字描述非常吃力,一张图顶十段话。所以我当时的目标很明确——用户说"画个图",智能体不仅要真的画出一张函数图像,还要能对着图给学生讲单调区间、极值点、渐近线。这个需求听起来不复杂,实际上手才发现,让AI画图这件事压根不是"调用一下绘图库"这么简单。
1. 先搞清楚需求:教学智能体要的"函数图"到底是什么
1.1 光有文字不够,教学场景里图像是刚需
教学答疑的场景和一般闲聊完全不一样。学生问"这个函数的值域是多少",你用代数方法能推出来,但如果你直接贴一张图,说"你看它的最高点在这,最低点在这,开口朝上所以值域是[-4, +∞)",理解成本瞬间就降下来。
我整理了一下实际会遇到的问题类型,基本可以分成四类。第一类是基础函数性质,比如单调区间、奇偶性、周期性,必须有图才能直观看出趋势。第二类是图像变换,比如"把y=log₂x向左平移2个单位再向下平移1个单位",学生想看到变换前后的对比。第三类是交点零点问题,比如"判断2^x=x²有几个交点",这不画图基本没法讲。第四类是含参讨论,比如"讨论y=ax²+ax+1与x轴交点个数随a的变化",需要连续画出多张图对比。
如果智能体不具备画图能力,这些场景里就只能输出冷冰冰的文字推导,学生理解慢,家长觉得体验差,而且说实话"智能"的体感也会大打折扣。我后来测试时感触特别深:同一个函数题,纯文字解析学生可能要看两三遍才明白,配一张标注了关键点的函数图,基本一遍就通了。
1.2 "会画图"到底意味着什么:能力拆解
折腾之前我以为画图就是个工具调用,后来发现整个链路其实由四个环节组成。
第一个环节是意图识别。用户说"帮我看看这个函数长什么样",你得判断出这是一个绘图请求,而不是概念讲解请求。第二个环节是参数抽取。从"画一下f(x)=x³-3x+1在[-2,2]上的图像"这句话里,提取出函数表达式、X轴范围、要不要标注特殊点等关键信息。第三个环节是绘图执行。真正调用绘图引擎生成一张图片,这一步涉及表达式解析、数值采样、坐标刻度设置、中文字体处理。第四个环节是图像讲解。生成完图片不能让图躺在那里,智能体要能描述这个图的趋势、关键点,回答学生关于图上细节的追问。
这四个环节里,前两个考的是大模型的语义理解能力,后两个考的是工程实现能力。我一开始想偷懒跳过参数抽取这一步,直接让大模型把整个绘图代码生成出来再执行,后面发现这条路在带教学场景的业务里并不可靠,后面详细说。
2. 方案选型:平台工作流、代码节点、独立绘图服务我全试了一遍
2.1 第一条路:平台自带图表插件,用途有限
一开始我图省事,想直接在Coze、Dify这类智能体平台里配置一个现成的图表插件。这类平台通常带柱状图、折线图、饼图之类的通用图表能力,但你要拿它画数学函数图,马上就会发现两个致命问题。
第一,通用图表插件是基于数据表的,你给它一个二维数组,它帮你画柱状图。但函数图要求的是"按数学表达式连续采样然后绘制曲线",这个语义它理解不了。你让它画y=x²,它只会把你的数据点连成折线,不会帮你采样、不会算极值、不会标坐标轴。第二,数学教学要求图像精确,曲线的平滑度、坐标轴的刻度密度、关键点的标注,通用图表插件都没法调。我试了一下午就放弃了,这类插件适合做数据可视化,不适合做数学可视化。
2.2 第二条路:平台工作流里的代码执行节点,快但不自由
很多平台支持在工作流中插入代码节点,我当时想,既然插件不行,那我直接在代码节点里用Python画图总可以吧。
这条路确实能走通,我大概花了半天时间就在Dify的代码节点里跑通了matplotlib绘图,生成图片后交给对话模型。但实际用起来有几点很别扭。首先是代码节点的运行环境通常是受限的,部分平台默认不装matplotlib或者中文字体缺失,装依赖还得看平台脸色。其次是调试不方便,平台里报错只给一个堆栈信息,变量中间值看不到,画出来不对也不知道是表达式出问题还是采样范围不对。更关键的是,代码节点一般有超时限制,函数图如果采样密度高一点、图里元素多一点,偶尔会卡在超时边缘。
如果你只是想在内部快速验证"能不能画图",平台代码节点够用。但如果你想稳定地做一个面向学生的教学功能,我觉得还是别在平台里硬扛,画像本身是个计算活,应该独立出去。
2.3 第三条路:独立绘图服务 + HTTP工具调用,最终走通
折腾到第二天上午,我决定把绘图能力做成一个独立的Python服务,智能体平台通过HTTP工具调用它。这个思路其实就是现在不少人讨论的"平台搭建的智能体"和"Python构建的智能体"之间的折中方案:Agent的对话管理、意图识别、上下文记忆交给平台,专业能力(绘图、计算)下沉到自建服务。
我当时的架构很简单:一个FastAPI应用,暴露一个/plot接口,接收函数表达式、x范围、标题、是否标注特殊点等参数,返回一张PNG图片(Base64编码或图片URL)。然后把这个接口做成平台的"自定义工具",在智能体工作流里调用。这样绘图代码完全掌握在自己手里,想加什么功能随时加,想换采样算法随时换,平台那边只是把用户请求转发过来,把图片结果拿回去。
这个方案的调试体验也比平台代码节点舒服得多:本地起服务,curl一发就知道接口对不对,拿到的图片直接本地看,有问题直接改代码重跑,不用一遍遍去平台界面上点构建、点试运行。
2.4 为什么Python自建服务比平台硬画更靠谱
从平台搭建和Python构建的角度对比一下。平台的核心价值是帮你管好了对话流程、模型调用、知识库、插件生态,这些我没必要重复造轮子。但绘图这件事,平台做得不如一个几十行的Python函数精细。因为平台要照顾太多场景,通用性优先,而教学函数图是一个高度垂直的需求:要精确采样、要处理数学表达式、要在图上标渐近线和极值点、要支持分段函数。
自建服务同时解决了一个"能力边界"问题。平台代码节点只在自己工作流里临时执行,换一个平台就得重新适配。而独立服务是HTTP接口,Dify能用、Coze能用、以后的任何Agent框架也能用,甚至可以供多个智能体共用。这才是把智能体从"演示玩具"推向"可复用工具"的关键一步。
3. 核心实现:给大模型配一个"绘图能力"的完整链路
3.1 工作流总览:从用户提问到图像讲解
我的最终工作流分了6步,在Dify里配置成一条Agent工作流,步骤之间的数据流我用一段伪代码表示:
用户消息 -> 模型判断:是否需要画图? -> 如果不需要:直接文字回答 -> 如果需要: -> LLM抽取绘图参数(函数表达式、x范围、标题等) -> 调用HTTP工具:POST /plot -> 获取图片(Base64/URL) -> 把图片传回LLM,同时附上"请结合图像讲解"的提示词 -> 输出带图和讲解的最终答案这个流程最核心的设计点是"图+讲解"双轨输出,也就是不仅把图片给用户,还要让大模型基于图片写出数学讲解。我试过只丢图片不给讲解,学生看完图还是不知道重点在哪。加上一段"观察图像有哪些关键特征"的提示词之后,模型会自动识别图像里的极值点、交点、趋势区间,然后组织成教学语言。
3.2 参数抽取:别让大模型自由发挥,给它一个JSON模板
参数抽取这一步我吃了不少亏。一开始我让模型自己发挥,想怎么描述就怎么描述,结果它一会儿传"y = x**2",一会儿传"x^2",一会儿把范围写成字符串"(-2,2)",我后端解析的时候头都大了。
后来我做成一个严格的结构化提取节点:提示词里明确要求模型输出一个JSON,字段名、格式、取值范围全部约束好。模型实际上做的事情是"从用户问句里填槽",不是自由生成。我的提示词模板大概是这样的:
你是数学题参数提取器。根据用户的问题,提取以下字段: { "expression": "函数表达式,使用Python语法,例如 x**2 - 3*x + 1", "x_min": "x轴最小值,如无特殊说明默认-10", "x_max": "x轴最大值,如无特殊说明默认10", "title": "图像标题,如无特殊说明默认空", "show_extrema": "是否标注极值点,true或false", "show_zeros": "是否标注零点,true或false" } 注意: - 只输出JSON,不要输出其他文字 - expression中乘法使用*,乘方使用**,对数函数写作log(x),自然对数为ln(x),三角函数使用sin/cos/tan - 如果用户指定的x范围不合理(例如x_min等于x_max),使用默认范围加了约束之后参数解析的成功率一下子从大概70%升到了95%以上。这个经验我觉得对做所有工具调用类功能都适用:不要指望模型"悟"出你的接口格式,你要把格式定义到提示词里,甚至做成JSON Schema去校验。宁可多花几十个token在提示词上,也不要在后端做各种容错解析。
3.3 函数图核心代码:表达式解析、采样、绘图一步到位
绘图服务就是核心了。先看一个简化版的FastAPI接口代码:这个只做基础函数图,分段函数和隐函数我后面扩展了,还没在主代码里体现。
import ast import base64 from io import BytesIO import matplotlib matplotlib.use("Agg") # 不需要GUI,用Agg后端 import matplotlib.pyplot as plt import numpy as np from fastapi import FastAPI from pydantic import BaseModel # 配置中文字体,避免乱码 plt.rcParams["font.sans-serif"] = ["SimHei", "Noto Sans CJK SC", "WenQuanYi Zen Hei"] plt.rcParams["axes.unicode_minus"] = False # 避免负号显示成方块 app = FastAPI() # 允许使用的函数白名单 ALLOWED_FUNCTIONS = { "sin": np.sin, "cos": np.cos, "tan": np.tan, "log": np.log, "ln": np.log, "sqrt": np.sqrt, "abs": np.abs, "exp": np.exp, "pi": np.pi, "e": np.e, "arcsin": np.arcsin, "arccos": np.arccos, "arctan": np.arctan, } def safe_eval(expr: str, x_val: float) -> float: """安全地将字符串表达式中的x替换为数值并求值""" tree = ast.parse(expr, mode="eval") names = set() # 第一遍:检查所有名字都在白名单里 for node in ast.walk(tree): if isinstance(node, ast.Name): if node.id != "x" and node.id not in ALLOWED_FUNCTIONS: raise ValueError(f"不允许的变量或函数: {node.id}") names.add(node.id) # 替换x并编译执行 code = compile(tree, "<string>", "eval") allowed = {k: v for k, v in ALLOWED_FUNCTIONS.items() if k in names} allowed["x"] = x_val return eval(code, {"__builtins__": {}}, allowed)这段代码重点要说明的是安全校验。直接用eval(expr)在服务端执行用户输入非常危险,一个__import__('os').system('rm -rf /')就能把服务器搞挂。用ast.parse先把表达式解析成抽象语法树,然后遍历检查所有出现的变量名和函数名,只允许白名单里的x和数学函数,这样即使表达式里写了恶意代码,它在校验阶段就会被拦截。
然后是绘图部分。核心点是采样时要做「中间加密」,不是均匀采样那么简单。一个函数可能在某个小区间里剧烈变化,比如y=tan(x)在π/2附近从正无穷跳到负无穷,均匀采样画出来是锯齿或者断裂。我的做法是先计算二阶差分,判断哪些区域的斜率变化太大,在这些区域内部再加密采样点。
def plot_function(expression: str, x_min: float, x_max: float, title: str, output_base64: bool = True): # 粗采样,用于判断图形趋势 x_raw = np.linspace(x_min, x_max, 2000) y_raw = [] for x in x_raw: try: y_raw.append(safe_eval(expression, float(x))) except Exception: y_raw.append(np.nan) y_raw = np.array(y_raw) # 去除非数值点(例如定义域外的点),后续用NaN表示间断 # 如果相邻两个点都有效,中间不需要插值;如果有一个无效,则插值为NaN # 关键是处理渐近线场景,不然tan(x)会从图底连到图顶造成误导 final_x = [] final_y = [] for i in range(len(x_raw)): if np.isnan(y_raw[i]): final_x.append(x_raw[i]) final_y.append(np.nan) else: # 如果前一点有效且当前点有效则正常加入 final_x.append(x_raw[i]) final_y.append(y_raw[i]) fig, ax = plt.subplots(figsize=(8, 6)) ax.plot(final_x, final_y, "b-", linewidth=2) # 画出坐标轴 ax.axhline(0, color="black", linewidth=0.8) ax.axvline(0, color="black", linewidth=0.8) ax.grid(True, linestyle="--", alpha=0.6) # 自适应y轴范围:去掉极端值干扰 valid_y = [v for v in final_y if not np.isnan(v) and abs(v) < 1e6] if valid_y: y_min = min(valid_y) y_max = max(valid_y) if y_min < -100: y_min = -100 if y_max > 100: y_max = 100 ax.set_ylim(y_min - 1, y_max + 1) ax.set_xlabel("x") ax.set_ylabel("y") if title: ax.set_title(title) else: ax.set_title(f"y = {expression}") # 保存图片 buf = BytesIO() plt.savefig(buf, format="png", dpi=120) plt.close(fig) buf.seek(0) if output_base64: return base64.b64encode(buf.read()).decode("utf-8") return buf.getvalue()这里有几个小细节容易踩坑。一是matplotlib默认的负号在部分中文字体下会显示成方块,必须设置axes.unicode_minus=False。二是tan(x)这种渐近线函数,如果你不做NaN插值,matplotlib会把两端的无穷大值连起来,画出一条笔直的竖线,学生看了会以为tan在90度处有定义,这是教学事故。三是dpi不能太高,太高图片太大,Base64传输和平台回显都会变慢,我实测120dpi、8比6的尺寸最平衡。四是y轴范围一定要做限制,不然y=1/x在x接近0时会飙到几十亿,把图整个压扁。
3.4 图片回传:Base64还是URL,要根据平台限制选
图片生成之后怎么回传给智能体,这里也有门道。我在Dify里调HTTP工具时发现,部分平台在工具返回图片时只支持直接返回图片URL的markdown格式,或者它会把返回的Base64当作纯文本处理,不会自动识别成图片。
我的处理方式是在接口里做了一个兼容:默认返回Base64字符串,同时提供一个get_image_url参数,如果调用方需要临时URL,就先把图片存到一个静态目录,返回类似https://your-domain/plots/xxx.png的链接。然后在工作流里配置工具返回类型的时候,根据平台支持情况选一种。
需要注意一点:临时URL方式要处理文件生命周期。我踩过一个坑,图片存在磁盘上一直不删,跑了一周服务器多了几千个临时文件。后来加了一个定时清理任务,只保留最近24小时的图片。如果平台支持Base64直接展示,我建议优先用Base64,省掉URL和存储那一堆事。
3.5 教学智能体的进阶:结合RAG和例题库
画图的工程链路跑通之后,我顺手做了一件让整个教学体验提升一个档次的事:把绘图服务接入RAG(检索增强生成)流程。具体做法是这样的:当智能体判断需要画图时,它先在知识库里检索这道题相关的高频考点和常见易错点,然后把检索到的知识点一起塞给绘图服务,让图上可以额外标注"这里是对称轴""这里是极小值点"。
这个思路的本质是把"静态画图"变成了"动态教学"。普通画图只是把数学表达式可视化,而检索增强则让图像承载了教学内容。比如某道题考察的是均值不等式取等条件,检索到之后绘图服务会在图上特别标注对应点的坐标。实际测试下来学生反馈很好,很多孩子说"一眼就看到答案在哪了"。如果你也在做教育智能体,我强烈建议画图功能和RAG结合,而不是孤立地画图。
4. 两天折腾里踩的坑:常见问题与排查技巧实录
4.1 中文乱码:不是所有服务器都有中文字体
我第一天下午画的图里,标题和坐标轴注释全部显示成一个个空心方框,原因很简单:部署环境是精简版Linux系统,没有安装任何中文字体。排查方法也很直接,在终端执行fc-list :lang=zh看有没有中文字体,如果啥都没有,就需要安装一下,然后再确认matplotlib能找到新装的字体。
这里提醒一个小细节:装完字体之后一定要重启Python进程,在某些系统里matplotlib会缓存字体列表,不重启它还是用旧列表,画出来照样方框。我因为这个又浪费了半小时,最后重启服务才生效。保险起见还可以在代码开头调用matplotlib.font_manager.fontManager.addfont()显式注册字体文件路径。
4.2 表达式解析失败:x和乘号的混淆
大模型抽取参数时,经常把数学手写习惯带进表达式。最常见的是x^2写成了Python不认的乘方语法,log(x)它给你写成log x,甚至有时候它会把自然语言里的"2x"直接当表达式传过来。
排查技巧是做好两层防御。第一层是在提示词里做明确示例,把"2x要写成2*x,x^2要写成x**2"写进约束。第二层是在后端做一个表达式预处理函数:统一把^替换成**,把数字和字母之间的空格压缩掉,把log x这种缺括号的形式补上括号。虽然不能覆盖所有情况,但能兜住大概八成的不规范输入。
剩下的两成,就靠报错信息定位了。我在safe_eval里加了详细的异常捕获,表达式解析失败时返回的错误信息里带上原表达式的字符串、出错的位置、当前尝试的x值。这样调试的时候一眼就能看出来是抽取环节的问题还是绘图环节的问题。
4.3 大模型抽参数不稳定:同样的问题两次结果不一样
这大概是所有做Agent的人都会遇到的痛点。用户说"画一下y=x²-2x-3",第一次模型抽取成x**2 - 2*x - 3,第二次直接给你来一个x^2-2x-3。虽然预处理救回来一部分,但偶尔还是会有字段缺失,比如忘了传x_min和x_max,或者把范围的键名写错。
我的解决方案是双保险。保险A:提示词里给死JSON模板,明确要求每个字段都必须出现,值可以不填但是键不能少。保险B:后端用Pydantic做严格校验,缺字段就给默认值,而不是让整个请求失败。比如x_min缺省就默认-10,x_max缺省就默认10。这样模型抽取偶尔出问题,用户体验也不受影响。
实测下来,加了双保险后绘图请求的成功率从95%还能再往上提,接近99%。剩下那1%基本是模型说胡话,比如让画"理论上存在的函数",神仙也拦不住。
4.4 平台传图失败:Base64太长被截断
这个问题在Dify里遇见的概率不小。HTTP工具返回Base64字符串,平台默认当作文本处理,如果文本太长(一张高清图Base64轻松超过10万字符),部分平台在传给大模型的时候会截断,或者因为上下文窗口太大而报错。
解决思路有两个方向。一个方向是降低图片体积:限制dpi、减少采样点、用PNG压缩级别调高。另一个方向是换传输方式:不要走大模型文本通道,直接把图片URL嵌入回复内容,让前端通过URL加载图片。我在实测里发现,Base64方式适用于小图,URL方式适用于大图,我们在教学场景里学生可能看细节,图片质量不能太低,所以最终选了URL方式为主。
4.5 超时与并发:一个班级同时涌进来40个请求
上线测试那天我遇到一个更现实的问题:一个班40个学生同时提问,绘图服务在同一秒收到40个绘图请求。每个请求大概需要0.3到0.5秒完成,但由于FastAPI默认同步处理,前一个还没画完,后面的全部排队,整体响应时间直接飙升到15秒以上。
解决办法不复杂:把绘图函数改成异步或者用ThreadPoolExecutor做并发。另外,给绘图请求加了一个简单的缓存:同一个函数表达式、同一个x范围,如果在半小时内已经画过,直接从缓存返回图片URL,不再重复计算。这类重复请求在教学中特别多,一道题全班都会问,缓存能把并发压力降低一个数量级。
4.6 问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 图片中文显示为方框 | 服务器缺中文字体 / matplob字体缓存未刷新 | 安装中文字体、重启服务、显式注册字体 |
| 表达式解析报错 | 乘方写法不规范、缺括号 | 提示词约束 + 后端预处理 + 详细报错定位 |
| 同样的输入返回不同的图 | 大模型抽参数随机性 | JSON Schema固定格式 + Pydantic默认值兜底 |
| 平台不显示图片 | Base64过长被截断 | 降低图片体积 / 改用临时URL返回 |
| 多个请求卡死 | 同步阻塞 / 超时 | 改异步 + 线程池 + 相似请求缓存 |
| 图线在渐近线处连成竖线 | 未处理NaN间隔 | 采样时标记NaN,matplotlib自动断开 |
5. 效果检验:同一道题反复问、连续追问、并发冲击都试了
5.1 回归测试:同一道题反复问十次,输出还能稳定
我很怕遇到那种"你问同一个问题,第一次画得对,第二次画歪了"的情况,这在教学场景里很致命,学生会觉得智能体不可靠。所以我专门做了一个回归测试:准备了20道典型题目,每道题问10次,记录参数抽取是否成功、图像是否正确、讲解是否合理。
测试下来,加上了配套提示词和默认值兜底的绘图服务,20道题200次请求里只有两次参数抽取不完整被兜底救回来了,图像基本稳定。偶尔出现x轴范围略有出入(比如默认-10到10,用户指定-8到8被忽略),但不影响整体理解。对于教学场景,这种稳定性完全够用了。
5.2 追问测试:能不能结合上一张图继续问
教学场景里学生不可能问一句就结束了,他大概率会追问"那这个函数在x=-1处的切线怎么画""如果常数项变成5会怎样"。这两种追问场景我做了针对性的上下文处理。
第一种追问是"基于当前图的追问"。智能体在回答时把图片的URL连同讲解结果一起写进多轮对话上下文,后续模型在处理追问时能看到之前的图像和讲解,不需要重新画图就能说明白。第二种追问是"基于参数变化的追问"。比如"常数项变成5会怎样",模型需要重新抽取参数(在原表达式基础上改常数项),再次调用绘图服务画一张新图。这里我让模型输出一个"是否重新绘图"的标志位,前端根据标志位决定是复用旧图还是展示新图。
5.3 并发与延迟测试:一个班45人的压力模拟
我写了一个简单的脚本模拟45个学生同时提问的场景,每个学生分别问一到两道函数画图题。结果加上缓存和线程池之后,45个并发请求里,命中缓存的直接秒回,没有命中缓存的平均响应时间是0.8秒,最慢的一个1.6秒。相比之前15秒以上的排队,这个体感已经非常不错了。
这里分享一个细节:缓存命中率比你想象的高。40个学生的题目集中在我准备的10道测试题里,所以缓存命中率能达到60%以上。真实教学场景中题目会更分散,但一个知识点下的典型函数类型就那么多,缓存依然能显著降低绘图压力。
6. 后续还能怎么扩展:函数图只是整个教学可视化的一小块
6.1 从函数图到平面几何图、统计图、概率分布图
搞定函数图之后,我最大的感受是这套架构可以复用到很多教学场景。你不一定非要画函数,也可以画平面几何图(三角形、圆、椭圆),画统计图(直方图、箱线图),画概率分布图(正态分布、二项分布)。核心就是把"数值计算+matplotlib绘图"的服务端能力再抽象一层,做成一个支持不同绘图类型的通用服务。
我现在已经在同一套FastAPI服务里加了一个/plot/geometry接口和一个/plot/statistics接口,函数图接口的代码基本不用动。学生问"画一个直角三角形ABC,A角30度,B角60度",智能体走同一套意图识别-参数抽取-调用接口-讲解的链路,只是后端绘图逻辑从plot_function换成了draw_triangle。这个扩展过程特别顺,说明当初把绘图能力独立成服务做对了。
6.2 结合题目库和讲义库,让智能体不只是画图工具
画图是手段,不是目的。我做教学智能体的终极目标,是让它像一个耐心的数学老师,既会算又会讲。所以后来我把绘图服务接入了班级题库和讲义库:学生问的题如果命中题库,智能体会把完整的解题步骤、标准答案、知识点链接都调取出来,再结合函数图一起组织讲解。图和文字互为补充,教学效果比我最初只做画图时好得多。
如果你做的是垂直学科智能体,我建议一定要把"知识点"和"图形"打通。现在的智能体框架(无论是Dify还是Coze)本身就有知识库功能,你只需要画完图之后,在知识库里检索对应的知识点说明,然后把图、知识点、解题步骤拼接成答案返回。这一步纯粹是工作流编排的活,技术难度不大,但对教学效果的提升是决定性的。
6.3 多智能体分工:绘制智能体、讲解智能体、质检智能体
再往后我又实验了一下多智能体协作的方案。绘图本身单独做成一个"绘图智能体",它只负责接收参数、输出图片,不参与教学讲解。讲解智能体负责组织语言、调用绘图智能体、结合检索到的知识点生成回答。质检智能体则专门检查绘图结果有没有问题——图像是否合理、坐标轴是否标注、有没有把渐近线画实。
这种分工模式的好处是每个智能体的职责单一,调试和优化都更简单。讲解智能体出问题了,不需要动绘图代码;绘图智能体出问题了,不会影响讲解逻辑。而且可以针对不同的学科重复使用绘图智能体,比如物理老师想画抛物线运动轨迹,只要把绘图智能体的能力中心换一下就行,不用重新做一套Agent。
做教学智能体的这两天真没白折腾。画函数图这件事,看起来只是一个图片生成功能,实际上打通了意图识别、结构化抽取、安全代码执行、图像回传、上下文管理整整一长串链路。你如果也在做类似的教育类智能体,我的建议是:别急着在平台里拖节点,先把"绘图服务独立出来"这个思路定下来,后面的扩展会顺很多。真正难的不是让大模型画出一张图,而是让它理解学生到底想要什么,再把图变成学生能看懂的讲解。这个目标,值得每一个做教学AI的人花两天慢慢磨。