onet-match

io.github.Benjamin-V-Chan由 GitHub 账号 Benjamin-V-Chan 发布v0.3.5更新于 Nov 1, 2026

Occupation classifier: free text to ranked O*NET-SOC codes with tasks, skills and RIASEC. Local.

发布方自己的服务:match.bot

概览

AI 生成的概览

本地 MCP 服务器,把职位名称、简历或技能清单等自由文本映射为排序后的 O*NET-SOC 职业代码,并附任务、技能与 RIASEC 兴趣。

可以做什么
  • 匹配职业:把描述工作的自由文本转换为带校准置信度的 O*NET-SOC 代码排序结果
  • 查询职业:按 SOC 代码获取职业详情,包括任务、技能、知识与 RIASEC 兴趣
  • 返回汇总画像:每条结果包含大类、职业分区、描述,以及按置信度加权的技能与兴趣汇总
适用场景
适合需要把杂乱的职位名称或招聘信息标准化、把简历条目和技能清单编码为标准职业,或让职业探索与人力规划的回答基于 O*NET 数据而非自由生成的场景。
运行要求
以 stdio 在本地运行,通常用 uvx 从 onet-match 包启动(或 pip 安装并带 mcp 附加项)。匹配无需 API 密钥或账号。首次调用会从 Hugging Face Hub 下载约 0.5 GB 的模型包并缓存,因此首次运行需要网络。
安装前请注意
首次运行会从 Hugging Face Hub 下载较大的模型包。匹配是统计性的:所服务的模型在 O*NET 文本上把正确职业排在第一位的比例约为一半,结果应人工复核而非视为权威。MCP 工具为只读;另外的 O*NET Web Services 检索需要免费的 O*NET 账号。

安装

在 SourceWeft 中

  1. 打开 控制台中的 onet-match,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

onet-match

[PyPI] [DOI] [MCP Registry] [Demo]

Occupation classification and job-title normalization against the O*NET-SOC taxonomy. onet-match turns messy, unstructured text about work (job titles, resume bullets, job postings, project descriptions, interest statements, skill lists) into standardized SOC codes with a fine-tuned retrieve-and-rerank transformer pipeline. On held-out O*NET text it ranks the right occupation first 51% of the time, against 21% for a TF-IDF baseline, and 65% vs 17% on messy, human-style inputs. Each result is enriched with O*NET tasks, skills, knowledge and RIASEC interests, and comes with a calibrated confidence. Use it from Python, the command line, a REST API or any MCP-compatible agent, with no API keys.

  • Try it in your browser: no install, and the model runs on your device.
  • Open source (MIT) and runs locally. No API key or account needed to match.
  • Fine-tuned bge-base bi-encoder + cross-encoder reranker over all 923 O*NET 31.0 occupations. About 47 ms per query on a laptop.
  • Python API, CLI, REST API (FastAPI), MCP server for AI assistants, and Docker.
  • Also includes the O*NET data pipeline it's built on: bulk database download, a Web Services client and analysis tools.
use caseinputtop match (confidence)
Career explorationdesigned our robot in CAD and wired its circuits for my high school robotics teamRobotics Engineers, 17-2199.08 (0.38); #2 Robotics Technicians
Career explorationI love weather, maps and tracking stormsAtmospheric and Space Scientists, 19-2021.00 (0.87)
Skills-based hiringMust know survival analysis, R, SAS and clinical trial dataBiostatisticians, 15-2041.01 (0.60)
Skills-based hiringMust know Solidity, smart contracts and EthereumBlockchain Engineers, 15-1299.07 (0.81)
Resume parsingLed a team of 12 nurses on a cardiac ICU night shiftCritical Care Nurses, 29-1141.03 (0.42); #2 Acute Care Nurses
Resume parsingExcel, QuickBooks, payroll, AP/ARPayroll and Timekeeping Clerks, 43-3051.00 (0.49)
Career counselingskills: Python, SQL, Tableau; interests: sports statisticsStatisticians, 15-2041.00 (0.59)
Job-posting normalization{"title": "Wind Turbine Tech", "requirements": ["climbing", "hydraulics", "electrical troubleshooting"]}Wind Turbine Service Technicians, 49-9081.00 (0.81)

