daily-q:基于 LLM 的每日一题面试练习 CLI

一个用 Rust 写的「每日一题」命令行工具:每天出一道针对你薄弱点的面试题 → 编辑器作答 → AI 评分反馈 → 后台分析薄弱点 → 明天的题自动偏向薄弱点。

一、要解决的问题

面试准备最大的痛点是「不知道练什么」:

  • 刷题不针对:通用题库按知识点随机出,反复练自己已经会的
  • 练完没反馈:写完答案没人告诉你答得怎么样、差在哪
  • 薄弱点靠感觉:全凭印象判断自己哪里弱,没有数据支撑
  • 刷题要不显眼:在职准备跳槽的人,浏览器里开着刷题页面,一眼就暴露意图;终端窗口和日常敲代码毫无差别,工作间隙也能自然练习

daily-q 面向后端/数据方向的程序员,把「出题 → 作答 → 评判 → 复盘」闭环进一个命令行工具,核心循环:

每天出 1 道题 → 作答 → AI 评分反馈 → 后台分析薄弱点 → 下次出题自动偏向薄弱点

典型场景:在职后端程序员想跳槽,午休或工作间隙在终端敲下 dq 就能完成一整套练习------出题、作答、评分都在一个黑框里,看起来和日常写代码没有任何区别,不必背着同事偷偷打开浏览器刷题。

实际运行效果(隔离环境 + mock LLM,可复现):

bash 复制代码
$ dq          # 今日无题,LLM 按画像+薄弱点动态出题
今日题目 TCP 三次握手 medium
请描述TCP三次握手的过程

$ dq answer   # 编辑器作答,AI 评判
请在编辑器中作答,保存并退出 (难度: medium)
得分 75
反馈 整体正确,细节不足
参考答案 SYN -> SYN-ACK -> ACK
[summary] 完成 --- 1 个薄弱项已更新

答完题 dq stats 能直接看到掌握度数据,谁强谁弱一目了然:

bash 复制代码
$ dq stats
掌握度统计
[~] TCP  1/2 ( 50%) [一般]
[+] 三次握手  1/1 (100%) [掌握]

最新总结
薄弱知识点 TCP
建议
  - 多练习传输层

二、解决方案:项目与技术架构

定位 :单文件 CLI,零运维,数据全部在本地 ~/.daily-q/。技术栈 Rust + SQLite + LLM (OpenAI 兼容 /v1/chat/completions 协议,可接国内任意兼容服务商)。

数据流

复制代码
用户执行 dq
   │
   ▼
quiz::get_or_create_question()
   ├─ 今天已有题? ──YES──► 直接返回
   ▼ NO
llm::generate_question() ◄── 薄弱知识点、规则、画像
   ▼
db.insert_question() ──► SQLite
   ▼
显示题目 ◄── 用户执行 dq answer
   ▼
编辑器作答 → llm::judge_answer() → 评分+反馈+标签
   ▼
db.insert_answer() ──► 更新 topic_mastery
   ▼
summary::spawn_background_summary()(后台线程)
   ▼
llm::summarize_history() → 薄弱点/关联/建议 ──► summary_cache

模块职责

模块 职责 行数
main.rs CLI 入口,clap 命令路由 ~419
config.rs 配置读写 + 7 个服务商预设 ~251
db.rs SQLite 5 张表增删改查 ~385
llm.rs LLM 调用(出题/评判/总结)+ JSON 提取 ~264
quiz.rs 出题/评判/统计业务逻辑 ~247
summary.rs 后台线程触发 LLM 总结 ~49
profile.rs 交互式画像引导 ~139
models.rs 数据结构定义 ~109
lang.rs 中文文案(texts! 宏) ~187
display.rs 终端颜色输出 ~38

四个关键设计取舍

1. 选 CLI 而不是网页 / GUI

刷题工具的主流形态是网页端或 App,daily-q 刻意反着来:终端即界面,dq 一个命令走完出题、作答、评分、复盘全流程,启动快、可进 shell 历史、可脚本化。最重要的是不显眼------终端窗口和日常开发毫无差别,在职准备面试时不引人注意,这是 CLI 形态对一个「在职跳槽」用户最实在的价值。

  • 得到:启动快、可脚本化、界面隐蔽、数据全本地、零运维
  • 放弃:可视化图表、鼠标点选。交互短板用 prompt_select() 交互式问答(画像、服务商配置)弥补

