面灵AI→

快手 AI全栈开发 一面:Skill 取舍与 MQ 可靠投递

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

Skill 与 Agent 设计

  1. Skill 和 workflow 的对比,为什么要把原来的 workflow 改成 Skill?
  2. workflow 有哪些优缺点?Skill 有哪些优缺点?
  3. 怎么判断一个 Skill 是好 Skill?
  4. 这个 Skill 具体怎么设计的?这套设计怎么达成项目目标?
  5. AI 怎么判断分析已经完成,然后停下来?(停止条件)
  6. 开发 Skill 遇到过哪些坑,怎么解决的?
  7. Agent 适配是否只是模型适配?除了模型,不同 Agent 之间真正的差异在哪?
  8. 模型可能不遵循 Skill 文档,高合规场景下怎么保证不越权?
  9. 你怎么验证 Skill 按预期运行?
  10. 怎么降低 Skill 的评测成本?离线大规模评测怎么搭?

业务侧(SQL 注入与医药单据)

  1. 为什么做 SQL 注入防护?你负责的第三层具体逻辑是什么?
  2. 黑名单样本从哪来?
  3. 怎么验证这套防护真的生效?
  4. 能不能用 AI 做自动化安全测试?落地会有什么问题?
  5. 通义千问在单据业务里具体做了哪些事?
  6. 你这个场景里,哪些问题适合 AI,哪些不适合?

RabbitMQ

  1. 延迟消息有哪几种实现方式?
  2. 消费失败怎么处理?核心业务和普通业务有什么区别?
  3. 自动重试怎么设计?指数退避的思路是什么?
  4. 消费端宕机,怎么确认消费成功还是失败,怎么避免丢消息?
  5. 消费幂等怎么做?你项目里怎么做的?
  6. 用 Redis 做幂等,key 的过期时间怎么设?消息还没处理完 key 就过期了会怎样?

《参考解析》

Skill 和 workflow 的分界不是「新旧」,是「谁来决策下一步」。 workflow 是把步骤写死的有向图:节点固定、分支条件固定、输入输出契约明确,好处是可预测、可测试、成本可控、出错能定位到某个节点,坏处是覆盖窄——现实任务的分支一多,图会爆炸,维护成本比写代码还高,而且模型只能当节点里的一个函数用。Skill 是把「能力」和「使用说明」打包给模型:一段描述告诉它什么时候该用、正文写清步骤与注意事项、附带脚本处理确定性动作、按需加载资源文件(渐进式披露,不会一次性塞满上下文),由模型自己决定调用与组合。它的覆盖广、迭代快(改文档就能改行为),代价是不可预测、难评测、token 成本不可控。所以正确的取舍是分层:高频且路径稳定的部分留在 workflow 当骨架,长尾、开放式、需要模型判断的部分做成 Skill 挂在骨架的某个节点上;「为什么改成 Skill」要有具体证据——原来的 workflow 分支数已经超过维护阈值、或者新增一类需求就要改图,这类理由比「Skill 更先进」有说服力得多。

好 Skill 的判据可以量化成五条。 一是触发边界清楚:描述里写清「什么时候用、什么时候不要用」,能被模型在多个候选 Skill 里正确挑中;二是单一职责:一个 Skill 只干一件事,失败时能独立定位;三是输入输出有契约:入参、出参、失败返回都结构化,能被程序校验而不是靠模型自觉;四是可验证:跑完有确定的成功判据(schema 校验、单测、断言),不是「模型说完成了」;五是成本可控:正文分步、脚本化确定性动作、资源按需加载,避免每次调用都灌几千 token 的说明。反过来,坏 Skill 的典型症状是:描述写成产品介绍、正文里塞满例子、什么都想管、没有失败分支、改一次就要重训或重新调 prompt。「这套设计怎么达成项目目标」要能把 Skill 的结构和指标对上:比如把「抽取—校验—落库」拆成三个脚本 + 一个判断节点,是为了把抽取错误率压下去、把人工复核率降下来,而不是为了结构好看。

