🧠미니 프로젝트
25/05/8-25/05/12 걸쳐서 2차 미니 프로젝트가 진행되었습니다.
이번 2차 미니 프로젝트에서 저희 주제는 AI 면접관 Agent 구축 이었습니다.
취업 준비를 할 때, 면접준비라는게 채용 과정에서 가장 부담스럽지만 연습하는 것이 쉽지 않습니다. 또한 면접 후 개인 피드백이 없다보니 자신이 무엇을 잘 하고 못 하는지 알 수 없는 문제가 있습니다. 이러한 문제를 해결하기 위하여 개인화된 질문과 답변에 대한 피드백을 해주는 AI 면접관 Agent 시스템 구축하는 프로젝트를 진행합니다. LangChain, RAG, AI Agent를 사용하여 LLM 기반 Text인식, 요약 및 생성을 활용하고 LangGraph를 활용하여 AI Agent 구축하는 것이 목표입니다.
핵심기술요소
- LangGraph 기반 Agent 흐름 설계
- LLM 기반 질문 생성
- 사전 준비 : 이력서 요약 ㅡ> 전략 수립
- 면접 절차 : 질문 생성 ㅡ> 사용자 응답 ㅡ> 추가 질문 ㅡ> 평가 및 최종 피드백
LLM (AI 면접관 Agent의 핵심 Brain)
- OpenAI / GPT-4o mini
LangGraph (Agent 구축 도구)
- LangGraph란 LLM과 Tool을 노드로 구성해 순차 또는 분기적으로 실행
- 복잡한 흐름을 시각적으로 구성 가능
- 조건 분기 및 반복 처리 지원 ㅡ>에이전트 구현에 최적화
🧠Mission 1
✨Mission1?
AI 면접관 기본 기능으로 전체 틀 구성하기
- 1) 면접 사전 준비 절차 구현
- 이력서/자소서 인식
- State 정의서류분석
- 요약 및 키워드 도출(LLM)
- 질문 전략 수립(LLM)
- 2) 위의 항목을 각각 함수나 클래스로 구성한 후 , 하나의 함수로 묶기

✨ 과정
🧹 (2) State 선언
class InterviewState(TypedDict):
# 고정 정보
resume_text: str
resume_summary: str
resume_keywords: List[str]
question_strategy: Dict[str, Dict]
# 인터뷰 로그
current_question: str
current_answer: str
current_strategy: str
conversations: List[Dict[str, str]]
evaluations : List[Dict[str, str]]
next_step : str
reflection_status: str
report: str
strategy_status: Dict[str, StrategyStatusDict]

🧹 (3) Resume 분석
개인화된 질문을 뽑기 위해 이력서 핵심 내용 및 키워드 추출
입출력
- 입력 : 자소서 text
- 출력 : 핵심 요약 str, 주요 키워드 List
처리
- 프롬프트 탬플릿을 구성하여 LLM 활용 처리
resume_analyze_llm = ChatOpenAI(model = "gpt-4o-mini", temperature = 0)
analyze_prompt = ChatPromptTemplate.from_messages([
("system", "너는 채용 지원자의 이력서 및 자기소개서를 바탕으로 심화된 질문을 생성하기 위해서 {step}하는 전문가야"),
("human", "나는 면접관인데 지원자의 이력서 및 자기소개서를 면접 평가를 위해 {step}해줘\n 자기소개서:{resume_text}"),
("system", "{format_instructions}")
])
def summary_resume(resume_text: str) -> str:
summary_messages = analyze_prompt.format_messages(
step = "요약",
resume_text = resume_text,
format_instructions = "Return the response as a plain string."
)
summary_response = resume_analyze_llm.invoke(summary_messages)
return summary_response.content
def keyword_resume(resume_text: str) -> List[str]:
parser = CommaSeparatedListOutputParser()
keyword_messages = analyze_prompt.format_messages(
step = "지원자의 기술, 경험, 관심사에 대한 키워드 도출",
resume_text = resume_text,
format_instructions = parser.get_format_instructions()
)
keyword_response = resume_analyze_llm.invoke(keyword_messages)
return parser.parse(keyword_response.content)
def analyze_resume(state: InterviewState) -> InterviewState:
resume = state["resume_text"]
# return 코드는 제공합니다.
return {
**state,
"resume_summary": summary_resume(resume),
"resume_keywords": keyword_resume(resume)
}
🧹 (4) 질문 전략 수립 (LLM)
- 입력: 핵심 요약 str, 주요 키워드 List
- 출력 : 3가지 분야에 대한 전략 수립
처리
프롬프트 템플릿 구성하여 LLM 처리

class QuestionStrategy(BaseModel):
strategy: str = Field(..., description = "분야에 따른 질문 생성 전략")
example: List[str] = Field(..., description = "전략에 따른 예시 질문 최소 2개")
class InterviewStrategy(BaseModel) :
expertise: QuestionStrategy = Field(..., description = "경력 및 경험에 대한 영역")
motivation: QuestionStrategy = Field(..., description = "동기 및 커뮤니케이션에 대한 영역")
logical_thinking: QuestionStrategy = Field(..., description = "논리적 사고에 대한 영역")
def generate_question_strategy(state: InterviewState) -> InterviewState:
prompt_messages = [
("system", "너는 채용 지원자를 평가하는 면접관이야"),
("human", "자기소개서 분석 정보에 맞춰 질문 전략을 수립해줘.\n자기소개서 요약:{resume_summary}\n\n 자기소개서 키워드: {resume_keywords}"),
]
input_fields = {
'resume_summary': state["resume_summary"],
'resume_keywords': state["resume_keywords"]
}
strategy_dict = invoke_pydantic_chain(
InterviewStrategy,
prompt_messages,
input_fields,
temperature = 0.3
)
# 전략 상태 초기화
strategy_status = {}
for field in strategy_dict:
strategy_status[field] = {
'step': 0,
'isdone': False
}
# return 코드는 제공합니다.
return {
**state,
"question_strategy": strategy_dict,
"strategy_status": strategy_status
}
🧹 (5) 사전 준비 절차를 하나의 함수로 묶기
사전 준비 절차가 1회성으로 실행되도록 각 단계를 하나의 함수로 묶음
입출력
- 입력 : 이력서/자소서 파일
- 출력 : State
처리
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": {},
"current_question": '',
"current_answer": '',
"current_strategy": '',
"conversations": [],
"evaluations": [],
"next_step" : '',
"visited_strategy": [],
"reflection_status": ''
}
state = analyze_resume(state)
state = generate_question_strategy(state)
selected_question = state["question_strategy"]["expertise"]['example'][0]
# return 코드는 제공합니다.
return {
**state,
"current_question": selected_question,
"current_strategy": "expertise"
}
🧠Mission 2
✨ Mission2?
면접 절차 구현
- 답변 입력
- 답변 평가 : 관점 별 상,중,하 평가
- 인터뷰 진행 여부 판단
- 질문 생성
- 최종 출력

✨ 과정
- 답변에 대한 평가 : 면접자의 답변이 적절한지 평가 (LLM,파서)
- 인터뷰 진행 검토 : 인터뷰가 길어지지 않도록 진행 제한 (if~else로만 구현)
- 질문 생성 : 이전 질문 답변을 검토한 후 추가 심화 질문 생성 (LLM)
- 인터뷰 피드백 보고서 : 인터뷰를 종료하며, 인터뷰 내용과 평가내용 화면 출력
- 면접 Agent : 사용자 답변 입력 ~ 질문 출력/종료 까지 한 Agent로 구성 (Agent)
🧠Mission 3
고도화 과정 진행
✨ 과정
- 답변에 대한 평가고도화 : LLM이 자동 평가한 결과에 대해 스스로 점검하는 과정 추가
- 인터뷰 진행 검토 고도화 : current_strategy에서 계속 질문할지, 혹은 다음 전략으로 넘어갈지 여부
- 질문 생성 고도화 : 질문을 일관된 방향으로 만들기 위해 질문 DB를 구축하고 RAG 사용하여 유사 질문 검색 후 프롬프트 참조에 포함하도록 함
- 인터뷰 피드백 보고서 : 단순한 대화 출력이 아닌 면접자가 개선할 수 있는 피드백 제공
- Gradio: ai모델이나 파이썬 함수를 웹 인터페이스로 아주 쉽게 만들 수 있는 프레임워크를 사용하여 면접과정 진행에 도움을 줌
이 과정에서 저는 질문 생성 고도화를 담당하였고,
# 1. 임베딩 모델 로드
embedding_function = OpenAIEmbeddings(model="text-embedding-3-small")
path = "/content/drive/MyDrive/project_genai/"
csv_path = path + "interview_questions.csv" # 파일명에 맞게 수정
loader = CSVLoader(csv_path, encoding="utf-8")
documents = loader.load()
persist_directory = path + "chroma_db"
collection = Chroma.from_documents(documents, embedding_function, persist_directory=persist_directory)
retriever = collection.as_retriever(search_type = 'mmr', search_kwargs = {"k": 3})
ChromaDB를 생성하고
# 2. 유사 질문 검색 함수
def retrieve_similar_questions(query_text: str) -> List[str]:
docs = retriever.invoke(query_text)
print(docs)
questions = []
for doc in docs:
for line in doc.page_content.split("\n"):
if line.startswith("question:"):
questions.append(line.replace("question:", "").strip())
return questions
유사 질문 검색 함수를 활용하여 질문 생성하였을 때, retrieve_similar_questions함수를 불러와 유사 질문 검색을 하고, LLM으로 유사 질문을 참고하여 심층 역량을 확인할 수 있는 질문을 1개 생성하도록 함. ㅡ> current_question에 반환
느낀점
아직도 LLM,AGENT등의 코드를 구현하는게 어려워요. 파서 사용, 원하는 내용을 dict형태로 만들어서 state구조에 담는 과정 등을 구현하는 부분이 힘들었습니다.
이번에 고도화 작업을 진행할 때 뭘 해결해야하는지 제대로 이해하지 못한 채 진행하여 2시간동안 헛수고(?)를 하고, 2시간이 지나서야 제가 뭘 해결해야하는지 제대로 이해했어요. 이러한 문제는 코테준비를 할 때도 반복되는 것 같아요. 앞으로는 문제를 제대로 읽고 내가 뭘 풀어야 하는지 정리할 필요성을 느낍니다!
그래도 전체적으로는 몇몇 팀원들의 도움을 받아서 최종 통합본을 완성할 수 있었어요. 많이 어려웠기도 했지만 도움도 많이 된 프로젝트 같아요.
'KT에이블스쿨' 카테고리의 다른 글
| [KT AIVLE SCHOOL] 3차 미니프로젝트 후기 (0) | 2025.05.20 |
|---|---|
| [KT AIVLE SCHOOL] 클라우드 (0) | 2025.05.13 |
| [KT AIVLE SCHOOL] KT 에이블스쿨 7기 합격후기 (1) | 2025.04.28 |
| [KT AIVLE SCHOOL] AGENT (0) | 2025.04.27 |
| [KT AIVLE SCHOOL] RAG (1) | 2025.04.23 |