from src.Agents.models.Resume_model import ResumeState,ResumeSchema import logging from src.Agents.llm.llm_loader import llm from src.Agents.prompts import resumeGeneration_prompts as ResumeGenerationPrompt from utils.asyncHandler import asyncHandler from langsmith import traceable @asyncHandler @traceable(name="resume_builder",tags=['summary_generator']) async def resume_maker(state: ResumeState): logging.info("Entering resume_maker node") logging.info(f"userDetails for LLM: {state.userDetails}") prompt_template = ResumeGenerationPrompt # Connect prompt to structured output with include_raw=True llm_with_schema = prompt_template | llm.with_structured_output(ResumeSchema, include_raw=True) # Invoke LLM logging.info("Invoking LLM for resume generation") full_result = await llm_with_schema.ainvoke({"userDetails": state.userDetails}) result = full_result.get("parsed") raw_output = full_result.get("raw") logging.info(f"LLM parsed result: {result}") # Fallback parsing logic if the LLM returned text instead of a formal tool call if result is None and raw_output and hasattr(raw_output, 'content'): logging.info("Attempting fallback parsing for LLM content") content = raw_output.content if isinstance(content, list): content = " ".join([c.get('text', '') if isinstance(c, dict) else str(c) for c in content]) try: import json import re # Try to extract JSON from the text # It might be a direct JSON or inside a "parameters" block of a pseudo-tool-call json_match = re.search(r'\{.*\}', content, re.DOTALL) if json_match: json_str = json_match.group(0) data = json.loads(json_str) # If it looks like a tool call wrapper (e.g., {"type": "function", "parameters": {...}}) if "parameters" in data: data = data["parameters"] # Clean up "null" strings that should be actual None def clean_nulls(d): if isinstance(d, dict): return {k: clean_nulls(v) for k, v in d.items()} elif isinstance(d, list): return [clean_nulls(v) for v in d] elif d == "null": return None return d data = clean_nulls(data) result = ResumeSchema(**data) logging.info("Successfully extracted ResumeSchema via fallback parser") except Exception as parse_err: logging.error(f"Fallback parsing failed: {str(parse_err)}") if result is None: logging.error(f"LLM raw output (could not be parsed): {raw_output}") # Update state - return a dict of updates logging.info("Exiting resume_maker node") return {"ai_generated_schema": result}