Download router.py from codegood/job-chat: direct link, hf CLI and curl.
- Browser
- Download file 5.32 kB
-
https://huggingface.co/spaces/codegood/job-chat/resolve/main/router.py
- Command line
-
hf download hf://spaces/codegood/job-chat/router.py
-
curl -L -o router.py https://huggingface.co/spaces/codegood/job-chat/resolve/main/router.py
5.32 kB
| """Semantic router: three answer tiers plus the four skills. | |
| Queries are compared against the centroid of a few reference examples per | |
| route. Routing is deliberately asymmetric: sending a real question to a route | |
| without the profile produces a confident answer about the candidate with no | |
| evidence behind it, while sending a greeting to the full profile merely wastes | |
| tokens. So anything short of a clear, confident match falls back to FULL. | |
| The skill routes use the same rule: a request must clearly ask for a skill to | |
| get one, otherwise it is answered as ordinary chat. The route names equal the | |
| skill keys in skills.py. | |
| """ | |
| import logging | |
| import numpy as np | |
| logger = logging.getLogger(__name__) | |
| TRIVIAL = "trivial" | |
| SPECIFIC = "specific" | |
| FULL = "full" | |
| # Skill routes. Names match the keys in skills.SKILLS. | |
| REVIEW = "review" | |
| ATS = "ats" | |
| COVER_LETTER = "cover_letter" | |
| EMAIL = "email" | |
| SKILL_ROUTES = (REVIEW, ATS, COVER_LETTER, EMAIL) | |
| REFERENCE_QUERIES = { | |
| TRIVIAL: [ | |
| "hello", | |
| "hi there", | |
| "hey", | |
| "how are you?", | |
| "thanks!", | |
| "thank you", | |
| "good morning", | |
| "good evening", | |
| "who are you?", | |
| "what can you do?", | |
| "bye", | |
| ], | |
| SPECIFIC: [ | |
| "do I know Rust?", | |
| "what is my Python experience?", | |
| "have I used Kubernetes?", | |
| "which projects used FAISS?", | |
| "tell me about my volunteer work", | |
| "what certifications do I have?", | |
| "where did I use Docker?", | |
| "what did I do at my last job?", | |
| "which languages do I know?", | |
| ], | |
| FULL: [ | |
| "am I a fit for this role?", | |
| "what are my weakest areas?", | |
| "how should I position myself?", | |
| "summarise my background", | |
| "what should I emphasise in the interview?", | |
| "what gaps do I have for this job?", | |
| "what do I bring to the table?", | |
| "how do I compare to this job description?", | |
| "what will they push back on?", | |
| ], | |
| REVIEW: [ | |
| "review my resume", | |
| "critique my resume for this job", | |
| "how can I improve my resume?", | |
| "give me feedback on my resume", | |
| "tear down my resume against this job description", | |
| "which resume bullets are weak?", | |
| "rewrite my weakest resume bullets", | |
| ], | |
| ATS: [ | |
| "what is my ATS score?", | |
| "run an ATS check", | |
| "which keywords am I missing for this job?", | |
| "will my resume pass the applicant tracking system?", | |
| "compare my resume keywords to the job description", | |
| "how well do my keywords match this posting?", | |
| ], | |
| COVER_LETTER: [ | |
| "write me a cover letter", | |
| "draft a cover letter for this role", | |
| "I need a cover letter for this job", | |
| "can you write my cover letter?", | |
| "cover letter for this position", | |
| ], | |
| EMAIL: [ | |
| "write an application email", | |
| "draft an email to the recruiter for this job", | |
| "write a short email applying for this role", | |
| "help me email the hiring manager", | |
| "give me subject lines and an email to apply", | |
| ], | |
| } | |
| # Tuned against the reference set: correct routes score 0.38-0.60 with margins | |
| # above 0.25, so these thresholds reject genuinely ambiguous queries only. | |
| MIN_SCORE = 0.35 | |
| MIN_MARGIN = 0.10 | |
| class SemanticRouter: | |
| def __init__(self, embed_fn, references=None, min_score=MIN_SCORE, min_margin=MIN_MARGIN): | |
| self.min_score = min_score | |
| self.min_margin = min_margin | |
| references = references or REFERENCE_QUERIES | |
| self.routes = list(references) | |
| centroids = np.stack([embed_fn(references[route]).mean(axis=0) for route in self.routes]) | |
| norms = np.linalg.norm(centroids, axis=1, keepdims=True) | |
| self.centroids = centroids / np.where(norms == 0, 1, norms) | |
| self.embed_fn = embed_fn | |
| def scores(self, query) -> dict: | |
| vector = self.embed_fn([query])[0] | |
| norm = np.linalg.norm(vector) | |
| if norm: | |
| vector = vector / norm | |
| return dict(zip(self.routes, (self.centroids @ vector).tolist())) | |
| def route(self, query) -> str: | |
| # Defensive: an unroutable query must never crash the turn. | |
| if not isinstance(query, str) or not query.strip(): | |
| logger.info("Route %s | unroutable query, not scored", FULL) | |
| return FULL | |
| scores = self.scores(query) | |
| ranked = sorted(scores.items(), key=lambda item: item[1], reverse=True) | |
| (winner, top), (runner_up_route, runner_up) = ranked[0], ranked[1] | |
| margin = top - runner_up | |
| # Leaving the profile behind requires confidence; FULL is the safe default. | |
| if winner == FULL: | |
| chosen, why = FULL, "top match" | |
| elif top < self.min_score: | |
| chosen, why = FULL, f"{winner} below min_score {self.min_score}" | |
| elif margin < self.min_margin: | |
| chosen, why = FULL, f"{winner} over {runner_up_route} below min_margin {self.min_margin}" | |
| else: | |
| chosen, why = winner, "top match" | |
| logger.info( | |
| "Route %s | %s | margin %.3f | %s | query: %.60r", | |
| chosen, | |
| why, | |
| margin, | |
| " ".join(f"{route}={score:.3f}" for route, score in ranked), | |
| query, | |
| ) | |
| return chosen | |