mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 11:49:10 +00:00
feat: init rag service
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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="전체 청크 수")
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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="평균 신뢰도")
|
||||
Reference in New Issue
Block a user