Job Searcher

Hugging Face:Blog(RSS)·2026-06-06 23:36·80天前
AI 导读

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

Hugging Face:Blog(RSS)
精选
65AI 编辑部评分,满分 100

Job Searcher

2026-06-06 23:36· 80天前
AI 导读

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

推荐理由

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

正文 · AI 翻译

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

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

工作原理

一次运行包含三个步骤。

  1. 查询。模型会读取简历和你设定的偏好(工作类型、工作模式、地点、自由备注),然后边推理边草拟一小套类似 LinkedIn 风格的搜索查询。
  2. 搜索。这些查询通过 JobSpy 逐一访问 LinkedIn。
  3. 评分。对于每个招聘信息,模型会读取(简历,职位)配对,并写出一个五维匹配度评分:* 技能匹配度 * 经验相关性 * 教育与认证 * 行业/领域匹配度 * 资历对齐度

图 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 经验;岗位要求五年”而非“技术匹配度高”),这一改进通过知识蒸馏传递了下去。学生模型也习得了同样的习惯。

来源:Hugging Face:Blog(RSS)· huggingface.co