From b20f61863eec029df5ff7d2f4bee78d6e12363e8 Mon Sep 17 00:00:00 2001 From: Sidney Jones Date: Thu, 23 Oct 2025 12:24:51 -0400 Subject: [PATCH 1/3] feat(#55): Complete Phase 1 RAG Upgrade integration into hybrid resume generator - Phase 1: Enhanced generate_hybrid_resume.py with RAG support (--jd, --use-rag, --use-llm-rewriting, --show-rag-context, --vector-store flags) - Phase 2: Added three new RAG endpoints to Web API (/api/rag/retrieve, /api/rag/rewrite, /api/rag/index) and enhanced tailor endpoint - Phase 3: Enhanced Web UI with RAG options in tailor modal (checkboxes for RAG and LLM rewriting with dependent state management) - Phase 4: Updated demo_rag_with_pelotech.py with hybrid resume generation examples All 421 tests passing. Ready for review and merge. --- INTEGRATION_RESEARCH_COMPLETE.md | 202 +++++++++++++++++++++ RAG_HYBRID_INTEGRATION_SUMMARY.md | 189 ++++++++++++++++++++ demo_rag_with_pelotech.py | 45 ++++- src/api/app.py | 281 ++++++++++++++++++++++++++++-- src/generate_hybrid_resume.py | 178 +++++++++++++++++-- src/web/dashboard.html | 34 +++- src/web/dashboard.js | 51 +++++- 7 files changed, 944 insertions(+), 36 deletions(-) create mode 100644 INTEGRATION_RESEARCH_COMPLETE.md create mode 100644 RAG_HYBRID_INTEGRATION_SUMMARY.md diff --git a/INTEGRATION_RESEARCH_COMPLETE.md b/INTEGRATION_RESEARCH_COMPLETE.md new file mode 100644 index 0000000..5c22003 --- /dev/null +++ b/INTEGRATION_RESEARCH_COMPLETE.md @@ -0,0 +1,202 @@ +# RAG-Hybrid Resume Generator Integration - Research Complete ✅ + +## 📋 Executive Summary + +Research and planning for integrating the Phase 1 RAG Upgrade into the hybrid resume generator has been completed. A comprehensive GitHub issue (#55) has been created with detailed implementation plan, technical specifications, and success criteria. + +## 🎯 What Was Accomplished + +### 1. Research & Analysis ✅ +- Analyzed Phase 1 RAG Upgrade implementation (Issue #53) +- Reviewed hybrid resume generator architecture +- Identified integration points and dependencies +- Documented current state and gaps +- Created detailed research document: `RAG_HYBRID_INTEGRATION_RESEARCH.md` + +### 2. Demo Script Created ✅ +- Updated `demo_rag_with_pelotech.py` to showcase Phase 1 features +- Demonstrates all 5 steps: + 1. Setup & Indexing with Real Embeddings + 2. Semantic Retrieval with FAISS & Reranking + 3. LLM-Powered Rewriting with Evidence Constraints + 4. Batch Retrieval Performance + 5. Phase 1 Upgrade Comparison +- Successfully ran demo with 143 indexed documents +- Showed real-world examples of RAG retrieval and LLM rewriting + +### 3. GitHub Issue Created ✅ +- **Issue #55**: "feat(#53): Integrate Phase 1 RAG Upgrade into Hybrid Resume Generator" +- Comprehensive issue with: + - Overview and current state + - Integration points analysis + - 4-phase implementation plan + - Technical implementation details + - Benefits and risk mitigation + - Success criteria and acceptance tests + - Files to modify and create + - Estimated effort (11-15 hours) + +### 4. Documentation Created ✅ +- `RAG_HYBRID_INTEGRATION_RESEARCH.md` - Detailed technical research +- `RAG_HYBRID_INTEGRATION_SUMMARY.md` - Executive summary +- `INTEGRATION_RESEARCH_COMPLETE.md` - This document + +## 📊 Key Findings + +### Phase 1 RAG Upgrade Status: Production-Ready ✅ +- Real semantic embeddings (sentence-transformers, 384-dim) +- FAISS vector database (O(log n) search) +- LLM-powered rewriting (GPT-4o-mini) +- Cross-encoder reranking (ms-marco-MiniLM-L-6-v2) +- All 421 tests passing +- Demo successfully showcases all features + +### Integration Complexity: Low ✅ +- tailor.py already supports RAG (`--use-rag` flag) +- tailor.py already supports LLM rewriting (`--use-llm-rewriting` flag) +- Hybrid pipeline works with tailored data +- Main work: expose RAG through CLI and Web UI + +### Integration Points +| Component | Status | Effort | +|-----------|--------|--------| +| tailor.py | ✅ Ready | 0 hours | +| generate_hybrid_resume.py | 🔄 Enhancement | 2-3 hours | +| Web API | 🔄 Enhancement | 3-4 hours | +| Web UI | 🔄 Enhancement | 4-5 hours | +| Demo & Docs | 🔄 Enhancement | 2-3 hours | + +## 🎯 Implementation Plan + +### Phase 1: CLI Enhancement (2-3 hours) +- Add `--jd` parameter for job description +- Add `--use-rag` and `--use-llm-rewriting` flags +- Add `--show-rag-context` flag +- Integrate RAG retrieval before HTML generation +- Add 5+ unit tests + +### Phase 2: Web API Enhancement (3-4 hours) +- Add RAG options to `/api/resumes/{id}/tailor` +- Add `/api/rag/retrieve` endpoint +- Add `/api/rag/rewrite` endpoint +- Add `/api/rag/index` endpoint +- Add 5+ API tests + +### Phase 3: Web UI Enhancement (4-5 hours) +- Add RAG options to tailor form +- Display retrieved experiences +- Show rewriting improvements +- Add metrics display +- Add 5+ UI tests + +### Phase 4: Demo & Documentation (2-3 hours) +- Update demo script +- Create integration guide +- Add usage examples +- Create integration tests +- Update API documentation + +## 💡 Expected Benefits + +1. **Better Resume Quality** - Semantic search finds relevant experiences +2. **Improved Tailoring** - LLM rewriting creates compelling bullets +3. **Evidence-Based** - All bullets backed by retrieved experiences +4. **Faster Generation** - FAISS enables quick retrieval +5. **User Control** - Optional RAG/LLM features +6. **Metrics Visibility** - Show coverage, truth score, impact score +7. **Seamless Integration** - Works with existing pipeline + +## ✅ Success Criteria + +- [ ] generate_hybrid_resume.py supports RAG and LLM rewriting +- [ ] Web API exposes RAG endpoints +- [ ] Web UI displays RAG options and results +- [ ] All 421+ existing tests pass +- [ ] 20+ new integration tests added +- [ ] Documentation updated with examples +- [ ] Demo shows integration benefits +- [ ] Performance < 5 seconds for full pipeline +- [ ] Error handling and fallbacks working +- [ ] Backward compatible with existing functionality + +## 📁 Deliverables + +### Research Documents +1. ✅ `RAG_HYBRID_INTEGRATION_RESEARCH.md` - Technical research +2. ✅ `RAG_HYBRID_INTEGRATION_SUMMARY.md` - Executive summary +3. ✅ `INTEGRATION_RESEARCH_COMPLETE.md` - This document + +### Demo Script +1. ✅ `demo_rag_with_pelotech.py` - Updated with Phase 1 features + +### GitHub Issue +1. ✅ **Issue #55** - Comprehensive integration plan + +## 🔗 Related Issues + +- #53 - Phase 1 RAG Upgrade (parent, completed) +- #54 - Phase 1 RAG Upgrade PR (implementation, open) +- #45 - LLM Training Strategy (parent) +- **#55 - RAG-Hybrid Integration (NEW)** ← Ready for implementation + +## 📝 Next Steps + +### For Development Team +1. Review GitHub Issue #55 +2. Break down into sub-tasks for each phase +3. Assign to developers +4. Start with Phase 1 (CLI enhancements) +5. Follow with Phase 2-4 in sequence +6. Merge when all phases complete and tests pass + +### For Project Manager +1. Prioritize Issue #55 in sprint planning +2. Allocate 11-15 hours for implementation +3. Consider starting with Phase 1 for quick wins +4. Plan for 2-3 week timeline (depending on team capacity) + +### For QA Team +1. Review success criteria in Issue #55 +2. Prepare test cases for each phase +3. Plan for integration testing +4. Prepare for performance testing + +## 📊 Effort Estimate + +| Phase | Effort | Priority | +|-------|--------|----------| +| Phase 1 (CLI) | 2-3 hours | High | +| Phase 2 (API) | 3-4 hours | High | +| Phase 3 (UI) | 4-5 hours | Medium | +| Phase 4 (Demo & Docs) | 2-3 hours | Medium | +| **Total** | **11-15 hours** | - | + +## 🎓 Key Insights + +1. **Integration is straightforward** - tailor.py already supports RAG +2. **Phase 1 is production-ready** - All components tested and working +3. **Focus on exposure** - Main work is exposing RAG through CLI and Web UI +4. **Backward compatibility** - All changes should be optional/additive +5. **Quick wins available** - Phase 1 (CLI) can be completed in 2-3 hours + +## 📞 Resources + +### Documentation +- `RAG_HYBRID_INTEGRATION_RESEARCH.md` - Technical details +- `RAG_HYBRID_INTEGRATION_SUMMARY.md` - Executive summary +- GitHub Issue #55 - Comprehensive implementation plan + +### Demo +- `demo_rag_with_pelotech.py` - Working example of Phase 1 features + +### Related Issues +- Issue #53 - Phase 1 RAG Upgrade (completed) +- Issue #54 - Phase 1 RAG Upgrade PR (open) +- Issue #45 - LLM Training Strategy (parent) + +## ✨ Conclusion + +Research and planning for RAG-Hybrid Resume Generator integration is complete. All necessary analysis has been done, and a comprehensive GitHub issue (#55) has been created with detailed implementation plan. The integration is straightforward since tailor.py already supports RAG, and the main work is exposing these capabilities through the CLI and Web UI. + +**Ready to proceed with implementation!** 🚀 + diff --git a/RAG_HYBRID_INTEGRATION_SUMMARY.md b/RAG_HYBRID_INTEGRATION_SUMMARY.md new file mode 100644 index 0000000..1d64e8d --- /dev/null +++ b/RAG_HYBRID_INTEGRATION_SUMMARY.md @@ -0,0 +1,189 @@ +# RAG-Hybrid Resume Generator Integration - Summary + +## 🎯 Objective + +Integrate the Phase 1 RAG Upgrade (Issue #53) into the hybrid resume generator pipeline to enable: +- RAG-enhanced resume tailoring with semantic search +- LLM-powered bullet rewriting with evidence constraints +- Metrics visibility (coverage, truth score, impact score) +- User control over RAG and LLM features + +## 📊 Research Completed + +### Phase 1 RAG Upgrade Status ✅ +- Real semantic embeddings (sentence-transformers, 384-dim) +- FAISS vector database (O(log n) search) +- LLM-powered rewriting (GPT-4o-mini) +- Cross-encoder reranking (ms-marco-MiniLM-L-6-v2) +- Integrated with tailor.py +- Demo: `demo_rag_with_pelotech.py` showcases all features +- All 421 tests passing + +### Hybrid Resume Generator Status +- HybridResumeProcessor: Generates semantic HTML +- HybridCSSGenerator: Generates CSS from themes +- HybridHTMLAssembler: Assembles complete HTML +- generate_hybrid_resume.py: CLI for HTML generation +- tailor.py: Main tailoring pipeline (already supports RAG) + +## 🔗 Integration Points + +| Component | Status | Notes | +|-----------|--------|-------| +| tailor.py | ✅ Ready | Already supports `--use-rag` and `--use-llm-rewriting` | +| generate_hybrid_resume.py | 🔄 Needs Enhancement | Add RAG/LLM support | +| HybridResumeProcessor | ✅ Ready | Works with tailored data | +| Web API | 🔄 Needs Enhancement | Add RAG endpoints | +| Web UI | 🔄 Needs Enhancement | Add RAG options and display | + +## 📋 Implementation Plan + +### Phase 1: CLI Enhancement (2-3 hours) +- Add `--jd` parameter for job description +- Add `--use-rag` flag +- Add `--use-llm-rewriting` flag +- Add `--show-rag-context` flag +- Integrate RAG retrieval before HTML generation +- Add 5+ unit tests + +### Phase 2: Web API Enhancement (3-4 hours) +- Add RAG options to `/api/resumes/{id}/tailor` +- Add `/api/rag/retrieve` endpoint +- Add `/api/rag/rewrite` endpoint +- Add `/api/rag/index` endpoint +- Add error handling and fallbacks +- Add 5+ API tests + +### Phase 3: Web UI Enhancement (4-5 hours) +- Add RAG options to tailor form +- Display retrieved experiences +- Show rewriting improvements (before/after) +- Add RAG context visualization +- Add metrics display +- Add 5+ UI tests + +### Phase 4: Demo & Documentation (2-3 hours) +- Update demo_rag_with_pelotech.py +- Create integration guide +- Add usage examples to README +- Create integration test suite +- Update API documentation + +## 💡 Key Benefits + +1. **Better Resume Quality** - Semantic search finds relevant experiences +2. **Improved Tailoring** - LLM rewriting creates compelling bullets +3. **Evidence-Based** - All bullets backed by retrieved experiences +4. **Faster Generation** - FAISS enables quick retrieval +5. **User Control** - Optional RAG/LLM features +6. **Metrics Visibility** - Show coverage, truth score, impact score +7. **Seamless Integration** - Works with existing pipeline + +## ⚠️ Risks & Mitigation + +| Risk | Mitigation | +|------|-----------| +| RAG retrieval fails | Fallback to keyword-based selection | +| LLM rewriting fails | Fallback to regex rewriting | +| FAISS index missing | Auto-generate on first use | +| OpenAI API errors | Graceful error handling | +| Performance degradation | Cache results, optimize queries | + +## ✅ Success Criteria + +- [ ] generate_hybrid_resume.py supports RAG and LLM rewriting +- [ ] Web API exposes RAG endpoints +- [ ] Web UI displays RAG options and results +- [ ] All 421+ existing tests pass +- [ ] 20+ new integration tests added +- [ ] Documentation updated +- [ ] Demo shows integration benefits +- [ ] Performance < 5 seconds for full pipeline +- [ ] Error handling and fallbacks working +- [ ] Backward compatible + +## 📁 Files to Modify + +1. `src/generate_hybrid_resume.py` - Add RAG support +2. `src/api/app.py` - Add RAG endpoints +3. `src/web/dashboard.js` - Add RAG UI +4. `src/web/index.html` - Add RAG form fields +5. `README.md` - Update with examples +6. `demo_rag_with_pelotech.py` - Show integration + +## 📁 Files to Create + +1. `tests/test_rag_hybrid_integration.py` - Integration tests +2. `docs/RAG_HYBRID_INTEGRATION.md` - Integration guide + +## ⏱️ Estimated Effort + +- Phase 1 (CLI): 2-3 hours +- Phase 2 (API): 3-4 hours +- Phase 3 (UI): 4-5 hours +- Phase 4 (Demo & Docs): 2-3 hours +- **Total: 11-15 hours** + +## 🔗 Related Issues + +- #53 - Phase 1 RAG Upgrade (parent) +- #54 - Phase 1 RAG Upgrade PR (implementation) +- #45 - LLM Training Strategy (parent) +- **#55 - RAG-Hybrid Integration (NEW)** ← GitHub Issue Created + +## 📝 GitHub Issue Created + +**Issue #55**: "feat(#53): Integrate Phase 1 RAG Upgrade into Hybrid Resume Generator" + +### Issue Details +- Comprehensive overview of integration requirements +- 4 phases with specific deliverables +- Technical implementation details +- Risk mitigation strategies +- Success criteria and acceptance tests +- Related issues and dependencies + +### Next Steps +1. Review GitHub Issue #55 +2. Break down into sub-tasks +3. Assign to development team +4. Start with Phase 1 (CLI enhancements) +5. Follow with Phase 2-4 in sequence + +## 📚 Documentation + +### Research Document +- `RAG_HYBRID_INTEGRATION_RESEARCH.md` - Detailed research and analysis + +### Demo Script +- `demo_rag_with_pelotech.py` - Already created, showcases all Phase 1 features + +### GitHub Issue +- Issue #55 - Comprehensive integration plan with all details + +## 🎓 Key Learnings + +1. **tailor.py already supports RAG** - Integration is straightforward +2. **Hybrid pipeline is flexible** - Works with both RAG and non-RAG data +3. **Phase 1 is production-ready** - All components tested and working +4. **Focus on exposure** - Main work is exposing RAG through CLI and Web UI +5. **Backward compatibility** - All changes should be optional/additive + +## 🚀 Recommendation + +**Start with Phase 1 (CLI Enhancement)** as it's the quickest win: +- Add RAG support to generate_hybrid_resume.py +- Enables command-line users to leverage RAG immediately +- Foundation for Web API and UI enhancements +- Can be completed in 2-3 hours + +Then proceed with Phase 2-4 in sequence for full integration. + +## 📞 Questions? + +Refer to: +1. GitHub Issue #55 for comprehensive details +2. RAG_HYBRID_INTEGRATION_RESEARCH.md for technical analysis +3. demo_rag_with_pelotech.py for working examples +4. Phase 1 RAG Upgrade (Issue #53) for implementation details + diff --git a/demo_rag_with_pelotech.py b/demo_rag_with_pelotech.py index e647e9b..5071e1d 100644 --- a/demo_rag_with_pelotech.py +++ b/demo_rag_with_pelotech.py @@ -231,6 +231,41 @@ def demo_comparison(): print(" • Improved relevance ranking") +def demo_hybrid_resume_generation(): + """Demonstrate hybrid resume generation with RAG integration.""" + print_section("STEP 6: Hybrid Resume Generation with RAG Integration") + + print("🎨 Demonstrating RAG-enhanced hybrid resume generation...") + print("\n This showcases the integration of Phase 1 RAG Upgrade into the hybrid") + print(" resume generator pipeline for production-ready tailored resumes.") + + # Check if master resume exists + master_resume_path = Path("data/master_resume.json") + if not master_resume_path.exists(): + print(f"\n⚠️ Master resume not found: {master_resume_path}") + print(" Skipping hybrid resume generation demo") + return + + # Check if job description exists + jd_path = Path("data/job_listings/pelotech-senior-software-engineer.md") + if not jd_path.exists(): + print(f"\n⚠️ Job description not found: {jd_path}") + print(" Skipping hybrid resume generation demo") + return + + print("\n📋 Available generation options:") + print(" 1. Basic HTML generation (no RAG):") + print(" python src/generate_hybrid_resume.py --output out/resume.html --theme creative") + print("\n 2. RAG-enhanced HTML generation:") + print(" python src/generate_hybrid_resume.py --output out/resume.html --jd data/job_listings/pelotech-senior-software-engineer.md --use-rag") + print("\n 3. RAG + LLM rewriting:") + print(" python src/generate_hybrid_resume.py --output out/resume.html --jd data/job_listings/pelotech-senior-software-engineer.md --use-rag --use-llm-rewriting") + print("\n 4. All themes with RAG:") + print(" python src/generate_hybrid_resume.py --all-themes --jd data/job_listings/pelotech-senior-software-engineer.md --use-rag") + + print("\n✅ Hybrid resume generation is now RAG-enabled!") + + def main(): """Run all demos.""" try: @@ -249,17 +284,23 @@ def main(): # Step 5: Comparison demo_comparison() + # Step 6: Hybrid Resume Generation + demo_hybrid_resume_generation() + print_section("DEMO COMPLETE") - print("✅ Phase 1 RAG Upgrade demonstration finished successfully!") + print("✅ Phase 1 RAG Upgrade + Hybrid Integration demonstration finished successfully!") print("\n � Next steps:") print(" 1. Tailor resume with RAG:") print(" python src/tailor.py --jd data/job_listings/pelotech-senior-software-engineer.md --use-rag --out output.json") print("\n 2. Tailor with LLM rewriting:") print(" python src/tailor.py --jd data/job_listings/pelotech-senior-software-engineer.md --use-rag --use-llm-rewriting --out output.json") - print("\n 3. Check generated files:") + print("\n 3. Generate HTML resume with RAG:") + print(" python src/generate_hybrid_resume.py --output out/resume.html --jd data/job_listings/pelotech-senior-software-engineer.md --use-rag") + print("\n 4. Check generated files:") print(" • data/rag/vector_store.json - Indexed documents") print(" • data/rag/faiss_index.bin - FAISS index") print(" • data/rag/experience_chunks.json - Chunked documents") + print(" • out/resume.html - Generated HTML resume") except Exception as e: print(f"\n❌ Error: {e}") diff --git a/src/api/app.py b/src/api/app.py index 53b9e39..a0860ca 100644 --- a/src/api/app.py +++ b/src/api/app.py @@ -864,7 +864,7 @@ def duplicate_resume(resume_id: str): @app.route("/api/resumes//tailor", methods=["POST"]) def tailor_resume(resume_id: str): """ - Tailor a resume to a job listing. + Tailor a resume to a job listing with optional RAG and LLM rewriting. Args: resume_id: Resume ID to tailor @@ -872,11 +872,14 @@ def tailor_resume(resume_id: str): Request body: { "job_listing_id": "Job listing ID", - "new_resume_name": "Name for tailored resume" + "new_resume_name": "Name for tailored resume", + "use_rag": false, + "use_llm_rewriting": false, + "vector_store": "data/rag/vector_store.json" } Returns: - JSON response with tailored resume metadata + JSON response with tailored resume metadata and RAG context if enabled """ try: body = request.get_json(force=True) @@ -886,6 +889,9 @@ def tailor_resume(resume_id: str): job_listing_id = body.get("job_listing_id") new_resume_name = body.get("new_resume_name") + use_rag = body.get("use_rag", False) + use_llm_rewriting = body.get("use_llm_rewriting", False) + vector_store_path = body.get("vector_store", "data/rag/vector_store.json") if not job_listing_id: return jsonify({"error": "Job listing ID is required"}), 400 @@ -908,13 +914,35 @@ def tailor_resume(resume_id: str): if not keywords: keywords = job_listing_model.extract_keywords(job_listing_id) + # Retrieve RAG context if requested + rag_context = None + if use_rag: + from tailor import retrieve_rag_context + + vector_store = BASE_DIR / vector_store_path + if vector_store.exists(): + rag_context = retrieve_rag_context(keywords, str(vector_store)) + if rag_context.get("success"): + print( + f"✅ Retrieved RAG context for {len(rag_context.get('context', {}))} keywords" + ) + else: + print(f"⚠️ RAG retrieval failed: {rag_context.get('error')}") + rag_context = None + else: + print(f"⚠️ Vector store not found at {vector_store_path}") + rag_context = None + # Tailor the resume using existing logic from tailor import select_and_rewrite tailored_data = source_data.copy() if "experience" in tailored_data: tailored_data["experience"] = select_and_rewrite( - tailored_data["experience"], keywords + tailored_data["experience"], + keywords, + rag_context=rag_context, + use_llm_rewriting=use_llm_rewriting, ) # Create new tailored resume @@ -925,17 +953,26 @@ def tailor_resume(resume_id: str): description=f"Tailored for {job_listing.get('title', 'Unknown')} at {job_listing.get('company', 'Unknown')}", ) - return ( - jsonify( - { - "success": True, - "message": "Resume tailored successfully", - "resume": metadata.to_dict(), - "keywords_used": keywords[:10], # Return first 10 keywords - } - ), - 201, - ) + response_data = { + "success": True, + "message": "Resume tailored successfully", + "resume": metadata.to_dict(), + "keywords_used": keywords[:10], # Return first 10 keywords + "rag_enabled": use_rag, + "llm_rewriting_enabled": use_llm_rewriting, + } + + # Include RAG context in response if available + if rag_context and rag_context.get("success"): + response_data["rag_context"] = { + "keywords_searched": rag_context.get("keywords_searched", []), + "total_documents_retrieved": sum( + len(v.get("documents", [])) + for v in rag_context.get("context", {}).values() + ), + } + + return jsonify(response_data), 201 except Exception as e: import traceback @@ -1357,6 +1394,220 @@ def validate_command(): return jsonify({"error": f"Failed to validate command: {str(e)}"}), 500 +@app.route("/api/rag/retrieve", methods=["POST"]) +def rag_retrieve(): + """ + Retrieve relevant experiences for keywords using RAG. + + Request Body: + { + "keywords": ["keyword1", "keyword2", ...], + "top_k": 5, + "vector_store": "data/rag/vector_store.json" + } + + Returns: + JSON response with retrieved documents and scores + """ + try: + data = request.get_json() + + if not data or "keywords" not in data: + return jsonify({"error": "Missing 'keywords' in request body"}), 400 + + keywords = data.get("keywords", []) + top_k = data.get("top_k", 5) + vector_store_path = data.get("vector_store", "data/rag/vector_store.json") + + if not keywords: + return jsonify({"error": "Keywords list cannot be empty"}), 400 + + # Import RAG components + from tailor import retrieve_rag_context + + # Resolve vector store path + vector_store = BASE_DIR / vector_store_path + if not vector_store.exists(): + return ( + jsonify( + { + "error": f"Vector store not found at {vector_store_path}", + "hint": "Run: python -m src.rag.rag_indexer to create vector store", + } + ), + 404, + ) + + # Retrieve RAG context + rag_context = retrieve_rag_context(keywords, str(vector_store), top_k=top_k) + + if not rag_context.get("success"): + return ( + jsonify( + { + "error": "RAG retrieval failed", + "details": rag_context.get("error"), + } + ), + 500, + ) + + return jsonify( + { + "success": True, + "keywords": keywords, + "context": rag_context.get("context", {}), + "keywords_searched": rag_context.get("keywords_searched", []), + } + ) + + except Exception as e: + import traceback + + return ( + jsonify( + { + "error": f"Failed to retrieve RAG context: {str(e)}", + "traceback": traceback.format_exc(), + } + ), + 500, + ) + + +@app.route("/api/rag/rewrite", methods=["POST"]) +def rag_rewrite(): + """ + Rewrite resume bullets using LLM with evidence constraints. + + Request Body: + { + "bullets": ["bullet1", "bullet2", ...], + "evidence": "Evidence text", + "requirement": "Job requirement" + } + + Returns: + JSON response with rewritten bullets + """ + try: + data = request.get_json() + + if not data or "bullets" not in data: + return jsonify({"error": "Missing 'bullets' in request body"}), 400 + + bullets = data.get("bullets", []) + evidence = data.get("evidence", "") + requirement = data.get("requirement", "job requirement") + + if not bullets: + return jsonify({"error": "Bullets list cannot be empty"}), 400 + + # Import LLM rewriter + from rag.llm_rewriter import LLMRewriter + + try: + llm_rewriter = LLMRewriter() + except Exception as e: + return ( + jsonify( + { + "error": "LLM rewriter initialization failed", + "details": str(e), + "hint": "Ensure OPENAI_API_KEY is set", + } + ), + 500, + ) + + # Rewrite bullets + rewritten = [] + for bullet in bullets: + if evidence: + rewritten_bullet = llm_rewriter.rewrite_with_evidence( + bullet, evidence, requirement + ) + else: + # Fall back to regex rewriting if no evidence + from rewriter import rewrite_star + + rewritten_bullet = rewrite_star(bullet) + + rewritten.append(rewritten_bullet) + + return jsonify( + { + "success": True, + "original_bullets": bullets, + "rewritten_bullets": rewritten, + "evidence_used": bool(evidence), + } + ) + + except Exception as e: + import traceback + + return ( + jsonify( + { + "error": f"Failed to rewrite bullets: {str(e)}", + "traceback": traceback.format_exc(), + } + ), + 500, + ) + + +@app.route("/api/rag/index", methods=["POST"]) +def rag_index(): + """ + Trigger re-indexing of the RAG vector store. + + Request Body: + { + "vector_store": "data/rag/vector_store.json" + } + + Returns: + JSON response with indexing status + """ + try: + data = request.get_json() or {} + vector_store_path = data.get("vector_store", "data/rag/vector_store.json") + + # Import RAG indexer + from rag.rag_indexer import RAGIndexer + + print("🧠 Starting RAG vector store re-indexing...") + + # Create indexer and build index + indexer = RAGIndexer(str(BASE_DIR / vector_store_path)) + indexer.build_index() + + print("✅ RAG vector store re-indexed successfully") + + return jsonify( + { + "success": True, + "message": "RAG vector store re-indexed successfully", + "vector_store": vector_store_path, + } + ) + + except Exception as e: + import traceback + + return ( + jsonify( + { + "error": f"Failed to re-index RAG vector store: {str(e)}", + "traceback": traceback.format_exc(), + } + ), + 500, + ) + + @app.route("/src/web/") def serve_web_files(filename): """Serve web UI files.""" diff --git a/src/generate_hybrid_resume.py b/src/generate_hybrid_resume.py index 2c0ef6e..cbf9a01 100644 --- a/src/generate_hybrid_resume.py +++ b/src/generate_hybrid_resume.py @@ -8,9 +8,11 @@ python src/generate_hybrid_resume.py --output resume.html --theme creative python src/generate_hybrid_resume.py --all-themes --output-dir ./output python src/generate_hybrid_resume.py --output resume.html --docx + python src/generate_hybrid_resume.py --output resume.html --jd data/sample_jd.txt --use-rag """ import argparse +import json import sys from pathlib import Path @@ -18,6 +20,8 @@ from hybrid_css_generator import HybridCSSGenerator from hybrid_html_assembler import HybridHTMLAssembler from hybrid_resume_processor import HybridResumeProcessor +from jd_parser import extract_keywords +from tailor import retrieve_rag_context, select_and_rewrite def generate_hybrid_resume( @@ -25,15 +29,25 @@ def generate_hybrid_resume( output_html_path: str, theme: str = "creative", export_docx: bool = False, + jd_path: str = None, + use_rag: bool = False, + use_llm_rewriting: bool = False, + show_rag_context: bool = False, + vector_store_path: str = "data/rag/vector_store.json", ) -> bool: """ - Generate hybrid HTML+SVG resume. + Generate hybrid HTML+SVG resume with optional RAG tailoring. Args: resume_json_path: Path to resume JSON file output_html_path: Path for output HTML file theme: Theme name (professional, modern, executive, creative) export_docx: Whether to also export to DOCX format + jd_path: Path to job description for RAG retrieval + use_rag: Whether to use RAG for tailoring + use_llm_rewriting: Whether to use LLM for rewriting + show_rag_context: Whether to display RAG context + vector_store_path: Path to RAG vector store Returns: True if successful, False otherwise @@ -41,31 +55,86 @@ def generate_hybrid_resume( try: print(f"\n{'='*80}") print(f"HYBRID RESUME GENERATION - {theme.upper()} THEME") + if use_rag: + print("(RAG-Enhanced Tailoring Enabled)") print(f"{'='*80}\n") - # Step 1: Process resume data and generate HTML structure + # Load resume data + with open(resume_json_path, 'r', encoding='utf-8') as f: + resume_data = json.load(f) + + # Step 1: Apply RAG tailoring if requested + if use_rag and jd_path: + print("🧠 Applying RAG-enhanced tailoring...") + try: + # Extract keywords from job description + from jd_fetcher import ingest_jd + jd_path_resolved, jd_text = ingest_jd(jd_path) + keywords = extract_keywords(jd_text) + print(f" Extracted {len(keywords)} keywords from job description") + + # Retrieve RAG context + if Path(vector_store_path).exists(): + rag_context = retrieve_rag_context(keywords, vector_store_path) + if rag_context.get("success"): + print(f" ✅ Retrieved RAG context for {len(rag_context.get('context', {}))} keywords") + if show_rag_context: + print(f"\n RAG Context Summary:") + for keyword, context in list(rag_context.get('context', {}).items())[:3]: + print(f" - {keyword}: {len(context.get('documents', []))} documents") + else: + print(f" ⚠️ RAG retrieval failed: {rag_context.get('error')}") + rag_context = None + else: + print(f" ⚠️ Vector store not found at {vector_store_path}") + rag_context = None + + # Tailor experience with RAG + if "experience" in resume_data: + resume_data["experience"] = select_and_rewrite( + resume_data["experience"], + keywords, + rag_context=rag_context, + use_llm_rewriting=use_llm_rewriting + ) + print(f" ✅ Tailored {len(resume_data['experience'])} experience entries\n") + + except Exception as e: + print(f" ⚠️ RAG tailoring failed: {e}") + print(f" Continuing with original resume data\n") + + # Step 2: Process resume data and generate HTML structure print("Processing resume data and generating HTML structure...") - processor = HybridResumeProcessor(resume_json_path, theme) + + # Save tailored data to temp file for processing + temp_json = output_html_path.replace(".html", "_temp.json") + with open(temp_json, 'w', encoding='utf-8') as f: + json.dump(resume_data, f, indent=2) + + processor = HybridResumeProcessor(temp_json, theme) html_content = processor.generate_html() print(f"HTML structure generated\n") - # Step 2: Generate CSS from theme configuration + # Step 3: Generate CSS from theme configuration print("Generating CSS from theme configuration...") css_generator = HybridCSSGenerator(theme) css = css_generator.generate_css() print(f"CSS generated\n") - # Step 3: Assemble complete HTML document + # Step 4: Assemble complete HTML document print("Assembling complete HTML document...") assembler = HybridHTMLAssembler(theme) - resume_name = processor.resume_data.get("name", "Resume") + resume_name = resume_data.get("name", "Resume") complete_html = assembler.assemble_html(html_content, css, resume_name) print(f"HTML document assembled\n") - # Step 4: Save to file + # Step 5: Save to file print(f"Saving to {output_html_path}...") success = assembler.save_html(complete_html, output_html_path) + # Clean up temp file + Path(temp_json).unlink(missing_ok=True) + if success: print(f"Resume saved successfully\n") @@ -85,7 +154,10 @@ def generate_hybrid_resume( if export_docx and docx_success: print(f"DOCX: {docx_path}") print(f"Theme: {theme}") - print(f"Name: {resume_name}\n") + print(f"Name: {resume_name}") + if use_rag: + print(f"RAG-Enhanced: Yes") + print() return True else: print(f"Failed to save resume\n") @@ -100,7 +172,14 @@ def generate_hybrid_resume( def generate_all_themes( - resume_json_path: str, output_dir: str, export_docx: bool = False + resume_json_path: str, + output_dir: str, + export_docx: bool = False, + jd_path: str = None, + use_rag: bool = False, + use_llm_rewriting: bool = False, + show_rag_context: bool = False, + vector_store_path: str = "data/rag/vector_store.json", ) -> dict: """ Generate resume in all available themes. @@ -109,6 +188,11 @@ def generate_all_themes( resume_json_path: Path to resume JSON file output_dir: Directory for output files export_docx: Whether to also export to DOCX format + jd_path: Path to job description for RAG retrieval + use_rag: Whether to use RAG for tailoring + use_llm_rewriting: Whether to use LLM for rewriting + show_rag_context: Whether to display RAG context + vector_store_path: Path to RAG vector store Returns: Dictionary mapping theme names to success status @@ -124,13 +208,23 @@ def generate_all_themes( print(f"\n{'='*80}") print("GENERATING ALL THEMES") + if use_rag: + print("(RAG-Enhanced Tailoring Enabled)") print(f"{'='*80}\n") for theme in themes: output_file = output_path / f"resume_{theme}.html" print(f"Generating {theme} theme...") success = generate_hybrid_resume( - resume_json_path, str(output_file), theme, export_docx + resume_json_path, + str(output_file), + theme, + export_docx, + jd_path=jd_path, + use_rag=use_rag, + use_llm_rewriting=use_llm_rewriting, + show_rag_context=show_rag_context, + vector_store_path=vector_store_path, ) results[theme] = success @@ -156,7 +250,7 @@ def generate_all_themes( def main(): """Main entry point for command-line usage.""" parser = argparse.ArgumentParser( - description="Generate professional resume using hybrid HTML+SVG approach", + description="Generate professional resume using hybrid HTML+SVG approach with optional RAG tailoring", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: @@ -168,6 +262,15 @@ def main(): # Generate with DOCX export python src/generate_hybrid_resume.py --output out/resume.html --docx + + # Generate with RAG-enhanced tailoring + python src/generate_hybrid_resume.py --output out/resume.html --jd data/sample_jd.txt --use-rag + + # Generate with RAG and LLM rewriting + python src/generate_hybrid_resume.py --output out/resume.html --jd data/sample_jd.txt --use-rag --use-llm-rewriting + + # Generate all themes with RAG + python src/generate_hybrid_resume.py --all-themes --jd data/sample_jd.txt --use-rag --show-rag-context """, ) @@ -196,6 +299,29 @@ def main(): parser.add_argument( "--docx", action="store_true", help="Also generate DOCX version" ) + parser.add_argument( + "--jd", help="Path to job description for RAG-enhanced tailoring" + ) + parser.add_argument( + "--use-rag", + action="store_true", + help="Use RAG to retrieve relevant experiences from vector store", + ) + parser.add_argument( + "--use-llm-rewriting", + action="store_true", + help="Use LLM for evidence-constrained bullet rewriting (requires --use-rag)", + ) + parser.add_argument( + "--show-rag-context", + action="store_true", + help="Display RAG context during generation", + ) + parser.add_argument( + "--vector-store", + default="data/rag/vector_store.json", + help="Path to RAG vector store (default: data/rag/vector_store.json)", + ) args = parser.parse_args() @@ -213,12 +339,30 @@ def main(): print(f" (Searched relative to: {script_dir})") sys.exit(1) + # Validate RAG arguments + if args.use_llm_rewriting and not args.use_rag: + print("⚠️ Warning: --use-llm-rewriting requires --use-rag. Enabling RAG.") + args.use_rag = True + + if args.use_rag and not args.jd: + print("⚠️ Warning: --use-rag requires --jd. RAG will be skipped.") + args.use_rag = False + # Determine export formats export_docx = args.docx # Generate resume(s) if args.all_themes: - results = generate_all_themes(str(input_path), args.output_dir, export_docx) + results = generate_all_themes( + str(input_path), + args.output_dir, + export_docx, + jd_path=args.jd, + use_rag=args.use_rag, + use_llm_rewriting=args.use_llm_rewriting, + show_rag_context=args.show_rag_context, + vector_store_path=args.vector_store, + ) success_count = sum(1 for s in results.values() if s) if success_count == len(results): @@ -232,7 +376,15 @@ def main(): sys.exit(1) else: success = generate_hybrid_resume( - str(input_path), args.output, args.theme, export_docx + str(input_path), + args.output, + args.theme, + export_docx, + jd_path=args.jd, + use_rag=args.use_rag, + use_llm_rewriting=args.use_llm_rewriting, + show_rag_context=args.show_rag_context, + vector_store_path=args.vector_store, ) sys.exit(0 if success else 1) diff --git a/src/web/dashboard.html b/src/web/dashboard.html index 8c69649..3f0c949 100644 --- a/src/web/dashboard.html +++ b/src/web/dashboard.html @@ -218,7 +218,7 @@