# Job Searcher

- 来源：Hugging Face：Blog（RSS）
- 发布时间：2026-06-06 23:36
- AIHOT 分数：65
- AIHOT 标记：精选
- AIHOT 链接：https://aihot.virxact.com/items/cmq2j2aqq0111slbv2442dsoa
- 原文链接：https://huggingface.co/blog/build-small-hackathon/job-search-blog

## 精选理由

这个 hackathon 项目把教师蒸馏和 LoRA 微调 8B 模型的流程全部开源在 HF 上，做模型定制和部署的开发者能直接抄作业，尤其是推理部署踩的坑（ZeroGPU 上下文重用）很实用。

## AI 摘要

Hugging Face 发布 Job Searcher，一个基于 AI 的求职搜索工具。用户上传简历并设定偏好后，系统使用教师模型 DeepSeek V4 Pro 生成 LinkedIn 搜索查询，通过 JobSpy 抓取职位，再对学生模型 Qwen3-8B（8B 参数）进行 LoRA 微调，对每个职位从技能匹配、经验相关性、教育背景、行业领域契合度和资历对齐五个维度给出评分和推理。训练在 Modal 平台单张 A100 上完成。推理部署于 Hugging Face ZeroGPU Space，使用 llama.cpp 实现流式输出。项目开源。

## 正文

作为应届毕业生，找工作本身就是一份全职工作。你每周要从数百个招聘信息中筛选出少数几个值得投递的。你不停地点击“快速申请”，直到眼睛酸痛。你把同一封求职信写上四十遍。到了求职的第二个月，你开始申请那些你本来不会接受的职位，进入你并不在意的行业——因为到那时，思考每个招聘信息的成本已经高于提交申请的成本了。

观看快速演示：上传一份简历，观察查询流，阅读针对每个职位的推理过程。

工作原理

一次运行包含三个步骤。

查询。模型会读取简历和你设定的偏好（工作类型、工作模式、地点、自由备注），然后边推理边草拟一小套类似 LinkedIn 风格的搜索查询。

搜索。这些查询通过 JobSpy 逐一访问 LinkedIn。

评分。对于每个招聘信息，模型会读取（简历，职位）配对，并写出一个五维匹配度评分：* 技能匹配度 * 经验相关性 * 教育与认证 * 行业/领域匹配度 * 资历对齐度

图 1. 该框架的端到端步骤。

你得到的不是一份包含五十个职位的列表，而是一份带有可解释推理的简短候选名单。你可以阅读模型认为排名第二的职位为何优于排名第三的理由。

技术细节

数据集构建——教师模型与学生模型

教师模型是 DeepSeek V4 Pro。擅长结构化推理，愿意遵循严格的输出格式，且成本足够低，可以离线在大规模语料库上运行一次。它被用作标签生成器，而非推理时的依赖项。

学生模型是 Qwen3-8B。在量化到 Q4_K_M 后，它足够小，可以适配到单个 ZeroGPU 切片上；同时又足够大，能够吸收教师模型的结构化判断。

语料库来自一个闭环的、感知简历的端到端流程：

简历。2500 份，基于 Divyaamith/Kaggle-Resume 构建。

查询。教师模型首先根据每份简历草拟出类似 LinkedIn 风格的搜索查询。

职位。然后 JobSpy 从 LinkedIn 抓取这些查询实际返回的结果。大约 10,000 个招聘信息，每一个都是由教师模型为那份特定简历所写的查询所发现的。

标签。随后，教师根据推理时使用的同一套五个维度，对每一对（简历，职位）结果进行评分，每个维度附带一句推理说明。

所有内容以四个外键清晰的配置形式，打包在 build-small-hackathon/job-search-distill 中。

训练（Modal）

通过 Modal 在单张 A100 上运行两次 LoRA SFT 训练，每个任务一次：

适配器。秩为 16，alpha 为 16，关闭 dropout，使用注意力加 MLP 投影。

调度。每个任务一个 epoch。每 200 步保存一次 epoch 中间检查点，以便在完整运行完成前，可以对部分运行结果进行合理性检查。

输出。Safetensors 格式文件位于 build-small-hackathon/job-searcher-qwen3-8B，以及一个 Q4_K_M 基础模型加 LoRA-GGUF 侧车文件位于 build-small-hackathon/job-searcher-qwen3-8B-gguf，用于 llama.cpp 服务路径。

LoraConfig( r=16, lora_alpha=16, task_type="CAUSAL_LM", target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj", ], )

Space - 推理（llama.cpp）

该 Space 在 HuggingFace ZeroGPU Space 上，使用预构建的 CUDA wheel 运行 llama-cpp-python。有两个关键的设计选择：

在 @spaces.GPU 内部使用 Llama。ZeroGPU 每次调用都会回收 CUDA 上下文，因此模块级实例在第二次使用时将持有失效的上下文。

每次提交调用一次 GPU，而非每个职位调用一次。一次提交的所有适配度评估都在单个 @spaces.GPU 调用内完成。模型加载一次，并为每个职位生成事件，而不是为每个岗位发布都支付一次全新的冷启动和代理 token 请求开销。

流式传输使用 OpenAI 风格的 create_chat_completion(stream=True)，因此推理过程会逐 token 地显示在 UI 中。在线演示位于 build-small-hackathon/job-search-assistant。

追踪记录

构建此 Space 的整个 Claude Code 会话已作为 HuggingFace agent-traces 数据集发布在 build-small-hackathon/job-search-assistant-agent-trace。包含原始 JSONL 事件、原生 HuggingFace 追踪查看器，以及每一次死胡同和恢复的记录。如果你想了解这个东西实际是如何组合起来的，而不是阅读清理后的版本，这会很有用。

试试看

把你的简历放到 huggingface.co/spaces/build-small-hackathon/job-search-assistant。别再大海捞针了。

我的收获

两个适配器胜过单个。我曾尝试将查询生成和适配评估合并到单个 LoRA 中。结果模型在两种任务间泄露了格式：查询任务中混入了 JSON，评估任务中混入了散文体。将两者拆分为同一基座模型上的两个独立头，每次调用时热切换，彻底消除了这类错误。

教师模型的提示词比学生模型的规模更重要。重写教师模型的标注提示词，使其针对简历中的具体细节进行评分（例如“四年 Rust 经验；岗位要求五年”而非“技术匹配度高”），这一改进通过知识蒸馏传递了下去。学生模型也习得了同样的习惯。
