一、Go语言
题目 1.1【Topic: Go工程化 - protobuf/gRPC】
场景设计题
假设你负责设计一个高性能的微服务框架,需要在服务间进行高效的序列化通信。当前有 Protobuf (gRPC) 和 JSON (REST) 两种方案可供选择。
问题:
- 请分析 Protobuf 相比 JSON 在序列化效率和带宽占用方面的核心优势,并解释其底层原理。
- 在实际项目中,你如何设计 gRPC 的 Protobuf 文件?请给出关键的 best practices。
- 如果需要支持部分字段更新(Partial Update),Protobuf 有哪些方案可以实现?
题目 1.2【Topic: Go场景解决方案 - 缓存设计】
代码分析题
以下是一个常见的缓存读写实现,请分析其中的问题并给出优化方案:
go
func GetUserProfile(userID string) (*UserProfile, error) {
// 尝试从缓存获取
cached, err := redis.Get(ctx, "user:profile:"+userID).Result()
if err == nil {
var profile UserProfile
json.Unmarshal([]byte(cached), &profile)
return &profile, nil
}
// 缓存未命中,从数据库查询
profile, err := db.QueryUser(userID)
if err != nil {
return nil, err
}
// 写入缓存
data, _ := json.Marshal(profile)
redis.Set(ctx, "user:profile:"+userID, data, 30*time.Minute)
return profile, nil
}
问题:
- 请指出上述代码存在的 3 个主要问题(考虑并发、错误处理、性能等方面)。
- 请给出优化后的代码实现。
题目 1.3【Topic: Go工程化 - monorepo】
简答题
- 请解释什么是 Monorepo 架构,它相比 Multi-repo 有哪些优势和劣势?
- 在 Go 项目中实现 Monorepo 有哪些常用工具(如 pnpm workspace、Turbo、Bazel 等)?请对比它们的特点。
- 假设你在 Monorepo 中有多个服务共享一个
internal/pkg包,如何处理包的版本管理和依赖升级问题?
二、后端架构
题目 2.1【Topic: Redis - 分布式锁】
场景设计题
你需要在分布式环境中实现一个可靠的分布式锁来保证幂等性操作。
问题:
- 请描述使用 Redis 实现分布式锁的标准流程,包括加锁、解锁、过期时间等关键参数。
- 为什么简单的
SET key value NX EX timeout方案可能不够安全?请分析原因。 - 如果需要实现一个可重入的分布式锁,Redis 支持吗?请给出设计方案。
- 请对比 Redis 分布式锁与 ZooKeeper/etcd 分布式锁的优劣。
题目 2.2【Topic: MQ - Kafka】
场景设计题
公司业务需要引入 Kafka 来处理高吞吐量的日志采集系统,日均消息量达到 10 亿级别。
问题:
- 请说明 Kafka 的核心概念(Topic、Partition、Consumer Group、Offset)以及它们之间的关系。
- 如何保证 Kafka 消息的exactly-once 语义?请分析不同场景下的实现方案。
- 假设某个 Consumer 消费出现严重积压,请列出排查思路和可能的解决方案。
- Kafka 如何实现消息的顺序性保证?
题目 2.3【Topic: 高性能架构 - 限流】
场景设计题
你的 API 服务需要对外提供,每秒 QPS 预计达到 10 万次,需要设计一套完整的限流方案。
问题:
- 请介绍常见的限流算法(固定窗口、滑动窗口、漏桶、令牌桶),并对比它们的优缺点。
- 在分布式环境下如何实现全局限流?请给出至少 2 种方案。
- 如果既要限流又要保证用户体验,你如何在限流时提供友好的降级响应?
- 请使用 Go 语言实现一个简单的令牌桶限流器。
三、AI Native Software Engineering
题目 3.1【Topic: AI编程方法论 - Multi-Agent Development】
场景设计题
你正在设计一个 AI 代码审查系统,需要多个 AI Agent 协同工作。
问题:
- 请解释什么是 Multi-Agent Development,它相比单个 Agent 的优势是什么?
- 请设计一个多 Agent 协作的代码审查流程,包括 Agent 的角色分工和信息传递方式。
- 在 Multi-Agent 系统中,如何处理 Agent 之间的通信和状态同步问题?
- Multi-Agent 系统面临哪些挑战?请列举并给出解决方案。
题目 3.2【Topic: AI编程方法论 - Context Engineering】
论述题
- 什么是 Context Engineering?为什么在 AI 编程中上下文管理至关重要?
- 请介绍几种常见的大模型上下文管理策略(如 context compression、memory hierarchy、semantic retrieval 等)。
- 在实际项目中,你如何平衡上下文长度和信息密度?请举例说明。
- 如果遇到 token 超限的情况,你有哪些优化策略?
四、AI Agent
题目 4.1【Topic: RAG - rerank】
技术深度题
你正在开发一个企业知识库问答系统,使用 RAG 架构来检索相关文档。
问题:
- 请解释 RAG 中 rerank 的作用是什么?为什么需要在 embedding 检索后再进行 rerank?
- 请介绍几种常见的 rerank 算法(如 BERT-based、Cross-Encoder、LLM-based)。
- 在实际实现中,如何平衡检索速度(latency)和准确性(accuracy)?
- 如果知识库规模很大(如千万级文档),请设计一个高效的 RAG 检索架构。
- 请分析 embedding 检索和 rerank 在语义理解上的差异。
题目 4.2【Topic: Agent Runtime - tool retry】
代码设计题
你正在设计一个 Agent Runtime 系统中的 Tool Calling 组件,需要处理 tool 执行失败的情况。
问题:
- 请分析 Agent 执行 tool 时可能遇到哪些类型的失败?
- 请设计一个健壮的 tool retry 机制,包括重试策略(指数退避、最大重试次数等)和错误分类。
- 如何在 retry 过程中避免对外部系统造成雪崩效应?
- 请使用 Go 语言实现一个简单的 tool retry 框架。
go
type ToolResult struct {
Success bool
Data interface{}
Error error
Attempts int
}
// 请实现 RetryToolCall 函数
func RetryToolCall(ctx context.Context, tool Tool, params map[string]interface{}) ToolResult {
// TODO: 实现重试逻辑
}
五、系统设计
题目 5.1【Topic: AI Native System Design - RAG系统】
系统设计题
设计一个企业级 RAG(Retrieval-Augmented Generation)知识库问答系统。
要求:
- 需求分析:系统需要支持千万级文档的语义检索,QPS 达到 1000,平均延迟 < 500ms。
- 架构设计:请给出整体系统架构,包括数据摄入 pipeline、检索层、生成层。
- 核心组件 :
- 如何设计文档 chunk 策略以平衡信息完整性和检索粒度?
- 如何选择和配置 embedding 模型?
- 如何实现 hybrid search(向量检索 + 关键词检索)?
- 性能优化:如何优化检索速度?有哪些缓存策略?
- 质量保障:如何监控和评估 RAG 系统的效果?请列出关键指标。
- 扩展性:如何支持多租户场景?
六、通用面试题
题目 6.1【Topic: 项目深挖 - AI客服架构】
STAR 追问题
请描述你最复杂的一个 AI 客服项目,我会从以下几个维度进行深入追问:
背景追问:
- 这个客服系统的业务背景是什么?日均咨询量有多少?
- 为什么选择 AI 客服方案?人工客服的痛点是什么?
技术实现追问: 3. 系统的整体技术架构是怎样的?请画图说明。 4. 你是如何处理多轮对话的状态管理的? 5. RAG 模块是如何设计的?embedding 和 retrieval 的具体实现是什么? 6. 如何保证回答的准确性和降低幻觉率? 7. 模型调用成本是如何控制的?
挑战与解决方案追问: 8. 在项目开发过程中遇到的最大技术挑战是什么? 9. 如何处理用户意图识别错误的情况? 10. 如何实现情绪识别和安抚?
效果评估追问: 11. 如何量化 AI 客服的效果?有哪些核心指标? 12. 与人工客服相比,AI 客服的满意度如何?
题目 6.2【Topic: AI时代工程师能力 - 如何与AI协作】
开放讨论题
- 在你日常工作中,你是如何将 AI 工具融入到开发流程中的?请分享一个具体的案例。
- 你如何对 AI 生成的代码进行有效的 review?请描述你的 review 流程和关注点。
- 你认为 AI 编程时代,工程师最重要的能力是什么?哪些能力会被 AI 强化,哪些会被弱化?
- 如何拆解任务给 AI?请举例说明你在实际项目中的 prompt 设计和任务拆解经验。
- 在使用 AI 编程时,你遇到过哪些 "AI 幻觉" 导致的问题?你是如何发现和解决的?
- 面对 AI 生成代码的不稳定性(相同 prompt 多次执行结果不同),你是如何保证代码质量一致的?
参考答案
题目 1.1 参考答案
1. Protobuf 相比 JSON 的核心优势:
- 体积小:Protobuf 使用二进制格式,而 JSON 是文本格式。同样的数据,Protobuf 通常比 JSON 小 3-10 倍。
- 序列化速度快:二进制格式无需解析字符串,序列化和反序列化速度比 JSON 快 5-20 倍。
- 类型安全 :Protobuf 在
.proto文件中定义字段类型,编译时就能发现类型错误。 - 向前/向后兼容:通过 field number 和 tag机制,添加或删除字段不影响兼容性。
底层原理:
- Protobuf 使用 Varint 编码表示整数,对于小数值有极高的压缩率。
- 每个字段由
tag(字段号 + wire type)和value组成。 - Tag 的编码:
field_number << 3 | wire_type,使用 base-128 varint 编码。
2. gRPC Protobuf 设计最佳实践:
protobuf
syntax = "v3"; // 使用 v3 语法
package user.service.v1; // 使用 semver 版本号
// 消息命名:使用 CamelCase
message UserProfile {
// 字段命名:使用 snake_case
string user_id = 1; // 始终使用注释,便于生成文档
string username = 2;
google.protobuf.Timestamp created_at = 3; // 使用 Well-Known Types
// 使用 oneof 处理互斥字段
oneof contact {
string email = 4;
string phone = 5;
}
}
// 枚举:首字母大写,值用大写下划线
enum UserStatus {
USER_STATUS_UNSPECIFIED = 0; // 必须有 UNSPECIFIED
USER_STATUS_ACTIVE = 1;
USER_STATUS_INACTIVE = 2;
}
// 服务定义
service UserService {
// 方法命名:使用 RPC 动词 + Request/Response 后缀
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (stream ListUsersResponse); // 使用 stream
}
3. Partial Update 方案:
-
方案一:使用
google.protobuf.FieldMaskprotobufmessage UpdateUserRequest { string user_id = 1; google.protobuf.FieldMask update_mask = 2; UserProfile profile = 3; } -
方案二:使用
google.protobuf.Value动态字段protobufmessage PartialUpdateRequest { string user_id = 1; map<string, google.protobuf.Value> fields = 2; } -
方案三:使用特定 Update 消息
protobufmessage UpdateUserRequest { string user_id = 1; optional string username = 2; // v3 支持 optional optional string email = 3; }
题目 1.2 参考答案
问题 1:代码存在的 3 个主要问题
-
缓存击穿(Thundering Herd)问题:当缓存过期瞬间,大量并发请求同时查询数据库,可能导致数据库压力过大。
-
JSON 序列化性能差:使用 JSON 序列化效率低,建议使用 msgpack、protobuf 或 go-faster-stripes 等更高效的序列化方式。
-
错误处理不当:
- 忽略了
redis.ErrNil和其他错误类型的区分 - JSON 序列化错误被忽略了(
data, _ := json.Marshal(profile))
- 忽略了
-
缺少并发控制:没有使用 singleflight 等机制防止缓存击穿。
-
Context 未传递:数据库查询时没有使用 context。
问题 2:优化后的代码
go
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/redis/go-redis/v9"
"golang.org/x/sync/singleflight"
)
var (
ErrUserNotFound = errors.New("user not found")
ErrCacheMiss = errors.New("cache miss")
)
func GetUserProfile(ctx context.Context, userID string) (*UserProfile, error) {
const cacheKey = "user:profile:"
const cacheTTL = 30 * time.Minute
// 使用 singleflight 防止缓存击穿
result, err, _ := sfGroup.Do(userID, func() (interface{}, error) {
// 1. 尝试从缓存获取
cached, err := rdb.Get(ctx, cacheKey+userID).Result()
if err == nil {
var profile UserProfile
if err := json.Unmarshal([]byte(cached), &profile); err != nil {
return nil, err
}
return &profile, nil
}
// 2. 缓存未命中或错误,查询数据库
profile, err := db.QueryUser(ctx, userID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound
}
return nil, err
}
// 3. 写入缓存(即使是空值也缓存,防止缓存穿透)
if data, err := json.Marshal(profile); err == nil {
// 使用 SetNX 防止覆盖正在写入的值
rdb.Set(ctx, cacheKey+userID, data, cacheTTL)
}
return profile, nil
})
if err != nil {
return nil, err
}
return result.(*UserProfile), nil
}
进一步优化建议:
- 使用 Protobuf 替代 JSON 序列化
- 添加缓存预热机制
- 实现异步更新缓存策略
题目 1.3 参考答案
1. Monorepo 概念与优劣势
Monorepo 是指将多个相关项目(应用、服务、库)放在同一个代码仓库中管理的架构模式。
优势:
- 代码共享方便:多个项目可以方便地共享代码和类型定义
- 统一依赖管理:所有项目使用相同的依赖版本,避免版本冲突
- 一致的代码规范:统一的代码风格、lint 配置
- 原子提交:一次提交可以修改多个相关项目
- 简化 CI/CD:统一的构建和测试流程
- 易于重构:跨项目重构更安全
劣势:
- 仓库体积膨胀:代码量增加导致 clone、CI 时间变长
- 权限管理困难:无法细粒度控制不同项目的访问权限
- 构建复杂度增加:需要智能增量构建工具
2. Go Monorepo 工具对比
| 工具 | 特点 | 适用场景 |
|---|---|---|
| pnpm workspace | 轻量、易用、与 npm 生态兼容 | 前后端混合项目 |
| Turbo | 增量构建、缓存、任务编排强大 | 中大型项目 |
| Bazel | 高度可扩展、远程缓存、隔离性好 | 超大型项目、Google 内部实践 |
| Nix | 可重现构建、环境隔离 | 需要环境一致性的场景 |
| Go workspaces | Go 官方支持、轻量 | 纯 Go 项目 |
3. 包版本管理和依赖升级策略
go
// go.mod 示例
module github.com/myorg/monorepo
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/redis/go-redis/v9 v9.3.0
)
策略:
- 统一
go.mod文件,共享依赖版本 - 使用
go work管理工作区 - 定期使用
go get -u或 Dependabot 自动升级 - 重要依赖添加版本约束:
github.com/pkg v1.2.0 // indirect - 使用 Renovate Bot 进行自动依赖更新
题目 2.1 参考答案
1. Redis 分布式锁标准流程
go
func AcquireLock(redis *redis.Client, key, value string, expiration time.Duration) (bool, error) {
// 使用 SET NX EX 原子命令
result, err := redis.SetNX(context.Background(), key, value, expiration).Result()
return result, err
}
func ReleaseLock(redis *redis.Client, key, value string) error {
// Lua 脚本保证原子性
script := `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`
_, err := redis.Eval(context.Background(), script, []string{key}, value).Result()
return err
}
关键参数:
- Lock Key:唯一标识锁
- Lock Value:唯一标识锁持有者(如 UUID),用于安全释放
- Expiration:防止死锁,必须设置
2. 简单方案的安全问题
简单 SET key value NX EX timeout 方案的问题:
- 锁续期问题:如果业务执行时间超过过期时间,锁会被自动释放,导致其他请求进入
- 错误释放问题:如果持有者错误地释放了不属于它的锁
- 主从切换问题:主节点加锁成功但未同步到从节点时,主节点宕机,导致锁丢失
3. 可重入分布式锁设计
Redis 不直接支持可重入,但可以通过以下方式实现:
go
type ReentrantLock struct {
redis *redis.Client
locks map[string]int // 存储 {key: {value: count}}
mu sync.Mutex
}
func (r *ReentrantLock) Lock(ctx context.Context, key, value string, ttl time.Duration) bool {
r.mu.Lock()
defer r.mu.Unlock()
if count, ok := r.locks[key]; ok && count[value] > 0 {
// 可重入:增加计数
r.locks[key][value]++
return true
}
// 获取锁
ok, err := r.redis.SetNX(ctx, key, value, ttl).Result()
if !ok || err != nil {
return false
}
r.locks[key] = map[string]int{value: 1}
return true
}
4. Redis vs ZooKeeper/etcd 分布式锁对比
| 特性 | Redis | ZooKeeper | etcd |
|---|---|---|---|
| 实现复杂度 | 低 | 中 | 中 |
| 性能 | 极高 | 中 | 高 |
| 可靠性 | 低(单点需 RedLock) | 高(ZAB 协议) | 高(Raft 协议) |
| 锁公平性 | 不支持 | 支持 | 支持 |
| Watch 机制 | Pub/Sub | 原生支持 | 原生支持 |
| 使用场景 | 简单场景、高性能 | 一致性要求高 | Kubernetes 等 |
推荐:对于高可靠场景,使用 Redisson 或 etcd;对于高性能简单场景,使用 Redis。
题目 2.2 参考答案
1. Kafka 核心概念
sql
Topic (日志主题)
├── Partition 0 (有序、不可变的消息序列)
│ ├── Offset 0 → Message
│ ├── Offset 1 → Message
│ └── Offset 2 → Message
├── Partition 1
│ └── ...
└── Partition N
└── ...
Consumer Group (消费组)
├── Consumer A (消费 Partition 0, 1)
└── Consumer B (消费 Partition 2, N)
- Topic:消息的逻辑分类
- Partition:物理存储单位,每个 Partition 有序且不可变
- Offset:消息在 Partition 中的唯一序号
- Consumer Group:消费者组内共享消费状态,实现负载均衡
2. Exactly-Once 语义实现
方案一:事务 + 幂等性 Producer
java
properties.put("enable.idempotence", true);
properties.put("transactional.id", "producer-1");
producer.initTransactions();
producer.beginTransaction();
producer.send(record1);
producer.send(record2);
producer.commitTransaction();
方案二:消费者端幂等消费
- 使用外部存储(如数据库)记录已处理的 offset
- 消费逻辑与 offset 提交放在同一个事务中
方案三:EOS (Exactly-Once Semantics)
- Kafka 0.11+ 支持事务 API
- 支持在消费者端实现 EOS
3. Consumer 消费积压排查与解决
排查步骤:
- 检查 Consumer CPU 和内存是否正常
- 检查网络带宽是否足够
- 查看 Consumer 日志是否有异常
- 使用
kafka-consumer-groups.sh查看 lag
bash
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group my-group --describe
解决方案:
- 增加 Consumer 数量(不超过 Partition 数)
- 优化业务逻辑,减少单条消息处理时间
- 增加 Consumer 资源
- 使用批处理提高吞吐量
- 检查是否有消息处理失败导致重试
4. 消息顺序性保证
- 单 Partition:同一个 Partition 内的消息是有序的
- Key 分区:使用相同 key 的消息会发送到同一 Partition
- 多 Partition 场景:如果需要全局有序,可以使用单 Partition 或在业务层做排序
题目 2.3 参考答案
1. 限流算法对比
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 固定窗口 | 将时间划分为固定窗口,窗口内计数器 | 实现简单 | 边界突发问题 |
| 滑动窗口 | 使用多个固定窗口加权计算 | 精度高 | 实现复杂 |
| 漏桶 | 以固定速率处理请求 | 平滑、稳定 | 无法应对突发 |
| 令牌桶 | 按速率生成令牌,取 token 通过 | 支持突发、通过 | 实现稍复杂 |
令牌桶核心公式:
- 桶容量:能够容纳的最大令牌数
- 生成速率:每秒生成的令牌数
- 请求消耗:每个请求消耗的令牌数
2. 分布式限流方案
方案一:Redis + Lua 脚本
lua
-- 令牌桶 Lua 脚本
local key = KEYS[1]
local rate = tonumber(ARGV[1]) -- 每秒生成令牌数
local capacity = tonumber(ARGV[2]) -- 桶容量
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local fill_time = capacity / rate
local ttl = math.floor(fill_time) + 1
local data = redis.call("HMGET", key, "last_token", "tokens")
local last_token = tonumber(data[1]) or 0
local tokens = tonumber(data[2]) or capacity
local delta = math.max(0, (now - last_token) * rate)
tokens = math.min(capacity, tokens + delta)
local allowed = 0
if tokens >= requested then
tokens = tokens - requested
allowed = 1
end
redis.call("HMSET", key, "last_token", now, "tokens", tokens)
redis.call("EXPIRE", key, ttl)
return {allowed, tokens}
方案二:Redis Cluster + Redisson
方案三:滑动窗口日志算法
- 每个请求记录时间戳到 Redis Sorted Set
- 统计时间窗口内的请求数
3. 限流友好降级响应
json
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
{
"code": 429,
"message": "请求过于频繁,请稍后再试",
"data": {
"retry_after": 60,
"quota_remaining": 0
}
}
4. Go 令牌桶实现
go
type RateLimiter struct {
rate float64 // 每秒生成的令牌数
capacity int64 // 桶容量
tokens float64 // 当前令牌数
lastTime time.Time // 上次更新时间
mu sync.Mutex
}
func NewRateLimiter(rate float64, capacity int64) *RateLimiter {
return &RateLimiter{
rate: rate,
capacity: capacity,
tokens: float64(capacity),
lastTime: time.Now(),
}
}
func (r *RateLimiter) Allow() bool {
return r.AllowN(1)
}
func (r *RateLimiter) AllowN(n int64) bool {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
elapsed := now.Sub(r.lastTime).Seconds()
r.lastTime = now
// 补充令牌
r.tokens += elapsed * r.rate
if r.tokens > float64(r.capacity) {
r.tokens = float64(r.capacity)
}
if r.tokens >= float64(n) {
r.tokens -= float64(n)
return true
}
return false
}
// HTTP 中间件示例
func RateLimitMiddleware(limiter *RateLimiter) gin.HandlerFunc {
return func(c *gin.Context) {
if !limiter.Allow() {
c.JSON(429, gin.H{
"code": 429,
"message": "请求过于频繁",
})
c.Abort()
return
}
c.Next()
}
}
题目 3.1 参考答案
1. Multi-Agent Development 概念
定义:Multi-Agent Development 是指多个 AI Agent 协同工作,每个 Agent 负责特定角色,通过通信和协作完成复杂任务的开发模式。
优势相比单 Agent:
- 专业化分工:不同 Agent 负责不同任务域
- 并行处理:多个 Agent 可以同时工作
- 更好的上下文管理:每个 Agent 只需关注自己的上下文
- 可扩展性:可以方便地添加新角色
- 容错性:某个 Agent 失败不影响整体
2. AI 代码审查 Multi-Agent 流程设计
scss
┌─────────────────────────────────────────────────────────┐
│ Orchestrator Agent │
│ (任务编排协调) │
└─────────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 语法检查 Agent │ │ 安全审查 Agent │ │ 最佳实践 Agent │
│ - AST 分析 │ │ - 漏洞检测 │ │ - 代码风格 │
│ - 编译检查 │ │ - 依赖安全 │ │ - 设计模式 │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└───────────────────┼───────────────────┘
▼
┌─────────────────────┐
│ Review Reporter │
│ (汇总报告生成) │
└─────────────────────┘
信息传递方式:
- 消息队列:使用 Kafka 或 Redis Pub/Sub
- 共享存储:使用数据库或文件系统
- API 调用:Agent 之间通过 HTTP/gRPC 通信
3. 通信和状态同步问题
挑战:
- Agent 之间的依赖关系管理
- 共享状态的并发访问
- 任务进度的追踪
解决方案:
- 使用 DAG(有向无环图)管理任务依赖
- 使用分布式锁或乐观锁管理共享状态
- 使用消息队列实现异步通信
- 使用状态机管理整体流程
4. Multi-Agent 系统挑战
- Agent 协作效率:过多通信开销 → 使用批处理和缓存
- 错误传播:某个 Agent 错误影响整体 → 添加重试和降级机制
- 上下文一致:不同 Agent 看到的信息不一致 → 统一的信息源
- 调试困难:难以追踪问题根因 → 完整的日志和追踪系统
- 成本控制:多次 LLM 调用成本高 → 模型路由和缓存
题目 3.2 参考答案
1. Context Engineering 概念
定义:Context Engineering 是指在大模型应用开发中,有意识地设计、管理和优化输入上下文的技术和实践。
重要性:
- 模型能力受限于上下文长度
- 信息密度决定输出质量
- 上下文管理直接影响 token 成本
- 好的上下文设计可以显著提升 AI 编程效果
2. 常见上下文管理策略
| 策略 | 描述 | 适用场景 |
|---|---|---|
| Context Compression | 压缩无关信息,保留核心内容 | 长文档处理 |
| Memory Hierarchy | 分层存储(短期/长期/永久记忆) | 多轮对话 |
| Semantic Retrieval | 基于语义相似度检索相关上下文 | RAG 系统 |
| Summarization | 生成摘要代替完整内容 | 会议记录、长对话 |
| Hierarchical Chunking | 按层级组织文档块 | 结构化文档 |
3. 平衡上下文长度和信息密度
策略:
- 相关性过滤:只保留与当前任务相关的信息
- 结构化表示:使用 JSON、Markdown 等结构化格式
- 关键信息提取:从长文本中提取关键点而非全文
- 动态上下文:根据任务类型动态调整上下文内容
- 渐进式加载:先加载概要,必要时再加载详情
示例:Code Review 场景
markdown
Bad: 加载整个代码库(10000+ 行)
Good:
1. 加载变更文件列表
2. 加载每个文件的 diff(仅变更部分)
3. 加载相关的单元测试
4. 加载相关的架构文档摘要
4. Token 超限优化策略
- 提前规划:分析任务复杂度,预估 token 使用
- 分块处理:将大任务拆分为多个小任务
- 使用高效模型:简单任务使用小模型(如 GPT-3.5)
- Prompt Cache:缓存不变的上下文部分(如果 API 支持)
- 外部存储:将代码存储在外部,模型按需读取
- 增量上下文:只传递变更,而非全量上下文
题目 4.1 参考答案
1. Rerank 的作用
Rerank(重排序)是在初步向量检索后,使用更精确但计算成本更高的模型对结果进行二次排序的技术。
核心原因:
- Embedding 模型是双编码器结构,计算效率高但语义理解有限
- Cross-Encoder(Rerank 模型)能够进行更精细的语义交互
- 初步检索追求召回率,Rerank 追求精度
2. Rerank 算法对比
| 算法 | 原理 | 特点 |
|---|---|---|
| BM25 | 基于词频和文档频率的传统算法 | 快速、适合关键词匹配 |
| BERT Cross-Encoder | 将 query 和 doc 一起输入 BERT | 精度高、速度慢 |
| Cohere Rerank | Cohere 商业服务 | 云端、效果好 |
| LLM Rerank | 使用大模型进行排序 | 语义理解最强、成本高 |
最佳实践:
python
# 典型 RAG + Rerank 流程
1. Hybrid Search(稀疏 + 稠密检索)→ Top-100
2. Cross-Encoder Rerank → Top-10
3. 返回给 LLM
3. 平衡速度与准确性
- 两阶段检索:先快速召回(如 Top-100),再精确 Rerank(如 Top-10)
- 模型选择:根据延迟要求选择合适的 Rerank 模型
- 异步处理:Rerank 可以异步执行,减少首token延迟
- 缓存:缓存常见 query 的 Rerank 结果
4. 千万级文档 RAG 架构设计
scss
┌─────────────────────────────────────────────────────────────┐
│ Ingestion Pipeline │
│ 文档 → Chunking → Embedding → Vector Index → Storage │
│ (并行/分布式处理) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Query Pipeline │
│ │
│ Query → Query Rewrite → Hybrid Search (HNSW + BM25) │
│ → Rerank (Cross-Encoder) → Generation (LLM) │
└─────────────────────────────────────────────────────────────┘
关键优化:
- 分层索引:建立粗粒度和细粒度索引
- 分区检索:按类别/时间分区分开检索
- 近似最近邻:使用 HNSW、FAISS 等高效向量索引
- 异步 Embedding:文档处理和查询分离
5. Embedding vs Rerank 语义理解差异
| 维度 | Embedding (Bi-Encoder) | Rerank (Cross-Encoder) |
|---|---|---|
| 输入方式 | Query 和 Doc 独立编码 | Query 和 Doc 联合输入 |
| 交互方式 | 仅向量点积 | 注意力机制交互 |
| 语义捕捉 | 整体语义相似性 | 细粒度匹配程度 |
| 计算量 | 低(一次性编码) | 高(需要交叉计算) |
| 适用场景 | 粗召回 | 精排序 |
题目 4.2 参考答案
1. Tool 执行失败的类型
| 类型 | 描述 | 处理策略 |
|---|---|---|
| Transient | 临时性错误(网络抖动、超时) | 重试 |
| Logic Error | 业务逻辑错误(参数错误) | 不重试,返回错误 |
| Rate Limit | 限流 | 等待后重试 |
| Auth Error | 认证过期 | 刷新 token 后重试 |
| System Error | 服务端系统错误 | 重试(有限次数) |
| Timeout | 执行超时 | 重试(增加超时时间) |
2. Tool Retry 机制设计
go
type RetryConfig struct {
MaxAttempts int // 最大重试次数
InitialBackoff time.Duration // 初始退避时间
MaxBackoff time.Duration // 最大退避时间
BackoffFactor float64 // 退避因子
RetryableErrors []error // 可重试的错误列表
}
func DefaultRetryConfig() *RetryConfig {
return &RetryConfig{
MaxAttempts: 3,
InitialBackoff: 100 * time.Millisecond,
MaxBackoff: 30 * time.Second,
BackoffFactor: 2.0,
}
}
func IsRetryableError(err error) bool {
// 网络错误、超时、限流等可重试
if errors.Is(err, context.DeadlineExceeded) ||
errors.Is(err, syscall.ECONNRESET) {
return true
}
// HTTP 429 / 5xx
if status, ok := status.FromError(err); ok {
return status.Code() == codes.Unavailable ||
status.Code() == codes.ResourceExhausted
}
return false
}
3. 避免雪崩效应
- 熔断器模式:失败次数过多时暂时停止调用
- 限流:对重试请求本身限流
- 抖动(Jitter):随机化退避时间
- 舱壁模式:隔离不同的调用
- 超时控制:设置合理的超时时间
go
// 添加抖动的指数退避
func (c *RetryConfig) NextBackoff(attempt int) time.Duration {
backoff := float64(c.InitialBackoff) * math.Pow(c.BackoffFactor, float64(attempt))
if backoff > float64(c.MaxBackoff) {
backoff = float64(c.MaxBackoff)
}
// 添加 jitter: ±25%
jitter := backoff * 0.25 * (2*rand.Float64() - 1)
return time.Duration(backoff + jitter)
}
4. Go Tool Retry 实现
go
type Tool interface {
Execute(ctx context.Context, params map[string]interface{}) (*ToolResult, error)
Name() string
}
type ToolResult struct {
Success bool
Data interface{}
Error error
Attempts int
}
func RetryToolCall(ctx context.Context, tool Tool, params map[string]interface{}, config *RetryConfig) ToolResult {
if config == nil {
config = DefaultRetryConfig()
}
var lastErr error
for attempt := 0; attempt < config.MaxAttempts; attempt++ {
result, err := tool.Execute(ctx, params)
if err == nil {
result.Attempts = attempt + 1
return *result
}
lastErr = err
// 检查是否可重试
if !IsRetryableError(err) {
return ToolResult{
Success: false,
Error: err,
Attempts: attempt + 1,
}
}
// 达到最大重试次数
if attempt == config.MaxAttempts-1 {
break
}
// 等待后重试
select {
case <-ctx.Done():
return ToolResult{
Success: false,
Error: ctx.Err(),
Attempts: attempt + 1,
}
case <-time.After(config.NextBackoff(attempt)):
}
}
return ToolResult{
Success: false,
Error: lastErr,
Attempts: config.MaxAttempts,
}
}
题目 5.1 参考答案(RAG 系统设计)
1. 需求分析
| 指标 | 要求 |
|---|---|
| 文档规模 | 千万级 |
| QPS | 1000 |
| 平均延迟 | < 500ms |
| 可用性 | 99.9% |
2. 整体架构
scss
┌────────────────────────────────────────────────────────────────┐
│ Client Layer │
│ (API Gateway + Load Balancer) │
└────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Query Service │ │ Ingest Service │ │ Admin Service │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────────────┐
│ Vector Store │ │ Search Engine │ │ LLM Gateway │
│ (Milvus/Pinecone) │ │ (Elasticsearch)│ │ (OpenAI/Anthropic) │
└───────────────┘ └───────────────┘ └───────────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
▼
┌────────────────────────────────────────────────────────────────┐
│ Data Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Object Storage│ │ Metadata DB │ │ Cache Layer │ │
│ │ (S3/MinIO) │ │ (PostgreSQL) │ │ (Redis) │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
3. Chunk 策略设计
python
# 自适应 Chunking 策略
class ChunkingStrategy:
def __init__(self):
self.default_chunk_size = 512
self.default_overlap = 50
def chunk_document(self, doc: Document) -> List[Chunk]:
chunks = []
if doc.type == "markdown":
# Markdown 按标题结构分块
chunks = self.chunk_by_headings(doc)
elif doc.type == "pdf":
# PDF 按段落 + 表格分块
chunks = self.chunk_pdf(doc)
elif doc.type == "code":
# 代码按函数/类分块
chunks = self.chunk_by_functions(doc)
else:
# 默认按句子分块
chunks = self.chunk_by_sentences(doc)
# 后续处理:添加 metadata、生成 summary
for chunk in chunks:
chunk.embedding = self.compute_embedding(chunk.content)
chunk.summary = self.generate_summary(chunk.content)
return chunks
def chunk_by_headings(self, doc: MarkdownDocument) -> List[Chunk]:
sections = doc.split_by_headings(level=[1, 2])
chunks = []
current_chunk = []
for section in sections:
if self.estimate_tokens(current_chunk) + self.estimate_tokens(section) > self.default_chunk_size:
if current_chunk:
chunks.append(Chunk(content="".join(current_chunk)))
# 保留 overlap
current_chunk = current_chunk[-self.default_overlap:]
current_chunk.append(section)
if current_chunk:
chunks.append(Chunk(content="".join(current_chunk)))
return chunks
4. Hybrid Search 实现
python
async def hybrid_search(query: str, top_k: int = 100, rerank_top_k: int = 10):
# 1. 向量检索
vector_results = await vector_store.search(
query_vector=embed_model.encode(query),
top_k=top_k
)
# 2. BM25 关键词检索
bm25_results = await search_engine.search(
query=query,
top_k=top_k
)
# 3. RRF 融合
fused_scores = {}
k = 60 # RRF 参数
for rank, (doc_id, score) in enumerate(vector_results):
fused_scores[doc_id] = fused_scores.get(doc_id, 0) + 1 / (k + rank + 1)
for rank, (doc_id, score) in enumerate(bm25_results):
fused_scores[doc_id] = fused_scores.get(doc_id, 0) + 1 / (k + rank + 1)
# 4. 排序取 Top-K
sorted_docs = sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)[:rerank_top_k]
# 5. Rerank
reranked = await rerank_model.rerank(
query=query,
documents=[get_doc(doc_id) for doc_id, _ in sorted_docs]
)
return reranked
5. 性能优化策略
| 优化方向 | 方案 |
|---|---|
| 向量索引优化 | 使用 HNSW 图索引,平衡精度和速度 |
| 缓存策略 | Semantic Cache 缓存相似 query 结果 |
| 预计算 | 提前计算热门文档的 embedding |
| 异步处理 | 非关键路径异步化 |
| 批量处理 | 批量 embedding 请求 |
| 分片部署 | 按租户/文档类型分片 |
6. 质量评估指标
| 指标类型 | 具体指标 | 监控方式 |
|---|---|---|
| 系统指标 | QPS、Latency、Error Rate | Prometheus |
| 检索质量 | Recall@K、NDCG@K | 人工标注数据集 |
| 生成质量 | Faithfulness、Relevance | LLM-as-Judge |
| 业务指标 | 用户满意度、任务完成率 | 用户反馈 |
题目 6.1 参考答案(STAR 追问示例)
由于这是开放性项目深挖题,没有标准答案,但以下是回答框架:
S - Situation(情境)
- 描述业务背景和规模
- 说明项目的紧急程度和资源情况
T - Task(任务)
- 你的具体职责是什么
- 目标 KPI 是什么
A - Action(行动)
- 技术选型原因
- 架构设计思路
- 解决的关键问题
- 团队协作方式
R - Result(结果)
- 量化的成果(提升 X%、降低成本 Y元)
- 个人成长和收获
- 可复用的经验
建议的回答时长:每个项目 3-5 分钟,涵盖核心亮点和量化成果。
题目 6.2 参考答案
1. AI 工具融入开发流程
markdown
日常开发流程(AI 增强版):
1. 需求分析 → Claude: 帮我理解这个 PRD,提取技术要点
2. 任务拆解 → Cursor: 自动拆分 Subtask
3. 代码生成 → Copilot: 辅助完成模板代码
4. 代码 Review → Claude: Review 代码并提出优化建议
5. 测试生成 → AI: 生成单元测试和集成测试
6. 文档生成 → AI: 自动生成 API 文档
2. AI 代码 Review 流程
markdown
AI Code Review Checklist:
□ 逻辑正确性:AI 生成的代码逻辑是否正确?
□ 边界条件:是否处理了边界情况和异常?
□ 性能问题:是否有性能隐患(N+1、内存泄漏)?
□ 安全问题:是否有 SQL 注入、XSS 等安全漏洞?
□ 代码风格:是否符合团队规范?
□ 测试覆盖:是否添加了必要的测试?
□ 可读性:代码是否易于理解和维护?
3. AI 时代工程师核心能力
| 被 AI 强化 | 可能被弱化 |
|---|---|
| 快速原型能力 | 底层编码能力 |
| 知识广度 | 知识深度(依赖 AI) |
| 调试和问题定位 | 重复性编码 |
| 系统设计能力 | 简单 CRUD 编码 |
新增核心能力:
- Prompt Engineering
- AI 结果验证能力
- 任务拆解能力
- 跨学科整合能力
4. Prompt 设计和任务拆解经验
markdown
好的任务拆解示例:
原始需求:实现一个用户登录功能
拆解后的任务:
1. 设计用户表结构(含索引)
2. 实现密码加密和校验逻辑
3. 实现登录 API(含参数校验)
4. 实现 JWT Token 生成和验证
5. 实现登录限流(防暴力破解)
6. 编写单元测试
7. 编写 API 文档
对应 Prompt:
"帮我实现一个安全的用户登录 API,要求:
- 使用 bcrypt 加密密码
- 使用 JWT 进行身份认证
- Token 有效期 24 小时
- 支持 Refresh Token
- 添加登录限流(5分钟内失败5次则锁定15分钟)"
5. AI 幻觉发现和解决
常见幻觉类型:
- API 用法错误:LLM 生成的 API 调用参数错误
- 不存在的函数:LLM "发明" 不存在的库函数
- 逻辑错误:看似正确但实际有 bug 的代码
- 过期信息:使用了已废弃的 API 或语法
解决方法:
- 启用严格的 lint 和 type check
- 添加 AI Review 环节验证 AI 代码
- 使用单元测试覆盖关键路径
- 参考官方文档而非 AI 输出
6. 保证 AI 生成代码质量一致性
markdown
策略:
1. 使用固定的角色和格式 Prompt
2. 提供足够的上下文(代码规范、架构要求)
3. 设置质量门槛(必须通过 lint、type check、unit tests)
4. 使用 AI Self-Correction:让 AI 检查自己的输出
5. 建立 AI 代码准入标准(必须有人工 review)