ellina.kim(김예린)‐문서화 - 100-hours-a-week/3-team-ssammu-wiki GitHub Wiki
기업별 최신 이슈 업데이트 기능
1. 기능 개요
기업별 최신 이슈 업데이트 기능은 기업별 최근 공시자료와 뉴스 데이터를 주기적으로 수집하고, LLM을 통해 핵심 이슈를 요약하여 백엔드 DB의 recent_issue 필드에 저장하는 자동화 시스템입니다.
- 목적: 취준생이 기업 최신 이슈를 빠르게 파악할 수 있도록 최신 공시 및 뉴스를 요약 제공
- 작동 주기: 매주 주기적으로 전체 기업 대상 최신 이슈 업데이트
- 최종 연동 구조: FastAPI 서버 → LLM 요약 → Spring API로 DB 업데이트
2. 기능 업데이트 흐름
2.1 초기 목표 및 구조
- 초기에는 단일 기업(예:카카오)을 대상으로 크롤링, 요약, 저장까지 파이프라인 검증
- MySQL 직접 업데이트 구조였으며 FastAPI 서버에서 vLLM 호출 후 DB를 갱신하는 구조
- Chroma DB를 사용해 수집한 뉴스/공시 텍스트 임베딩 저장
- 추론 성공시
recent_issue
필드 업데이트 목표로 시작
2.2 단계별 주요 개선 내용
(1) LLM 모델 변경
- Mistral-7B → Aya Expanse 8B 모델 변경
- Aya Expanse 8B(SFT + DPO 기반)를 현재 사용중이며, 추론 안정성과 산업적 적합성 강화
- vLLM 서버는 FlashAttention-2 적용으로 추론 속도 최적화
(2) 데이터 수집 로직 개선
- 공시자료: DART API 기반으로 최신 사업보고서 우선 수집, 없을 시 일반 공시 보조 활용
- 뉴스자료: 한국경제 웹크롤러 구축 → Selenium 기반 → 최근 1년 이내 상위 15개 뉴스 수집
- 키워드 필터링: 기업명과 중복되는 일반 명사(예: 케이크, 구름) 필터링 로직 적용
- 중복 필터링: 동일 뉴스 제거, 100자 미만 뉴스 제거
(3) 임베딩 저장 최적화
- SentenceTransformer 모델 사용(
snunlp/KR-SBERT-V40K-klueNLI-augSTS
) - ChromaDB 내 기업명별 임베딩 저장 → 기존 뉴스는 삭제 후 재저장 → 최신성 보장
- 뉴스/공시 type 필드로 메타데이터 관리
(4) LLM 요약 최적화
- Few-Shot Prompt 적용, 공시 우선 + 뉴스 보조 형태로 요약
- 프롬프트 조건:
- 500자 이내, 단락 형태, 항목 기호 금지, 정중한 문어체, 불필요한 줄바꿈 제거
- 후처리 단계: 조사(~은/는)보정, 어미 통일(했다), 불필요 문장 삭제, 마크다운 제거, 길이 제한
- 실패 시 Retry, JSON 검증 추가 (Fallback 메시지 반환)
(5) 벡앤드 API 연동 구조 변경
- MySQL 직접 업데이트 제거 → Spring 서버 PATCH API 연동 구조로 변경
PATCH /api/v1/companies/recent-issue
엔드포인트 사용- FastAPI → latest_issues.json 파일 기준으로 일괄 전송
(6) 스케줄링 구조 변경
- Crontab → FastAPI 내 APScheduler로 전환
- 서버 내 단일 프로세스로 관리
- 매주 월요일 12시, KST 시간대 기준 주기 실행
(7) 디렉토리 구조 정비
- FastAPI 프로젝트
app/
기준 서비스 분리 (routes/, services/, utils/) - 환경변수(.env) 적용, DB 정보 및 API 엔드포인트 보호
3. 전체 흐름
flowchart TD
A[APScheduler main] --> B[run_summary_pipeline]
B --> C[기업명 리스트 생성 summary_service]
C --> D[generate_issue_summaries 호출 summarizer]
D --> E[기존 뉴스 삭제 ChromaDB]
D --> F[한국경제 뉴스 크롤링 crawler]
E --> G[ChromaDB 저장]
F --> G
G --> H[generate_latest_issue 호출 summarizer_core]
H --> I[vLLM 요약 생성 및 후처리]
I --> J[latest_issues.json 저장]
J --> K[Spring API PATCH 요청 batch]
K --> L{응답 상태 확인}
L -- "202" --> M[성공 로그 기록 후 종료]
순번 | 단계 | 설명 |
---|---|---|
1 | 기업명 로드 | catch_company_details.csv 로드, 기업명 리스트 생성 |
2 | 뉴스 크롤링 | crawler_hankyung.py → 최근 1년, 상위 뉴스 수집 |
3 | 공시자료 로딩 | DART API → 최신 사업보고서 또는 최신 공시 가져오기 |
4 | ChromaDB 저장 | 기업별 기존 뉴스 삭제 후 뉴스/공시 최신화 후 재저장 |
5 | LLM 요약 | vLLM 서버 호출, Aya Expanse 8B Few-Shot Prompt 적용 |
6 | 후처리 및 정제 | clean_summary()로 조사 보정, 불필요 문장 제거, 500자 제한 |
7 | Spring API 전송 | FastAPI에서 latest_issues.json 기준 일괄 PATCH 요청 |
8 | 모니터링 및 에러 처리 | 예외 발생시 fallback 처리 |
4. 주요 파일 및 역할
파일 위치 | 주요 기능 |
---|---|
main.py |
FastAPI 서버 실행, 주기적 스케줄링 등록 |
services/summary_service.py |
전체 파이프라인 실행 관리 |
utils/summarizer.py |
뉴스 크롤링, 임베딩 저장, 요약 생성 |
utils/summarizer_core.py |
LLM 호출, Prompt 세팅, 후처리 |
utils/batch.py |
latest_issues.json → Spring API 일괄 전송 |
utils/crawler.py |
한국경제 뉴스 크롤링 |
utils/chroma_handler.py |
ChromaDB 임베딩 처리 |
5. API 스펙 정리
- Endpoint:
PATCH /api/v1/companies/recent-issue
- 요청 형식: (JSON Array)
[
{"companyName": "카카오", "newRecentIssue": "카카오는 최근..."},
{"companyName": "삼성전자", "newRecentIssue": "삼성전자는 최근..."}
]
- 응답 코드:
- 202: Spring 서버 수락, 비동기 처리
6. 기술 스택 요약
- FastAPI, APScheduler
- vLLM + Aya Expanse 8B (FlashAttention)
- ChromaDB + KR-SBERT 임베딩
- Selenium 크롤러
이력서 정보 추출 기능
1. 기능 개요
사용자가 업로드한 PDF 이력서에서 주요 정보(자격증 수, 프로젝트 수, 전공, 최근 근무이력, 대외활동 등) 를 정형화된 JSON 형태로 추출하는 기능입니다. AI 백엔드(FastAPI)가 이를 수행하며, 기업 이력서 생성 서비스의 기반을 마련합니다.
2. 문제점 및 이슈
구분 | 상세 이슈 |
---|---|
LLM 추론 정확도 저하 | - 프로젝트와 경력, 자격증 구분 오류 빈번 |
- additional_experiences에 근무 이력 혼입
- 자격증 수 추출 누락 | | JSON 파싱 실패 | - LLM 추론 시 JSON 형태가 불완전해 파싱 오류 다수 발생
- 종종 리스트, 잘림 현상 발생 | | 비효율적 PDF 처리 | - PyPDF2 사용 시 포맷 누락, 이미지 PDF 처리 불가
- 글머리표/제목 구분 실패 | | 낮은 안정성 | - LLM 실패 시 fallback 처리 없음
- LLM 추론 실패 시 서비스 실패 빈도 높음 | | 느린 속도 | - 동기 요청 사용으로 처리 대기 시간 길어짐 |
3. 주요 개선 내역
3.1 PDF 텍스트 추출
기존 | 개선 후 |
---|---|
PyPDF2 단순 텍스트 추출 | PyMuPDF 기반, 글자 크기/굵기 인식 |
제목/본문 구분 불가 | [📌 제목 추정] 태그로 제목 구분 |
이미지 PDF 미지원 | OCR 적용 고려 가능성 확보 |
→ LLM이 이력서 섹션을 명확히 인식
3.2 LLM 프롬프트 최적화
- StructuredOutputParser 도입 → JSON 응답 강제
- 상세 시스템 프롬프트 작성 → 7개 항목 기준, 추출 규칙 명시
- “추측 금지”, “단일 값만 추출”, “리스트 금지” 등 엄격 기준 적용
- [📌 제목 추정] 태그 활용법 명시 → 추론 정확도 향상
3.3 LLM 추론 안정화
기존 | 개선 후 |
---|---|
LLM 실패 시 에러 반환 | 최대 2회 재시도 후 fallback 기본값 반환 |
추론 실패 시 500 오류 발생 | 200 OK + fallback JSON 반환 |
JSONDecodeError 빈번 | LangChain structured 파서로 안정성 확보 |
→ 사용자 입장에서는 항상 일관된 JSON 응답 보장
3.4 성능 최적화
개선 전 | 개선 후 |
---|---|
동기 처리 | httpx 비동기 처리 적용 |
OpenAI API 연동 | 자체 vLLM 서버(Aya Expanse 8B)로 추론 비용 절감 |
추가 최적화 옵션:
- max_tokens=512 제한
- temperature=0.3 고정
- max_length 절단으로 token overflow 방지
4. 전체 흐름
4.1 최종 흐름
flowchart TD
A[/resume/extract API 요청/] --> B[PDF 다운로드 및 유효성 검사]
B --> C[텍스트 추출 - 제목 추정 포함]
C --> D[LLM 추론 → JSON 변환]
D --> E{LLM 추론 성공 여부}
E -- 성공 --> F[정상 JSON 반환 - ResumeInfo]
E -- 실패 --> G[Fallback JSON 반환 - 0/null 처리]
- 입력: file_url (GCS/S3 PDF 링크)
- 출력: ResumeInfo JSON (7개 항목 포함)
- 특징: PDF → 포맷 인식 텍스트 → LangChain 기반 LLM 추론 → fallback 보장
4.2 주요 흐름 요약
-
file_url 요청 → PDF 검증 → 텍스트 추출([📌 제목 추정] 포함) → LLM 추론 → 실패시 fallback 반환
-
최종 반환 JSON 예시
{ "certification_count": 3, "project_count": 2, "major_type": "MAJOR", "company_name": "카카오", "work_period": 26, "position": "데이터 분석 인턴", "additional_experiences": "대학교 소프트웨어 학술 동아리 활동, 경진대회 수상" }
5. 주요 파일 및 역할
파일 | 주요 역할 |
---|---|
resume_extract_service.py | 전체 파이프라인 orchestrator |
llm_handler.py | LLM 추론 & LangChain Structured 파싱 |
pdf_parser.py | PyMuPDF 기반 텍스트 추출 및 제목 태깅 |
resume_extract.py | Pydantic 기반 추출 결과 스키마 정의 |
6. API 스펙 정리
-
Endpoint:
POST /resume/extract
-
요청 형식:
{ "file_url": "https://storage.googleapis.com/bucket/resume.pdf" }
-
응답 형식:
{ "message": "success", "data": { "certification_count": 2, "project_count": 3, "major_type": "MAJOR", "company_name": "카카오", "work_period": 24, "position": "백엔드 엔지니어", "additional_experiences": "교내 동아리, 전국 공모전 수상" } }
-
실패 시 fallback:
{ "message": "extraction_failed", "data": { "certification_count": 0, "project_count": 0, "major_type": "NON_MAJOR", "company_name": null, "work_period": 0, "position": null, "additional_experiences": null } }
→ 응답 코드: 200 OK (성공/실패 모두 200, message로 구분)
7. 기술 스택 요약
- Backend: FastAPI, Python 3.11
- LLM: aya-expanse-8b (vLLM, FlashAttention)
- Prompt: LangChain Few-shot Prompt, StructuredOutputParser
- PDF Parsing: PyMuPDF, Tesseract OCR
- Async 처리: httpx.AsyncClient
- Infra: Docker, GCP L4 GPU
기업 추천 기능
1. 기능 개요
기업 추천 기능은 사용자의 자연어 입력을 기반으로 LLM, 벡터 검색, 정형 필터링을 결합하여 최적의 기업을 추천하는 서비스입니다.
- 자연어 → 조건 파싱 → 유사 기업 검색 → 조건 필터링 → 추천 결과 반환
- FastAPI 기반 API 제공
2. 아키텍처 흐름도
사용자 ─▶ FastAPI ─▶ LLM 파싱 ─▶ ChromaDB 유사도 검색 ─▶ 정형 필터링 ─▶ 최종 기업 추천 반환
- 세부 흐름:
- 자연어 입력 → LLM이 location, salary, company_size, tags 추출
- 추출된 tags 기반 ChromaDB에서 top_k 유사 기업 검색
- location, salary, company_size 기준 추가 필터링 후 최종 결과 반환
3. LLM 파싱 설계
항목 | 내용 |
---|---|
모델 | Gemini-2.5-Flash API 활용 |
라이브러리 | LangChain Google GenAI Wrapper 사용 |
주요 추출값 | location, salary, company_size, tags |
salary 세부형식 | {"value": "5000", "operator": "이상"} |
tags | 자연어 입력 내 핵심 키워드 추출 (예: 자율출퇴근, MZ) |
- 예시 입력/출력:
-
입력: "강남역 근처 자율출퇴근 가능한 스타트업 추천해줘"
-
출력:
{ "location": "강남역", "salary": {"value": "4000", "operator": "이상"}, "company_size": "스타트업", "tags": ["자율출퇴근"] }
-
4. 벡터 검색 설계
항목 | 내용 |
---|---|
벡터DB | ChromaDB (Persistent Client) |
임베딩 모델 | snunlp/KR-SBERT-V40K-klueNLI-augSTS |
입력값 | tags + company_size 조합 |
검색방식 | cosine similarity top_k 탐색 |
메타데이터 | company_name, address, avg_salary, company_size, lat, lon 포함 |
5. 정형 필터링 세부 구조
필터링 기준 | 설명 |
---|---|
location | Kakao Local API → 위경도 변환 → Haversine 거리 계산 (반경 5km 이내 필터) |
salary | 연봉 상한/하한 필터링 |
company_size | 대기업, 중견기업, 스타트업 등 필터링 |
6. API 명세
-
Endpoint:
POST /api/v1/recommend
-
Request Body 예시
{ "query": "강남역 근처 자율출퇴근 가능한 스타트업 추천해줘" }
-
성공 응답 예시
{ "message": "recommendation_success", "data": ["카카오", "토스", "컬리"] }
-
실패 응답 예시
{ "message": "recommendation_failed", "data": [] }
7. 디렉토리 구조
app/
├── routes/recommend.py # FastAPI 엔드포인트
├── services/
│ ├── recommend_service.py # 추천 로직 전체 흐름
│ ├── parser.py # LLM 파싱 모듈
│ ├── vector_search.py # 벡터 검색 모듈
│ └── location_utils.py # 위치 필터링 유틸
└── data/company_data.csv # 기업 메타데이터 CSV
scripts/
└── build_company_chroma.py # 초기 ChromaDB 벡터 저장 스크립트