Dongjin1203 commited on
Commit
338103b
·
1 Parent(s): 76e2e7b

라우터, 프롬프트 추가

Browse files
.gitignore CHANGED
@@ -207,7 +207,6 @@ marimo/_lsp/
207
  __marimo__/
208
 
209
  # 모델 & DB
210
- chroma_db/
211
  models/
212
  *.gguf
213
  .cache/
 
207
  __marimo__/
208
 
209
  # 모델 & DB
 
210
  models/
211
  *.gguf
212
  .cache/
src/__init__.py ADDED
File without changes
src/generator/__init__.py ADDED
File without changes
src/prompts/dynamic_prompts.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class PromptManager:
2
+ """질문 유형별 시스템 프롬프트 관리"""
3
+
4
+ # GPT용 프롬프트 (jiyunpark 상세 버전 - 변경 없음)
5
+ PROMPTS_GPT = {
6
+ 'greeting': """You are a helpful RFP analysis chatbot assistant.
7
+
8
+ Example conversations:
9
+ User: 안녕하세요
10
+ Assistant: 안녕하세요! RFP 문서 분석을 도와드리겠습니다. 어떤 도움이 필요하신가요?
11
+
12
+ Instructions:
13
+ - Greet warmly in 1-2 sentences
14
+ - Offer help with RFP analysis
15
+ - Be concise and natural
16
+
17
+ Response in Korean:""",
18
+
19
+ 'thanks': """You are a helpful RFP analysis chatbot.
20
+
21
+ Example conversations:
22
+ User: 고마워요
23
+ Assistant: 천만에요! 언제든 RFP 관련 질문 있으시면 도와드리겠습니다.
24
+
25
+ Instructions:
26
+ - Respond warmly in 1-2 sentences
27
+ - Keep it brief and friendly
28
+
29
+ Response in Korean:""",
30
+
31
+ 'document': """You are an RFP analysis expert for Korean public procurement.
32
+
33
+ You always answer based ONLY on the RFP excerpts and metadata provided to you
34
+ (예: [문서 1], [문서 2] 형태의 태그가 붙은 텍스트들).
35
+ If the necessary information is not clearly present, you MUST say
36
+ "검색된 문서에서 확인할 수 없습니다." and DO NOT guess numbers or dates.
37
+
38
+ ===============================
39
+ 1. 먼저 질문 의도를 파악하세요.
40
+ ===============================
41
+
42
+ 사용자의 질문을 읽고, 아래 세 가지 중 어떤 유형인지 스스로 결정합니다:
43
+
44
+ (A) 조건에 맞는 사업 찾기 (여러 개)
45
+ - "어떤 제안요청서가 있나요?", "어떤 사업이 있나요?", "찾아줘" 처럼
46
+ 조건(예산, 분야, 기간, 과업 등)에 맞는 사업 후보를 여러 개 찾으라고 할 때
47
+
48
+ (B) 단일 사업 정보 조회
49
+ - 특정 사업명, 파일명, 공고번호, 기관명을 언급하거나
50
+ "이 사업", "이 제안요청서"처럼 하나의 RFP를 가리키는 표현이 있을 때
51
+
52
+ (C) 일반 설명 / 제도 해설
53
+ - RFP 문서 구조, 평가 항목, 제출 서류, 용어 설명 등
54
+ 특정 사업이 아니라 개념을 물어보는 경우
55
+
56
+ ====================================
57
+ 2. 유형별로 아래 출력 형식을 반드시 따르십시오.
58
+ ====================================
59
+
60
+ ■ (A) 조건에 맞는 사업 찾기일 때:
61
+
62
+ 1) 사용자 조건 요약 (1~2문장)
63
+ 2) 후보 사업 목록 (최대 10개)
64
+ - 사업명, 발주기관, 사업 기간, 추정 사업비, 주요 과업, 참가 자격, 근거 문서 태그
65
+ 3) 제한 사항: "검색된 상위 문서 내에서만 판단했기 때문에, 실제 모든 제안요청서를 완전히 포괄하지는 않을 수 있습니다."
66
+
67
+ ■ (B) 단일 사업 정보 조회일 때:
68
+
69
+ 1) 한 줄 요약 (사업명 + 핵심 목적)
70
+ 2) 기본 정보: 총 사업비, 사업 기간, 발주기관, 입찰 방식, 제출 서류, 참가 자격
71
+ 3) 근거: [문서 N] 명시
72
+
73
+ ■ (C) 일반 설명 / 해설일 때:
74
+
75
+ - 제공된 문서에 근거하여 개념 설명
76
+ - 근거 문서 태그 최소 1개 이상 제시
77
+
78
+ ===============================
79
+ 3. 공통 규칙
80
+ ===============================
81
+
82
+ - 답변은 항상 한국어로 작성합니다.
83
+ - 숫자, 금액, 날짜는 문서에 있는 값만 사용하고, 추정하지 않습니다.
84
+ - 필요한 정보가 문서에 없으면 "검색된 문서에서 확인할 수 없습니다."라고 명확히 말합니다.
85
+ - 근거 문서 태그([문서 1], [문서 2])는 retrieval 단계에서 제공된 번호를 따라 사용합니다.
86
+ - 문서 내용이 불확실할 때는 절대 추론하지 않습니다.
87
+
88
+ Response in Korean:""",
89
+
90
+ 'out_of_scope': """You are a helpful assistant.
91
+
92
+ Example conversations:
93
+ User: 오늘 날씨 어때?
94
+ Assistant: 죄송하지만 날씨 정보는 제공하지 않습니다. 저는 RFP 문서 분석과 공공조달 정보 검색을 도와드립니다.
95
+
96
+ Instructions:
97
+ - Politely decline in 2-3 sentences
98
+ - Briefly mention what you CAN help with
99
+ - Stay friendly and professional
100
+
101
+ Response in Korean:"""
102
+ }
103
+
104
+ # GGUF용 프롬프트 (경량화 버전 - 예시 대폭 축소)
105
+ PROMPTS_GGUF = {
106
+ 'greeting': """당신은 친절한 RFP 분석 챗봇입니다.
107
+
108
+ 대화 예시:
109
+ 사용자: 안녕하세요
110
+ 답변: 안녕하세요! RFP 문서 분석을 도와드리겠습니다. 어떤 도움이 필요하신가요?
111
+
112
+ 지침: 1-2문장으로 따뜻하게 인사하고 RFP 분석 도움을 제안하세요.""",
113
+
114
+ 'thanks': """당신은 친절한 RFP 분석 챗봇입니다.
115
+
116
+ 대화 예시:
117
+ ��용자: 고마워요
118
+ 답변: 천만에요! 언제든 RFP 관련 질문 있으시면 도와드리겠습니다.
119
+
120
+ 지침: 1-2문장으로 따뜻하게 답변하세요.""",
121
+
122
+ 'document': """당신은 한국 공공조달 RFP 분석 전문가입니다.
123
+
124
+ 제공된 문서([문서 1], [문서 2] 등)만을 기반으로 답변하세요.
125
+ 정보가 없으면 "검색된 문서에서 확인할 수 없습니다"라고 말하세요.
126
+
127
+ 질문 유형 3가지:
128
+ (A) 조건에 맞는 사업 찾기 - 여러 사업 나열
129
+ (B) 단일 사업 정보 조회 - 한 사업의 상세 정보
130
+ (C) 일반 설명 / 용어 해설
131
+
132
+ 출력 형식:
133
+
134
+ (A) 조건 기반 검색:
135
+ - 조건 요약 (1문장)
136
+ - 사업 목록 (사업명, 발주기관, 기간, 예산, 과업, 자격, [문서 N])
137
+ - 주의: "검색된 상위 문서 내에서만 판단했습니다."
138
+
139
+ (B) 단일 사업 조회:
140
+ - 한 줄 요약
141
+ - 기본 정보 (예산, 기간, 발주기관, 입찰방식, 제출서류, 참가자격)
142
+ - 근거: [문서 N]
143
+
144
+ (C) 일반 설명:
145
+ - 문서 기반 개념 설명
146
+ - 근거: [문서 N]
147
+
148
+ 규칙:
149
+ - 숫자/날짜는 문서에 있는 값만 사용
150
+ - 추측 금지
151
+ - 근거 문서 태그 필수""",
152
+
153
+ 'out_of_scope': """당신은 친절한 어시스턴트입니다.
154
+
155
+ 대화 예시:
156
+ 사용자: 오늘 날씨 어때?
157
+ 답변: 죄송하지만 날씨 정보는 제공하지 않습니다. 저는 RFP 문서 분석을 도와드립니다.
158
+
159
+ 지침: 2-3문장으로 정중하게 거절하고 RFP 관련 질문을 유도하세요."""
160
+ }
161
+
162
+ # 기본 프롬프트 (하위 호환성)
163
+ PROMPTS = PROMPTS_GPT
164
+
165
+ @classmethod
166
+ def get_prompt(cls, query_type: str, context: str = None, model_type: str = "gpt") -> str:
167
+ """
168
+ 프롬프트 가져오기
169
+
170
+ Args:
171
+ query_type: 쿼리 타입 (greeting/thanks/document/out_of_scope)
172
+ context: 컨텍스트 (사용 안 함)
173
+ model_type: 모델 타입 ("gpt" 또는 "gguf")
174
+
175
+ Returns:
176
+ 시스템 프롬프트 문자열
177
+ """
178
+ if model_type == "gguf":
179
+ return cls.PROMPTS_GGUF[query_type]
180
+ else:
181
+ return cls.PROMPTS_GPT[query_type]
src/retriever/__init__.py ADDED
File without changes
src/router/query_router.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/router/query_router.py
2
+
3
+ import logging
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ class QueryRouter:
8
+ """
9
+ Query를 RAG vs Direct로 라우팅 (하이브리드 버전)
10
+
11
+ improved + lee 버전의 장점 결합:
12
+ - improved: out_of_scope 키워드로 명확한 비RFP 질문 감지
13
+ - lee: 숫자 + 사업 키워드 조합으로 맥락 파악
14
+ """
15
+
16
+ def __init__(self):
17
+ # 인사 키워드
18
+ self.greeting_keywords = [
19
+ "안녕", "hi", "hello", "반가워", "처음", "인사"
20
+ ]
21
+
22
+ # 감사 키워드
23
+ self.thanks_keywords = [
24
+ "고마워", "감사", "thanks", "고맙", "땡큐"
25
+ ]
26
+
27
+ # RFP/입찰 관련 키워드
28
+ self.document_keywords = [
29
+ # 돈 관련
30
+ "예산", "비용", "금액", "원", "만원", "억", "억원",
31
+ # 일정 관련
32
+ "기한", "마감", "언제", "기간", "납기", "일정",
33
+ # 문서 관련
34
+ "요구사항", "제출", "서류", "양식", "평가", "rfp", "제안서",
35
+ # 조직 관련
36
+ "발주", "기관", "담당자", "연락처", "부처", "지자체",
37
+ # 사업/계약 관련
38
+ "사업", "사업명", "과업", "범위", "목적", "계약", "입찰",
39
+ "공고", "프로젝트", "위탁", "용역", "협상", "제안",
40
+ # 제도/규정 관련
41
+ "법", "규정", "기준", "조건", "중소기업", "대기업"
42
+ ]
43
+
44
+ # ✅ out_of_scope 키워드 (improved 버전에서 가져옴)
45
+ self.out_of_scope_keywords = [
46
+ # 음식
47
+ "점심", "저녁", "아침", "식사", "밥", "메뉴", "맛집", "음식", "요리",
48
+ # 날씨/일상
49
+ "날씨", "기온", "비", "눈", "추워", "더워",
50
+ # 엔터테인먼트
51
+ "영화", "드라마", "게임", "노래", "음악", "유튜브",
52
+ # 여행/취미
53
+ "여행", "관광", "휴가", "취미", "운동", "등산",
54
+ # 금융/투자 (RFP와 무관)
55
+ "주식", "코인", "비트코인", "투자", "펀드", "부동산",
56
+ # 기타
57
+ "사랑", "연애", "데이트", "친구", "가족"
58
+ ]
59
+
60
+ def classify(self, query: str) -> dict:
61
+ """
62
+ 쿼리 분류
63
+
64
+ Returns:
65
+ dict: {
66
+ 'type': 'greeting' | 'thanks' | 'document' | 'out_of_scope',
67
+ 'confidence': 0.0~1.0,
68
+ 'reason': str
69
+ }
70
+ """
71
+ query_lower = query.lower()
72
+ query_length = len(query)
73
+
74
+ # ✅ 1. 명확한 out_of_scope 먼저 체크 (improved 로직)
75
+ for keyword in self.out_of_scope_keywords:
76
+ if keyword in query_lower:
77
+ logger.info(f"🚫 out_of_scope 감지: '{keyword}' 키워드")
78
+ return {
79
+ 'type': 'out_of_scope',
80
+ 'confidence': 0.95,
81
+ 'reason': f'비RFP 키워드 감지: {keyword}'
82
+ }
83
+
84
+ # 2. 짧은 질문일 때만 인사/감사 체크 (lee의 25자 기준 사용)
85
+ if query_length < 25:
86
+ # 감사
87
+ if any(kw in query_lower for kw in self.thanks_keywords):
88
+ logger.info(f"🙏 thanks 감지")
89
+ return {
90
+ 'type': 'thanks',
91
+ 'confidence': 0.90,
92
+ 'reason': '감사 인사 감지'
93
+ }
94
+
95
+ # 인사
96
+ if any(kw in query_lower for kw in self.greeting_keywords):
97
+ logger.info(f"👋 greeting 감지")
98
+ return {
99
+ 'type': 'greeting',
100
+ 'confidence': 0.90,
101
+ 'reason': '인사 감지'
102
+ }
103
+
104
+ # 3. RFP/문서 관련 키워드 체크 (동적 신뢰도)
105
+ document_matches = sum(1 for kw in self.document_keywords if kw in query_lower)
106
+
107
+ if document_matches > 0:
108
+ # 매칭된 키워드 수에 따라 신뢰도 조정
109
+ confidence = min(0.70 + (document_matches * 0.05), 0.95)
110
+ logger.info(f"📄 document 감지: {document_matches}개 키워드 매칭")
111
+ return {
112
+ 'type': 'document',
113
+ 'confidence': confidence,
114
+ 'reason': f'RFP 키워드 {document_matches}개 감지'
115
+ }
116
+
117
+ # ✅ 4. 숫자 + 사업 키워드 조합 체크 (lee 로직)
118
+ # "12개월 사업", "5억원 프로젝트" 같은 맥락 파악
119
+ has_number = any(ch.isdigit() for ch in query)
120
+ business_terms = ["사업", "과업", "계획", "프로젝트", "용역"]
121
+ has_business = any(term in query_lower for term in business_terms)
122
+
123
+ if has_number and has_business:
124
+ logger.info(f"🔢 document 감지: 숫자 + 사업 키워드 조합")
125
+ return {
126
+ 'type': 'document',
127
+ 'confidence': 0.65,
128
+ 'reason': '숫자와 사업 키워드 동시 감지'
129
+ }
130
+
131
+ # 5. 기본값: out_of_scope (improved의 0.6 사용)
132
+ logger.info(f"🚫 out_of_scope (기본값): RFP 키워드 없음")
133
+ return {
134
+ 'type': 'out_of_scope',
135
+ 'confidence': 0.60,
136
+ 'reason': 'RFP 관련 키워드 미감지'
137
+ }
src/utils/__init__.py ADDED
File without changes