파이썬쟁이
[AI] 사내 HR 도우미 만들기 (with. GPT) 본문
요즘(아니 진작에) 업무의 자동화가 굉장히 중요해진 시기에,
늦게나마 우리도 도입해볼까? 하고 시작한 사이드 프로젝트에
진행했던 과정들과 해설을 정리하는 글이 되겠다.
1. PDF 에서 TEXT 추출
회사 내에 복리후생에 관련된 내용은 PDF로 깔끔히 정리된 문서가 있어서 이를 활용하기로 했다.
프로젝트 내 `pdfs` 폴더를 만들고 이 폴더를 타겟으로 모든 pdf 파일들을 text로 만들어주는 코드를 작성했다.
pdf 파일을 읽는 부분은 pypdf를 사용했다.
# ---------------------------
# 1) PDF -> 텍스트 추출
# ---------------------------
def pdf_to_text(pdf_path: str) -> str:
reader = PdfReader(pdf_path)
pages = []
for p in reader.pages:
text = p.extract_text() or ""
pages.append(text)
return "\n\n".join(pages)
2. 텍스트 리스트를 벡터로 변환 및 벡터 데이터 생성
현재 복리후생에 관련된 내용이 길지 않으므로 문장이나 문단 단위로 chunk를 자를 필요가 없겠다 싶어
그냥 문서 단위로 임베딩 하기로 했다.
문서단위로 쪼개진 파일 별 텍스트가 벡터로 임베딩되고, 이는 FAISS에 넣을 백터 데이터가 된다.
이를 faiss.index에 저장하고 원본 및 출처 정보의 메타 데이터는 meta.json으로 저장한다.
# ---------------------------
# 2) 전체 파이프라인: PDF -> 인덱스 + 메타 저장
# ---------------------------
def ingest_folder_by_document(
folder_path: str, index_path="faiss.index", meta_path="meta.json"
):
folder = Path(folder_path)
docs, sources = [], []
for pdf_file in folder.glob("*.pdf"):
text = pdf_to_text(str(pdf_file))
if text.strip(): # 내용이 비어있지 않은 경우만
docs.append(text)
sources.append(pdf_file.name)
print(f"총 {len(docs)}개의 문서 로드 완료")
embeddings = create_embeddings(docs)
index = build_faiss_index(embeddings)
faiss.write_index(index, index_path)
meta = {"chunks": docs, "sources": sources}
with open(meta_path, "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
print(f"📚 {index_path}, {meta_path} 저장 완료!")
# ---------------------------
# 3) OpenAI 임베딩 생성
# ---------------------------
def create_embeddings(
texts: List[str], model: str = EMBEDDING_MODEL
) -> List[List[float]]:
"""OpenAI 임베딩 생성. texts 리스트 입력 -> 벡터 리스트 반환."""
# 한 번에 많은 텍스트 전송 시 rate/size 고려 필요 (여기선 단순 동기 호출)
embeddings = []
batch_size = 16
for i in range(0, len(texts), batch_size):
batch = texts[i : i + batch_size]
resp = openai.embeddings.create(model=model, input=batch)
# response['data']의 순서가 입력 순서와 같다
for item in resp.data:
embeddings.append(item.embedding)
return embeddings
# ---------------------------
# 4) FAISS 인덱스 만들기 / 저장 / 불러오기
# ---------------------------
def build_faiss_index(embeddings: List[List[float]]) -> faiss.IndexFlatL2:
d = len(embeddings[0])
index = faiss.IndexFlatL2(d)
mat = np.array(embeddings).astype("float32")
index.add(mat)
return index
3. 호출
만들어진 벡터 데이터를 호출하여 top-k 유사 벡터를 검색하여 관련 문서를 반환한다.
# ---------------------------
# 5) 벡터 데이터 로드
# ---------------------------
def load_index(index_path: str = "faiss.index", meta_path: str = "meta.json"):
index = faiss.read_index(index_path)
with open(meta_path, "r", encoding="utf-8") as f:
meta = json.load(f)
return index, meta
# ---------------------------
# 5) 유사한 문서 검색
# ---------------------------
def search_similar_chunks(query: str, index, meta, top_k: int = 3):
q_emb = create_embeddings([query])[0]
qv = np.array([q_emb]).astype("float32")
D, I = index.search(qv, top_k)
results = []
for idx in I[0]:
results.append(meta["chunks"][idx])
return results
4. GPT 호출
유사도가 높은 문서를 GPT의 유저 메시지에 넣어 보내면 된다.
# ---------------------------
# 6) GPT 호출
# ---------------------------
def call_chat_with_context(
system_prompt: str, user_question: str, context_chunks: List[str]
):
# system role은 고정 지침으로 넣고, user message에 질문 + 발췌된 정책을 넣음
context_text = "\n\n--- 관련 정책 발췌 ---\n\n" + "\n\n---\n\n".join(context_chunks)
user_message = f"{user_question}\n\n참고자료:\n{context_text}"
# Chat Completion 호출 (openai.ChatCompletion 예시)
# 계정/라이브러리에 따라 Responses API 사용 권장 문서 참고
response = openai.chat.completions.create(
model=CHAT_MODEL,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message},
],
max_tokens=800,
temperature=0.0,
)
# 응답 텍스트 반환
return response.choices[0].message.content
if __name__ == "__main__":
PDF_PATH = "pdfs"
if not Path("faiss.index").exists():
ingest_folder_by_document(PDF_PATH)
index, meta = load_index()
system_prompt = (
"너는 우리 회사의 HR 어시스턴트다. 항상 회사 정책 문서에 근거해 정확하게 답변해라. "
"정책에 없는 내용은 추측하지 말고 '해당 정책은 시스템에 등록되어 있지 않습니다'라고 말해라."
)
user_question = "복리 후생에 대해서 알려줘"
chunks = search_similar_chunks(user_question, index, meta, top_k=3)
call_chat_with_context(system_prompt, user_question, chunks)