본문 바로가기
KT aivleschool

2차 미니프로젝트 : AI 면접관 Agent 시스템 구축

by 수박주스으 2025. 5. 19.

🚩 주제 : AI 면접관 Agent 시스템 구축

- 중점 사항

1) LLM 기반 Text 인식, 요약 및 생성 활용

2) LangGraph를 활용한 AI Agent 구축

 

개요

1. 데이터 : 이력서/자기소개서 문서(.pdf, .docx)

2. LLM : OpenAI / GPT-4o mini (비용효율성 + 성능)

3. LangGraph : LangChain 기반 워크플로우 설계

 

 

✅ 진행 과정

[사전 준비 단계]

0. 파일을 입력받는 함수, State 선언

# pdf 또는 docx 파일 입력받기
def extract_text_from_file(file_path: str) -> str:
    ext = os.path.splitext(file_path)[1].lower()

    if ext == ".pdf":
        doc = fitz.open(file_path)
        text = "\n".join(page.get_text() for page in doc)
        doc.close()
        return text

    elif ext == ".docx":
        doc = Document(file_path)
        return "\n".join(p.text for p in doc.paragraphs if p.text.strip())

    else:
        raise ValueError("지원하지 않는 파일 형식입니다. PDF 또는 DOCX만 허용됩니다.")
from typing import TypedDict, List, Dict

class InterviewState(TypedDict):
    # 고정 정보
    resume_text: str
    resume_summary: str
    resume_keywords: List[str]
    question_strategy: Dict[str, Dict]
    # 추가
    keyword_weights: Dict[str, str]
    triggers: List[str]

    # 인터뷰 로그
    current_question: str
    current_answer: str
    current_strategy: str
    conversation: List[Dict[str, str]]
    evaluation : List[Dict[str, str]]
    next_step : str
    # 추가
    reflect_count: int
    reflection_status: str
    total_feedback: str

 

 

1. Resume 분석

  • [고도화] 키워드별 중요도와 이력서 트리거 포인트 기능 추가
    • 처음 도입 질문 생성 시, 추가한 속성을 반영하여 생성되도록
class ResumeInfo(BaseModel):
    summary: str
    keywords: List[str]
    keyword_weights: Dict[str, str] # {"답러닝" : "높음", "영상처리": "보통"}
    triggers: List[str]             # {"단점", "2개월 단기 근무"}
def analyze_resume(state: InterviewState) -> InterviewState:
    # 여기에 코드를 완성합니다.
    parser = PydanticOutputParser(pydantic_object=ResumeInfo)

    prompt = ChatPromptTemplate.from_messages([
        ("system", """너는 유능한 인재를 선발하는 기업의 면접관이야.
        다음 주어지는 이력서 데이터로 지원자에게 질문을 해야해.

        다음과 같은 분석을 수행해줘:

        1. 핵심 키워드 추출 : 지원자의 이력서와 자기소개서에서 핵심 키워드 5가지를 쉼표로 구분해서 추출해줘.
        2. 키워드 중요도 분류 : 추출한 키워드를 '높음', '보통', '낮음' 세 단계로 중요도를 분류해줘.
        3. 이력서 트리거 포인트 탐지 : 면접 시에 추가 설명을 요구하거나 의문을 유발할 수 있는 특이사항(예: 경력 공백, 단점, 짧은 근무, 중단된 활동 등)을 'trigger point'로 뽑아줘.
        4. 전체 이력서 요약 : 위 내용을 바탕으로 지원자의 이력서와 자기소개서를 요약해줘.

        응답 예시는 아래와 같아.

        {{
            "summary": "지원자는 전기전자공학을 전공하고, AI 영상처리 프로젝트와 관련된 다양한 경험을 보유하고 있다...",
            "keywords": ["딥러닝", "OpenCV", "연합동아리", "인턴십", "캡스톤디자인"],
            "keyword_weights": {{
                "딥러닝": "높음",
                "OpenCV": "높음",
                "연합동아리": "보통",
                "인턴십": "보통",
                "캡스톤디자인": "낮음"
            }},
            "triggers": ["2개월 단기 인턴십", "중단된 팀 프로젝트"]
        }}

        """),
        ("human", state["resume_text"]),
        ("system", "{format_instructions}")
    ])

    messages = prompt.format_messages(format_instructions=parser.get_format_instructions())

    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.5) # temperature 조정
    response = llm.invoke(messages)
    resume_info = parser.parse(response.content)

    resume_summary = resume_info.summary
    resume_keywords = resume_info.keywords
    keyword_weights = resume_info.keyword_weights # 추가
    triggers = resume_info.triggers # 추가


    # return 코드는 제공합니다.
    return {
        **state,
        "resume_summary": resume_summary,
        "resume_keywords": resume_keywords,
        "keyword_weights": resume_info.keyword_weights, # 추가
        "triggers": resume_info.triggers # 추가
    }

 

 

