Files
santifer--career-ops/docs/RUNNING_ON_A_BUDGET.md
T
wehub-resource-sync d083df1fdb
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s
chore: import upstream snapshot with attribution
2026-07-13 12:02:43 +08:00

280 lines
13 KiB
Markdown

# Running Career-Ops on a Budget
Token usage costs and rate limits are the most common bottlenecks when setting up a high-volume job search pipeline. Since Career-Ops processes full job descriptions, evaluates them against your CV across 10 dimensions, and tailors resumes/cover letters, the context size can grow quickly.
Fortunately, **Career-Ops is completely AI-agnostic.** The pipeline relies on the AI coding CLI (or standalone scripts) to process prompt files under `modes/`. This means you can point your CLI to cheaper API providers or local models with **zero code changes** in Career-Ops.
---
## 1. The Core Concept: Model Agnosticism
Career-Ops is composed of local templates, Markdown prompts, and Node/Playwright scripts. The AI logic is driven entirely by whichever AI coding CLI you run it in (e.g., Claude Code, OpenCode, Qwen CLI, Codex, Antigravity CLI, or Grok Build CLI).
By choosing a CLI that supports custom model configurations and routing it to a cheaper API provider or local LLM, you can drastically reduce your pipeline running costs without losing any functionality.
---
## 2. Pick Your Spend Tier
Before diving into CLI configuration, know that career-ops has a built-in knob for controlling evaluation cost: the `spend_tier` setting in [`config/profile.yml`](../config/profile.example.yml). It controls which model tier your CLI uses to evaluate offers — no provider setup required.
| Tier | Behaviour |
|------|-----------|
| **economy** | Cheapest/fastest model, no extended thinking. Best for high-volume scanning. |
| **standard** | Balanced model, no extended thinking. Default if the key is absent. |
| **premium** | Most capable model, adaptive extended thinking. Best for high-stakes offers. |
The **economy** tier is the high-volume scanning choice — it processes the most offers per dollar. On **standard** and **premium**, a pre-screen gate automatically trims batch spend by skipping obvious mismatches before the full evaluation runs.
Set it once in your profile:
```yaml
# config/profile.yml
spend_tier: standard
```
The actual model behind each tier depends on your CLI. See the mapping table in [`modes/_shared.md`](../modes/_shared.md) for the full breakdown.
---
## 3. Configuring Alternative CLI Setups
Different CLIs offer different levels of flexibility for model routing. The two most common options for budget setups are **OpenCode** and **Qwen CLI**.
### OpenCode CLI
OpenCode is an open-source coding agent that easily routes to custom API providers (like DeepSeek, OpenRouter, Together AI) or local endpoints (Ollama).
To configure OpenCode with a custom provider:
1. Initialize/open OpenCode in the project directory:
```bash
opencode
```
2. Open its configuration settings (usually located in `.opencode/config.json` or configured via CLI prompts/settings).
3. Set the `provider` to your chosen endpoint (e.g., OpenRouter or a custom OpenAI-compatible endpoint).
4. Configure the environment variables for custom endpoints if needed:
```bash
# For Git Bash / Linux / macOS:
export OPENAI_API_BASE="https://openrouter.ai/api/v1"
export OPENAI_API_KEY="your_openrouter_api_key_here"
# For Windows CMD:
set OPENAI_API_BASE=https://openrouter.ai/api/v1
set OPENAI_API_KEY=your_openrouter_api_key_here
# For Windows PowerShell:
$env:OPENAI_API_BASE="https://openrouter.ai/api/v1"
$env:OPENAI_API_KEY="your_openrouter_api_key_here"
```
### Qwen CLI
Qwen CLI natively supports Qwen models but can be configured to point to any custom OpenAI-compatible API base URL:
```bash
# For Git Bash / Linux / macOS:
export QWEN_API_BASE="https://api.deepseek.com/v1"
export QWEN_API_KEY="your_deepseek_api_key_here"
# For Windows CMD:
set QWEN_API_BASE=https://api.deepseek.com/v1
set QWEN_API_KEY=your_deepseek_api_key_here
# For Windows PowerShell:
$env:QWEN_API_BASE="https://api.deepseek.com/v1"
$env:QWEN_API_KEY="your_deepseek_api_key_here"
```
---
## 4. Recommended Cost-Efficient Models
When choosing a budget-friendly model, you need strong reasoning capabilities to handle the multi-dimensional scoring and resume tailoring. Here are the recommended models that hold up well under evaluation:
| Model | Provider / Endpoint | Price per 1M Input / Output Tokens | Why use it |
|-------|---------------------|------------------------------------|------------|
| **DeepSeek V3** | DeepSeek API / OpenRouter | ~$0.14 / ~$0.28 | **Top Recommendation.** Unmatched reasoning-to-price ratio; performs close to frontier models at a fraction of the cost. |
| **DeepSeek-Coder-V2** | DeepSeek API / OpenRouter | ~$0.14 / ~$0.28 | Excellent instruction-following for structured Markdown and resume tailoring. |
| **Qwen-2.5-Coder (32B / 72B)** | OpenRouter / DeepInfra | ~$0.07 - ~$0.30 | Strong coding and structured reasoning, highly cost-effective. |
| **GLM-4-Air / GLM-4** | Zhipu AI / OpenRouter | Very Cheap | Reliable multi-turn reasoning and JSON/Markdown generation. |
| **Gemini 2.5 Flash** | Google AI Studio | Free Tier (15 RPM) | Available via the standalone script `node gemini-eval.mjs`. Excellent for zero-cost low-volume runs, but subject to rate limits. |
> **Standalone evaluator (no CLI config needed):** every OpenAI-compatible provider above (DeepSeek, Qwen, GLM, Together, Groq, OpenRouter, …) works directly through `node openai-eval.mjs` — just set a base URL, model, and key:
> ```bash
> OPENAI_BASE_URL=https://openrouter.ai/api/v1 \
> OPENAI_MODEL=deepseek/deepseek-chat \
> OPENAI_API_KEY=your_key \
> node openai-eval.mjs --file ./jds/job.txt
> ```
> Run `node openai-eval.mjs --help` for per-provider examples. For 100% local/private use, point `--url` at a local server (LM Studio / llama.cpp / vLLM) or use `node ollama-eval.mjs`.
---
## 5. Local LLM Tradeoffs (Ollama / Llama.cpp)
Running a model 100% locally via Ollama is completely free, but it comes with significant tradeoffs:
### The Size vs. Quality Tradeoff
- **Avoid Small Models (e.g., 8B parameters)**: Models like Llama 3 8B or Qwen-2.5-Coder 7B are generally **too weak** for Career-Ops. They frequently fail to follow the complex evaluation schemas (A-G blocks), fail to output valid Markdown/JSON structures, or generate low-quality, generic resume customizations.
- **Minimum Recommended Size**: Use at least a **32B+ or 70B+ model** (such as Qwen 2.5 Coder 32B/72B or Llama 3.1 70B) for reliable scoring and high-quality resume tailoring.
### Hardware & VRAM Requirements
Running 32B or 70B models locally requires substantial system resources:
- A **32B model** requires a GPU with at least **16GB - 24GB VRAM** (e.g., RTX 3090/4090, Mac Studio, or Apple Silicon Mac with 32GB+ unified memory).
- A **70B model** requires at least **48GB VRAM** to run at decent speeds.
> 💡 **Budget Tip**: For most users, running **DeepSeek V3** or **Qwen 2.5 Coder 72B** via a cheap hosted API (like DeepSeek directly or OpenRouter) is far more efficient and cost-effective than investing in local hardware, costing only a few cents for dozens of evaluations.
---
## 6. Token-Saving Best Practices
To prevent unnecessary API costs or hitting rate limits, implement the following practices:
1. **Use the Batch Limit Flag**:
Instead of manually splitting `batch/batch-input.tsv`, use the `--limit <N>` flag to process only a small capped number of offers (e.g. 5-10) in a single run. This lets you inspect the output quality before committing to a larger run:
```bash
./batch/batch-runner.sh --limit 5
```
2. **Use the Dry Run Flag**:
Always run a dry run first to verify which offers will be processed:
```bash
./batch/batch-runner.sh --dry-run
```
3. **Resume Interrupted Runs**:
If a batch run is interrupted by a rate limit or network error, do not restart from scratch. Use the `--resume-paused` flag to continue from where it left off, skipping completed jobs and preventing wasted tokens:
```bash
./batch/batch-runner.sh --resume-paused
```
4. **Use `--verify` on Scans**:
When running job board scans, use the liveness verifier to filter out expired postings before they enter your pipeline. This prevents wasting LLM tokens evaluating closed jobs:
```bash
npm run scan -- --verify
```
---
## 7. Worked Example: Running the Pipeline Cheaply
Here is a concrete, end-to-end walkthrough of scanning for jobs and evaluating a single posting using **DeepSeek V3 via OpenRouter** and the standalone `openai-eval.mjs` evaluator. This bypasses the need for an expensive CLI agent for the heavy evaluation block.
### Step 1: Scan for Job Offers (0 Tokens)
The portal scanner queries ATS APIs directly using Playwright and standard HTTPS requests. It doesn't use the LLM to read job boards.
```bash
node scan.mjs
```
**Cost:** 0 tokens, $0.00.
*(This generates a list of new job URLs and populates `data/pipeline.md`.)*
### Step 2: Fetch the Job Description (0 Tokens)
Open one of the URLs found by the scanner, copy the text of the job description, and save it locally (e.g., `jds/my-target-role.txt`).
### Step 3: Evaluate the Offer (~4,500 Tokens)
We'll run the evaluation against OpenRouter's DeepSeek V3 endpoint. The script reads your `cv.md` and the job description, then generates the full A-G evaluation report and tracker entry.
```bash
OPENAI_API_KEY="sk-or-your_openrouter_key" \
node openai-eval.mjs \
--url https://openrouter.ai/api/v1 \
--model deepseek/deepseek-chat \
--file ./jds/my-target-role.txt
```
**Approximate Token Usage:**
- **Input:** ~3,500 tokens (System prompt + your `cv.md` + JD)
- **Output:** ~1,000 tokens (The A-G evaluation report)
- **Cost:** ~4,500 tokens total. At DeepSeek V3 prices (~$0.14/1M input, ~$0.28/1M output), this costs **less than $0.001** per evaluation.
### Step 4: Tailor the CV HTML (~3,000 Tokens)
Now, use the headless tailor to inject JD keywords, reorder experience, and build the customized HTML for the role.
```bash
OPENAI_API_KEY="sk-or-your_openrouter_key" \
node openai-tailor.mjs \
--url https://openrouter.ai/api/v1 \
--model deepseek/deepseek-chat \
--jd ./jds/my-target-role.txt \
--report reports/001-companyname-2026-07-07.md
```
**Cost:** ~3,000 tokens (less than $0.001). This outputs a customized HTML file in the `output/` directory.
### Step 5: Generate ATS-Optimized PDF (0 Tokens)
Once you have the tailored HTML file, the PDF generator uses Playwright to compile it into a tailored CV PDF.
```bash
node generate-pdf.mjs output/cv-candidate-companyname.html output/cv-candidate-companyname-2026-07-07.pdf --format=letter --report=001
```
**Cost:** 0 tokens, $0.00.
By routing the heaviest step (Evaluation) to a cheap OpenAI-compatible endpoint, a complete end-to-end job application cycle drops from ~$0.05 - $0.15 on frontier models to a fraction of a cent, allowing you to run bulk batch processing affordably.
---
## 8. Zero-Cost Paths (No Claude / Paid CLI Required)
Career-ops ships a full pipeline that runs **entirely on free models** — no Claude Code, no Anthropic API key, no paid CLI subscription. Everything below works out of the box after a one-time `.env` setup.
### Path A: OpenRouter Free Models (`or:*` scripts)
No Claude Code CLI required — uses OpenRouter free models with automatic fallback.
**npm shortcuts** (cover the whole pipeline):
```bash
npm run or:scan # Scan portals for new listings (Greenhouse API, 0 tokens)
npm run or:pipeline # Process all pending URLs from data/pipeline.md
npm run or:eval # Evaluate a single offer (paste URL or text)
npm run or:apply # Generate draft application answers for a report
```
**Usage**
```bash
node openrouter-runner.mjs scan # Scan Greenhouse API companies for new listings
node openrouter-runner.mjs evaluate <url> # Evaluate a job by URL
node openrouter-runner.mjs evaluate # Paste job text interactively
node openrouter-runner.mjs pipeline # Process all pending URLs from pipeline.md
node openrouter-runner.mjs apply <report_no> # Generate draft application form answers
node openrouter-runner.mjs models # List available free models
node openrouter-runner.mjs help # Show this help
```
**Setup:**
```bash
1. copy .env.example .env
2. Add OPENROUTER_API_KEY=sk-or-v1-... to .env
3. Free API key: https://openrouter.ai
```
### Path B: Fully Local with Ollama (`ollama:eval`)
If you want **zero network calls** and complete privacy, run evaluations against a local Ollama instance:
```bash
npm run ollama:eval
```
This calls `ollama-eval.mjs` which hits your local Ollama server. No API key, no internet, no cost. See [Section 5](#5-local-llm-tradeoffs-ollama--llamacpp) for model size recommendations (32B+ minimum for reliable scoring).
### Path C: Any OpenAI-Compatible Endpoint (`openai:eval`)
Point at **any** endpoint that speaks the OpenAI chat-completions API — NVIDIA NIM (free tier), Zhipu GLM, Together, Groq, LM Studio, llama.cpp, vLLM, or even Ollama's `/v1` route:
```bash
npm run openai:eval
```
Configure via `.env`:
```dotenv
OPENAI_BASE_URL=https://integrate.api.nvidia.com/v1 # or any compatible base URL
OPENAI_MODEL=meta/llama-3.1-70b-instruct # model name at that endpoint
OPENAI_API_KEY=your_provider_key_here # some free endpoints need a key
```
Run `node openai-eval.mjs --help` for per-provider examples with exact URLs and model names.
**Which to pick:** Start with **Path A** (one env var, full pipeline). Use B for air-gapped/local-only, C if you already run your own inference endpoint.