feat: init rag service

This commit is contained in:
djeon
2025-10-29 05:54:08 +09:00
parent 44ae9c546f
commit 5d897cb845
54 changed files with 6425 additions and 0 deletions
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+137
View File
@@ -0,0 +1,137 @@
"""
관련자료 데이터 모델
"""
from typing import Optional, List, Dict, Any
from datetime import datetime
from pydantic import BaseModel, Field
from uuid import UUID
class DocumentMetadata(BaseModel):
"""문서 메타데이터"""
folder: Optional[str] = Field(None, description="폴더명")
business_domain: Optional[str] = Field(None, description="업무 도메인")
additional_fields: Optional[Dict[str, Any]] = Field(None, description="추가 필드")
class Document(BaseModel):
"""문서 모델"""
document_id: str = Field(..., description="문서 ID")
document_type: str = Field(..., description="문서 타입 (meeting_minutes, org_document 등)")
business_domain: Optional[str] = Field(None, description="업무 도메인")
title: str = Field(..., description="문서 제목")
content: str = Field(..., description="문서 전체 내용")
summary: str = Field(..., description="문서 요약 (3-5 문장)")
keywords: List[str] = Field(default_factory=list, description="키워드 목록")
created_date: Optional[str] = Field(None, description="생성일시")
participants: List[str] = Field(default_factory=list, description="참석자 목록 (회의록의 경우)")
metadata: Optional[DocumentMetadata] = Field(None, description="메타데이터")
embedding: Optional[List[float]] = Field(None, description="임베딩 벡터 (1536차원)")
class Config:
json_schema_extra = {
"example": {
"document_id": "고객-MM-001",
"document_type": "meeting_minutes",
"business_domain": "고객서비스",
"title": "상담 품질 향상 워크샵 1차",
"content": "회의 일시: 2025-10-02...",
"summary": "고객 만족도 지표 검토와 VOC 트렌드 분석을 논의...",
"keywords": ["CSAT", "고객응대", "챗봇"],
"participants": ["김민준", "이미준"]
}
}
class DocumentChunk(BaseModel):
"""문서 청크 (Azure AI Search 인덱싱용)"""
id: str = Field(..., description="청크 ID (document_id_chunk_N)")
document_id: str = Field(..., description="원본 문서 ID")
document_type: str = Field(..., description="문서 타입")
title: str = Field(..., description="문서 제목")
folder: Optional[str] = Field(None, description="폴더명")
created_date: Optional[str] = Field(None, description="생성일시")
participants: List[str] = Field(default_factory=list, description="참석자 목록")
keywords: List[str] = Field(default_factory=list, description="키워드 목록")
agenda_id: Optional[str] = Field(None, description="안건 ID (회의록의 경우)")
agenda_title: Optional[str] = Field(None, description="안건 제목")
chunk_index: int = Field(..., description="청크 인덱스")
content: str = Field(..., description="청크 내용")
content_vector: List[float] = Field(..., description="내용 임베딩 벡터")
token_count: int = Field(..., description="토큰 수")
class DocumentSearchRequest(BaseModel):
"""문서 검색 요청"""
query: str = Field(..., min_length=1, description="검색 쿼리")
top_k: int = Field(3, ge=1, le=10, description="반환할 최대 결과 수")
relevance_threshold: float = Field(0.70, ge=0.0, le=1.0, description="최소 관련도 임계값")
folder: Optional[str] = Field(None, description="폴더 필터 (같은 폴더 우선)")
document_type: Optional[str] = Field(None, description="문서 타입 필터")
business_domain: Optional[str] = Field(None, description="업무 도메인 필터")
semantic_ranking: bool = Field(True, description="Semantic Ranking 사용 여부")
class Config:
json_schema_extra = {
"example": {
"query": "고객 만족도 개선 방안",
"top_k": 3,
"relevance_threshold": 0.70,
"folder": "고객서비스팀",
"semantic_ranking": True
}
}
class DocumentSearchResult(BaseModel):
"""문서 검색 결과"""
document_id: str
title: str
document_type: str
created_date: Optional[str]
relevance_score: float = Field(..., ge=0.0, le=1.0)
relevance_level: str = Field(..., description="HIGH (>90%), MEDIUM (70-90%), LOW (<70%)")
content_excerpt: str = Field(..., description="관련 내용 발췌")
folder: Optional[str] = None
class RelatedMeetingRequest(BaseModel):
"""관련 회의록 검색 요청"""
meeting_id: str = Field(..., description="현재 회의 ID")
top_k: int = Field(3, ge=1, le=5, description="반환할 최대 결과 수")
relevance_threshold: float = Field(0.70, ge=0.0, le=1.0, description="최소 관련도 임계값")
class RelatedMeeting(BaseModel):
"""관련 회의록"""
meeting_id: str
title: str
meeting_date: Optional[str]
relevance_score: float = Field(..., ge=0.0, le=1.0)
relevance_level: str = Field(..., description="HIGH, MEDIUM, LOW")
similar_content_summary: Optional[str] = Field(None, description="유사 내용 요약 (3문장)")
url: str = Field(..., description="회의록 URL")
class DocumentSummarizeRequest(BaseModel):
"""문서 요약 요청"""
document_id: str = Field(..., description="문서 ID")
current_meeting_id: Optional[str] = Field(None, description="현재 회의 ID (비교용)")
summary_type: str = Field("similar_content", description="요약 타입 (similar_content, full)")
class DocumentSummary(BaseModel):
"""문서 요약"""
document_id: str
summary: str = Field(..., description="요약 내용")
generated_by: str = Field("claude-3-5-sonnet", description="생성 모델")
tokens_used: int = Field(..., description="사용된 토큰 수")
cached: bool = Field(False, description="캐시 여부")
class DocumentStats(BaseModel):
"""문서 통계"""
total_documents: int = Field(..., description="전체 문서 수")
by_type: Dict[str, int] = Field(..., description="타입별 문서 수")
by_domain: Dict[str, int] = Field(..., description="도메인별 문서 수")
total_chunks: int = Field(..., description="전체 청크 수")
+108
View File
@@ -0,0 +1,108 @@
"""
회의록 데이터 모델
"""
from typing import Optional, List, Dict, Any
from datetime import datetime
from pydantic import BaseModel, Field
class MinutesSection(BaseModel):
"""회의록 섹션"""
section_id: str = Field(..., description="섹션 ID")
type: str = Field(..., description="섹션 타입")
title: str = Field(..., description="섹션 제목")
content: Optional[str] = Field(None, description="섹션 내용")
order: int = Field(0, description="순서")
verified: bool = Field(False, description="검증 여부")
class RagMinutes(BaseModel):
"""RAG 회의록 모델"""
# Meeting 정보
meeting_id: str = Field(..., description="회의 ID")
title: str = Field(..., description="회의 제목")
purpose: Optional[str] = Field(None, description="회의 목적")
description: Optional[str] = Field(None, description="회의 설명")
scheduled_at: Optional[str] = Field(None, description="예약 일시")
location: Optional[str] = Field(None, description="장소")
organizer_id: str = Field(..., description="주최자 ID")
# Minutes 정보
minutes_id: str = Field(..., description="회의록 ID")
minutes_status: str = Field(..., description="회의록 상태")
minutes_version: int = Field(..., description="회의록 버전")
created_by: str = Field(..., description="작성자")
finalized_by: Optional[str] = Field(None, description="확정자")
finalized_at: Optional[str] = Field(None, description="확정 일시")
# 회의록 섹션 (JSON)
sections: List[MinutesSection] = Field(default_factory=list, description="회의록 섹션 목록")
# 전체 회의록 내용 (검색용 텍스트)
full_content: str = Field(..., description="전체 회의록 내용")
# Embedding
embedding: Optional[List[float]] = Field(None, description="임베딩 벡터 (1536차원)")
# 메타데이터
created_at: Optional[str] = Field(None, description="생성 일시")
updated_at: Optional[str] = Field(None, description="수정 일시")
class Config:
json_schema_extra = {
"example": {
"meeting_id": "MTG-2025-001",
"title": "2025 Q1 마케팅 전략 회의",
"purpose": "2025년 1분기 마케팅 전략 수립",
"minutes_id": "MIN-2025-001",
"minutes_status": "FINALIZED",
"minutes_version": 1,
"created_by": "user@example.com",
"organizer_id": "organizer@example.com",
"sections": [
{
"section_id": "SEC-001",
"type": "DISCUSSION",
"title": "시장 분석",
"content": "2025년 시장 동향 분석...",
"order": 1,
"verified": True
}
],
"full_content": "2025 Q1 마케팅 전략 회의..."
}
}
class MinutesSearchRequest(BaseModel):
"""회의록 검색 요청"""
query: str = Field(..., min_length=1, description="검색 쿼리 (회의록 내용)")
top_k: int = Field(5, ge=1, le=20, description="반환할 최대 결과 수")
similarity_threshold: float = Field(0.7, ge=0.0, le=1.0, description="최소 유사도 임계값")
class Config:
json_schema_extra = {
"example": {
"query": "마케팅 전략 수립",
"top_k": 5,
"similarity_threshold": 0.7
}
}
class MinutesSearchResult(BaseModel):
"""회의록 검색 결과"""
minutes: RagMinutes
similarity_score: float = Field(..., ge=0.0, le=1.0, description="유사도 점수")
class Config:
json_schema_extra = {
"example": {
"minutes": {
"meeting_id": "MTG-2025-001",
"title": "2025 Q1 마케팅 전략 회의",
"minutes_id": "MIN-2025-001"
},
"similarity_score": 0.92
}
}
+97
View File
@@ -0,0 +1,97 @@
"""
용어집 데이터 모델
"""
from typing import Optional, List, Dict, Any
from datetime import datetime
from pydantic import BaseModel, Field
from uuid import UUID, uuid4
class DocumentSource(BaseModel):
"""문서 출처 정보"""
type: str = Field(..., description="문서 타입 (업무매뉴얼, 정책 및 규정 등)")
title: str = Field(..., description="문서 제목")
url: Optional[str] = Field(None, description="문서 URL")
excerpt: Optional[str] = Field(None, description="문서 발췌")
class Term(BaseModel):
"""용어 모델"""
term_id: str = Field(..., description="용어 ID")
term_name: str = Field(..., description="용어명")
normalized_name: str = Field(..., description="정규화된 용어명 (소문자, 공백 제거)")
category: str = Field(..., description="카테고리")
definition: str = Field(..., description="용어 정의")
context: Optional[str] = Field(None, description="회사 내 사용 맥락")
synonyms: List[str] = Field(default_factory=list, description="동의어 목록")
related_terms: List[str] = Field(default_factory=list, description="관련 용어 목록")
document_source: Optional[DocumentSource] = Field(None, description="출처 문서")
confidence_score: float = Field(0.0, ge=0.0, le=1.0, description="신뢰도 점수")
usage_count: int = Field(0, ge=0, description="사용 횟수")
last_updated: Optional[str] = Field(None, description="마지막 업데이트 일시")
embedding: Optional[List[float]] = Field(None, description="임베딩 벡터 (1536차원)")
class Config:
json_schema_extra = {
"example": {
"term_id": "cs_int_001",
"term_name": "VoC (Voice of Customer)",
"normalized_name": "voc voice of customer",
"category": "고객서비스-분석",
"definition": "고객이 상품이나 서비스를 이용하면서 느낀 경험을 수집하고 분석하는 활동",
"context": "당사의 VoC 관리 시스템은 모든 채널에서 수집된 의견을 통합 분석합니다.",
"synonyms": ["고객의소리", "Customer Voice"],
"related_terms": ["CS", "CRM"],
"confidence_score": 0.95,
"usage_count": 247
}
}
class TermSearchRequest(BaseModel):
"""용어 검색 요청"""
query: str = Field(..., min_length=1, description="검색 쿼리")
top_k: int = Field(5, ge=1, le=20, description="반환할 최대 결과 수")
confidence_threshold: float = Field(0.7, ge=0.0, le=1.0, description="최소 신뢰도 임계값")
search_type: str = Field("hybrid", description="검색 타입 (keyword, vector, hybrid)")
class Config:
json_schema_extra = {
"example": {
"query": "고객 만족도 조사",
"top_k": 5,
"confidence_threshold": 0.7,
"search_type": "hybrid"
}
}
class TermSearchResult(BaseModel):
"""용어 검색 결과"""
term: Term
relevance_score: float = Field(..., ge=0.0, le=1.0, description="관련도 점수")
match_type: str = Field(..., description="매칭 타입 (keyword, vector, hybrid)")
class TermExplainRequest(BaseModel):
"""용어 설명 요청"""
term_id: str = Field(..., description="용어 ID")
meeting_context: Optional[str] = Field(None, description="회의 맥락")
max_context_docs: int = Field(3, ge=1, le=10, description="최대 참고 문서 수")
class TermExplanation(BaseModel):
"""용어 설명"""
term: Term
explanation: str = Field(..., description="맥락 기반 설명")
context_documents: List[Dict[str, Any]] = Field(default_factory=list, description="참고 문서")
generated_by: str = Field("claude-3-5-sonnet", description="생성 모델")
cached: bool = Field(False, description="캐시 여부")
class TermStats(BaseModel):
"""용어 통계"""
total_terms: int = Field(..., description="전체 용어 수")
by_category: Dict[str, int] = Field(..., description="카테고리별 용어 수")
by_source_type: Dict[str, int] = Field(..., description="출처 타입별 용어 수")
avg_confidence: float = Field(..., description="평균 신뢰도")