openrecruiter
The recruiting engine behind Open Recruiter: parse resumes and job descriptions, retrieve and rank candidates, draft outreach, and run an agent that does all of it through tools.
The desktop app is a consumer of this package, so everything shipped here is exercised by a real application rather than only by its own tests.
Install
Straight from the repository:
pip install "git+https://github.com/miao4ai/open_recruiter.git#subdirectory=sdk/core"
Or a released wheel, from the Releases page:
pip install https://github.com/miao4ai/open_recruiter/releases/download/sdk-core-v0.1.0/openrecruiter-0.1.0-py3-none-any.whl
Not on PyPI yet, so plain
pip install openrecruiterwill not find it. GitHub Packages has no Python registry, which is why the wheel is attached to a Release rather than appearing in the repository's Packages panel.
No local model is downloaded, at import or at runtime. Embeddings are an API call and chat is a hosted provider, so it runs on CPU, on macOS, and in a container with no GPU — 97 packages installed, none of them a training stack.
Quick start
from openrecruiter import Recruiter
r = Recruiter(anthropic_api_key="sk-ant-...", voyage_api_key="pa-...")
job = r.add_job(open("jd.txt").read()) # parsed into title, skills, requirements
r.add_candidate(open("resume.txt").read()) # parsed into a structured profile
for match in r.rank(job.id, top_k=10):
print(f"{match.score:.2f} {match.candidate_id} {match.reasoning}")
Without a Voyage key, retrieval is disabled and ranking falls back to the LLM — the package still works, it just reads every candidate instead of shortlisting first.
The agent
The model is given real tools and decides what to call and when it is done, so one request can span several steps: rank a job, read the result, then draft the emails.
for event in r.chat("who are the three strongest fits for the CUDA role, and draft an intro to each"):
match event:
case TextDelta(): print(event.text, end="", flush=True)
case ToolCall(): print(f"\n[{event.name}]")
case ApprovalRequired(): ... # a gated tool is waiting for a human
Events are TextDelta, ToolCall, ToolResult, ApprovalRequired, and Finished. Text
streams as it is generated; tool calls are only emitted once their arguments are complete.
For a one-liner, r.ask("...") returns just the final text.
Approval gates
A tool marked requires_approval stops the run rather than acting. Anything the model
queued behind it is held too, so the gate cannot be stepped around:
events = list(r.chat("email the top candidate"))
if agent.pending: # serialisable — store it, decide later
list(agent.resume(agent.pending, approved=True))
Your own tools
from openrecruiter import Tool
check_calendar = Tool(
name="check_calendar",
description="Look at the recruiter's availability this week",
parameters={"type": "object", "properties": {"days": {"type": "integer"}}},
fn=lambda days=7: my_calendar.free_slots(days),
)
r = Recruiter(anthropic_api_key="...", extra_tools=[check_calendar])
Ranking
Ranking is the main extension point. One interface, several backends:
Ranker
├── EmbeddingRanker vector similarity — the default, cheap enough for the whole pool
├── APIRanker an LLM scores each candidate and explains itself
├── TwoStageRanker retrieve with one, rerank the shortlist with the other
└── your own implement rank(job, candidates, top_k) and pass it in
TwoStageRanker is the shape the ranking research targets — the first stage optimises
recall, the second optimises relevance over a few hundred candidates:
from openrecruiter import APIRanker, EmbeddingRanker, TwoStageRanker
r.ranker = TwoStageRanker(
EmbeddingRanker(r.index),
APIRanker(r.llm),
shortlist=200,
)
Backends that need a local model live in their own distributions — recruitgpt for the
distilled ranker, openrecruiter-fairness for bias-aware reranking — so nothing heavy
reaches this install. Both must lazy-load: no download until a user selects that backend.
Bringing your own storage
Store and VectorIndex are protocols. Implement them over a database you already have and
nothing above the storage layer changes — that is how the desktop app keeps its existing
schema while running on this package.
class MyStore:
def add_job(self, job): ...
def get_job(self, job_id): ...
def list_jobs(self, limit=100): ...
# ... six more, all in openrecruiter.store.base
r = Recruiter(config, store=MyStore())
The default SQLiteStore is a working reference in four tables.
Development
cd sdk/core
uv sync
uv run pytest # no network calls: the LLM is faked end to end
uv build --out-dir dist
Releasing: bump version in pyproject.toml, then push a matching tag.
git tag sdk-core-v0.1.1 && git push origin sdk-core-v0.1.1
CI checks the tag against the version, runs the tests, builds, installs the wheel in a clean environment and imports it, verifies no training stack came along, and attaches the artifacts to a Release.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openrecruiter-0.1.0.tar.gz.
File metadata
- Download URL: openrecruiter-0.1.0.tar.gz
- Upload date:
- Size: 38.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd6902f477aa7591deed79b7d7081314ae4cfc5cd216cbe2d618cb595daa834a
|
|
| MD5 |
26d2821c5bbaf2112334796fccce5643
|
|
| BLAKE2b-256 |
86edd9201d3f27b34ec9631f85d39e41e87602d794d1c6582118182d63255f6c
|
Provenance
The following attestation bundles were made for openrecruiter-0.1.0.tar.gz:
Publisher:
publish-sdk.yml on miao4ai/open_recruiter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openrecruiter-0.1.0.tar.gz -
Subject digest:
bd6902f477aa7591deed79b7d7081314ae4cfc5cd216cbe2d618cb595daa834a - Sigstore transparency entry: 2568517788
- Sigstore integration time:
-
Permalink:
miao4ai/open_recruiter@fa74bb52600d64493de15c53de8a00ccaf1a2407 -
Branch / Tag:
refs/tags/sdk-core-v0.1.0 - Owner: https://github.com/miao4ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-sdk.yml@fa74bb52600d64493de15c53de8a00ccaf1a2407 -
Trigger Event:
push
-
Statement type:
File details
Details for the file openrecruiter-0.1.0-py3-none-any.whl.
File metadata
- Download URL: openrecruiter-0.1.0-py3-none-any.whl
- Upload date:
- Size: 37.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44f0abba90898ff1ac58de8cc72f4332d918114ecae23971541b26750febd813
|
|
| MD5 |
85da698a39f71fc1df8e390d176c5cd9
|
|
| BLAKE2b-256 |
2a00be3e450cd461bd7f2ff8ed83d1e7c9caec01ef8f1bd00fa72539993445fe
|
Provenance
The following attestation bundles were made for openrecruiter-0.1.0-py3-none-any.whl:
Publisher:
publish-sdk.yml on miao4ai/open_recruiter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openrecruiter-0.1.0-py3-none-any.whl -
Subject digest:
44f0abba90898ff1ac58de8cc72f4332d918114ecae23971541b26750febd813 - Sigstore transparency entry: 2568517805
- Sigstore integration time:
-
Permalink:
miao4ai/open_recruiter@fa74bb52600d64493de15c53de8a00ccaf1a2407 -
Branch / Tag:
refs/tags/sdk-core-v0.1.0 - Owner: https://github.com/miao4ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-sdk.yml@fa74bb52600d64493de15c53de8a00ccaf1a2407 -
Trigger Event:
push
-
Statement type: