Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 29 additions & 25 deletions apps/worker/src/five08/worker/crm/skills_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import re
from typing import Any

from five08.llm import ProviderModel
from five08.openai_fallback import FallbackOpenAIClient
from five08.skills import (
DISALLOWED_RESUME_SKILLS,
Expand Down Expand Up @@ -63,7 +64,12 @@ class SkillsExtractor:
"""Extract skills with LLM when configured, fallback heuristics otherwise."""

def __init__(self) -> None:
self.model = settings.resolved_resume_ai_model
self.provider_model = ProviderModel.openai_compatible(
model=settings.resolved_resume_ai_model,
api_key=settings.resolved_resume_ai_api_key,
base_url=settings.resolved_resume_ai_base_url,
)
self.model = self.provider_model.model
self.client: Any = None

if settings.resolved_resume_ai_provider_attempts and OpenAIClient is not None:
Expand All @@ -72,10 +78,7 @@ def __init__(self) -> None:
client_factory=OpenAIClient,
)
elif settings.resolved_resume_ai_api_key and OpenAIClient is not None:
self.client = OpenAIClient(
api_key=settings.resolved_resume_ai_api_key,
base_url=settings.resolved_resume_ai_base_url,
)
self.client = OpenAIClient(**self.provider_model.client_kwargs())

def extract_skills(self, resume_text: str) -> ExtractedSkills:
"""Extract skills from resume text."""
Expand All @@ -85,26 +88,27 @@ def extract_skills(self, resume_text: str) -> ExtractedSkills:
prompt = self._create_prompt(resume_text)
try:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{
"role": "system",
"content": (
"You extract professional skills from resumes for a CRM. "
"Focus on white-collar skills for product development orgs: "
"engineering, product, data, design, growth, and marketing. "
"Return JSON only, no prose. "
"Normalize skills to concise canonical names, lowercase. "
"Provide a strength from 1-5 when known, where 5 is strongest. "
"If uncertain, you may omit it or leave it blank. "
"Bias 3 for simple mentions, 4-5 for recent/current project usage, "
"and 1-2 for weak, outdated, or minimal exposure."
),
},
{"role": "user", "content": prompt},
],
temperature=0.1,
max_tokens=1200,
**self.provider_model.chat_completion_kwargs(
messages=[
{
"role": "system",
"content": (
"You extract professional skills from resumes for a CRM. "
"Focus on white-collar skills for product development orgs: "
"engineering, product, data, design, growth, and marketing. "
"Return JSON only, no prose. "
"Normalize skills to concise canonical names, lowercase. "
"Provide a strength from 1-5 when known, where 5 is strongest. "
"If uncertain, you may omit it or leave it blank. "
"Bias 3 for simple mentions, 4-5 for recent/current project usage, "
"and 1-2 for weak, outdated, or minimal exposure."
),
},
{"role": "user", "content": prompt},
],
temperature=0.1,
max_tokens=1200,
)
)
content = response.choices[0].message.content
if not content:
Expand Down
1 change: 1 addition & 0 deletions packages/shared/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ packages = ["src/five08"]

[tool.hatch.build.targets.wheel.force-include]
"src/five08/data/model-profiles.json" = "five08/data/model-profiles.json"
"src/five08/llm_model_profiles.json" = "five08/llm_model_profiles.json"
18 changes: 18 additions & 0 deletions packages/shared/src/five08/data/model-profiles.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@
}
}
},
"gpt-5.1": {
"name": "gpt-5.1",
"provider": "openai-compatible",
"model": "gpt-5.1",
"api_key_env": "OPENAI_API_KEY",
"base_url": "https://api.openai.com/v1",
"request_options": {
"response_format": {
"type": "json_object"
}
},
"chat_completion_options": {
"max_tokens_parameter": "max_completion_tokens",
"reasoning_effort": "low",
"verbosity": "low",
"supports_temperature": false
}
},
"gpt-5": {
"name": "gpt-5",
"provider": "openai-compatible",
Expand Down
73 changes: 43 additions & 30 deletions packages/shared/src/five08/job_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Any

from five08.discord_webhook import DiscordWebhookLogger
from five08.llm import ProviderModel
from five08.skills import normalize_skill, normalize_skill_list

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -692,25 +693,31 @@ def rerank_shortlisted_candidates(
except ImportError as exc:
raise RuntimeError("openai package is not installed") from exc

client = _OpenAI(api_key=api_key, base_url=base_url or None)
provider_model = ProviderModel.openai_compatible(
model=model,
api_key=api_key,
base_url=base_url or None,
)
client = _OpenAI(**provider_model.client_kwargs())
prompt = _build_rerank_prompt(posting_text, requirements, candidates)

try:
response = client.chat.completions.create(
model=model,
temperature=0.1,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": (
"You are a senior recruiting coordinator. Rerank a shortlist "
"using only the provided evidence. Return only valid JSON."
),
},
{"role": "user", "content": prompt},
],
max_tokens=2500,
**provider_model.chat_completion_kwargs(
temperature=0.1,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": (
"You are a senior recruiting coordinator. Rerank a shortlist "
"using only the provided evidence. Return only valid JSON."
),
},
{"role": "user", "content": prompt},
],
max_tokens=2500,
)
)
except Exception as exc:
logger.error("OpenAI candidate rerank call failed: %s", exc)
Expand Down Expand Up @@ -794,27 +801,33 @@ def extract_job_requirements(
except ImportError as exc:
raise RuntimeError("openai package is not installed") from exc

client = _OpenAI(api_key=api_key, base_url=base_url or None)
provider_model = ProviderModel.openai_compatible(
model=model,
api_key=api_key,
base_url=base_url or None,
)
client = _OpenAI(**provider_model.client_kwargs())

hints = _regex_hints(posting_text)
prompt = _build_prompt(posting_text, hints)

try:
response = client.chat.completions.create(
model=model,
temperature=0.1,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": (
"You are a recruiting assistant. Extract structured hiring requirements "
"from job postings. Return only valid JSON."
),
},
{"role": "user", "content": prompt},
],
max_tokens=2048,
**provider_model.chat_completion_kwargs(
temperature=0.1,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": (
"You are a recruiting assistant. Extract structured hiring requirements "
"from job postings. Return only valid JSON."
),
},
{"role": "user", "content": prompt},
],
max_tokens=2048,
)
)
except Exception as exc:
logger.error("OpenAI job extraction call failed: %s", exc)
Expand Down
Loading