2. 질문 전략 수립

  • 질문 분야, 질문 방향, 예시질문 생성
    • 질문 분야 : 경력 및 경험, 동기 및 커뮤니케이션, 논리적 사고
  • 중괄호 오류 해결함수 ~ 괄호 문제 처리 코드를 넣은 이유는,
    {state['keyword_weights']}가 {"답러닝" : "높음", "영상처리": "보통"} 이런형식으로 저장되어 있는데
    "human"에서 이력서 중요도를 전달할 때, str.format() 과정에서 {~~~}를 변수로 인식하려고 시도하면서 keyerror가 난 것으로 파악된다.
  • ChatPromptTemplate.from_messages() 내부에서는 str.format()이 사용되는데, 그 과정에서 입력 문자열에 중괄호가 포함되어 있어 키 오류가 발생한 것. 예를 들어, 만약 이력서 요약의 값이 "AI 연구소 인턴 경험 {딥러닝 모델 개발}"이면 {딥러닝 모델 개발}을 변수로 인식하려고 해서 KeyError가 발생하는 것.
class Strategy(BaseModel):
    questions: Dict[str, Dict]
def generate_question_strategy(state: InterviewState) -> InterviewState:
    # 여기에 코드를 완성합니다.
    parser = PydanticOutputParser(pydantic_object=Strategy)

    # 중괄호 오류 해결 함수
    def escape_braces(s: str) -> str:
        return s.replace("{", "{{").replace("}", "}}")

    # 키워드 중요도를 사람이 읽기 좋은 문자열로 변환
    keyword_weights_str = ", ".join([f"{k}: {v}" for k, v in state['keyword_weights'].items()])

    # 괄호 문제 처리
    keyword_weights = escape_braces(keyword_weights_str)

    prompt = ChatPromptTemplate.from_messages([
        ("system", """너는 유능한 인재를 선발하는 기업의 면접관이야.
        다음 주어지는 이력서&자기소개서 요약과 주요 키워드, 키워드 중요도를 바탕으로 면접자에게 질문을 해야 해.

        이러한 점을 주의하여 다음 3가지 분야에 대해 질문 방향과 예시 질문을 만들어줘.

        3가지 분야는 다음과 같아.
        1. 경력 및 경험
        2. 동기 및 커뮤니케이션
        3. 논리적 사고.

        응답에 대한 예시는 다음과 같아.
        {{
            "경력 및 경험": {{
                "질문 방향": "지원자의 전기정보 공학 배경과 관련된 실무 경험을 파악하고, 그에 따른 직무 적합성을 평가 할 수 있는 질문들.",
                "예시 질문": [
                    "전기정보공학을 전공하며 습득한 지식이 머신러닝 및 데이터 마이닝 프로젝트에 어떻게 활용되었는지 구체적으로 설명해 주실 수 있나요?",
                    "빅데이터 학생연합에서 기술부장으로 활동하면서 겪은 도전과 이를 통해 배운 점은 무엇인가요?"
                ]
            }}
        }}

        위 예시를 보고 포맷에 맞춰 응답해줘.
        """),
        ("human", f"""이력서 요약: {state['resume_summary']},
                      이력서 키워드: {state['resume_keywords']},
                      이력서 중요도 : {keyword_weights}"""), # 수정
        ("system", "{format_instructions}")
    ])

    messages = prompt.format_messages(format_instructions=parser.get_format_instructions())

    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.5)
    response = llm.invoke(messages)
    strategy = parser.parse(response.content)

    strategy_dict = strategy.questions


    # return 코드는 제공합니다.
    return {
        **state,
        "question_strategy": strategy_dict
    }

 

 

3. 위의 기능들 하나로 묶기

  • 첫 질문 생성 시, 질문 분야와 해당 분야의 예시질문 모두 랜덤으로 선택되게
def select_random_question_with_category(question_strategy:Dict[str, Dict]):
    all_example_questions=[]

    for category, strategy in question_strategy.items():
        questions = strategy.get("예시 질문", [])
        for question in questions:
            all_example_questions.append((category, question))  # (카테고리, 질문)

    return random.choice(all_example_questions) if all_example_questions else (None, None)
def preProcessing_Interview(file_path: str) -> InterviewState:

    resume_text = extract_text_from_file(file_path)

    state: InterviewState = {
        "resume_text": resume_text,
        "resume_summary": '',
        "resume_keywords": [],
        "question_strategy": {},
        # 추가
        "keyword_weights": {},
        "triggers": [],

        "current_question": '',
        "current_answer": '',
        "current_strategy": '',
        "conversation": [],
        "evaluation": [],
        "next_step" : '',
        # 추가
        "reflect_count": 0,
        "reflection_status": '',
        "total_feedback": '',
    }

    state = analyze_resume(state)
    state = generate_question_strategy(state)

    current_strategy, selected_question = select_random_question_with_category(state['question_strategy'])

    # return 코드는 제공합니다.
    return {
            **state,
            "current_question": selected_question,
            "current_strategy": current_strategy,
            }

 

 

✅ 면접 Agent 단계

0. 답변 입력 함수

def update_current_answer(state: InterviewState, user_answer: str) -> InterviewState:
    return {
        **state,
        "current_answer": user_answer.strip()
    }

 

 

1. 답변 평가 : 면접자의 답변이 적절한지 평가

  • [고도화] '답변의 구체성' 평가 기준 추가
  • 분기 판단을 위한 reflect_count 연산을 evaluate_answer(노드함수)에 적기 : state 변경은 일반 노드함수에서만 가능
    • 분기함수에서 처리하면 state 변경이 적용되지 않아 무한루프에 빠질 수 있음 
    • 분기함수는 입력받은 상태를 변형하지 않고 반환값만 사용하는 것이 중요
class Evaluation(BaseModel):
    content: Dict[str, str]
def evaluate_answer(state: InterviewState) -> InterviewState:
    # 여기에 코드를 완성합니다.
    parser = PydanticOutputParser(pydantic_object=Evaluation)

    prompt = ChatPromptTemplate.from_messages([
        ("system",
        """너는 기업의 면접관이야.
        다음 주어지는 질문과 질문에대한 관련 분야와 그에 대한 답변에 평가를 해줘.

        평가의 기준은 다음과 같아.
        1. 질문과의 관련성
            상(우수): 질문의 핵심 의도에 정확히 부합하며, 전반적인 내용을 명확히 다룸.
            중(보통): 질문과 관련은 있지만 핵심 포인트가 부분적으로 누락됨.
            하(미흡): 질문과 관련이 약하거나 엉뚱한 내용중심.
        2. 답변의 구체성
            상(우수): 질문에 대한 답변이 대부분 구체적으로 다루어짐.
            중(보통): 질문에 대한 답변이 일부 구체적이나 핵심적인 구체성이 떨어짐.
            하(미흡): 질문에 대한 답변이 대부분 구체적이지 못함.

        응답에 대한 예시는 다음과 같아.
        {{
            "분야": "경력 및 경험",
            "질문": "전기정보공학을 전공하며 습득한 지식이 머신러닝 및 데이터 마이닝 프로젝트에 어떻게 활용되었는지 구체적으로 설명해 주실 수 있나요?",
            "답변": "잘 모르겠습니다",
            "질문과의 관련성": "하(미흡)",
            "답변의 구체성": "하(미흡)",
            "평가종합": "하(미흡)",
            "평가에 대한 이유": "지원자의 답변은 질문의 핵심 의도인 전공 지식의 실무 적용 사례와 전혀 연결되지 않았으며, 내용 또한 단편적이고 구체적인 예시나 설명이 부족해 질문에 대한 명확한 이해와 준비가 부족한 것으로 판단됨."
        }}

        위 예시를 보고 포맷에 맞춰 응답해줘. 특히 평가에 대한 이유 부분은 질문에 대한 답변이 어느 부분에서 어떻게 부족했는지 300자 내외로 상세히 작성해줘.
        """),
        ("human", f"분야: {state['current_strategy']}, 질문: {state['current_question']}, 답변: {state['current_answer']}"),
        ("system", "{format_instructions}")
    ])

    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.5)
    messages = prompt.format_messages(format_instructions=parser.get_format_instructions())
    response = llm.invoke(messages)
    evaluation = parser.parse(response.content).content

    if len(state["conversation"]) > 0:
        prev_evaluation = state["conversation"][-1]

        if evaluation["질문"] == prev_evaluation["질문"] and evaluation["답변"] == prev_evaluation["답변"]:
            state["conversation"].pop()
            state['reflect_count'] += 1
        else:
            state['reflect_count'] = 0

    state["conversation"].append(evaluation)

    # return 코드는 제공합니다.

    return {
        **state,
        "evaluation": [evaluation]
    }