2. 无本地题库,全由 LLM 动态出题

放弃维护题库,出题完全交给 LLM:把「画像 + 薄弱知识点 + 自定义规则 + 难度」拼进 prompt,每次生成一道针对性的题。

  • 得到:个性化、零题库维护成本
  • 放弃:题库的稳定性与可离线(依赖 LLM 可用性,可接受的代价)

3. 后台线程做 LLM 总结,但必须 join

答完题后让 LLM 分析全部历史是异步任务(几百毫秒到十几秒)。用 std::thread::spawn + 独立 tokio current_thread runtime 不阻塞终端;但 CLI 进程在 main 返回后立即退出,detach 线程会被杀死导致总结丢失,所以答题流程末尾必须 join() 等落库。

  • 得到:作答反馈即时返回,总结不丢
  • 放弃:完全异步的「零等待」(进程生命周期决定必须同步收尾)

4. 配置「一键 + 测试通过才保存」

配置三要素(api_key / base_url / model)缺一不可还容易填错。dq config ai 一键完成:选服务商 → 输 Key → 自动带出 base_url 与默认模型 → 调 LLM 连通性测试 → 通过才保存(配错不落盘,马上重来)。内置 7 家 OpenAI 兼容服务商:

bash 复制代码
$ dq config base-url list
常用 API 服务商
  1. deepseek    https://api.deepseek.com/v1(默认模型:deepseek-v4-flash)
  2. siliconflow https://api.siliconflow.cn/v1(默认模型:Qwen/Qwen2.5-72B-Instruct)
  3. openai      https://api.openai.com/v1(默认模型:gpt-4o-mini)
  4. moonshot    https://api.moonshot.cn/v1(默认模型:moonshot-v1-8k)
  5. zhipu       https://open.bigmodel.cn/api/paas/v4(默认模型:glm-4-flash)
  6. qwen        https://dashscope.aliyuncs.com/compatible-mode/v1(默认模型:qwen-turbo)
  7. ollama      http://localhost:11434/v1(默认模型:qwen2.5)

存储与 LLM 交互

存储 :SQLite 单文件 5 张表,数据在 ~/.daily-q/

作用
questions 每日一题(date 唯一约束,一天一条)
answers 作答记录 + score + topic_tags(JSON 字符串)
topic_mastery 知识点掌握度(total / correct / last_practiced)
summary_cache 最新学习总结(薄弱点 / 关联 / 建议)
rules 用户自定义出题规则

LLM 交互统一为 JSONllm.rschat_json<T>,直接反序列化为结构体):

json 复制代码
// 出题
{"topic":"TCP 三次握手","difficulty":"medium","question":"...","reference_answer":"..."}
// 评判
{"score":75,"feedback":"整体正确,细节不足","topic_tags":["TCP","三次握手"]}
// 总结
{"weak_topics":["TCP"],"related_weaknesses":[...],"suggestions":["多练习传输层"]}

三、期间面临的挑战

1. 后台总结线程被进程退出杀死

  • 背景:std::thread::spawn 启动总结线程后 main 返回
  • 怎么错的:进程退出直接杀掉 detach 线程,summary_cache 永远写不进去;本地快速跑好像没问题,真实使用总是丢总结
  • 结论:CLI 进程退出会杀死所有后台线程spawn_background_summary() 返回 JoinHandle,答题流程末尾 join() 等落库

2. mock 掩盖的真实 bug:LLM 不按格式返回

  • 背景:本地用 mock server(固定返回 JSON)测试 38 条全绿
  • 怎么错的:换真实 LLM 后,三个 system prompt 没写「只返回 JSON」约束,LLM 自由发挥返回了题目数组 / 夹杂文本,反序列化直接失败
  • 结论:mock 的固定返回是测试盲区。测试全绿 ≠ 上线没问题,必须在真实环境跑一遍端到端;同时把 JSON schema 写进 system prompt 显式约束

