File size: 3,046 Bytes
c01955c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74


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}