def reflect_evaluation(state: InterviewState) -> Literal["decide_next_step", "evaluate_answer"]:
    if state['reflect_count'] > 2:
        state['reflect_count'] = 0
        return "decide_next_step"

    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

    latest_eval = state["evaluation"][0]
    reflection_prompt = ChatPromptTemplate.from_messages([
        ("system", """너는 기업 면접의 평가 보조 시스템이야.
        다음은 면접 질문, 지원자의 답변, 그리고 그것에 대한 평가와 평가에 대한 이유야.

        이 평가가 적절한지 판단해줘. 만약 평가가 적절하지 않다면 '재평가 필요'를 반환하고, 적절하면 '정상'을 반환해.

        판단 기준:
        - 평가가 질문과 답변의 내용을 제대로 반영했는가?
        - 평가가 너무 과하거나 부족하지 않은가?

        출력: "정상" 또는 "재평가 필요"
        """),
        ("human", f"질문: {state['current_question']}\n답변: {state['current_answer']}\n평가: {latest_eval['평가종합']}\n평가이유: {latest_eval['평가에 대한 이유']}"),
    ])
    messages = reflection_prompt.format_messages()
    response = llm.invoke(messages)
    reflection_status = response.content.strip()
    state['reflection_status'] = reflection_status

    return "decide_next_step" if reflection_status == "정상" else "evaluate_answer"

 

 

2. 인터뷰 진행 검토 : 인터뷰가 길어지지 않도록 진행 제한

  • 전략 3개(경력 및 경험, 동기 및 커뮤니케이션, 논리적 사고)를 모두 커버했는지 확인
  • 5개 이상의 질문, 답변 진행 시 종료
def decide_next_step(state: InterviewState) -> InterviewState:
    next_step = state["next_step"]

    # (1) 전략 3개를 모두 커버했는지 확인
    strategy_covered = set(turn.get("분야") for turn in state["conversation"])
    all_strategies = {"경력 및 경험", "동기 및 커뮤니케이션", "논리적 사고"}

    # 5개 이상 질문,답변 진행 시 종료
    if len(state["conversation"]) >= 5:
        next_step = "end"
    else:
        last_eval = state["conversation"][-1] if state["conversation"] else {}

        # 평가 값 상, 중, 하
        relevance = last_eval.get("질문과의 관련성", "")
        specificity = last_eval.get("답변의 구체성", "")

        # 평가가 모두 "중" 이상이면 다음 전략으로 전환
        if relevance in ["중(보통)", "상(우수)"] and specificity in ["중(보통)", "상(우수)"]:
            next_step = "next_strategy"
        else:
            next_step = "additional_question"

    return {
        **state,
        "next_step": next_step
    }

 

 

3. 질문 생성

  • 기존 전략 예시 질문과 최근 답변을 기반으로 심화질문 생
  • [고도화] Chroma Vector DB를 생성하여 LLM프롬프트에 질문 생성 참고 정보로 포함
    • 예시 질문들이 저장되어 있는 Vector DB
    • 질문 생성 시 참고만. 그대로 사용X