「什么时候停」这题的通病是拿模型的置信度当停止条件,而置信度还是模型自己给的。 可复述的答法是把停止拆成四类可观测信号:任务完成(可验证的产物出现了,比如文件生成、编译通过、接口返回 200、断言全绿)、无进展(连续 N 轮没有新增信息或产物没变化,N 取 2~3)、预算耗尽(最大轮次、最大 token、最长时长,按 P99 定而不是拍脑袋)、以及需要人介入(命中权限边界、检测到冲突、或不确定度高于阈值)。前两类是硬信号,第三类是保险丝,第四类是出口。模型的自评只能用来排序下一步做什么,不能用来决定结束,因为它在「差一点就对」的时候最自信。如果一定要用置信度,就把它校准成外部信号:多次采样的一致率、工具返回码、检索命中率,这些是可测的;单一模型的「我觉得完成了」不可测。高合规场景还要加一条:完成声明必须附带证据(引用了哪条数据、跑了哪个校验),没有证据的一律按未完成处理。

高合规下防越权,靠的是工具层的权限,不是 prompt 里的「请不要」。 具体做法:把所有副作用操作收敛成有限个工具,每个工具声明所需权限和参数约束,由策略层(policy engine)在执行前判定——不在白名单的路径直接拒绝、参数做类型与范围校验、危险操作要求二次确认或 dry-run 先出计划、所有调用落审计日志(谁、什么参数、什么时候、结果)。模型能做的是「提出意图」,能不能落地由外层决定,这样即使模型被注入或跑偏,最坏结果也只是提了一个被拒的请求。Skill 文档里要写的是「遇到 X 情况必须停下问人」,但这句话只是提高体验,不是安全边界。验证 Skill 是否按预期运行,三档手段:单测式 eval(固定输入,断言工具调用序列、参数、产物 schema)、轨迹回放(把线上真实会话的中间态重放,比对决策点)、线上灰度(小流量 + 人工抽检 + 关键指标告警)。降低评测成本靠分层:L0 静态与结构校验(免费、全量跑)、L1 小模型或规则 judge(覆盖大部分用例)、L2 人工只评分歧样本和抽样;再叠加缓存(同样的输入不重复跑)、失败用例优先回归、以及把指标做成看板(通过率、工具调用正确率、平均轮次、单次成本),让「评测」变成每次改动都能跑的常规动作而不是专项工程。

SQL 注入防护答「分层」比答「过滤关键词」高级得多。 第一层是根治:所有 SQL 走参数化查询/预编译,拼接一律禁止,这是唯一能真正消灭注入的手段;第二层是收敛入口:DAO 层统一封装、禁止裸 SQL 逃逸、数据库账号最小权限(不给 DROP、不给跨库)、报错信息脱敏不回显。第三层才是检测与拦截,也就是「黑名单 + 语法分析」这一层:不能只用正则匹配 union select,因为注释、编码、大小写、内联注释、宽字节都能绕过;正确做法是先用词法/语法解析把 SQL 归一化(去注释、统一大小写、还原编码、提取关键字与结构),再判断是否存在「不该出现的结构」——比如本该是值的位置出现了关键字、出现了堆叠查询、出现了 information_schema、出现了睡眠函数。我负责的第三层如果按这个思路答,要能说清判定顺序:先解析、解析失败按可疑处理(fail-closed),再比对结构白名单,再匹配样本特征,最后给风险分并决定放行/拦截/记录。黑名单样本的来源要具体:历史攻击日志与 WAF 告警、公开 payload 库(sqlmap 的 tamper 变体)、漏洞报告与红队测试、以及线上灰度期的影子流量。验证是否生效要用「漏报 + 误报」两个指标:用变异后的 payload 跑靶场测漏报,用正常业务 SQL 的回放测误报(误报高会被业务方要求关掉,这是这类系统最常见的死法),并且要看线上拦截量与人工复核的一致率。AI 做自动化安全测试可行但有明确短板:它能生成语义合理的变异 payload、能读代码找拼接点,但覆盖不可证明、结果不可复现、误报高、且容易生成「看起来能打」的无效样本;落地方式是 AI 生成候选 + 确定性引擎验证 + 人工确认,把 AI 定位成「提高样本多样性」,不是「替代验证」。