Real outputs of the served model, saved in outputs/examples/readme_examples.json.

Quickstart

bash
pip install "onet-match[matching]"
python
from onet_match import match
result = match("Must know survival analysis, R, SAS and clinical trial data", top_k=5)for m in result.matches:    print(m.soc, m.title, m.confidence)print(result.profile["riasec_code"])      # aggregate interest code, e.g. "ICS"

The package imports as onet_match and provides the onet-match command. The first call downloads the pretrained model bundle (about 0.5 GB) from the Hugging Face Hub (BenjaminChan12/onet-match) and caches it.

bash
onet-match match "Excel, QuickBooks, payroll, AP/AR"          # CLI (add --json for JSON)pip install "onet-match[matching,serve]"onet-match serve --port 8000                                   # REST APIcurl -s localhost:8000/match -H 'content-type: application/json' \     -d '{"text": "I love weather, maps and tracking storms", "top_k": 3}'

REST routes: POST /match, POST /match/batch, GET /occupation/{soc} and GET /health. The full request and response contract and the Docker setup are in docs/API.md.

Use from AI assistants (MCP)

onet-match mcp runs a Model Context Protocol server with two read-only tools: match_occupations(text, top_k) and get_occupation(soc). Add it to any MCP client (Claude Desktop, Cursor, VS Code and others):

json
{  "mcpServers": {    "onet-match": {      "command": "uvx",      "args": ["--from", "onet-match[mcp]", "onet-match", "mcp"]    }  }}

Without uv, run pip install "onet-match[mcp]" and use "command": "onet-match", "args": ["mcp"]. For a remote server, use onet-match mcp --transport streamable-http --port 8001.

What each match contains

soc, title, confidence (calibrated, 0–1), score, major_group and its title, job_zone, riasec_code, description, top_tasks, top_skills and top_knowledge (with O*NET importance and level). Each result also carries a profile that aggregates skills, knowledge and all six RIASEC interests across the matches, weighted by confidence.

Use cases

  • Career exploration (EdTech). Map what students say they did or enjoy to occupations and skills.
  • Resume and profile parsing. Code experience bullets and skill lists to SOC codes.
  • Skills-based hiring and workforce planning. Turn a list of required skills into the roles that use them, to scope a job or find adjacent talent.
  • Job-posting normalization. Turn informal titles and descriptions into standard occupations.
  • Workforce and labor-market analytics. Batch-code large text collections.
  • Career counseling tools. Show the tasks and skills behind a suggestion.
  • LLM agents. Ground occupation questions in O*NET instead of free generation (see llms.txt).

Worked example: onet-match-job-postings codes 3,000 LinkedIn job postings and compares pay by occupation group.

Details and integration patterns: docs/APPLICATIONS.md.

Accuracy

All models are evaluated on the same held-out splits. Every number traces to a saved run in outputs/runs/.

modelO*NET test top-1O*NET test top-5messy-text test top-1messy-text test top-5p50 latency
TF-IDF baseline20.5%40.5%17.4%39.2%0.5 ms
BM2520.7%40.1%24.1%45.2%0.2 ms
bge-base-en-v1.5, off the shelf36.1%64.3%45.4%77.6%6.9 ms
fine-tuned bge-base50.2%78.2%65.2%92.6%6.8 ms
fine-tuned bge-base + reranker (served)51.1%79.1%65.4%92.9%46.7 ms
  • O*NET test: 6,792 held-out titles, task statements, work activities and descriptions.
  • Messy-text test: 4,703 informal snippets written by a different LLM than the one that wrote the training text.
  • Confidence: temperature-calibrated, ECE 0.037.
  • Full results: per-input-type tables, calibration, ablations, learning curves and error analysis are in docs/RESULTS.md.

How it works

