从零构建面试Agent:大模型+Redis实战指南
发布时间:2026/10/1 13:41:02来源:尧图网络
1. 从“码上面试”这个项目说起为什么我要啃 Agent 这块硬骨头最近在整理自己的学习路线时我给自己定了一个目标把 Agent 开发这条链路从头到尾走一遍而且不是看几篇科普文就完事是要真正动手做出一个能跑起来、能演示、能讲清楚原理的项目。选来选去我把切入点定在了“面试”这个场景上于是就有了《码上面试》这个 Agent 项目的学习记录。先说清楚这个项目到底是什么。简单讲它是一个面向技术面试场景的智能体应用用户输入岗位方向比如 Java 后端、算法工程师、前端 ReactAgent 会模拟面试官进行多轮提问根据回答动态追问最后给出一份结构化的评估反馈。它解决的核心问题是——传统题库式刷题太静态而真实面试是动态的、有上下文的、会追问的。Agent 的价值就在于把“动态追问”和“上下文记忆”这两件事做出来。这个内容适合谁看如果你是有一定编程基础、想入门 Agent 开发但不知道从哪下手的开发者或者你正在准备面试、想理解面试官提问逻辑的技术人再或者你是做 AI 应用落地、需要一套可复现的 Agent 项目骨架的工程师那这篇记录应该能给你不少参考。我会把选型理由、核心链路、踩过的坑、参数怎么定全部摊开讲。关键词里出现了 Agent、AI、面试、大模型、Redis这几个词基本勾勒出了项目的技术轮廓以大模型为推理内核以 Agent 框架为编排层以 Redis 为状态存储最终服务于面试这个垂直场景。下面我按实际搭建顺序一层层拆。2. 整体架构设计与技术选型为什么这么搭2.1 为什么是 Agent 而不是单纯的 Prompt 调用很多人第一反应是面试问答嘛写个大模型 Prompt 不就行了我一开始也这么想但真做起来发现不行。单次 Prompt 调用是无状态的你问完一轮模型就“忘了”。而面试的本质是多轮有状态对话每一轮的问题都依赖上一轮的回答。比如候选人说“我用过 Redis 做缓存”面试官下一句大概率会追问“那缓存穿透你怎么处理的”。这种依赖关系靠单次 Prompt 是拼不出来的。Agent 的核心区别在于它有决策循环观察当前状态 → 决定下一步动作 → 执行 → 再观察。放到面试场景里就是读取历史对话 → 判断该追问还是换题 → 生成问题 → 等待回答 → 评估回答质量 → 决定下一轮。这个循环才是 Agent 的灵魂也是我坚持用 Agent 架构而不是裸 Prompt 的根本原因。2.2 大模型选型为什么我最终选了通用大模型 提示词工程大模型这块我纠结了很久。市面上的选择无非几类通用大模型 API、开源本地部署模型、垂直微调模型。我最后的结论是项目初期用通用大模型 API 精细的提示词工程先跑通链路再考虑微调。理由很实在。第一微调成本高。你要准备高质量的面试问答数据集要标注要租算力一轮下来没个几天搞不定而项目初期最需要的是快速验证。第二通用大模型在面试这种偏通用推理的场景下表现已经够用尤其是追问逻辑本质是常识推理不需要领域微调。第三提示词工程与上下文工程能解决大部分问题——通过 System Prompt 定义面试官人设通过 Few-shot 示例约束提问风格通过上下文窗口管理控制历史长度。提示不要一上来就想着微调。我见过太多人卡在数据准备阶段就放弃了。先用 Prompt 把效果调到 80 分再判断那剩下的 20 分到底是不是微调能解决的。当然如果你要做的是非常垂直的领域比如某个冷门框架的深度面试微调确实有价值。但那是第二阶段的事。这里补充一句大模型提示词工程与上下文工程是当前 Agent 开发里性价比最高的技能值得花时间深挖。2.3 Redis 在项目里的角色不只是缓存热词里 Redis 出现频率很高很多人以为 Redis 在这个项目里就是个缓存。其实不是。Redis 在这里承担的是会话状态存储的角色这是 Agent 架构的关键一环。为什么状态要放 Redis 而不是内存因为 Agent 的对话可能很长而且服务可能多实例部署。如果状态放进程内存里用户请求打到另一个实例就丢了上下文。Redis 的 key-value 结构天然适合存 session而且它支持过期时间可以自动清理僵尸会话。我用到的 Redis 数据类型主要是这几种数据类型用途说明String存单轮对话内容简单直接适合小数据List存对话历史序列按顺序 push天然是消息队列Hash存会话元信息如用户ID、岗位方向、轮次计数ZSet存会话活跃度排序用于清理或统计具体选哪种取决于你的访问模式。我最后用的是 List 存历史 Hash 存元信息 String 存当前状态快照的组合。这个组合在读写性能和实现复杂度之间平衡得比较好。2.4 整体链路一图说清文字版因为不能用图表我用文字把链路串一遍用户请求进来 → 网关层解析 sessionId → 从 Redis 拉取该会话的历史对话和元信息 → 组装成 Agent 的输入上下文 → 调用大模型进行推理判断追问/换题/结束→ 解析模型输出 → 生成下一轮问题 → 把新的一轮问答写回 Redis → 返回给用户。这条链路里Redis 的读写是高频操作大模型调用是耗时操作所以工程上要注意Redis 操作要快大模型调用要异步或加超时控制。这个后面实操部分细讲。3. 核心模块拆解与实操要点把每个环节讲透3.1 会话状态管理Redis 的 key 设计是门学问状态管理这块最容易踩坑的就是 key 的设计。我一开始图省事直接用session:{id}存所有东西结果发现读写粒度太粗每次都要把整个会话读出来再写回去性能很差。后来我改成了分层 key 设计interview:session:{sessionId}:meta—— Hash 类型存岗位方向、当前轮次、开始时间、状态进行中/已结束interview:session:{sessionId}:history—— List 类型存每一轮的问答格式是 JSON 字符串interview:session:{sessionId}:state—— String 类型存当前 Agent 的决策状态快照这样设计的好处是读元信息不用碰历史读历史不用碰状态各取所需。而且 List 的RPUSH和LRANGE操作都是 O(1) 或 O(N) 但 N 可控性能很稳。注意List 存历史时一定要设过期时间。我见过有人忘了设 TTL结果 Redis 内存被历史会话撑爆。用EXPIRE给每个 key 设一个合理的过期时间比如 2 小时足够一次面试用完了。3.2 上下文窗口管理历史对话不能无限塞大模型的上下文窗口是有限的就算现在有些模型支持很长的上下文你也不能把几十轮对话全塞进去一是贵二是模型注意力会被稀释效果反而下降。我的做法是滑动窗口 摘要压缩。具体来说保留最近 N 轮完整对话我设的 N5更早的对话用大模型生成一段摘要作为“背景信息”塞在 System Prompt 里摘要只在历史超过阈值时触发一次不是每轮都算这个策略实测下来很稳。5 轮完整对话大概能覆盖 2000-3000 token加上摘要和 System Prompt总输入控制在 4000 token 以内成本和效果都能接受。参数怎么定我的经验是先看你的模型上下文上限留 30% 给输出剩下的 70% 里System Prompt 占 20%摘要占 20%最近对话占 60%。按这个比例反推 N 的值。比如模型上限 8K输出留 2.4K输入 5.6K最近对话占 3.36K每轮约 600 token那 N 大概就是 5-6 轮。3.3 面试官人设的 Prompt 设计让模型“像个人”Prompt 设计是 Agent 效果的天花板。我在这块反复调了很多版总结出几个关键点。第一人设要具体。不要写“你是一个面试官”要写“你是一个有 8 年经验的 Java 后端面试官风格偏技术深度喜欢追问底层原理对模糊回答会继续深挖”。越具体模型的表现越稳定。第二输出格式要约束。我要求模型每次输出必须是 JSON包含action追问/换题/结束、question问题内容、reason决策理由三个字段。这样程序好解析也方便调试。第三Few-shot 示例不能少。我放了 3 组示例对话覆盖“追问”“换题”“结束”三种情况。示例的作用是锚定风格比纯文字描述有效得多。{ action: follow_up, question: 你刚才提到用 Redis 做缓存那缓存和数据库的一致性你是怎么保证的, reason: 候选人提到了 Redis 但未展开一致性方案属于可深挖点 }这个 JSON 结构是我迭代了好几版才定下来的。早期我用的是纯文本输出解析起来各种边界情况换成 JSON 后清爽多了。3.4 评估反馈模块怎么让模型给出靠谱的评分面试结束后要给反馈这块我设计了一个独立的评估 Prompt。输入是完整对话历史输出是结构化的评分包括技术深度、表达清晰度、逻辑性三个维度每个维度 1-5 分外加一段文字点评。这里有个坑不要让模型直接给总分。我试过让模型给一个综合分结果发现分数波动很大同样的对话两次调用能差 1 分。后来改成让模型分维度打分程序再按权重算总分稳定性好很多。权重怎么定技术深度 0.5逻辑性 0.3表达清晰度 0.2。这个权重是根据面试场景调的技术岗当然技术深度最重要。你可以根据自己的场景调整。4. 完整实操流程从零把项目跑起来4.1 环境准备Redis 安装与连接先说 Redis 的安装。这块看起来简单但不同系统差异挺大我把自己踩过的都列一下。Linux 下最省事用包管理器直接装# Ubuntu/Debian sudo apt update sudo apt install redis-server sudo systemctl start redis-server # 验证 redis-cli ping # 返回 PONG 就成功了Windows 下稍微麻烦点官方没有原生支持需要用社区维护的版本。装完之后用 Redis Desktop Manager 这类工具连上去看看确认服务正常。连接配置这块我用的是 Python 的 redis 库import redis r redis.Redis( hostlocalhost, port6379, db0, decode_responsesTrue, # 自动解码省得手动处理 bytes socket_timeout5, socket_connect_timeout5 )decode_responsesTrue这个参数强烈建议加上不然读出来的都是 bytes每次都要 decode很烦。socket_timeout也要设防止 Redis 卡住导致整个请求挂死。提示如果你要部署多实例Redis 主从配置是绕不开的。用 Docker 装主从其实很快一条命令起一个从节点配置里指向主节点就行。但项目初期单机够用别过度设计。4.2 Agent 主循环的实现Agent 的主循环是整个项目的核心。我用伪代码把逻辑讲清楚def run_interview_turn(session_id, user_answer): # 1. 读取会话状态 meta get_session_meta(session_id) history get_session_history(session_id) # 2. 追加用户回答 history.append({role: user, content: user_answer}) # 3. 组装上下文滑动窗口 摘要 context build_context(history, meta) # 4. 调用大模型 response call_llm(context) # 5. 解析输出 result parse_json(response) # 6. 根据 action 分支处理 if result[action] end: feedback generate_feedback(history) update_session_status(session_id, finished) return {type: feedback, data: feedback} else: history.append({role: assistant, content: result[question]}) save_session_history(session_id, history) return {type: question, data: result[question]}这个循环里第 3 步的build_context和第 5 步的parse_json是最容易出问题的地方。build_context要处理窗口截断和摘要注入parse_json要处理模型输出格式不规范的情况比如多打了 markdown 代码块标记。4.3 大模型调用的超时与重试大模型调用是整条链路里最慢也最不稳定的环节。我的经验是必须设超时必须做重试必须做降级。超时我设的是 30 秒。超过 30 秒没返回直接判定失败。重试用指数退避第一次等 1 秒第二次等 2 秒最多重试 2 次。降级策略是如果重试都失败返回一个预设的兜底问题保证对话不中断。import time def call_llm_with_retry(context, max_retries2): for i in range(max_retries 1): try: return call_llm(context, timeout30) except TimeoutError: if i max_retries: return get_fallback_question() time.sleep(2 ** i)这个重试逻辑看起来简单但能挡掉大部分偶发的网络抖动。实测下来加了重试之后用户感知到的失败率从 3% 降到了 0.5% 以下。4.4 数据落库与调试日志调试 Agent 项目日志是命根子。我建议把每一轮的输入上下文、模型原始输出、解析后的结果、耗时全部记下来。这样出问题的时候能快速定位是 Prompt 的问题还是解析的问题。我用的是结构化日志每条记录是一个 JSONimport logging import json logger logging.getLogger(interview_agent) def log_turn(session_id, turn, context, raw_output, parsed, elapsed): logger.info(json.dumps({ session_id: session_id, turn: turn, context_tokens: len(context), raw_output: raw_output, parsed: parsed, elapsed_ms: elapsed }, ensure_asciiFalse))ensure_asciiFalse很重要不然中文会被转义成 unicode日志没法看。这些日志在排查“为什么模型这轮追问得莫名其妙”的时候特别有用你能直接看到当时喂进去的上下文长什么样。5. 常见问题与排查技巧实录5.1 模型输出格式不稳定怎么办这是最高频的问题。模型有时候会输出带 markdown 代码块的 JSON有时候会多写一段解释文字有时候字段名还会变。我的处理是三层防御第一层Prompt 里明确要求“只输出 JSON不要任何其他文字”。第二层解析前先做清洗用正则把json 和去掉。第三层解析失败时把原始输出再喂给模型一次让它“把上面的内容转成标准 JSON”。import re import json def parse_json_safe(text): # 清洗 markdown 标记 text re.sub(rjson\s*|\s*, , text).strip() try: return json.loads(text) except json.JSONDecodeError: # 尝试提取第一个 { 到最后一个 } 之间的内容 start text.find({) end text.rfind(}) if start ! -1 and end ! -1: return json.loads(text[start:end1]) raise这套组合拳下来解析成功率能到 99% 以上。5.2 Redis 连接超时和内存问题Redis 的问题主要有两类连接超时和内存增长。连接超时通常是网络问题或者 Redis 负载太高。排查思路是先用redis-cli --latency看延迟如果延迟高检查是不是有慢查询。慢查询用SLOWLOG GET看。我遇到过一次是因为有个 key 存了超大 ListLRANGE拉全量的时候卡住了后来改成只拉最近 N 条就好了。内存增长的问题核心是 TTL 没设好。我的做法是给所有会话相关的 key 都设 2 小时过期另外用一个定时任务扫描长时间不活跃的会话主动清理。监控上用INFO memory看used_memory和used_memory_peak如果峰值持续上涨说明有泄漏。5.3 上下文太长导致模型“失忆”这个问题的表现是模型突然忘了前面聊过什么或者重复问已经问过的问题。原因通常是上下文被截断得太狠或者摘要生成得不好。我的排查步骤是先看日志里实际喂进去的 context 有多长确认是不是超了窗口。如果没超那就是摘要的问题检查摘要 Prompt 是不是把关键信息丢了。我后来把摘要 Prompt 改成了“保留候选人的技术栈、项目经历、已问过的问题列表”效果明显好转。5.4 常见问题速查表问题现象可能原因排查方法解决方案模型输出非 JSONPrompt 约束不够看原始输出加清洗 二次转换追问逻辑混乱上下文截断看日志 context 长度调整窗口大小Redis 读写慢大 key 或慢查询SLOWLOG GET拆分 key限制范围会话状态丢失TTL 过期或 key 冲突检查 key 命名统一 key 前缀规范模型调用超时网络或模型负载看耗时日志加重试 降级评分波动大直接要总分多次调用对比分维度打分再加权这张表是我踩坑踩出来的基本覆盖了 80% 的日常问题。遇到新问题先往这几类里套能省不少时间。6. 一些掏心窝子的实操心得做这个项目最大的体会是Agent 开发的门槛不在模型在工程。模型能力现在都很强真正难的是怎么把状态管好、把上下文控好、把异常兜住。我见过太多 Demo 跑得飞起、一上真实场景就崩的项目问题全出在工程细节上。另一个心得是先跑通再优化。我一开始想一步到位把微调、多模型路由、复杂评估全做上结果卡了两周没进展。后来砍掉所有非核心功能先用最简单的 Prompt Redis 把主循环跑通一天就出效果了。有了能跑的东西再迭代才有方向。还有一点日志和可观测性要早做。Agent 的行为是概率性的没有日志你根本不知道它为什么这么决策。我现在的习惯是任何 Agent 项目第一版就把结构化日志加上后面省下的调试时间远超前期投入。最后分享一个小技巧调试 Prompt 的时候把模型的reason字段打出来看。我要求模型每次决策都输出理由这个理由虽然不直接给用户看但对调试极有价值。你能看到模型“在想什么”调 Prompt 的时候就有据可依而不是瞎猜。这个项目后续我打算往两个方向扩展一是加多轮评估的横向对比让候选人能看到自己每一轮的表现曲线二是把面试题库做成可配置的不同岗位加载不同的知识库。等这两块做完再回来写第二篇记录。
网站建设高端定制企业官网