변리사를 위한 특허 등록 플랫폼 IPX의 AI 서버
- [프로젝트 개요]
- [시스템 아키텍처]
- [기술 스택]
- [AI 파이프라인]
- [선행기술 탐색 파이프라인 (11단계)]
- [선행기술 탐색 진행률 조회 파이프라인]
- [특허 데이터 적재 파이프라인]
- [신규성 분석 파이프라인]
- [진보성 분석 파이프라인]
- [트러블 슈팅]
- [관련 레포지토리]
IPX는 변리사가 출원하려는 발명의 선행기술 조사 및 특허 분석을 자동화하는 플랫폼입니다.
이 레포지토리는 AI 분석을 담당하는 Python FastAPI 서버입니다.
| 기능 | 설명 |
|---|---|
| 선행기술 검색 | OpenSearch, pgvector 기반 하이브리드 검색 (벡터 및 키워드 검색) |
| 구성요소 추출 | 사용자 입력 발명 설명에서 청구항 구성요소 자동 추출 |
| 관련성 판단 | Claude LLM으로 각 특허의 관련성 점수(0~100) 산출 |
| 신규성 분석 | 사용자 발명 구성요소와 선행기술 청구항 1:1 대비 (동일/유사/신규) |
| 진보성 분석 | 주인용/부인용 발명(선행기술) 기반 4개 진보성 논리 자동 생성 |
- 타겟 사용자: 특허 변리사, 특허법인
- 기존 방식: 변리사가 직접 수십 건의 선행기술을 수작업으로 검토 → 수 시간 소요
- IPX 방식: 자동 검색 + LLM 분석 → 핵심 선행기술 및 논리 자동 제공
사용자(변리사)
│
│ HTTPS
▼
┌─────────────────────┐
│ Spring Boot 서버 │ ← 비즈니스 로직, 권한 검증, DB 관리
└─────────────────────┘
│
│ HTTP (내부 통신): 선행기술 탐색 요청, 구성요소 추출 요청, 신규성 및 진보성 분석 요청
▼
┌─────────────────────┐ ┌─────────────────────┐
│ Python FastAPI 서버│ ───────▶ │ Claude, Gemini API │ LLM 분석
│ (해당 레포) │ └─────────────────────┘
│ │ ┌─────────────────┐
│ │ ───────▶│ OpenSearch │ BM25 키워드 검색
│ │ └─────────────────┘
│ │ ┌─────────────────┐
│ │ ───────▶│ PostgreSQL │ 벡터 유사도 검색
└─────────────────────┘ │ (pgvector) │
│ └─────────────────┘
│ HTTP (분석 결과 반환)
▼
┌─────────────────────┐ ┌─────────────────┐
│ Spring Boot 서버 │ ───────▶│ PostgreSQL │ 결과 저장
└─────────────────────┘ │ (사용자/케이스) │
│ └─────────────────┘
│ HTTPS
▼
사용자(변리사)
- EC2: t3.large
- Docker Compose: nginx, certbot, spring, python, postgres(pgvector), opensearch, redis 총 6개 컨테이너
- 도메인: api.ipx-patent.com
- 특허 DB: 약 15만 건을 OpenSearch와 pgvector 각각에 사전 적재
- 주요 IPC 분포:
G06T컴퓨터 그래픽스/영상처리: 67%G06N인공지능/머신러닝: 47%G06Q비즈니스 ICT: 23%G06V컴퓨터 비전: 12%G16H/A61B디지털 헬스케어: 13%
| 분류 | 기술 |
|---|---|
| 웹 프레임워크 | FastAPI |
| LLM | Claude Sonnet, Gemini |
| 임베딩 모델 | BGE-M3 |
| 벡터 검색 | PostgreSQL + pgvector |
| 텍스트 검색 | OpenSearch |
사용자의 발명 정보를 입력받아 관련 선행기술을 검색하고 관련성을 판단합니다.
Step 0: 사용자가 지정한 특허 확인 및 자동 적제
Step 1: 사용자 입력 자연어 의도 해석 — LLM으로 키워드 및 IPC 코드 추출
Step 2: 동의어 확장 — 키워드 변형어 생성
Step 3: IPC 코드 통합 — 사용자 입력 + LLM 추정 합집합
Step 4: HyDE — 가상 특허 초록 생성
Step 5: BGE-M3 임베딩 — 가상 초록 벡터화
Step 6: 병렬 검색 — OpenSearch(BM25 키워드 검색) + pgvector(벡터 검색) 동시 실행
Step 7: RRF 병합 — 두 검색 결과 RRF 알고리즘으로 통합
Step 8: 정확한 개수 확보 — 중복 제거 후 목표 건수 필터링
Step 9: mget 준비 — OpenSearch에서 필터링된 후보의 청구항/초록 병렬 조회
Step 10: LLM 관련성 판단 — 각 특허에 대한 관련도 0~100 점수 산출 및 특허 특징, 요약 , 추천 이유 생성
Step 11: 최종 응답 조립 및 반환 (99%)
- 사용자가 직접 지정한 출원번호에가 DB(OpenSearch)에 적재되어 있는지 확인
- 없으면
KIPRIS API로 즉시 조회하여 DB에 자동 적재
- 없으면
- 이 단계로 사용자가 반드시 포함하고 싶은 특허가 결과에 항상 포함됨을 보장
- 사용자 입력(발명 명칭, 상세, 기술 분야 등)을
Gemini에 전달 - 검색 의도를 구조화된 JSON으로 추출: 유효한 입력 여부, 기술 키워드, 예상 IPC 코드 등
- 의도가 부적절하면 여기서 종료 (
invalid_input상태)- 예: 광고성 문구, 기술적 실체가 없는 내용
- 의도 해석에서 추출한 키워드를 사전에 작성한
동의어 사전과 매칭- 예:
클러스터→ [클러스터링, 군집, 집단화] 확장
- 예:
- OpenSearch 동의어 사전 (
synonyms_patent.txt) 활용
- 아래 2가지를 통합하여 최종 IPC 필터 구성
- 사용자가 입력한 IPC 코드 (
userInputIpc) - LLM이 추정한 IPC 코드 (
estimated_ipc)
- 사용자가 입력한 IPC 코드 (
- 검색 정밀도 향상
Claude에게 "이 발명 정보로 예상되는 선행 특허의 가상 초록"을 작성하게 함- 실제 특허와 유사한 서술 구조/전문 용어로 생성됨
- 이 가상 초록을 임베딩하면 실제 특허의 초록과 벡터 공간에서 더 가까워짐
- 검색 정확도 향상
- HyDE 가상 초록을
BGE-M3로 임베딩 - 1024차원 정규화 벡터 생성
- 코사인 유사도 계산을 위한 준비
두 검색 방식을 동시에 실행:
OpenSearch 검색:
- BM25 스코어링
- 동의어 사전으로 확장된 키워드로 쿼리
pgvector 검색:
- 코사인 유사도 검색
- IPC 코드 필터링
- 두 검색 결과의 순위를 RRF 공식으로 결합
- 순위만 사용하므로 스코어 정규화 불필요
- 병합된 순위로 재정렬
- 사용자가 요청한 개수 (
resultCount, 10~30건)만큼 상위에서 슬라이싱 - required 특허가 요청 개수 안에 반드시 포함되도록 보장
- 만약 병합 결과가 0건이면 그대로 종료
- 상위 특허들의 상세 정보(청구항, 초록 등)를 OpenSearch
mget으로 한 번에 조회 - 다음 LLM 분석에 필요한 재료 준비 과정
- 상위 특허들에 대해
Claude API병렬 호출 - 각 특허마다 LLM이 산출:
reason(추천 이유)summary(특허 요약)purpose(기술 목적)keywords(기술 키워드)features(핵심 특징)
- LLM 결과를 검색 결과와 매핑
- Spring에 최종 응답 반환
단일 검색 방식의 한계: 특허 검색은 키워드 매칭과 의미론적 유사성 모두 중
- 키워드 매칭 필요: 전문 용어는 정확히 일치해야 함
- 의미론적 유사성 필요: "차량 상태 진단 시스템"과 "자동차 이상 감지 장치"는 표현은 다르지만 같은 개념
옵션 A: 키워드 검색만 사용
- 장점: 정확한 매칭, 빠른 응답
- 단점: 표현이 다르면 유사 특허 놓침
- "AI로 자동차 진단"과 "머신러닝 기반 차량 이상 탐지"가 다른 특허로 판정됨
옵션 B: 벡터 검색만 사용
- 장점: 의미적 유사성 반영
- 단점: 정확한 용어 매칭에 약함
옵션 C: Hybrid Search
OpenSearch (BM25 + 키워드) 담당:
- 동의어 사전을 통한 키워드 확장
- 특허 명칭, 초록, 청구항에서 키워드 매칭
pgvector 담당:
BGE-M3모델로 생성한 1024차원 벡터의 코사인 유사도 검색- 텍스트 → 고차원 수치 배열 (1024차원)
- 벡터 공간에서 코사인 거리 측정 (각도가 가까울 수록 유사)
결과: 두 검색 결과를 RRF로 순위 병합해 최종 결과 반환
LLM이 가상 답변 문서를 먼저 작성하게 한 뒤, 벡터로 바꿔 검색하는 기법
- 발명 설명 (자연어) → Cluade API를 통해 해당 발명 정보로 가상 초록 생성
- 실제 특허 초록과 유사한 서술 구조/전문 용어로 생성됨
- 해당 가상 초록을 BGE-M3로 임베딩하여 벡터 생성
- 해당 벡터를 pgvector를 통해 벡터 공간에 저장된 실제 특허의 초록과 코사인 유사도 검색
- 벡터 공간에서 더 가까워져 검색 정확도 향상
병합의 필요성
OpenSearch와 pgvector가 각각 다른 기준으로 스코어링 후 결과 반환:
- OpenSearch: BM25 스코어 (0~수십)
- pgvector: 코사인 유사도 (0~1)
RRF_score = Σ 1 / (k + rank_i) (k=60)
-
rank_i(d): 검색 방법 i에서 문서 d의 순위 -
OpenSearch (키워드 기반) + pgvector (의미 기반) 두 순위를 통합
-
어느 한 방식에만 상위 랭크된 특허보다 두 방식 모두에서 높은 특허를 우선 반환
- 스코어 정규화 불필요
- 순위만 사용 → 스코어 단위 무관
- OpenSearch가 BM25 100점을 반환하든, pgvector가 0.8을 반환하든 순위로만 판단
- 구현 단순성
- 몇 줄의 파이썬 코드로 구현
- 튜닝 파라미터가
k하나k를 낮게 잡으면: 어느 한쪽 검색에서라도 최상위에 위치한 특허 우선시k를 높게 잡으면: 키워드와 벡터 양쪽에서 고르게 5~10등 안에 든 무난한 특허 우선시
1. 최대 8192 토큰 지원
- 특허 청구항 + 초록 전체를 하나의 벡터로 임베딩 가능
- 문서 분할/청킹 로직 불필요
2. 다국어 성능
- 100+ 언어 지원, 한국어 포함 최상위 벤치마크
3. 오픈소스 (MIT 라이선스)
- API 비용 없음
- 로컬 GPU/CPU에서 실행 가능 (임베딩 모델 다운로드 받아 사용)
- Sentence-Transformers 라이브러리 지원
4. 기타 모델들의 한계
- API 비용 발생 (15만 건 임베딩해야함)
- 외부 의존성 (장애 시 서비스 마비)
- 한국어 성능 낮음 (영어 중심 학습)
문제: 비동기로 진행되는 탐색 API에서, 현재 탐색 진행 단계 및 진행률을 프론트가 실시간으로 조회해야함
해결: 프론트는 검색 실행 응답으로 받은 caseId로 Spring을 통해 Redis에 진행 상태를 주기적으로 조회하여 완료 여부 확인
- 프론트가 “탐색 실행 API” 응답으로 받은
caseId로 폴링 - Spring이 해당
caseId를 키값으로 Redis에 직접 접근하여 상태 조회
| 상태 | 의미 | 프론트 대응 |
|---|---|---|
in_progress |
진행 중 | 계속 폴링 |
completed |
정상 완료 (결과 있음) | GET /api/cases/{caseId}/prior-arts로 결과 조회 |
no_results |
정상 완료 (결과 0건) | "선행기술을 찾지 못했습니다" 안내 |
invalid_input |
사용자 입력 부적절 | reasonInvalid 사유 표시 후 입력 화면으로 |
failed |
시스템 오류 | error 메시지 표시 후 재시도 유도 |
cancelled |
사용자 취소 | 초기 화면으로 이동 |
Key: search:{caseId}
Hash Fields:
| 필드 | 설명 |
|---|---|
status |
in_progress / completed / no_results / invalid_input / failed / cancelled |
step |
현재 단계 설명 (예: "검색 의도 분석 중") |
progress |
진행률 (0~100) |
started_at |
검색 시작 시각 |
updated_at |
마지막 갱신 시각 |
reason_invalid |
invalid_input 상태 시 사용자 입력 문제 사유 |
error |
failed 상태 시 시스템 오류 메시지 |
진행 중:
{
"success": true,
"data": {
"caseId": 1,
"status": "in_progress",
"step": "검색 의도 분석 중",
"progress": 45,
"error": null,
"reasonInvalid": null
},
"error": null
}정상 완료:
{
"success": true,
"data": {
"caseId": 1,
"status": "completed",
"step": "선행기술 탐색 완료",
"progress": 100,
"error": null,
"reasonInvalid": null
},
"error": null
}Google Colab에서 BGE-M3 임베딩 생성 후, EC2에 적재하는 방식을 사용합니다.
- Kipris Plus 사이트에서 특허 15만건 BULK 다운로드
- 로컬 특허 데이터(ZIP) → Google Drive 업로드
- Colab T4 GPU에서 BGE-M3 임베딩 생성 → checkpoint.json 저장
- AWS EC2로 전송 → 적재 로직
ingest_from_checkpoint.py실행 - pgvector + OpenSearch 동시 적재
- KIPRIS API는 최대 월 1,000회 무료 사용 가능
- IPX MVP 단계에서 적재할 특허는 15만 건
- KIPRIS API로는 건수 부족 → KIPRIS Plus 홈페이지에서 IPC 코드별 수동 BULK 다운로드
- 1회 다운로드당 최대 1만 건, 6개 파일(Bibliographic, Abstract, Claim, IPC, CPC, RelatedPerson) 제공
- 15만 건 기준 15회 다운로드, 3개 배치(batch_1, batch_2, batch_3)로 Google Drive에 분할 저장
- 15만건의 특허에 대해 벡터 검색을 위한 임베딩 과정을 거치고 적재해야함
- 로컬 혹은 EC2의 CPU 환경에서는 사실상 불가능
- Colab GPU를 통한 임베딩 (도합 약 3시간 소요)
- 환경 설치 (sentence-transformers, pandas)
- Google Drive 마운트 + 배치 경로 설정
- ZIP 압축 해제
- BULK 파일 파싱 및 병합 (Bibliographic + Abstract + Claim + IPC + CPC)
- 텍스트 전처리 (HTML 태그 제거, 상투어 제거)
- BGE-M3 임베딩 생성 (T4 GPU, batch_size=32)
- checkpoint.json Google Drive 저장
사용자가 등록하고자 하는 발명이 이미 존재하는 선행기술에는 없는 새로운 기능을 포함하고 있는 지의 여부 판별
Step 1: 상위 3건 선행기술 mget (OpenSearch에서 청구항/초록 조회)
Step 2: 3건에 대해 병렬 LLM 분석
— 사용자 발명 구성요소(A, B, C, D) vs 선행기술 청구항 1:1 대비
— 각 구성요소: 동일 / 유사 / 신규 판정
Step 3: 유사도 점수 계산 (동일=2, 유사=1, 신규=0) → 최고점 특허를 D1 선정
Step 4: DB 저장 (기존 분석 삭제 후 새로 저장, 단일 트랜잭션)
사용자가 등록하고자 하는 발명이 이미 존재하는 선행기술 대비 얼마나 진보적(아래 4개의 카테고리가 기준)인지 판별
사용자가 선택한 주인용(D1)을 기준으로 진보성 분석을 자동 실행합니다.
Step 1: D1 확정 및 D2 후보 준비 (후보는 D1 제외 상위 N건)
Step 2: OpenSearch mget — D1과 후보들의 청구항/초록 조회
Step 3: LLM으로 D1과 더불어 진보성 분석하기 적합한 D2 선정 (없으면 null, D1 단독 분석)
Step 4: D1/D2 기반으로 심사에서 부정 당하기 쉬운 진보성 논리 카테고리 선정
Step 5: 선정된 카테고리별로 진보성 논리 자동 생성
Step 6: DB 저장
| 카테고리 | 설명 | D2 필요 여부 |
|---|---|---|
NUMERICAL_LIMIT |
수치한정 — 본 발명이 정량적 성능 지표를 개시하고, D1 대비 개선률 명확한 경우 | D1만 필요 |
COMBINATION_MOTIVATION |
결합 동기 부재 (Teaching Away) — D1과 D2가 서로 다른 방향의 기술이거나 결합 동기가 없어 보이는 경우 | D2 필수 |
COMMON_TECHNIQUE |
주지관용기술 반박 — D1에 없는 구성요소 중 관용기술로 오해받을 만한 것이 있는 경우 | D1만 필요 |
SIMPLE_DESIGN |
단순설계변경 반박 — D1과 본 발명의 차이가 미묘하거나 파라미터 조정으로 보일 위험이 있는 경우 | D1만 필요 |
문제: @Async와 @Transactional을 같은 클래스에서 self-invocation하면 Spring AOP 프록시가 우회되어 두 어노테이션 모두 무효화됨
초기 코드
@Service
public class CaseSearchService {
public SearchStartResponse startSearch() {
asyncTask();
transactional();
}
@Async // self-invocation에서 무효
public void asyncTask() { ... }
@Transactional // self-invocation에서 무효
public void transactional() { ... }
}해결: Service를 3계층으로 분리
CaseSearchService → (트랜잭션 없음)
CaseSearchAsyncService → @Async 스레드 (트랜잭션 없음)
CaseSearchTxService → @Transactional 트랜잭션
- 각 계층이 하나의 책임만 (
SRP) - 프록시 우회 문제 해결
- 진보성/신규성/검색 모두 동일 패턴 적용
현재 코드
@Service
public class CaseSearchService {
/**
* 선행기술 탐색
*/
public SearchStartResponse startSearch(Long userId, SearchRequest request) {
// 0. 재검색 시 진행 중 상태 확인
if (request.caseId() != null) {
checkNotInProgress(request.caseId());
}
// 1. Case, 구성요소 저장 (별도 Service의 트랜잭션 경유)
Case caseEntity = txService.prepareSearch(userId, request);
// 2. resultCount 기본값 처리
int resultCount = request.resultCount() != null
? request.resultCount()
: DEFAULT_RESULT_COUNT;
// 3. Redis에 탐색 시작 상태 저장
searchProgressService.markStarted(caseEntity.getId());
// 4. 비동기 Python 호출
asyncService.executeSearchAsync(caseEntity.getId(), request, resultCount);
log.info("[Search] 검색 시작: caseId={}, userId={}",
caseEntity.getId(), userId);
return SearchStartResponse.of(caseEntity.getId());
}
...
}문제: 프론트의 탐색 결과 조회와 Spring의 탐색 결과 DB 저장 시점 충돌 가능
초기 설계
Python에서 선행기술 탐색 완료
↓
Python이 Redis에 탐색 완료 상태 저장(status=completed) 후, Spring에 결과 반환
↓ (여기서 Spring은 DB에 결과 저장 중)
프론트 폴링 → Redis에서 completed 상태 감지 → Spring에 결과 조회 API 호출
↓
DB에서 조회 : 아직 빈 결과일 수도.. (Spring이 DB에 아직 저장 안 했다면)Race Condition 해결책:
- Python에서
markCompleted호출 제거- Python의 탐색 파이프라인 완료 ≠ 사용자가 결과 조회 가능한 상태
- Spring이 DB 저장 완료 후 Redis에 탐색 완료 상태 갱신 (
markCompleted호출)
Python에서 선행기술 탐색 완료
↓
Python이 Spring에 결과 반환
↓
Spring은 DB에 결과 저장 후, Redis에 탐색 완료 상태 저장(status=completed)
↓
프론트 폴링 → Redis에서 completed 상태 감지 → Spring에 결과 조회 API 호출
↓
DB에서 조회 : 탐색 결과 조회@Transactional
public void saveSearchResults(Long caseId, ...) {
// DB 저장 로직
...
// 트랜잭션 커밋 후, Redis에 완료 상태 업데이트
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
searchProgressService.markCompleted(caseId);
}
}
);
}결과: DB에 실제로 데이터가 커밋된 순간에만 프론트가 completed 상태 감지
해결: 사용자별 Rate Limit 어노테이션 도입 → 시간당 요청 수 제한
- 인증된 사용자의
userId기준으로 Redis에 호출 횟수 저장
Spring AOP 기반 구현:
@RestController
@RequestMapping("/api")
public class CaseSearchController {
@RateLimit(apiName = "search") // Rate Limit 어노테이션
@PostMapping("/searches") // 선행기술 탐색 API
public ResponseEntity<ApiResponse<SearchStartResponse>> startSearch(
...
}yml 설정:
ratelimit:
limits:
search: 20 # apiName: 시간당 호출 가능 횟수
component-extract: 30
manual-add: 30
inventive-step: 10
novelty: 10 Redis 키값:
Key: rate:{apiName}:{userId}:{hourBucket}
Value: 해당 시간 동안의 요청 횟수 (카운트)
- hourBucket: 매 시간 자정부터 경과 시간 (h 단위)
동작 흐름:
-
[카운트 ≤ limit] → 허용
- 정상적으로 컨트롤러 로직을 실행하여 LLM 파이프라인을 호출
- Redis에서 해당 키의 값을 1 증가
-
[카운트 > limit] → 차단
컨트롤러 로직을 실행하지 않고, 즉시
429 Too Many Requests예외를 발생시켜 요청 차단
--
- IPX-BE — Spring Boot 백엔드 (비즈니스 로직, DB 관리, API Gateway)