mermaid
flowchart LR    B["O*NET 31.0 database<br/>(bulk download)"] --> C["Occupation catalog<br/>923 occupations"]    C --> P["Labeled pairs<br/>titles · tasks · activities"]    C --> S["LLM-written messy text<br/>train: model A · test: model B"]    P --> T["Fine-tune bi-encoder<br/>+ cross-encoder reranker"]    S --> T    T --> BU["Model bundle<br/>(Hugging Face Hub)"]    BU --> API["match() · onet-match CLI · REST API"]
  1. A fine-tuned bi-encoder (bge-base) embeds the input and retrieves the 20 nearest occupations.
  2. A fine-tuned cross-encoder rescores those 20.
  3. The two scores are blended with a weight chosen on validation data.
  4. A temperature-scaled softmax turns the blended scores into calibrated confidence.

The validation and test tasks are removed from the occupation documents the models search, so no test text leaks into retrieval. Synthetic training and test text come from different LLMs.

In-browser demo

The demo Space runs the same pipeline client-side with transformers.js: both models are exported to ONNX and quantized to int8 (about 130 MB, cached after the first visit). demo/build.py builds it from a bundle. Quantization and the browser runtime shift results slightly: on 800 test queries the browser picks the same top match as the Python package 95% of the time, with top-1 accuracy 61.5% vs 62.6% (outputs/demo/browser_parity.json). Use the Python package for anything that matters.

O*NET data pipeline

The onet_data_collector package (onet-collect) downloads and verifies O*NET database releases, which needs no credentials. It also wraps O*NET Web Services: keyword search, occupation details and flattened profiles. See docs/COLLECTOR.md.

bash
onet-collect bulk download                         # download + verify the latest O*NET databaseonet-match collect "healthcare" "technology"       # Web Services search (free O*NET account, see .env.example)onet-match eda | keywords | cluster                # classic analysis: coverage, keyword attribution, TF-IDF clusters

Train your own

bash
git clone https://github.com/Benjamin-V-Chan/onet-match && cd onet-matchpip install -e ".[dev]"onet-match matching build-data          # O*NET catalog, labeled pairs, splitsscripts/matching/reproduce.sh           # every model, ablation and the results reportonet-match matching bundle "<spec>" --run <run>   # package a model for serving
  • Synthetic text: WITH_SYNTH=1 TRAIN_LLM=<model> EVAL_PROVIDER=<provider> EVAL_LLM=<model> scripts/matching/reproduce.sh also generates the messy training and test text, using a different LLM for each. It needs LLM API keys in environment variables.
  • Training time: bge-base took 58 minutes for one epoch on a laptop GPU (Apple Silicon, MPS). The exact scripts behind every saved run are in scripts/matching/.
  • Tests: pytest runs fully offline, using tiny local models and a mocked LLM.

Set ONET_MATCH_BUNDLE=/path/to/bundle to serve your own model, or ONET_MATCH_HF_REPO=owner/repo to load one from the Hub.

Background

This project was originally built in 2024 as an open-source edtech tool for analyzing O*NET occupational data at scale, and as part of my capstone project for an engineering course. Its analytics pipeline (EDA, occupation clustering, and keyword attribution) was later integrated into production products at Project Leo, and has inspired related work at other companies and projects.

In 2026 it was rebuilt around semantic occupation matching, and the former ONET-Data-Collector repository was merged in.

Citation

bibtex
@software{chan_onet_match,  author = {Chan, Benjamin},  title  = {onet-match: mapping free text to O*NET occupations},  url    = {https://github.com/Benjamin-V-Chan/onet-match},  doi    = {10.5281/zenodo.23824326},  year   = {2026}}

License and data attribution

The code is MIT-licensed; see LICENSE.

This product includes information from the O*NET 31.0 Database by the U.S. Department of Labor, Employment and Training Administration (USDOL/ETA), used under the CC BY 4.0 license. O*NET® is a trademark of USDOL/ETA. USDOL/ETA has not approved, endorsed or tested this product.

来源:README.md,提交 c635b7a

工具

0
SourceWeft 曾在隔离沙箱中以无凭据方式启动该软件包一次,以列出其工具,但未能完成:服务器未能启动。

版本历史

3
  1. v0.3.5最新Nov 1, 2026
  2. v0.3.4Nov 1, 2026
  3. v0.3.2Oct 30, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分

其他 Match 集成