- 공시보고서 pdf올리면AI가 분석해주기.
- cf) 비동기 처리 레디스 + 레빗MQ/카프
- pdf파일 업로드 및 저장
- ai비용 절약을 위해 중복 방지 파일 해시 추출
- 해당 관련해서 반기 보고서 분기보고서만 쓰는데 회사명이 주식명과 다른경우 뱉는다. AI가 회사명을 파악하고 분석을 거절하는 시스템 구현필요
- 해당 파일이 바이러스 있는지 확인하는 기능 필요 ClamAV 같은 백신으로 체크가능
- DB에 올린 기록 저장. 파일명 사용자ID 업로드 시간.
- PDF 추출
- pdf에서 text를 PdfBox에서 전체 텍스트를 읽는다.
- 표는 Tabula-java를 실행해 재무제표 등 핵심 표 데이터를 추출해서 마크다운형식으로 변환한다.
- 섹션을 나눈다. 사업의내용 재무에 관한 사항같은
- 청킹 및 임베딩
- 청킹 : pdf에서 나눈 섹션별 텍스트와 표를 AI가 읽기 좋게 쪼갠다.
- 임베딩 : 쪼개진 텍스트 조각들을 임베딩 모델에 통과시켜 벡터로 만든다.
- DB에 저장
- 마리아DB에 문서 메타데이터와 요약본 검증결과를 저장
- 벡터DB에 3단계 벡터값과 원문 텍스트 조각 섹션정보를 저장한다.
- 구현 내용
- pdf를 업로드한다.
- pdf 내용의 해시값을 db에 저장한다.
- 텍스트를 추출한다 -> 청킹한다 -> 임베딩한다. 여기서 자바 힙이 터진다.
- 문서는 @Async로 비동기 처리한줄알았다.
- Async는 AOP 프록시 기반으로 동작합니다. 외부에서 이 서비스의 메서드를 호출할 때는 프록시가 가로채서 별도 쓰레드로 돌려주지만, 클래스 내부에서 자신의 메서드를 호출하면 프록시를 거치지 않고 직접 호출하게 됩니다.
- 비동기화 했고 청킹하고 임베딩. 처음 내생각은 필터링을 쭉하면 임베딩서비스가 알아서 벡터값으로 불러주고 알아서 벡터 db에 저장해주는줄알았는데 임베딩모델은 해당하는 언어의 유사도를 기준으로 벡터값을 정하기때문에 값자기 뜬금없는 2-3과같이 전혀관련없는게 나와버리면 java.lang.RuntimeException: status code: 500; body: {"error":"failed to encode response: json: unsupported value: NaN"}하고 뻗어버림
- ai는 단순하게 뭐를 지워라 뭐를 지워라 라는식으로 알려주기시작해서 다시 돌아가서 학습함 아래 글의 내용처럼 유의미하게 PDF를 자르는게 RAG에 중요 요소.
RAG | 현대자동차 챗봇 구현기 - PDF를 잘 추출 해야 하는 이유
PDF를 잘 읽는 게 중요한 이유 RAG(Recurrent Attention-Gated) 시스템을 구성하기 위해 가장 먼저 해야 할 작업은 문서를 텍스트 형태로 로드하는 작업이에요. 만약 문서의 종류가 Excel이나 Code 파일 같이
rimo.tistory.com
잘 자르기 변경 방향:
- 기존: 전체 추출 -> 청킹 -> 저장 (반복) -> 에러에러에러
- Ingestion: PDF 로드 및 북마크/목차 분석.
- Sectioning: 분석된 지점을 기준으로 텍스트 블록 생성.
- Table Processing: 표 섹션은 Tabula로 Markdown 변환, 일반 텍스트는 그대로 유지.
- Enrichment: 각 블록에 회사명, 공시종류, 섹션명을 메타데이터로 주입.
- Vectorization: 강화된 텍스트를 임베딩하여 벡터 DB 저장.
- 변경: 북마크 섹션 추출 -> 섹션~ 섹션 텍스트 +표 추출 -> 청킹 -> 임베딩 -> qdrant
- 그러나 섹션~섹션이 그래프로 20페이지 넘는경우 생기면 에러에러에러
- 변경: 북마크 섹션 추출 -> 섹션~ 섹션 텍스트 추출 (MAX5) -> 청킹 -> 임베딩 -> qdrant
- 300페이지가량 임베딩 완료 확인
1. 비동기 처리 분리 (Proxy 패턴 적용)
@Async가 제대로 동작하게 하려면 호출하는 곳과 실행하는 곳이 다른 빈(Bean)이어야 합니다.
Java
// 1. 비동기 실행 전담 클래스
@Service
@RequiredArgsConstructor
public class DocumentAsyncProcessor {
private final EmbeddingService embeddingService;
private final DisclosureSourceRepository sourceRepository;
@Async("documentTaskExecutor") // 별도 설정된 쓰레드풀 사용
public void processDocument(Long sourceId, String filePath) {
try {
// 페이지별로 읽어서 즉시 처리 (메모리 효율 극대화)
PdfUtil.extractAndProcessByPage(filePath, (pageNumber, text) -> {
// 청킹 로직 실행
List<TextChunk> chunks = TextChunkUtil.chunkSinglePage(text, pageNumber);
// 임베딩 저장
embeddingService.embedAndSaveChunks(sourceId, chunks);
});
sourceRepository.updateStatus(sourceId, "COMPLETED");
} catch (Exception e) {
sourceRepository.updateStatus(sourceId, "FAILED");
}
}
}
// 2. 컨트롤러에서 호출되는 메인 서비스
@Service
@RequiredArgsConstructor
public class DisclosureService {
private final DocumentAsyncProcessor asyncProcessor;
@Transactional
public void uploadDocument(MultipartFile file) {
// ... 파일 저장 및 DB Insert ...
// '남'인 asyncProcessor를 호출해야 비동기가 작동함
asyncProcessor.processDocument(sourceId, filePath);
}
}
'Archive(완료된 내용) > 포트폴리오 강화' 카테고리의 다른 글
| [Stock101] RAG PDF내용 요약과 전망을 도출하자 - 6일차 (0) | 2026.01.26 |
|---|---|
| [Stock101] PDF 데이터 추출이 1회 만에 멈췄던 이유 - 5일차 (0) | 2026.01.26 |
| [stock101] dart연동 및 ksi 연동 그리고 리팩토링 -4일차 (1) | 2026.01.22 |
| [stock101] KIS 코스피 코스닥 종목 정보 매일 최신화하기 - 3일차 (1) | 2026.01.21 |
| [stock101] LOCK을 누가 계속 잡는 문제. (0) | 2026.01.21 |