def generate_question(state: InterviewState) -> InterviewState:
    next_step = state['next_step']
    current_strategy = state['current_strategy']

    # 여기에 코드를 완성합니다.
    question_strategy = '\n'.join([
        f"""
        {x}:
            - 질문 방향: {state['question_strategy'][x]['질문 방향']}
            - 예시 질문: {", ".join(state['question_strategy'][x]['예시 질문'])}
        """
        for x in ['경력 및 경험', '동기 및 커뮤니케이션', '논리적 사고']
        ])

    conversation = '\n\n'.join([
        f"""분야: {x['분야']},
        질문: {x['질문']}
        답변: {x['답변']}
        질문과의 관련성: {x['질문과의 관련성']}
        답변의 구체성: {x['답변의 구체성']},
        평가종합: {x['평가종합']},
        평가에 대한 이유: {x['평가에 대한 이유']}
        """
        for x in state["conversation"]
        ])

    # vector db 기반 유사 질문 검색
    # strategy_focus = state["current_strategy"]
    if next_step == "next_strategy":
        all_strategies = {"경력 및 경험", "동기 및 커뮤니케이션", "논리적 사고"}
        used_strategies = set(turn.get("분야") for turn in state["conversation"])

        remaining_strategies = list(all_strategies - used_strategies)

        if remaining_strategies:
            strategy_focus = remaining_strategies[0]
        else:
            strategy_focus = "경력 및 경험"

    else:
        strategy_focus = state["current_strategy"]


    keywords = state["resume_keywords"]

    query = f"{strategy_focus}, {keywords}"

    retrieved = vectordb.similarity_search(query, k=2)
    reference_questions = '\n'.join([f"- {doc.page_content.strip()}" for doc in retrieved])


    # prompt 설정
    if next_step == "next_strategy":
        prompt_text = f"""다음 주어지는 면접자의 이력서 요약, 키워드, 질문전략, 이전 질문과 답변, 평가를 기반으로
        지원자의 '{strategy_focus}' 분야에 대한 새로운 질문을 생성해줘. 이때 '{state['triggers']}' 중 해당 분야와 관련이 있는 키워드가 있다면 해당 키워드를 활용해 질문이 만들어줘.
        이전 질문과 중복되지 않으면서 지원자의 역량을 효과적으로 평가할 수 있는 질문이어야 해
        유사 질문 예시는 그대로 쓰지 말고, 참고만 해서 새로운 질문을 만들어줘.

        [출력 예시 포맷]
        AI 연구소에서 OCR 기반 문서 처리 시스템을 고도화하는 과정에서, 특정 기술이나 알고리즘을 선택한 이유와 그 선택이 프로젝트에 미친 영향을 설명해 주실 수 있나요?
        """
    else:
        prompt_text = f"""다음 주어지는 면접자의 이력서 요약, 키워드, 질문전략, 이전 질문과 답변, 평가를 기반으로
        지원자의 가장 최근 답변{state['current_answer']}에 대한 '{strategy_focus}' 분야의 새로운 질문을 생성해줘
        지원자가 언급한 내용에 대해 더 깊이 있는 설명이나 구체적인 사례를 요구하는 질문이어야 해
        지원자의 이전 질문에 대한 평가가 좋지 못하다면, 같은 주제를 다른 각도에서 접근하는 질문을 생성해줘

        [출력 예시 포맷]
        딥러닝 모델을 활용한 프로젝트에서 데이터 전처리 과정에서 발생한 문제를 어떻게 분석하고 해결하였는지 설명해 주실 수 있나요?
        """

    prompt = ChatPromptTemplate.from_messages([
        ("system", prompt_text),
        ("human", f"""
         이력서 요약: {state['resume_summary']},
         키워드: {state['resume_keywords']},
         질문전략: {question_strategy},
         이전 질문과 평가: {conversation},
         유사 질문 예시: {reference_questions}
         """),
    ])

    messages = prompt.format_messages()

    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    response = llm.invoke(messages)

    # return 코드는 제공합니다.
    if next_step == "next_strategy":
        return {
            **state,
            "current_question": response.content.strip(),
            "current_answer": "",
            "current_strategy": strategy_focus
        }
    else:
        return {
            **state,
            "current_question": response.content.strip(),
            "current_answer": ""
        }

 

 

4. 인터뷰 피드백 보고서

  • 각 질문답변에 대한 피드백
  • [고도화] 종합 피드백
class FeedbackInfo(BaseModel):
    feedback: str = Field(..., description="면접 질문과 답변에 대한 3~5줄의 평가 피드백")
def generate_feedback_paragraph(question: str, answer: str, eval_result: str, eval_reason: str) -> str:
    """LLM을 이용해 면접 피드백 문단 생성 (LangChain 스타일)"""

    # Pydantic 출력 파서
    parser = PydanticOutputParser(pydantic_object=FeedbackInfo)

    # 프롬프트 정의
    prompt = ChatPromptTemplate.from_messages([
        ("system", """너는 AI 면접관이야.
질문과 답변, 그리고 다른 면접관이 판단한 종합 평가 결과를 보고, 평가자의 입장에서 3~5줄로 된 구체적인 평가 피드백을 작성해줘.
답변의 논리성, 구체성, 경험에 기반한 설명, 개선할 부분 등을 모두 고려해줘."""),
        ("human", "질문: {question}\n답변: {answer}\n평가: {eval_result}\n평가이유: {eval_reason}"),
        ("system", "{format_instructions}")
    ])

    # 메시지 포맷팅
    messages = prompt.format_messages(
        question=question,
        answer=answer,
        eval_result=eval_result,
        eval_reason=eval_reason,
        format_instructions=parser.get_format_instructions()
    )

    # LLM 호출
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.5)
    response = llm.invoke(messages)

    # 결과 파싱
    result = parser.parse(response.content)
    return result.feedback