3. LLM 输出天生不靠谱,JSON 要逐级兜底

  • 背景:LLM 可能把 JSON 包在 ```json 代码块里、前后带废话、括号嵌套
  • 怎么错的:直接 from_str 解析经常失败
  • 结论:写多级兜底的 extract_json------先找 ```json 代码块 → 找普通代码块 → 从第一个 {/[ 按括号匹配截取 → 原样 trim;max_tokens 提到 4096,避免长输出截断把 JSON 切碎

4. 配置步骤太多,用户根本走不完

  • 背景:早期配置拆三步手动填 base_url,还容易填错(多打 /v1 后缀、记错服务商地址)
  • 怎么错的:从安装到能用要对着文档抄 URL,摩擦巨大
  • 结论:dq config ai 一键配置 + 内置服务商预设 + 测试通过才保存,配错不落盘、马上重来

5. 本地零警告,CI 却红

  • 背景:本地 Rust 1.88 下 clippy -D warnings 零警告
  • 怎么错的:GitHub stable 是 1.97,新版 clippy 新增 collapsible-if / useless-borrows-in-formatting 两个 lint,CI 直接 5 处报错
  • 结论:本地工具链旧不代表 CI 过。CI 用 stable 时把「版本漂移」当常态,失败后按 lint 建议升级代码即可

四、经验收获

  1. 每个技术决策都写下「放弃了什么」:决策记录里每条都列了备选方案与否决理由,日后不反复纠结,也方便别人 review。
  2. mock 全绿只是起点,真实环境端到端必跑:mock 的确定性会掩盖 LLM 的不确定性,固定响应是测试的死角。
  3. CLI 的进程生命周期是隐性约束:后台线程、异步任务在进程退出时会失效,设计时就要想好「进程没了怎么办」。
  4. 对 LLM 输出做最坏假设:显式 JSON schema + 多级解析兜底 + 足够大的 max_tokens,缺一不可。
  5. 工具要「从安装到第一次用」零摩擦:一键配置、默认值、测试通过才保存,决定一个工具能不能被真的用起来。
  6. 把发布交给流水线 :打 v* 标签 → macOS x64/arm、Windows 32/64 四平台制品 + checksums 自动构建上传,发版成本趋近于零。

五、代码与安装

仓库:https://github.com/Angryshark128/daily-q (MIT)

依赖:Rust ≥ 1.88

bash 复制代码
# 方式一:GitHub Releases 下载对应平台压缩包(macOS x64/arm、Windows 32/64)
# https://github.com/Angryshark128/daily-q/releases

# 方式二:源码编译
git clone https://github.com/Angryshark128/daily-q && cd daily-q
cargo build --release
# 产物:target/release/dq(建议加入 PATH)

# 使用
dq config ai      # 一键配置 AI(选服务商 → 输 Key → 测试通过后保存)
dq profile setup  # 设置面试画像
dq                # 出今天的题
dq answer         # 作答 + AI 评分
dq stats          # 掌握度与学习建议

当前状态:v0.1.0 已发布,出题 / 评判 / 后台总结已通过真实 LLM(DeepSeek)端到端验证;发布流水线覆盖 macOS x64/arm 与 Windows 32/64 四平台。后续方向是完善掌握度算法与更多自定义规则。

相关推荐
kyriewen2 小时前
我排查了一个React内存泄漏——罪魁祸首是这3个被忽略的清理函数
前端·javascript·面试
windliang3 小时前
Claude Code 源码分析(七):Skill 如何进入 Agent
前端·人工智能·面试
缓冲中请稍后3 小时前
React Router 完全指南:从 HashRouter 到 BrowserRouter
前端·面试
MindUp4 小时前
面试录音视频的AI复盘方案:从语音转录到RAG问答的技术实践
人工智能·面试·音视频
李文旺5 小时前
意图识别精准度升级方案
面试
江畔柳前堤8 小时前
YOLO 目标检测全流程深度剖析
人工智能·yolo·目标检测·计算机视觉·unity·面试·vllm
无bug代码搬运工10 小时前
LeetCode 42 接雨水:双指针解法详解
算法·leetcode·职场和发展
Lumos18611 小时前
《嵌入式通讯协议栈实战》 3 通讯帧设计
面试
做前端的娜娜子11 小时前
移动端上拉加载与下拉刷新实现方案
前端·面试·掘金·金石计划
程序员爱钓鱼11 小时前
Go 布尔类型 bool 详解
后端·面试·go