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.
Bạn muốn thử Token.AI?
Tạo API Key cấp dự án, bật kênh trong bảng điều khiển và định cấu hình định tuyến, ngân sách và nhật ký kiểm tra.
注册 ThisToken.AI 并获取 API Key