通义千问在单据业务里的边界,就是「哪些问题适合 AI」的答案。 适合的是语言与结构类工作:非结构化文本里的字段抽取(药名、规格、数量、供应商)、口语化描述归一、单据类型识别、异常原因的自然语言说明、以及给审核人员的辅助问答。不适合的是确定性要求高的:金额与税额计算、审批权限判定、合规最终裁定、库存扣减——这些一旦错了不可解释也不可回滚,必须由规则和事务保证。判断标准可以归纳成三条:答案能不能被程序验证(能验证的才适合让模型产出)、错了的代价是否可承受、以及是否需要审计追溯。单据场景还有个具体约束:医药行业受监管,模型输出要能落到「引用了哪条原始文本」,所以设计上要把抽取结果和原文位置绑定,人工复核时能一眼看到依据。

RabbitMQ 的可靠性,本质是「每个环节都要有确认,且允许重复」。 延迟消息三种实现:TTL + 死信队列(消息设过期时间,到期进 DLX 被消费;缺点是队列级 TTL 有队头阻塞——前一条没到期后面的到期也不投递,所以要么用每消息 TTL 加多级队列,要么每延迟档位一个队列)、延迟插件 rabbitmq_delayed_message_exchange(服务端存延迟消息,精度好、实现简单,缺点是要装插件、消息在插件里落盘、规模大时不如专门的时间轮服务)、以及自建(Redis ZSet 或时间轮扫到期再投递,灵活但要自己做可靠性和分片)。生产端要开 publisher confirm + 持久化交换机和队列,否则「发出去了」不代表 Broker 收到了。消费失败要分业务等级:核心业务(订单、支付、扣款)走「nack 重回队列会死循环」的坑要避开,正确做法是重试队列分级(retry-1m / retry-5m / retry-30m),每级失败后进死信队列并告警,由人工或补偿任务处理,绝不允许静默丢弃;普通业务(通知、统计)可以有限重试后记录并丢弃,因为下游可以靠定时全量对齐。指数退避的思路是延迟 = base × 2^retry + 随机抖动,base 取 1s 量级、上限几分钟,抖动的目的是避免大批消息在同一时刻同时重试形成二次洪峰;重试次数要设上限并与死信衔接。消费端宕机不丢消息的前提是「手动 ack + 持久化 + 副本」:关掉 autoAck,处理成功才 ack,连接断开时未 ack 的消息会重新投递;队列用 quorum queue(Raft 复制)或镜像,交换机与消息都持久化。代价要主动说:这套组合是 at-least-once,重复投递一定会发生(网络抖动、ack 丢失、消费者重启),所以幂等不是可选项而是必需项。

幂等 key 的过期时间那问,正确答案是「TTL 不是拍一个数,而是跟着处理时长走」。 先说要解决的问题:Redis 做幂等通常是 SET key value NX EX ttl,key 存在表示「这个业务键已经处理过或正在处理」。如果 TTL 小于最长处理时间,消息还在跑 key 就过期了,此时重复消息进来会重新加锁成功,于是同一笔业务被执行两次——「消息处理了 12 分钟、key 只活 10 分钟」就是这个漏洞。三种解法,最好组合使用:一,把 TTL 设成「P99 处理时长 × 安全系数」(比如 P99 是 2 分钟就设 10~30 分钟),并监控「key 过期时任务仍未完成」的计数,这个计数大于 0 就说明 TTL 不够;二,把状态做成两态而不是一个锁:处理中写 processing 并带持有者与租约,处理完改成 done 并把 TTL 拉长到业务允许的去重窗口(比如 24 小时),重复消息看到 done 直接返回、看到 processing 就等或丢弃,而不是抢锁重跑;三,加租约续期(watchdog),处理期间定期延长 TTL,进程挂了租约自然过期让别人接手——这时要保证「接手」是安全的,也就是业务操作本身要么幂等(用数据库唯一索引或状态机 CAS 兜底),要么能回滚。最关键的兜底是别把幂等全押在 Redis 上:最终一致性要靠数据库的唯一约束或「带版本号的条件更新」,Redis 只是第一道快速拦截,因为它会丢数据(淘汰、故障切换未同步)。回答时如果能主动说出「TTL 只是加速器,唯一索引才是正确性保证」,这一题就算答透了。