class TotalFeedbackInfo(BaseModel):
    feedback: str = Field(..., description="면접 질문과 답변, 피드백에 대한 3~5줄의 종합 평가 피드백")
def summarize_interview(state: InterviewState) -> InterviewState:
    # 여기에 코드를 완성합니다.
    print('\n[summary]')
    print("-" * 100)

    for evaluate in state["conversation"]:
        question = evaluate.get("질문", "")
        answer = evaluate.get("답변", "")
        eval_result = evaluate.get("평가종합", "")
        eval_reason = evaluate.get("평가에 대한 이유", "")

        # LLM 기반 평가 피드백 생성
        feedback = generate_feedback_paragraph(question, answer, eval_result, eval_reason)
        evaluate["상세피드백"] = feedback

        # 출력
        print(f"질문: {question}")
        print(f"답변: {answer}")
        print(f"피드백:\n{feedback}")
        print("-" * 100)

    parser = PydanticOutputParser(pydantic_object=TotalFeedbackInfo)

    feedback_results = '\n'.join(
        [f'질문: {evaluate["질문"]}\n답변: {evaluate["답변"]}\n피드백: {evaluate["상세피드백"]}'
         for evaluate in state['conversation']]
        )

    prompt = ChatPromptTemplate.from_messages([
        ("system", """너는 AI 면접관이야. 질문, 답변에 대한 피드백을 보고, 평가자의 입장에서 3~5줄로 된 구체적인 종합 평가 피드백을 작성해줘.
답변의 논리성, 구체성, 경험에 기반한 설명, 개선할 부분 등을 모두 고려해줘."""),
        ("human", "{feedback_results}"),
        ("system", "{format_instructions}")
    ])

    # 메시지 포맷팅
    messages = prompt.format_messages(
        feedback_results=feedback_results,
        format_instructions=parser.get_format_instructions()
    )

    # LLM 호출
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.5)
    response = llm.invoke(messages)

    state['total_feedback'] = parser.parse(response.content).feedback
    print(f"종합 피드백: {state['total_feedback']}")

    # return 코드는 제공합니다.
    return state

 

 

5. 면접 Agent

  • LangGraph 연결
# 분기 판단 함수
def route_next(state: InterviewState) -> Literal["generate", "summarize"]:
    if state['next_step'] == "end":
        return "summarize"
    else:
        return "generate"


# 그래프 정의 시작
builder = StateGraph(InterviewState)

# 노드 추가
builder.add_node('evaluate_answer', evaluate_answer)
builder.add_node('decide_next_step', decide_next_step)
builder.add_node('generate', generate_question)
builder.add_node('summarize', summarize_interview)

# 노드 연결
builder.add_edge(START, 'evaluate_answer')
builder.add_conditional_edges('evaluate_answer', reflect_evaluation)
builder.add_conditional_edges('decide_next_step', route_next)
builder.add_edge('generate', END)
builder.add_edge('summarize', END)

# 컴파일
graph = builder.compile()
## 그래프 생성기
from IPython.display import Image, display

# 외부 서버를 호출하는 동안 대기시간 초과로 오류가 날 수 있습니다.
try:
    display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
    print(graph.get_graph().draw_mermaid())
    print('https://mermaid.live 에 복붙')
    pass

LangGraph

 

 

✅ 시스템 실행

1. 인터뷰 사전 준비

# 파일 입력
file_path = path + 'Resume_sample.pdf'
state = preProcessing_Interview(file_path)
state

 

 

2. Agent 실행

# 사용자 응답 루프
while True:
    print("\n[질문]")
    print(state["current_question"])

    state["current_answer"] = input("\n[답변 입력]:\n")

    # 그래프 실행: 평가 → 판단 → 다음 질문 생성 or 종료
    state = graph.invoke(state)

    if state["next_step"] == "end":
        break