For Indie Developers and Small Teams: Learn from Others' Failures Before You Start Building
1. The Business Pain Points
A core scenario for job platforms: after a user submits their resume, the system should automatically tell them "how well this JD matches you" and recommend suitable positions. It sounds simple, but there are plenty of pitfalls:
- JDs and resumes are both unstructured text, with all kinds of formats—PDF resumes, Word resumes, and directly pasted text;
- Matching is not similarity—"familiar with Java" and "five years of Java development experience" are semantically close, but the requirements are different;
- Low latency is critical—match results must be delivered the moment a user submits, not three minutes later;
- Cost-sensitive—a mid-sized platform may see tens of thousands of match requests per day; running every one through a top-tier LLM with long context will make the bill spiral out of control.
2. Common Failed Approaches (Anti-Patterns)
Anti-Pattern 1: Stuffing the entire JD and resume into an LLM
# 失败示范
prompt = f"以下是职位描述:\n{jd_full_text}\n以下是简历:\n{resume_full_text}\n请给出匹配度分数。"
response = llm.call(prompt)Consequence: a JD of 1,500 words plus a resume of 3,000 words means over ten thousand tokens of input per call. Match quality doesn't improve much (LLMs easily miss details in long-text comparison tasks), but latency and bills explode first. With long texts, models often only pay attention to the beginning and end—while hard requirements (like "must hold a certain certificate") are often buried in the middle.
Anti-Pattern 2: Using embedding cosine similarity as the match score
Vectorize both texts, compute cosine similarity, and return "78% match." This is an even bigger disaster: similarity reflects "whether the text topics are alike," not "whether the candidate meets the requirements." A fresh graduate who is "familiar with Python" and a position requiring "expert in Python, 5 years of experience" could have a cosine similarity as high as 0.9. Users get misled, and complaints pour in.
Anti-Pattern 3: Hard-coding keywords in a rules engine
if "Java" in resume and "Java" in jd: score += 20. Three months later, the requirement changes to "also match Java-related positions that accept a switch to Go." The rule table grows longer and longer until nobody dares touch it.
3. The Right Path: A Layered Hybrid Architecture
Core idea: separate "extraction" from "judgment," and use the most suitable tool for each.
简历入库 → 解析/结构化(一次性)
JD入库 → 解析/结构化(一次性)
匹配请求 → 粗筛(向量检索)→ 精排(LLM逐条比对)→ 输出结构化结果Architecture Layers
- Parsing layer: Convert resume PDFs to text, then use an open-source parsing library plus a small model to extract structured fields (skills, years of experience, education, certifications). JDs are likewise extracted into a structured "requirements list," distinguishing hard requirements from bonus points. This step is done once when the resume/JD enters the system—don't repeat it on every match request.
- Coarse filtering layer: Use embeddings for recall, but the goal isn't scoring—it's narrowing thousands of positions down to a Top 50 candidate list. It's fine to miss some; the fine-ranking stage will catch a portion of them.
- Fine-ranking layer: Use an LLM for item-by-item comparison. The key change is don't feed the full text—feed only structured fields:
def build_match_prompt(jd_reqs, resume_struct):
req_lines = "\n".join(
f"- {r['type']}: {r['content']}" for r in jd_reqs # 硬性/加分
)
return f"""你是招聘匹配专家。逐条判断候选人是否满足以下要求。
职位要求:
{req_lines}
候选人结构化信息:
{json.dumps(resume_struct, ensure_ascii=False)}
以JSON输出:
{{"hard_reqs": [{{"req": "...", "met": true/false, "evidence": "简历中的依据"}}],
"bonus_reqs": [...],
"match_score": 0-100,
"missing": ["未满足项"],
"summary": "一句话评价"}}"""Input drops from tens of thousands of tokens to a few hundred, latency drops from over ten seconds to under two seconds, and accuracy actually improves—because the model is judging item by item rather than "going on impressions" from a long text.
- Output layer: Persist match results to the database, and cache results for the same resume against the same JD. Only recalculate when the user updates their resume.
4. Why a Unified AI API Gateway Reduces Maintenance Costs
This solution requires at least three types of model capabilities: a small model for parsing and extraction, an embedding model for recall, and a mid-tier model for fine ranking. If you integrate each capability with a separate vendor's SDK, you'll face:
- Three sets of authentication, three SDKs, three error-handling implementations—when one vendor rate-limits you or another changes their response format, you're editing code in three separate places;
- Model upgrades become incidents—when a vendor decommissions an old model version, your hard-coded model names start throwing errors;
- Bills scattered across multiple consoles—cost attribution becomes difficult, and you can't tell how much a single match actually costs.
After consolidating through a unified AI API gateway:
- All calls use a unified protocol; switching to or canary-testing a new model only requires changing one parameter, without touching business code;
- Use a cheap model for extraction and a powerful model for fine ranking, with cost strategies configured centrally—no more hard-coding model names throughout your codebase;
- Call logs, failure rates, and token consumption are consolidated in a single dashboard—costs are attributable and incidents are traceable.
For small teams, this means AI-layer maintenance goes from "keeping track of N vendors for every project" to "maintaining a single unified channel," freeing up manpower for the business itself.
5. Implementation Checklist
- Choose a resume/JD parsing solution and produce a structured schema (skills, years of experience, education, certifications, hard-requirement flags);
- Build an embedding index and implement Top 50 coarse filtering;
- Implement the fine-ranking prompt per the template above, enforce JSON output with schema validation, and automatically degrade and retry on parse failures;
- Connect to a unified AI gateway, configuring different models and budget caps for extraction vs. fine ranking;
- Before launch, evaluate with 50 manually labeled JD-resume pairs; only roll out broadly once agreement between match conclusions and human judgment meets the bar (recommended ≥85%);
- Add caching and result persistence, and monitor per-match cost and P95 latency.
Conclusion
The essence of a matching system is not "having AI read two texts and score them," but decomposing an unstructured problem into item-by-item judgments where AI excels. Get the structuring step right, add a unified gateway to consolidate multi-model management, and even an indie developer can ship a working matching service in two or three weeks.
If you're ready to build this solution, you can start by registering for a unified AI gateway at https://api.thistoken.ai/register and get the three types of calls—extraction, recall, and fine ranking—working end to end. The implementation cost is far lower than you'd expect.
---
Tired of juggling provider integrations? Register at https://api.thistoken.ai/register and call every model through one base_url.
Token.AI を試してみませんか?
プロジェクトレベルの API Key を作成し、コンソールでチャネルを有効にして、ルーティング、予算、監査ログを設定しましょう。
注册 ThisToken.AI 并获取 API Key