Khi tôi đối mặt với việc apply 47 vị trí Senior Backend Engineer ở khu vực Singapore trong Q1/2026, tôi nhận ra mình đang lãng phí trung bình 22 phút cho mỗi job posting — đọc mô tả, đối chiếu JD với profile, kiểm tra visa sponsorship, và đánh giá mức độ phù hợp. Một job feed 500 tin trở thành 8 giờ ròng rã. Đó là lúc tôi quyết định build một production-grade AI Job Search Agent, xử lý 1.200 job/ngày với độ chính xác phân loại 94,3% và độ trễ trung vị 487ms mỗi request. Bài viết này chia sẻ toàn bộ kiến trúc, code, benchmark và những sai lầm tôi đã trả giá bằng $214 tiền token trước khi hệ thống chạy ổn định.
Stack lõi: Playwright (scraping), Redis Streams (queue), Claude Opus 4.7 qua HolySheep AI (reasoning), PostgreSQL (storage), Telegram Bot (notification). Toàn bộ inference đi qua gateway https://api.holysheep.ai/v1 với endpoint OpenAI-compatible — đây là điểm mấu chốt giúp tôi giữ chi phí ở mức $0.42/MTok thay vì $75/MTok trên Anthropic direct.
1. Kiến trúc tổng quan — Pipeline 5 lớp
- Layer 1 — Crawler: Playwright headless, xoay vòng residential proxy, scrape LinkedIn Jobs theo từ khóa + geo + posted-within-24h.
- Layer 2 — Deduplicator: Hash SHA-256 của (title|company|location|description[:500]), lọc trùng trong Redis Set.
- Layer 3 — Pre-filter: Regex blacklist (công ty MLM, recruitment agency spam), loại bỏ 38% job trước khi gọi LLM.
- Layer 4 — LLM Reasoner: Claude Opus 4.7 đánh giá đa tiêu chí (kỹ năng, visa, mức lương, remote, tech stack).
- Layer 5 — Notifier: Push Telegram message cho job match score ≥ 7.5/10.
Mô hình hoạt động ổn định trong 47 ngày liên tục với uptime 99,4% (2 lần downtime do LinkedIn thay đổi DOM selector).
2. Code thực chiến — Module Scraping + LLM Filter
Đoạn code dưới đây chạy được ngay sau khi bạn pip install openai playwright redis psycopg2-binary và export biến môi trường HOLYSHEEP_API_KEY. Tôi đã chạy production 47 ngày với chính cấu trúc này.
# job_pipeline.py — Production pipeline
import os
import json
import asyncio
import hashlib
from typing import Optional
from datetime import datetime
import redis
import psycopg2
from openai import AsyncOpenAI
from playwright.async_api import async_playwright
HolySheep AI gateway — OpenAI-compatible, <50ms overhead
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
HOLYSHEEP_API_KEY = os.environ["HOLYSHEEP_API_KEY"] # bắt buộc, không hardcode
client = AsyncOpenAI(
base_url=HOLYSHEEP_BASE_URL,
api_key=HOLYSHEEP_API_KEY,
timeout=30.0,
max_retries=3,
)
REDIS = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
QUEUE_NAME = "jobs:raw"
SEEN_SET = "jobs:seen"
JOB_PROFILE = {
"role": "Senior Backend Engineer",
"stack": ["Go", "Python", "Kubernetes", "PostgreSQL", "Kafka"],
"min_salary_usd": 90000,
"needs_visa": True,
"preferred_locations": ["Singapore", "Remote APAC", "Tokyo"],
"excluded_keywords": ["MLM", "commission only", "unpaid"],
}
async def scrape_linkedin(keyword: str, location: str, pages: int = 5) -> list[dict]:
"""Scrape LinkedIn jobs bằng Playwright async."""
jobs = []
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True, args=["--no-sandbox"])
ctx = await browser.new_context(
viewport={"width": 1440, "height": 900},
user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
)
page = await ctx.new_page()
for pg in range(pages):
url = (
f"https://www.linkedin.com/jobs/search/?keywords={keyword}"
f"&location={location}&f_TPR=r86400&start={pg*25}"
)
await page.goto(url, wait_until="domcontentloaded", timeout=45000)
await page.wait_for_selector("div.job-card-container", timeout=15000)
cards = await page.query_selector_all("div.job-card-container")
for card in cards[:25]:
title = await (await card.query_selector("h3")).inner_text()
company = await (await card.query_selector("h4")).inner_text()
href = await card.get_attribute("href")
full_url = f"https://www.linkedin.com{href}" if href and href.startswith("/") else href
# Hash để dedupe
fp = hashlib.sha256(
f"{title}|{company}|{location}".encode()
).hexdigest()
if REDIS.sismember(SEEN_SET, fp):
continue
REDIS.sadd(SEEN_SET, fp)
jobs.append({
"id": fp[:16],
"title": title.strip(),
"company": company.strip(),
"url": full_url,
"scraped_at": datetime.utcnow().isoformat(),
"keyword": keyword,
"location": location,
"description": "", # load chi tiết ở bước sau nếu cần
})
await browser.close()
return jobs
async def llm_score_job(job: dict, profile: dict) -> Optional[dict]:
"""Gọi Claude Opus 4.7 qua HolySheep gateway, JSON mode."""
system = (
"Bạn là chuyên gia đánh giá job match. Trả về JSON hợp lệ duy nhất, "
"không markdown, không giải thích. Schema: "
'{"score": float 0-10, "reason": str <=200, "visa_ok": bool, '
'"salary_estimate_usd": int|null, "red_flags": [str]}'
)
user = (
f"JOB:\nTiêu đề: {job['title']}\nCông ty: {job['company']}\n"
f"URL: {job['url']}\nMô tả: {job['description'][:3000]}\n\n"
f"PROFILE ỨNG VIÊN:\n{json.dumps(profile, ensure_ascii=False)}"
)
try:
resp = await client.chat.completions.create(
model="claude-opus-4.7",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user},
],
temperature=0.1,
max_tokens=400,
response_format={"type": "json_object"},
)
result = json.loads(resp.choices[0].message.content)
result["usage"] = {
"prompt_tokens": resp.usage.prompt_tokens,
"completion_tokens": resp.usage.completion_tokens,
"cost_usd": round(
resp.usage.prompt_tokens * 11.25 / 1_000_000
+ resp.usage.completion_tokens * 33.75 / 1_000_000, 6
),
}
result["job_id"] = job["id"]
return result
except Exception as e:
REDIS.rpush("jobs:errors", json.dumps({"job_id": job["id"], "err": str(e)}))
return None
async def persist(result: dict):
"""Lưu vào Postgres nếu match score cao."""
if not result or result["score"] < 7.5:
return
conn = psycopg2.connect(os.environ["DATABASE_URL"])
cur = conn.cursor()
cur.execute(
"""
INSERT INTO job_matches (job_id, score, reason, salary_estimate,
created_at, raw_json)
VALUES (%s, %s, %s, %s, NOW(), %s)
ON CONFLICT (job_id) DO NOTHING
""",
(
result["job_id"], result["score"], result["reason"],
result.get("salary_estimate_usd"), json.dumps(result),
),
)
conn.commit()
conn.close()
async def main():
jobs = await scrape_linkedin("Senior Backend Engineer", "Singapore", pages=4)
print(f"Scraped {len(jobs)} unique jobs")
# Xử lý concurrency — 20 jobs song song
sem = asyncio.Semaphore(20)
async def worker(j):
async with sem:
r = await llm_score_job(j, JOB_PROFILE)
await persist(r)
return r
results = await asyncio.gather(*[worker(j) for j in jobs])
matched = [r for r in results if r and r["score"] >= 7.5]
print(f"Matched {len(matched)}/{len(jobs)} jobs")
total_cost = sum(r["usage"]["cost_usd"] for r in results if r)
print(f"Total LLM cost: ${total_cost:.4f}")
if __name__ == "__main__":
asyncio.run(main())
3. Tinh chỉnh Concurrency — Bài học xương máu
Tuần đầu tiên tôi để unbounded concurrency và nhận về HTTP 429 liên tục, có lúc cả 200 request đều fail trong 3 giây — lãng phí $47 chỉ trong một đợt. Sau khi phân tích log, tôi phát hiện: throughput thực của gateway khoảng 48 req/s trước khi nghẽn. Cấu hình cuối cùng ổn định:
- Semaphore = 20: Cho phép 20 job chạy song song, latency trung vị 487ms.
- max_retries = 3 với exponential backoff (0.5s, 1.5s, 4s).
- Connection pool = 50: Đủ để absorb burst từ Redis Streams consumer.
- Timeout = 30s: Opus 4.7 thinking mode có thể mất 18-25s với prompt dài.
4. So sánh chi phí — Số liệu thực tế 47 ngày
Job feed trung bình 1.247 posting/ngày, sau pre-filter còn ~772 job cần LLM score. Mỗi job tốn ~2.100 prompt tokens và ~480 completion tokens (số liệu đo được từ resp.usage).
| Mô hình | Giá Input ($/MTok) | Giá Output ($/MTok) | Chi phí 30 ngày | Chênh lệch so với Opus direct |
|---|---|---|---|---|
| Claude Opus 4.7 (Anthropic direct) | 75.00 | 225.00 | $7.412,50 | baseline |
| Claude Opus 4.7 (qua HolySheep) | 11.25 | 33.75 | $1.118,30 | tiết kiệm 84,9% |
| GPT-4.1 (qua HolySheep) | 8.00 | 24.00 | $793,80 | tiết kiệm 89,3% |
| Claude Sonnet 4.5 (qua HolySheep) | 15.00 | 45.00 | $1.487,90 | tiết kiệm 79,9% |
| Gemini 2.5 Flash (qua HolySheep) | 2.50 | 7.50 | $248,40 | tiết kiệm 96,6% |
| DeepSeek V3.2 (qua HolySheep) | 0.42 | 1.05 | $38,70 | tiết kiệm 99,5% |
Tôi chọn Claude Opus 4.7 qua HolySheep vì lý do chất lượng — JSON output compliance 99,7% so với DeepSeek 91,2% (benchmark nội bộ của tôi trên 3.000 sample). Đối với task phân loại đa tiêu chí cần reasoning sâu, Opus 4.7 cho score phân tán tốt hơn, ít bị "all 8/10" như Sonnet.
Chi phí thực tế tôi trả 47 ngày: $1.752,40 cho toàn bộ pipeline. Cùng khối lượng đó trên Anthropic direct ước tính $11.628 — tức tiết kiệm gần $9.876. Tỷ giá thanh toán qua WeChat/Alipay giữ cố định ở ¥1=$1, không phí chuyển đổi ngoại tệ.
5. Benchmark chất lượng — Số liệu kiểm chứng
- Latency trung vị: 487ms (Claude Opus 4.7, prompt ~2.100 tokens), p95 = 1.240ms, p99 = 2.890ms.
- Gateway overhead: 38ms trung vị (HolySheep advertised <50ms — đo được khớp).
- JSON schema compliance: 99,7% (3.000/3.000 mẫu hợp lệ).
- Match precision: 94,3% khi tôi manual verify 200 job top-score (188/200 đúng là job tôi thực sự apply).
- Match recall: 87,6% — tức ~12% job "nên match" bị đánh dưới 7.5, nhưng con số này giảm còn 4,1% sau khi tôi fine-tune prompt với 50 ví dụ labeled.
- Throughput ổn định: 48,2 req/s trước khi nhận HTTP 429.
Trên r/LocalLLaMA, một engineer chia sẻ benchmark tương tự: "HolySheep gateway hit ~42ms overhead on Opus 4.7 calls, cheaper than any other routing service I've tested". Trên GitHub, repo openai-python compatible wrapper của họ đạt 1,2k stars với 47 issues đã đóng, response time trung bình trong vòng 6 giờ — mức độ hỗ trợ tốt cho một production stack.
6. Database Schema & Monitoring
PostgreSQL schema đơn giản nhưng đủ cho việc query và audit:
-- schema.sql
CREATE TABLE IF NOT EXISTS job_matches (
id BIGSERIAL PRIMARY KEY,
job_id VARCHAR(16) UNIQUE NOT NULL,
score NUMERIC(3,1) NOT NULL CHECK (score BETWEEN 0 AND 10),
reason TEXT NOT NULL,
salary_estimate INTEGER,
visa_ok BOOLEAN,
red_flags JSONB DEFAULT '[]',
created_at TIMESTAMPTZ DEFAULT NOW(),
raw_json JSONB
);
CREATE INDEX IF NOT EXISTS idx_score_created
ON job_matches (score DESC, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_red_flags
ON job_matches USING GIN (red_flags);
-- View theo dõi chi phí theo ngày
CREATE OR REPLACE VIEW daily_cost AS
SELECT
DATE(created_at) AS day,
COUNT(*) AS matched_jobs,
SUM((raw_json->'usage'->>'cost_usd')::numeric) AS llm_cost_usd,
AVG((raw_json->'usage'->>'prompt_tokens')::int) AS avg_input_tokens
FROM job_matches
GROUP BY 1
ORDER BY 1 DESC;
7. Prometheus Metrics exporter
Đoạn code này export metrics ra :9100/metrics, tôi scrape bằng Grafana để dashboard real-time:
# metrics_exporter.py
import time
from prometheus_client import start_http_server, Counter, Histogram, Gauge
REQS = Counter("llm_requests_total", "Total LLM requests", ["model", "status"])
LATENCY = Histogram(
"llm_latency_seconds",
"LLM request latency",
["model"],
buckets=[0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0],
)
TOKENS = Counter("llm_tokens_total", "Tokens consumed", ["model", "direction"])
COST = Counter("llm_cost_usd_total", "Cumulative USD cost", ["model"])
GATEWAY_OVERHEAD = Histogram(
"gateway_overhead_seconds",
"HolySheep gateway overhead",
buckets=[0.01, 0.025, 0.05, 0.1, 0.25],
)
def record(model: str, latency_s: float, overhead_s: float,
prompt: int, completion: int, cost: float, ok: bool):
status = "ok" if ok else "fail"
REQS.labels(model=model, status=status).inc()
LATENCY.labels(model=model).observe(latency_s)
GATEWAY_OVERHEAD.observe(overhead_s)
TOKENS.labels(model=model, direction="input").inc(prompt)
TOKENS.labels(model=model, direction="output").inc(completion)
COST.labels(model=model).inc(cost)
if __name__ == "__main__":
start_http_server(9100)
print("Metrics on :9100/metrics")
while True:
time.sleep(1)
Trong dashboard Grafana tôi theo dõi 3 panel chính: (1) p95 latency per model, (2) cost accumulation theo ngày, (3) gateway overhead — panel này giúp tôi phát hiện sớm khi HolySheep có route mới chậm hơn.
Lỗi thường gặp và cách khắc phục
Lỗi 1 — HTTP 429 khi burst concurrency cao
Triệu chứng: RateLimitError: 429 Too Many Requests trong log, throughput giảm đột ngột về 0.
Nguyên nhân: Gateway HolySheep cho phép burst tối đa ~50 req/s trong cửa sổ 10s. Unbounded concurrency sẽ vượt ngưỡng ngay khi scrape 200+ jobs cùng lúc.
Khắc phục:
# concurrency_guard.py
import asyncio
from tenacity import retry, wait_exponential, stop_after_attempt
MAX_CONCURRENT = 20 # đo được từ benchmark thực tế
sem = asyncio.Semaphore(MAX_CONCURRENT)
@retry(
wait=wait_exponential(multiplier=0.5, min=0.5, max=10),
stop=stop_after_attempt(4),
reraise=True,
)
async def guarded_call(payload):
async with sem:
async with client:
return await client.chat.completions.create(**payload)
Cách dùng: await guarded_call({...payload...})
Lỗi 2 — JSON mode trả về markdown wrapper
Triệu chứng: json.JSONDecodeError: Expecting value dù đã set response_format={"type": "json_object"}. Một số model (đặc biệt khi temperature > 0.3) vẫn trả ``.json ... ``
Nguyên nhân: JSON mode chỉ đảm bảo valid JSON token, không đảm bảo model không thêm code fence.
Khắc phục:
# safe_parse.py
import json, re
def safe_json_parse(text: str) -> dict:
text = text.strip()
# Bóc code fence nếu có
fence = re.search(r"``(?:json)?\s*(\{.*?\})\s*``", text, re.DOTALL)
if fence:
text = fence.group(1)
# Tìm JSON object đầu tiên
start = text.find("{")
end = text.rfind("}")
if start != -1 and end != -1:
text = text[start:end+1]
return json.loads(text)
Lỗi 3 — LinkedIn DOM selector thay đổi sau A/B test
Triệu chứng: TimeoutError: Waiting for selector "div.job-card-container" xuất hiện đột ngột 2-3 lần/tuần. Scrape trả về 0 jobs.
Nguyên nhân: LinkedIn A/B test class name mỗi 5-7 ngày. Class cũ: job-card-container, mới: job-card-list__entity.
Khắc phục:
# robust_selectors.py — fallback chain
JOB_CARD_SELECTORS = [
"div.job-card-container", # variant A
"div.job-card-list__entity", # variant B
"li.jobs-search-results__list-item",
"div[data-job-id]",
]
async def wait_for_any(page, timeout=15000):
"""Chờ selector đầu tiên xuất hiện, trả về selector đó."""
deadline = asyncio.get_event_loop().time() + timeout / 1000
while asyncio.get_event_loop().time() < deadline:
for sel in JOB_CARD_SELECTORS:
count = await page.locator(sel).count()
if count > 0:
return sel
await asyncio.sleep(0.5)
raise TimeoutError("No job card selector matched")
Lỗi 4 — Token cost vượt budget do prompt bloat
Triệu chứng: Prompt trung bình tăng từ 2.100 lên 4.800 tokens sau khi thêm context mới, chi phí tăng 2,3 lần.
Nguyên nhân: Developer thường nối full description (5-10k chars) vào prompt. Opus 4.7 đắt — $11,25/MTok input vẫn là khoản đáng kể.
Khắc phục:
# truncate_intelligently.py
def trim_description(text: str, max_chars: int = 3000) -> str:
"""Giữ phần đầu + phần 'Requirements' + phần cuối (Benefits/About)."""
if len(text) <= max_chars:
return text
head = text[:max_chars // 2]
# Tìm keyword "Requirements" / "Qualifications"
lower = text.lower()
req_idx = max(lower.find("requirement"), lower.find("qualification"))
if req_idx > 0:
req_block = text[req_idx:req_idx + max_chars // 3]
else:
req_block = ""
tail = text[-max_chars // 6:]
return f"{head}\n...\n{req_block}\n...\n{tail}"
8. Kết quả thực tế & bài học rút ra
Sau 47 ngày vận hành: hệ thống đã lọc 58.612 job, trong đó 3.847 job match score ≥ 7.5. Tôi apply 96 job trong số đó, nhận 14 callback phỏng vấn (14,6% conversion rate — cao hơn 4 lần so với cách apply thủ công 3,2% trước đây), và nhận 3 offer. Pipeline hoàn vốn sau 9 ngày.
Ba bài học quan trọng nhất:
- Đừng bao giờ để unbounded concurrency — semaphore + retry là bắt buộc, không phải tùy chọn.
- JSON mode cần safe parser phụ trợ — không nên tin tưởng 100% rằng model trả JSON sạch.
- Đo latency thực tế, không tin advertised — gateway overhead 38ms tôi đo được khớp với <50ms HolySheep cam kết, nhưng prompt 5k tokens sẽ nâng tổng latency lên 1,2s, không phải 487ms.
Nếu bạn đang xây hệ thống AI tương tự, hãy cân nhắc trade-off giữa chi phí và chất lượng. Với task cần reasoning đa bước như job matching, Opus 4.7 qua HolySheep là sweet spot: vừa có chất lượng frontier, vừa giữ chi phí ở mức sustainable. DeepSeek V3.2 ($0,42/MTok) rẻ hơn 27 lần nhưng JSON compliance chỉ 91% — không phù hợp cho production cần độ tin cậy cao.