快手 AI全栈开发 一面:Skill 取舍与 MQ 可靠投递
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
Skill 与 Agent 设计
- Skill 和 workflow 的对比,为什么要把原来的 workflow 改成 Skill?
- workflow 有哪些优缺点?Skill 有哪些优缺点?
- 怎么判断一个 Skill 是好 Skill?
- 这个 Skill 具体怎么设计的?这套设计怎么达成项目目标?
- AI 怎么判断分析已经完成,然后停下来?(停止条件)
- 开发 Skill 遇到过哪些坑,怎么解决的?
- Agent 适配是否只是模型适配?除了模型,不同 Agent 之间真正的差异在哪?
- 模型可能不遵循 Skill 文档,高合规场景下怎么保证不越权?
- 你怎么验证 Skill 按预期运行?
- 怎么降低 Skill 的评测成本?离线大规模评测怎么搭?
业务侧(SQL 注入与医药单据)
- 为什么做 SQL 注入防护?你负责的第三层具体逻辑是什么?
- 黑名单样本从哪来?
- 怎么验证这套防护真的生效?
- 能不能用 AI 做自动化安全测试?落地会有什么问题?
- 通义千问在单据业务里具体做了哪些事?
- 你这个场景里,哪些问题适合 AI,哪些不适合?
RabbitMQ
- 延迟消息有哪几种实现方式?
- 消费失败怎么处理?核心业务和普通业务有什么区别?
- 自动重试怎么设计?指数退避的思路是什么?
- 消费端宕机,怎么确认消费成功还是失败,怎么避免丢消息?
- 消费幂等怎么做?你项目里怎么做的?
- 用 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 只是加速器,唯一索引才是正确性保证」,这一题就算答透了。