realmagic.md
锐盟半导体
“你们背诵 agent 解决的具体问题是什么?为什么值得做?”
“85% 达标阈值、90% 准确率是怎么定出来的?”
“这个功能对家长端来说最重要的价值是什么?”
回答思路:
先用你 3 个 pain point 快速铺陈:
学生自己背诵没有反馈;
没复习计划,记了很快忘;
家长看不到过程,只看到结果。
再用 4 个能力接上:
实时检查 + 定位错误;
错误分类 + 记忆方法;
复习计划;
日/周报。
阈值 & 准确率:
85% 选的是一个内部验证比较舒服的平衡点:
小朋友背诵时有少量口误也不至于“永远过不了”;
但如果只对一半内容,仍然会被要求再背;
90% 准确率是在内部构造的一批样本上测出来的大致水平,你可以强调“目前是 demo 阶段的经验指标,将来会上线后按真实数据做 A/B 调整”。
“从孩子开始背到结果出来,后台完整链路是什么样的?”
“为什么要设计 run 和 run-async 两套接口?实际是怎么用的?”
“task_id 是怎么管理生命周期的?会不会无限堆积?”
回答思路:
完整链路:
前端发起“开始背诵” → 编排器识别意图为“背诵” → 调用 /api/agents/recitation/run:
校验参数 → 记录一条 history 草稿(user_id、content、开始时间…)→
调 LLM 做比对 + 错误抽取 → 更新 accuracy / errors / need_retry →
更新错误模式表 / 复习计划表(如果首次达标)→ 生成反馈 + 记忆方法 → 返回给客户端。
如果家长查看日报/周报,就是走 report/daily、report/weekly,从 history + error_pattern 聚合。
同步 vs 异步:
同步 run:适合当前 demo 场景——短文本古诗,一次调用就能在 2–3 秒内返回;
异步 run-async:
为后续长文本(整篇课文)、复杂 workflow(多轮对话 + 语音识别)预留;
调用后立即返回 task_id,前端周期性调 /task/{task_id} 查结果;
可以补一句:“目前主要在 demo 环境先用同步,但接口层已经为后续复杂场景预留了异步通路”。
task_id 生命周期:
存在 task_manager 里有状态(pending/succeeded/failed + 结果快照);
可以在后台定时清理过期 task,比如保留 24h 内的结果。
“为什么要拆成四张表,而不是一张 history 里塞 JSON?”
“怎么查一个用户今天有哪些内容需要复习?”
“报表幂等是怎么保证的?如果接口被重复调用会怎么样?”
“你估过数据量和存储压力吗?未来要怎么演进?”
回答思路:
为什么四张表:
history:高频写、长文本、保留原始事实;
error_pattern:频率统计 + 记忆方法,读多写少,便于单独索引;
review_schedule:小而精,用于待复习列表查询;
report:面向家长的缓存,减轻每次临时聚合的压力; => 这样做使得每类查询都有专门的索引,避免一张大 JSON 查啥都 full scan。
复习列表查询:
直接说出 SQL(你文档里已经有): where user_id = ? and status = 'pending' and next_review_at <= now() order by next_review_at;
说明对应的联合索引 (user_id, status, next_review_at),以及 demo 环境下响应从百毫秒降到三四十毫秒。
报表幂等:
报表表上 (user_id, report_type, report_date) 唯一索引;
生成逻辑是「存在就 update,不存在就 insert」,所以重复调用最多覆盖数据,不会产生多条记录。
数据量 & 演进:
你已经有单用户 → 10 万用户的估算,可以直接讲;
接下来补一句演进路线:
短期:按月归档冷数据,保留近三个月的热数据;
中期:按用户分库或按时间分表,日报/周报可迁到专门的报表库;
长期:配合读写分离 + Redis 缓存。
“accuracy 公式是怎么设计的?为什么按字数?”
“错误分类是怎么做的?纯规则还是模型?遇到模糊情况怎么办?”
“为什么要合并两次 LLM 调用?怎么确认合并后质量没下降?”
“讲一个你在错误去重 / 提示词上踩过的坑。”
“艾宾浩斯复习计划在你们这里是怎么落地的?”
回答思路:
accuracy:
现在采用「按字数」计算,是因为对低年级古诗场景,错几个字家长非常敏感,字级别反馈更直观;
同时配合 errors 数组给出具体错字/漏行位置,让家长能看懂;
可以顺带说一句:“后续也可以按句为单位做一个辅助指标,比如每句是否正确,用在复习计划上”。
错误分类:
先由 LLM 输出 expected/actual/type 等结构化结果;
再由规则按长度、差异位置判断是否为漏内容/字音错/顺序错;
纯模型方案延迟 & 成本太高,纯规则方案覆盖不了所有情况,所以用“模型提供候选 + 规则兜底常见模式”的组合;
模糊场景下,可以优先看规则;规则识别不了的保持 type=其他,只给出错误位置和正确答案。
合并 LLM 调用:
原来是“先检查、再鼓励”两次调用,平均延迟 >4s,token 也翻倍;
合并后在 prompt 里要求模型一次输出“评分 + 错误列表 + 文本鼓励 + 记忆方法”,延迟降到约 2–3s,token 明显减少;
质量验证方式:
选了几十个样本,比较前后输出是否有逻辑缺失,特别是错误定位和鼓励文案;
发现少数 case 需要调 prompt,比如防止鼓励里把错误答案重复说错之类,然后迭代提示词。
错误去重 & 提示词 bug 故事:
V1/V2/V3 三步迭代 + 楽观锁,这个你可以当一个完整 story 讲;
提示词坑:原来只让模型输出“第几句”,结果同一处错误拆成“第3句/第4句”,导致重复计数;
修改后:要求 position 输出“第3–4句,第 X–Y 字”,并对下游去重逻辑做适配,问题解决。
艾宾浩斯复习计划:
首次达标才建 plan,避免没背熟就进入复习;
复习时按准确率调整间隔:≥85% 拉长,不足则缩短;
允许学生在计划外多背,只有当作“复习”时才会更新 schedule。
“你们项目代码的分层是怎样的?为什么要这样拆?”
“LLM 调用、错误模式更新、复习计划创建这些操作是怎么做到不乱的?”
“有没有遇到并发/幂等相关的问题?怎么处理的?”
回答思路:
分层:
router 只处理 HTTP 协议和 DTO 校验;
service 组织业务流程(调用 LLM、写表、生成报告等);
repository 只做数据访问 + 索引友好的查询;
LLM repository 单独封装调用逻辑,方便以后换模型。
这样拆的好处:
每层职责清楚,改业务逻辑不需要碰 HTTP / ORM 细节;
单测时可以 mock 掉 LLM 和数据库,减少耦合。
并发 & 幂等:
ErrorPattern 更新频次数时,用乐观锁保证 “读旧值 + 加 1 + 更新” 不会丢;
报告用唯一索引 + upsert 逻辑保证幂等;
复习计划创建时先查是否已有 active 计划,避免重复计划。
如果面试官继续追问“异常处理 / 回滚怎么办”,你可以说:
目前 demo 阶段主要保证“不要生成脏数据”:
写多表的流程中,尽量把强一致性的部分放到同一个事务里(history + error_pattern / review_schedule);
LLM 调用失败则不写入后续表,只在 history 里记录失败状态并返回提示,后续的改进方向是引入重试队列和熔断策略。
坦诚说“现在做到这里、下一步准备做什么”,就好。
“如果这个系统真的服务 10 万以上学生,你预估最大的瓶颈在哪里?”
“按你估算的数据量,后端存储/查询会怎么演进?”
“如果以后要支持更多场景(听写、口语点评),背诵 agent 的设计还能复用吗?”
回答思路:
瓶颈:
首先是 LLM 调用(成本 & 延迟),其次是报表和复习列表查询;
LLM 层面可以做:prompt 优化、多模型分层(简单 case 用小模型)、批量处理、加缓存;
数据层面:按你估算的数据量,history 可能会到百 GB 量级,这时就需要按时间/用户分表 + 归档。
你的扩展性方案:
冷热数据:保留近三个月在主库,历史归档;
分库分表:按用户或年级做水平拆分,或者按月份拆 history;
读写分离 + Redis:读多的 report、review list 放缓存,写走主库。
多场景复用:
你可以说“背诵 agent 本质上是一套【对照标准答案 → 抽取错误 → 生成反馈 → 写入历史/计划】的模板”,听写/口语点评可以复用这套 pipeline,只是换了输入渠道(语音识别)和错误类型。
这时你和主 agent 重构的那条线就能连起来:通过编排器+workflow 把“背诵/听写/问答”都当成不同 workflow 管理。