An intelligent resume optimization platform that leverages MiniMax M3 API for dynamic LaTeX resume generation and ATS-optimized PDF outputs.
- AI-Powered Resume Optimization: Uses MiniMax M3 (Anthropic-compatible API) for intelligent resume tailoring
- Dynamic LaTeX Generation: Automatically generates ATS-optimized LaTeX resumes
- Conversational Interface: Natural language interaction for resume refinement
- Local File Storage: No database required - uses local JSON storage
- Real-time PDF Generation: Convert LaTeX to professional PDFs using Puppeteer
Create a .env file in the root directory with your MiniMax API credentials:
# MiniMax M3 API Configuration (Anthropic-compatible)
MINIMAX_API_KEY=your_minimax_api_key_here
ANTHROPIC_API_KEY=your_minimax_api_key_here
# API Base URLs (Anthropic-compatible for M3)
MINIMAX_BASE_URL=https://api.minimax.io/anthropic
ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic
# Model Configuration
MINIMAX_MODEL=MiniMax-M3
MINIMAX_MODEL_FALLBACK=MiniMax-M2
# Server Configuration
PORT=3001
NODE_ENV=development- Visit MiniMax API
- Sign up for an account
- Generate an API key
- Add the key to your
.envfile
npm install
cd frontend && npm install# Build the backend
npm run build
# Run the backend
npm run dev
# In another terminal, run the frontend
cd frontend && npm run devThe system uses MiniMax's Anthropic-compatible API for the M3 model:
- Base URL:
https://api.minimax.io/anthropic - Model:
MiniMax-M3(primary),MiniMax-M2(fallback) - API Format: Anthropic-compatible with
/messagesendpoint
If needed, you can switch to OpenAI-compatible format:
MINIMAX_BASE_URL=https://api.minimax.io/v1
OPENAI_API_KEY=your_minimax_api_key_here- Upload Resume: Upload your resume (PDF or DOCX)
- Enter Job Description: Paste the target job description
- AI Optimization: Use natural language to request resume optimizations
- Generate PDF: Download your ATS-optimized resume as PDF
- Backend: Node.js + TypeScript + Express
- Frontend: Next.js + React + Tailwind CSS
- AI: MiniMax M3 API (Anthropic-compatible)
- Storage: Local JSON files (no database required)
- PDF Generation: Puppeteer for HTML-to-PDF conversion
src/services/minimaxService.ts: MiniMax M3 API integrationsrc/services/resumeParser.ts: Resume parsing and LaTeX generationsrc/routes/conversation.ts: Conversation management with LaTeX optimizationsrc/routes/pdf.ts: PDF generation from LaTeX templates
npm test| Variable | Description | Default |
|---|---|---|
MINIMAX_API_KEY |
Your MiniMax API key | - |
MINIMAX_BASE_URL |
API base URL | https://api.minimax.io/anthropic |
MINIMAX_MODEL |
Primary model | MiniMax-M3 |
PORT |
Server port | 3001 |
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
MIT License - see LICENSE file for details.
Yes. Given your constraints—OpenAI APIs only, separate RAG/Data prompts, low latency—the production design I would use is this:
USER │ ▼ [0] Edge / Auth / Tenant │ ▼ [1] Input Safety ───────────────┐ │ │ ▼ │ parallel when safe [2] Context Builder │ │ │ ▼ │ [3] ONE Orchestrator LLM ◄──────┘ │ ├──── direct answer ├──── clarification ├──── RAG agent ├──── Data/MCP agent └──── RAG + Data in parallel │ ▼ [4] Specialist execution │ ▼ [5] Synthesis only if required │ ▼ [6] Selective output checks │ ▼ STREAM
The important optimization is that not every box is an LLM.
Layer Production implementation Extra LLM? Optimization 0. Edge auth, tenant, RBAC, rate limit, request ID No pure code
- Safety injection/basic abuse/tool-risk Usually no / optional parallel for reads; blocking for writes
- Context session state, entities, previous intent No deterministic state
- Orchestration follow-up + rewrite + route + clarification Yes, one call tiny structured output
- RAG/Data specialist prompt + specialist tools Yes when selected only selected agent runs
- Synthesis combine multi-agent evidence Sometimes skip for single-agent
- Validation schema/security/high-risk checks Usually code LLM only for uncertain/high-risk cases
- Observability traces, timings, route accuracy No async logging
Now optimize each layer.
- Edge layer
Do all known facts here:
JWT company_id user_id role permissions request_id rate limit
Never ask an LLM:
"Can Sai access company 123?"
Application code decides that.
OpenAI recommends unique client request IDs for production debugging.
⸻
- Prompt-injection / bad-input layer
Do not put another full GPT call before every request.
Use:
cheap deterministic checks + optional guardrail
For normal read-only requests:
┌─ guardrail
request ─────┤ └─ orchestrator run concurrently
For destructive/side-effect operations:
request ↓ BLOCKING security check ↓ authorization ↓ tool execution
The Agents SDK explicitly supports both parallel and blocking guardrails; parallel is lower latency, while blocking prevents downstream execution before the check completes.
⸻
- Conversation / follow-up context
This should not be another LLM.
Maintain state such as:
{ "company_id": "123", "previous_intent": "revenue_comparison", "entities": { "metric": "revenue", "period": "Q2" } }
Then:
User: "what about Q3?"
you send the orchestrator:
previous intent = revenue comparison metric = revenue previous period = Q2 current query = "what about Q3?"
Do not run:
followup_detector LLM → rewrite LLM → router LLM
That is exactly the latency explosion you’re trying to eliminate.
⸻
- Orchestrator — this is the one important routing call
Make one small structured call responsible for:
follow-up resolution + intent detection + query rewrite + clarification decision + agent selection + parallel/sequential decision
Output should be tiny:
{ "route": ["data"], "query": "Q3 revenue for company 123", "clarify": false, "parallel": false }
Not paragraphs of reasoning.
Your static prompt should stay stable:
SYSTEM agent contracts routing rules output schema security rules ──── stable/cached prefix ──── session context user query
OpenAI currently provides prompt-cache controls such as prompt_cache_key; keeping stable prefixes improves cache reuse.
Also version your orchestration prompt instead of modifying production prompts ad hoc. OpenAI’s current Responses architecture explicitly separates versioned prompts from application-side orchestration.
⸻
- Specialist agents
This is where your existing design makes sense.
Orchestrator
/ \
/ \
RAG Agent Data Agent
own prompt own prompt
own tools own MCP tools
own retrieval own DB logic
But avoid giving each specialist the entire conversation.
Send only:
normalized task relevant entities tenant/company required context
not 40 previous turns.
If using handoffs, OpenAI supports filtering what history the receiving agent sees specifically for this reason.
⸻
- Multi-agent execution
If both are independent:
"Compare revenue and explain policy impact" ┌─ Data Agent ────┐ Orchestrator ─┤ ├─ synthesis └─ RAG Agent ─────┘
Run them concurrently.
Do NOT do:
Data Agent ↓ wait RAG Agent
unless the RAG query actually depends on Data’s result.
This is one of the biggest real latency wins.
⸻
- Synthesis
This is where people often accidentally add another unnecessary LLM call.
If only Data Agent ran and its response is already final:
Data Agent → stream user
No synthesis call.
Same for RAG:
RAG Agent → stream user
Only use:
Data + RAG ↓ synthesis LLM
when multiple specialist outputs genuinely need combining.
That means:
single-agent request ≈ orchestrator + specialist multi-agent request ≈ orchestrator + parallel specialists + synthesis
instead of forcing synthesis everywhere.
⸻
- Validation
Do not use:
answer ↓ GPT validator ↓ answer
for every request.
First use deterministic validation:
required_fields_present JSON_schema_valid citations_present tenant_ids_match tool_status == success no forbidden data
LLM validation only for cases like:
conflicting agent outputs low-confidence answer high-risk response complex synthesis
Tool input/output guardrails can also protect individual custom tools instead of globally validating every final response.
⸻
- Failure handling
Don’t blindly restart the whole graph.
Example:
Data Agent timeout
should become:
retry Data tool once ↓ still fails ↓ return controlled partial/error
Not:
Router → Data agent → failure → router again → another agent → validator
Set hard:
tool timeout max agent turns max retries max total tool calls
The current Agents runner itself supports bounded turns, and tools support explicit timeouts.
⸻
- Observability
Every request should produce something resembling:
trace_id: abc preflight 4 ms orchestrator 610 ms route DATA data_agent 2100 ms MCP calls 340 ms TTFT 2840 ms total 3900 ms tokens 1850
Also log:
expected route selected route route corrected? tool failures clarification rate p50 / p95 TTFT p50 / p95 total
OpenAI tracing records model generations, tools, handoffs and guardrails, so you can inspect exactly where latency is accumulating.
⸻
The final architecture I’d build for your system is therefore:
REQUEST
│
deterministic code
│
┌────────────▼────────────┐
│ Auth / RBAC / Tenant │
│ basic validation/state │
└────────────┬────────────┘
│
┌─────────┴──────────┐
│ │
Safety check ORCHESTRATOR
parallel one small LLM
│ │
└─────────┬──────────┘
│
┌───────────┼───────────┐
│ │ │
DIRECT RAG DATA
│ │
RAG Agent Data Agent
│ │
Vector DB MCP
│ │
└─────┬─────┘
│
if >1 result only
│
SYNTHESIS
│
deterministic checks
│
STREAM
This is much closer to a production system than router → agent → validator → replanner everywhere.
One thing I want you to answer before we design the exact orchestrator schema:
For a normal request that routes only to your Data Agent, do you want the Data Agent itself to produce the final user-facing answer, or do you currently send its result back through the main agent again?