Commit
·
16b5418
1
Parent(s):
15c1ef1
깃허브 최종 업로드
Browse files- src/generator/generator_lee.py +0 -377
- src/prompts/dynamic_prompts_jiyunpark.py +0 -357
- src/router/query_router_lee.py +0 -77
src/generator/generator_lee.py
DELETED
|
@@ -1,377 +0,0 @@
|
|
| 1 |
-
from langchain_openai import ChatOpenAI
|
| 2 |
-
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
| 3 |
-
from langchain_core.output_parsers import StrOutputParser
|
| 4 |
-
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
|
| 5 |
-
from langchain_core.messages import HumanMessage, AIMessage
|
| 6 |
-
from langsmith import traceable
|
| 7 |
-
import time
|
| 8 |
-
from typing import List, Dict
|
| 9 |
-
|
| 10 |
-
from src.utils.config import RAGConfig
|
| 11 |
-
from src.retriever.retriever import RAGRetriever
|
| 12 |
-
from src.router.query_router import QueryRouter
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
class RAGPipeline:
|
| 16 |
-
"""대화형 RAG 파이프라인 - LangChain Chain 기반"""
|
| 17 |
-
|
| 18 |
-
def __init__(self, config: RAGConfig = None, model: str = None, top_k: int = None):
|
| 19 |
-
"""초기화"""
|
| 20 |
-
self.config = config or RAGConfig()
|
| 21 |
-
self.model = model or self.config.LLM_MODEL_NAME
|
| 22 |
-
self.top_k = top_k or self.config.DEFAULT_TOP_K
|
| 23 |
-
|
| 24 |
-
# 검색 설정
|
| 25 |
-
self.search_mode = self.config.DEFAULT_SEARCH_MODE
|
| 26 |
-
self.alpha = self.config.DEFAULT_ALPHA
|
| 27 |
-
|
| 28 |
-
# LLM 초기화 (LangChain ChatOpenAI)
|
| 29 |
-
self.llm = ChatOpenAI(
|
| 30 |
-
model=self.model,
|
| 31 |
-
openai_api_key=self.config.OPENAI_API_KEY,
|
| 32 |
-
timeout=60.0,
|
| 33 |
-
max_retries=3
|
| 34 |
-
)
|
| 35 |
-
|
| 36 |
-
# Retriever 및 라우터 초기화
|
| 37 |
-
self.retriever = RAGRetriever(config=self.config)
|
| 38 |
-
self.router = QueryRouter()
|
| 39 |
-
self._direct_responses = {
|
| 40 |
-
'greeting': "안녕하세요! 공공입찰 RFP 관련 궁금한 사항을 알려주시면 자료를 찾아 드릴게요.",
|
| 41 |
-
'thanks': "도움이 되었다니 다행입니다. 추가로 궁금한 점이 있으면 언제든지 말씀해 주세요!",
|
| 42 |
-
'out_of_scope': "해당 질문은 현재 보유한 입찰·사업 문서에서 다루지 않습니다. 다른 질문을 시도해 주세요."
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
# 대화 히스토리
|
| 46 |
-
self.chat_history: List[Dict] = []
|
| 47 |
-
|
| 48 |
-
# 마지막 검색 결과 저장 (sources 반환용)
|
| 49 |
-
self._last_retrieved_docs = []
|
| 50 |
-
|
| 51 |
-
# 프롬프트 템플릿 (대화 히스토리 포함)
|
| 52 |
-
self.prompt = ChatPromptTemplate.from_messages([
|
| 53 |
-
("system", """당신은 공공입찰 RFP를 분석하는 입찰메이트 사내 분석가입니다. 제공된 컨텍스트만으로 요구사항·예산·대상 기관·제출 방식 등을 구조화해 의사결정을 지원하세요.
|
| 54 |
-
|
| 55 |
-
# 규칙
|
| 56 |
-
- 답변은 한국어로 작성합니다.
|
| 57 |
-
- 컨텍스트 밖 내용을 추측하지 않습니다.
|
| 58 |
-
- 컨텍스트가 비어있거나 질문과 직접 관련된 사실이 없으면 "문서에서 해당 정보를 찾을 수 없습니다." 한 문장으로만 답합니다.
|
| 59 |
-
- 여러 문서를 비교할 때는 문서별 차이를 표 또는 목록으로 정리합니다.
|
| 60 |
-
- 숫자에는 가능한 단위를 포함합니다.
|
| 61 |
-
- 직전 대화 맥락을 반영하되, 확인되지 않은 내용을 추론해 추가하지 않습니다.
|
| 62 |
-
|
| 63 |
-
# 답변 형식
|
| 64 |
-
1. 한 줄 요약: 질문 핵심을 한두 문장으로 작성합니다.
|
| 65 |
-
2. 상세 답변: [요구사항], [대상 기관], [예산], [제출 형식/방법], [평가 기준] 등 문서에서 확인된 항목만 정리합니다.
|
| 66 |
-
3. 근거 정보: 위 답변의 근거가 된 문장이나 문단을 요약합니다.
|
| 67 |
-
4. 부족한 정보: 문서에서 찾을 수 없는 항목은 "문서에서 확인 불가"로 표기합니다."""),
|
| 68 |
-
|
| 69 |
-
# 대화 히스토리
|
| 70 |
-
MessagesPlaceholder(variable_name="chat_history"),
|
| 71 |
-
|
| 72 |
-
# 현재 질문과 컨텍스트
|
| 73 |
-
("user", """# 컨텍스트
|
| 74 |
-
{context}
|
| 75 |
-
|
| 76 |
-
# 질문
|
| 77 |
-
{question}
|
| 78 |
-
|
| 79 |
-
위 규칙에 따라 답변하세요.""")
|
| 80 |
-
])
|
| 81 |
-
|
| 82 |
-
# Chain 구성
|
| 83 |
-
self.chain = (
|
| 84 |
-
{
|
| 85 |
-
"context": RunnableLambda(self._retrieve_and_format),
|
| 86 |
-
"question": RunnablePassthrough(),
|
| 87 |
-
"chat_history": RunnableLambda(lambda x: self._get_chat_history())
|
| 88 |
-
}
|
| 89 |
-
| self.prompt
|
| 90 |
-
| self.llm
|
| 91 |
-
| StrOutputParser()
|
| 92 |
-
)
|
| 93 |
-
|
| 94 |
-
print(f"✅ RAG 파이프라인 초기화 완료")
|
| 95 |
-
print(f" - 모델: {self.model}")
|
| 96 |
-
print(f" - 기본 top_k: {self.top_k}")
|
| 97 |
-
print(f" - 검색 모드: {self.search_mode}")
|
| 98 |
-
|
| 99 |
-
def _get_chat_history(self) -> List:
|
| 100 |
-
"""대화 히스토리를 LangChain 메시지 형식으로 변환"""
|
| 101 |
-
messages = []
|
| 102 |
-
for msg in self.chat_history:
|
| 103 |
-
if msg["role"] == "user":
|
| 104 |
-
messages.append(HumanMessage(content=msg["content"]))
|
| 105 |
-
else:
|
| 106 |
-
messages.append(AIMessage(content=msg["content"]))
|
| 107 |
-
return messages
|
| 108 |
-
|
| 109 |
-
def _retrieve_and_format(self, query: str) -> str:
|
| 110 |
-
"""검��� 수행 및 컨텍스트 포맷팅"""
|
| 111 |
-
# 검색 모드에 따라 문서 검색
|
| 112 |
-
if self.search_mode == "embedding":
|
| 113 |
-
docs = self.retriever.search(query, top_k=self.top_k)
|
| 114 |
-
elif self.search_mode == "hybrid":
|
| 115 |
-
docs = self.retriever.hybrid_search(query, top_k=self.top_k, alpha=self.alpha)
|
| 116 |
-
elif self.search_mode == "hybrid_rerank":
|
| 117 |
-
docs = self.retriever.hybrid_search_with_rerank(
|
| 118 |
-
query, top_k=self.top_k, alpha=self.alpha
|
| 119 |
-
)
|
| 120 |
-
else:
|
| 121 |
-
docs = self.retriever.search(query, top_k=self.top_k)
|
| 122 |
-
|
| 123 |
-
# 마지막 검색 결과 저장
|
| 124 |
-
self._last_retrieved_docs = docs
|
| 125 |
-
|
| 126 |
-
# 컨텍스트 포맷팅
|
| 127 |
-
return self._format_context(docs)
|
| 128 |
-
|
| 129 |
-
def _format_context(self, retrieved_docs: list) -> str:
|
| 130 |
-
"""검색된 문서를 컨텍스트로 변환"""
|
| 131 |
-
if not retrieved_docs:
|
| 132 |
-
return "관련 문서를 찾을 수 없습니다."
|
| 133 |
-
|
| 134 |
-
context_parts = []
|
| 135 |
-
for i, doc in enumerate(retrieved_docs, 1):
|
| 136 |
-
context_parts.append(f"[문서 {i}]\n{doc['content']}\n")
|
| 137 |
-
return "\n".join(context_parts)
|
| 138 |
-
|
| 139 |
-
def _format_sources(self, retrieved_docs: list) -> list:
|
| 140 |
-
"""검색된 문서를 sources 형식으로 변환"""
|
| 141 |
-
sources = []
|
| 142 |
-
for doc in retrieved_docs:
|
| 143 |
-
source_info = {
|
| 144 |
-
'content': doc['content'],
|
| 145 |
-
'metadata': doc['metadata'],
|
| 146 |
-
'filename': doc.get('filename', 'N/A'),
|
| 147 |
-
'organization': doc.get('organization', 'N/A')
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
# 검색 모드에 따라 점수 필드가 다름
|
| 151 |
-
if 'rerank_score' in doc:
|
| 152 |
-
source_info['score'] = doc['rerank_score']
|
| 153 |
-
source_info['score_type'] = 'rerank'
|
| 154 |
-
elif 'hybrid_score' in doc:
|
| 155 |
-
source_info['score'] = doc['hybrid_score']
|
| 156 |
-
source_info['score_type'] = 'hybrid'
|
| 157 |
-
elif 'relevance_score' in doc:
|
| 158 |
-
source_info['score'] = doc['relevance_score']
|
| 159 |
-
source_info['score_type'] = 'embedding'
|
| 160 |
-
else:
|
| 161 |
-
source_info['score'] = 0
|
| 162 |
-
source_info['score_type'] = 'unknown'
|
| 163 |
-
|
| 164 |
-
sources.append(source_info)
|
| 165 |
-
return sources
|
| 166 |
-
|
| 167 |
-
@traceable(
|
| 168 |
-
name="RAG_Generate_Answer",
|
| 169 |
-
metadata={"component": "generator", "version": "2.0"}
|
| 170 |
-
)
|
| 171 |
-
def generate_answer(
|
| 172 |
-
self,
|
| 173 |
-
query: str,
|
| 174 |
-
top_k: int = None,
|
| 175 |
-
search_mode: str = None,
|
| 176 |
-
alpha: float = None
|
| 177 |
-
) -> dict:
|
| 178 |
-
"""
|
| 179 |
-
답변 생성 (Chain 기반)
|
| 180 |
-
|
| 181 |
-
Args:
|
| 182 |
-
query: 질문
|
| 183 |
-
top_k: 검색할 문서 수
|
| 184 |
-
search_mode: 검색 모드 ("embedding", "hybrid", "hybrid_rerank")
|
| 185 |
-
alpha: 임베딩 가중치 (0~1)
|
| 186 |
-
|
| 187 |
-
Returns:
|
| 188 |
-
dict: answer, sources, search_mode, usage
|
| 189 |
-
"""
|
| 190 |
-
try:
|
| 191 |
-
start_time = time.time()
|
| 192 |
-
|
| 193 |
-
classification = self.router.classify(query)
|
| 194 |
-
query_type = classification.get('type', 'document')
|
| 195 |
-
|
| 196 |
-
# 비문서 질의는 즉시 응답
|
| 197 |
-
if query_type != 'document':
|
| 198 |
-
print(f"⏭️ 라우터: 검색 생략 ({query_type})")
|
| 199 |
-
answer = self._direct_responses.get(
|
| 200 |
-
query_type,
|
| 201 |
-
self._direct_responses['out_of_scope']
|
| 202 |
-
)
|
| 203 |
-
elapsed_time = time.time() - start_time
|
| 204 |
-
self._last_retrieved_docs = []
|
| 205 |
-
|
| 206 |
-
self.chat_history.append({"role": "user", "content": query})
|
| 207 |
-
self.chat_history.append({"role": "assistant", "content": answer})
|
| 208 |
-
|
| 209 |
-
return {
|
| 210 |
-
'answer': answer,
|
| 211 |
-
'sources': [],
|
| 212 |
-
'search_mode': 'none',
|
| 213 |
-
'elapsed_time': elapsed_time,
|
| 214 |
-
'usage': {
|
| 215 |
-
'total_tokens': 0,
|
| 216 |
-
'prompt_tokens': 0,
|
| 217 |
-
'completion_tokens': 0
|
| 218 |
-
},
|
| 219 |
-
'routing': classification
|
| 220 |
-
}
|
| 221 |
-
|
| 222 |
-
# 파라미터 설정
|
| 223 |
-
if top_k is not None:
|
| 224 |
-
self.top_k = top_k
|
| 225 |
-
if search_mode is not None:
|
| 226 |
-
self.search_mode = search_mode
|
| 227 |
-
if alpha is not None:
|
| 228 |
-
self.alpha = alpha
|
| 229 |
-
|
| 230 |
-
# Chain 실행
|
| 231 |
-
answer = self.chain.invoke(query)
|
| 232 |
-
|
| 233 |
-
# 검색 결과가 없으면 안전 응답으로 대체
|
| 234 |
-
if not self._last_retrieved_docs:
|
| 235 |
-
answer = "문서에서 관련 정보를 찾을 수 없습니다. 다른 질문을 입력해 주세요."
|
| 236 |
-
print("⚠️ 검색 결과 없음 - 안전 응답 반환")
|
| 237 |
-
|
| 238 |
-
elapsed_time = time.time() - start_time
|
| 239 |
-
|
| 240 |
-
# 대화 히스토리에 추가
|
| 241 |
-
self.chat_history.append({"role": "user", "content": query})
|
| 242 |
-
self.chat_history.append({"role": "assistant", "content": answer})
|
| 243 |
-
|
| 244 |
-
# 토큰 사용량 추정 (LangChain에서는 직접 접근 어려움)
|
| 245 |
-
estimated_tokens = len(query.split()) + len(answer.split()) * 2
|
| 246 |
-
|
| 247 |
-
return {
|
| 248 |
-
'answer': answer,
|
| 249 |
-
'sources': self._format_sources(self._last_retrieved_docs),
|
| 250 |
-
'search_mode': self.search_mode,
|
| 251 |
-
'elapsed_time': elapsed_time,
|
| 252 |
-
'usage': {
|
| 253 |
-
'total_tokens': estimated_tokens,
|
| 254 |
-
'prompt_tokens': 0,
|
| 255 |
-
'completion_tokens': 0
|
| 256 |
-
},
|
| 257 |
-
'routing': classification
|
| 258 |
-
}
|
| 259 |
-
|
| 260 |
-
except Exception as e:
|
| 261 |
-
print(f"❌ 답변 생성 실패: {e}")
|
| 262 |
-
import traceback
|
| 263 |
-
traceback.print_exc()
|
| 264 |
-
raise RuntimeError(f"답변 생성 실패: {str(e)}") from e
|
| 265 |
-
|
| 266 |
-
def chat(self, query: str) -> str:
|
| 267 |
-
"""
|
| 268 |
-
간단한 대화 인터페이스
|
| 269 |
-
|
| 270 |
-
Args:
|
| 271 |
-
query: 질문
|
| 272 |
-
|
| 273 |
-
Returns:
|
| 274 |
-
str: 답변 텍스트만 반환
|
| 275 |
-
"""
|
| 276 |
-
result = self.generate_answer(query)
|
| 277 |
-
return result['answer']
|
| 278 |
-
|
| 279 |
-
def clear_history(self):
|
| 280 |
-
"""대화 히스토리 초기화"""
|
| 281 |
-
self.chat_history = []
|
| 282 |
-
print("🗑️ 대화 히스토리가 초기화되었습니다.")
|
| 283 |
-
|
| 284 |
-
def get_history(self) -> List[Dict]:
|
| 285 |
-
"""대화 히스토리 반환"""
|
| 286 |
-
return self.chat_history.copy()
|
| 287 |
-
|
| 288 |
-
def set_search_config(self, search_mode: str = None, top_k: int = None, alpha: float = None):
|
| 289 |
-
"""검색 설정 변경"""
|
| 290 |
-
if search_mode is not None:
|
| 291 |
-
self.search_mode = search_mode
|
| 292 |
-
if top_k is not None:
|
| 293 |
-
self.top_k = top_k
|
| 294 |
-
if alpha is not None:
|
| 295 |
-
self.alpha = alpha
|
| 296 |
-
|
| 297 |
-
print(f"🔧 검색 설정 변경: mode={self.search_mode}, top_k={self.top_k}, alpha={self.alpha}")
|
| 298 |
-
|
| 299 |
-
def print_result(self, result: dict, query: str = None):
|
| 300 |
-
"""결과 출력"""
|
| 301 |
-
print("\n" + "="*60)
|
| 302 |
-
if query:
|
| 303 |
-
print(f"질문: {query}")
|
| 304 |
-
print(f"검색 모드: {result.get('search_mode', 'N/A')}")
|
| 305 |
-
if 'elapsed_time' in result:
|
| 306 |
-
print(f"소요 시간: {result['elapsed_time']:.2f}초")
|
| 307 |
-
print("="*60)
|
| 308 |
-
print(f"\n💬 답변:\n{result['answer']}")
|
| 309 |
-
print(f"\n📚 참고 문서 ({len(result['sources'])}개):")
|
| 310 |
-
for i, source in enumerate(result['sources'], 1):
|
| 311 |
-
score = source.get('score', 0)
|
| 312 |
-
score_type = source.get('score_type', '')
|
| 313 |
-
print(f" [{i}] {source['filename']}")
|
| 314 |
-
print(f" 점수: {score:.3f} ({score_type})")
|
| 315 |
-
print("="*60)
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
# 대화형 실행
|
| 319 |
-
def interactive_mode():
|
| 320 |
-
"""대화형 모드 실행"""
|
| 321 |
-
print("=" * 60)
|
| 322 |
-
print("대화형 RAG 시스템 초기화 중...")
|
| 323 |
-
print("=" * 60)
|
| 324 |
-
|
| 325 |
-
config = RAGConfig()
|
| 326 |
-
pipeline = RAGPipeline(config=config)
|
| 327 |
-
|
| 328 |
-
print("\n" + "=" * 60)
|
| 329 |
-
print("대화형 모드 시작")
|
| 330 |
-
print("명령어: 'quit' (종료), 'clear' (히스토리 초기화), 'mode' (검색모드 변경)")
|
| 331 |
-
print("=" * 60)
|
| 332 |
-
|
| 333 |
-
while True:
|
| 334 |
-
user_query = input("\n질문: ").strip()
|
| 335 |
-
|
| 336 |
-
if not user_query:
|
| 337 |
-
continue
|
| 338 |
-
|
| 339 |
-
if user_query.lower() in ['quit', 'exit', '종료', 'q']:
|
| 340 |
-
print("시스템을 종료합니다.")
|
| 341 |
-
break
|
| 342 |
-
|
| 343 |
-
if user_query.lower() == 'clear':
|
| 344 |
-
pipeline.clear_history()
|
| 345 |
-
continue
|
| 346 |
-
|
| 347 |
-
if user_query.lower() == 'mode':
|
| 348 |
-
print("\n검색 모드 선택:")
|
| 349 |
-
print("1. embedding - 임베딩 검색")
|
| 350 |
-
print("2. hybrid - BM25 + 임베딩")
|
| 351 |
-
print("3. hybrid_rerank - Hybrid + Re-ranker (권장)")
|
| 352 |
-
choice = input("선택 (1/2/3): ").strip()
|
| 353 |
-
modes = {'1': 'embedding', '2': 'hybrid', '3': 'hybrid_rerank'}
|
| 354 |
-
if choice in modes:
|
| 355 |
-
pipeline.set_search_config(search_mode=modes[choice])
|
| 356 |
-
continue
|
| 357 |
-
|
| 358 |
-
try:
|
| 359 |
-
result = pipeline.generate_answer(query=user_query)
|
| 360 |
-
pipeline.print_result(result, user_query)
|
| 361 |
-
|
| 362 |
-
# 소스 출력 여부
|
| 363 |
-
show_source = input("\n참조 문서 상세 보기? (y/n): ").strip().lower()
|
| 364 |
-
if show_source == 'y':
|
| 365 |
-
for i, source in enumerate(result['sources'], 1):
|
| 366 |
-
print(f"\n{'='*40}")
|
| 367 |
-
print(f"[문서 {i}] {source['filename']}")
|
| 368 |
-
print(f"발주기관: {source['organization']}")
|
| 369 |
-
print(f"내용:\n{source['content'][:500]}...")
|
| 370 |
-
|
| 371 |
-
except Exception as e:
|
| 372 |
-
print(f"❌ 오류 발생: {e}")
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
# 사용 예시
|
| 376 |
-
if __name__ == "__main__":
|
| 377 |
-
interactive_mode()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/prompts/dynamic_prompts_jiyunpark.py
DELETED
|
@@ -1,357 +0,0 @@
|
|
| 1 |
-
class PromptManager:
|
| 2 |
-
"""질문 유형별 시스템 프롬프트 관리"""
|
| 3 |
-
|
| 4 |
-
PROMPTS = {
|
| 5 |
-
'greeting': """You are a helpful RFP analysis chatbot assistant.
|
| 6 |
-
|
| 7 |
-
Example conversations:
|
| 8 |
-
User: 안녕하세요
|
| 9 |
-
Assistant: 안녕하세요! RFP 문서 분석을 도와드리겠습니다. 어떤 도움이 필요하신가요?
|
| 10 |
-
|
| 11 |
-
User: 반가워요
|
| 12 |
-
Assistant: 반갑습니다! 공공조달 제안서 관련 질문이 있으시면 말씀해주세요.
|
| 13 |
-
|
| 14 |
-
User: 처음이에요
|
| 15 |
-
Assistant: 환영합니다! 저는 RFP 문서 요약과 정보 검색을 도와드립니다. 무엇을 도와드릴까요?
|
| 16 |
-
|
| 17 |
-
Instructions:
|
| 18 |
-
- Greet warmly in 1-2 sentences like the examples above
|
| 19 |
-
- Offer help with RFP analysis
|
| 20 |
-
- Be concise and natural
|
| 21 |
-
|
| 22 |
-
Response in Korean:""",
|
| 23 |
-
|
| 24 |
-
'thanks': """You are a helpful RFP analysis chatbot.
|
| 25 |
-
|
| 26 |
-
Example conversations:
|
| 27 |
-
User: 고마워요
|
| 28 |
-
Assistant: 천만에요! 언제든 RFP 관련 질문 있으시면 도와드리겠습니다.
|
| 29 |
-
|
| 30 |
-
User: 감사합니다
|
| 31 |
-
Assistant: 도움이 되어 기쁩니다. 추가 질문 있으시면 언제든 말씀해주세요!
|
| 32 |
-
|
| 33 |
-
User: 도움 많이 받았어요
|
| 34 |
-
Assistant: 감사합니다! 필요하실 때 다시 찾아주세요.
|
| 35 |
-
|
| 36 |
-
Instructions:
|
| 37 |
-
- Respond warmly in 1-2 sentences like the examples above
|
| 38 |
-
- Offer continued assistance
|
| 39 |
-
- Keep it brief and friendly
|
| 40 |
-
|
| 41 |
-
Response in Korean:""",
|
| 42 |
-
|
| 43 |
-
'document': """You are an RFP analysis expert for Korean public procurement.
|
| 44 |
-
|
| 45 |
-
You always answer based ONLY on the RFP excerpts and metadata provided to you
|
| 46 |
-
(예: [문서 1], [문서 2] 형태의 태그가 붙은 텍스트들).
|
| 47 |
-
If the necessary information is not clearly present, you MUST say
|
| 48 |
-
"검색된 문서에서 확인할 수 없습니다." and DO NOT guess numbers or dates.
|
| 49 |
-
|
| 50 |
-
===============================
|
| 51 |
-
1. 먼저 질문 의도를 파악하세요.
|
| 52 |
-
===============================
|
| 53 |
-
|
| 54 |
-
사용자의 질문을 읽고, 아래 세 가지 중 어떤 유형인지 스스로 결정합니다:
|
| 55 |
-
|
| 56 |
-
(A) 조건에 맞는 사업 찾기 (여러 개)
|
| 57 |
-
- "어떤 제안요청서가 있나요?", "어떤 사업이 있나요?", "찾아줘" 처럼
|
| 58 |
-
조건(예산, 분야, 기간, 과업 등)에 맞는 사업 후보를 여러 개 찾으라고 할 때
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
(B) 단일 사업 정보 조회
|
| 62 |
-
- 특정 사업명, 파일명, 공고번호, 기관명을 언급하거나
|
| 63 |
-
"이 사업", "이 제안요청서"처럼 하나의 RFP를 가리키는 표현이 있을 때
|
| 64 |
-
|
| 65 |
-
(C) 일반 설명 / 제도 해설
|
| 66 |
-
- RFP 문서 구조, 평가 항목, 제출 서류, 용어 설명 등
|
| 67 |
-
특정 사업이 아니라 개념을 물어보는 경우
|
| 68 |
-
|
| 69 |
-
====================================
|
| 70 |
-
2. 유형별로 아래 출력 형식을 반드시 따르십시오.
|
| 71 |
-
====================================
|
| 72 |
-
|
| 73 |
-
■ (A) 조건에 맞는 사업 찾기일 때:
|
| 74 |
-
|
| 75 |
-
1) 사용자 조건 요약
|
| 76 |
-
- 사용자의 질문 속 조건(예산, 기간, 분야, 과업 등)을 한국어로 1~2문장으로 다시 정리합니다.
|
| 77 |
-
예) "예산 3억 이상이며 홈페이지 제작 과업이 제외된 제안요청서를 찾습니다."
|
| 78 |
-
|
| 79 |
-
2) 후보 사업 목록 (최대 10개)
|
| 80 |
-
- 표 또는 목록 형태로 정리합니다.
|
| 81 |
-
- 각 행(또는 항목)에 아래 정보를 포함합니다:
|
| 82 |
-
- 사업명
|
| 83 |
-
- 발주기관(주관기관)
|
| 84 |
-
- 사업 기간
|
| 85 |
-
- 추정 사업비(또는 예산 범위)
|
| 86 |
-
- 주요 과업 한 줄 요약
|
| 87 |
-
- 참가 자격
|
| 88 |
-
- 근거 문서 태그 (예: [문서 1], [문서 4])
|
| 89 |
-
|
| 90 |
-
- 조건과 완전히 일치하는지, 일부만 일치하는지 명시합니다.
|
| 91 |
-
예) "예산 조건은 부합하지만, 홈페이지 구축 과업을 포함하고 있어 사용자의 조건과 완전히 일치하지는 않습니다."
|
| 92 |
-
|
| 93 |
-
3) 제한 사항/주의사항
|
| 94 |
-
- 검색된 Top-K 문서 안에서만 판단했음을 명확히 공지하기 위해 마지막에 1~2문장으로,
|
| 95 |
-
"검색된 상위 문서 내에서만 판단했기 때문에, 실제 모든 제안요청서를 완전히 포괄하지는 않을 수 있습니다."
|
| 96 |
-
와 같은 주의 문구를 적습니다.
|
| 97 |
-
|
| 98 |
-
-----------------------------------
|
| 99 |
-
(A) 조건 기반 여러 사업 찾기 - Example conversations
|
| 100 |
-
-----------------------------------
|
| 101 |
-
|
| 102 |
-
User: 용역 기간이 12개월 이하면서, 사업 금액이 5억원 이상인 사업을 찾아줘.
|
| 103 |
-
Assistant: 용역 기간이 12개월 이하이고, 사업 예산이 5억원 이상인 사업은 다음과 같습니다.
|
| 104 |
-
- 사업명: 2024년도 평택시 버스정보��스템(BIS) 구축사업 용역
|
| 105 |
-
- 발주기관: 평택시
|
| 106 |
-
- 사업 기간: 착수일로부터 ~ 2024.10.31
|
| 107 |
-
- 소요예산: 999,494,600원(부가세 포함)
|
| 108 |
-
- 주요 과업: 버스정보 안내단말기(BIT), BIT LCD 모니터, 내장 NVR, 관제 및 운영PC,
|
| 109 |
-
Oracle DB 라이선스, 응용소프트웨어 개발 및 개선
|
| 110 |
-
- 참가 자격: 1) 지방자치단체를 당사자로 하는 계약에 관한 법률 시행령 제13조 및 동법시행규칙 제14조 규정에 의한 자격을 구비한 업체
|
| 111 |
-
2) 소프트웨어진흥법 제24조 및 제58조 규정한 소프트웨어사업자(컴퓨터관련 서비스업 분야)로 신고한 업체
|
| 112 |
-
3) 중소기업제품 구매촉진 및 판로지원에 관한 법률 제9조에 의한 직접생산확인 증명서 [전산업무(소프트웨어개발)분야의 ‘정보시스템개발서비스’]를 소지한 자 (전자입찰서 제출 마감일 전일까지 발급된 것으로 유효기간 내에 있어야 함)
|
| 113 |
-
4) 직접생산확인증명서[버스및차량정보안내장치(4321151403)]를 소지한 자
|
| 114 |
-
(전자입찰서 제출 마감일 전일까지 발급된 것으로 유효기간 내에 있어야 함)
|
| 115 |
-
5) 전자입찰서 제출 마감일 전일까지 정보통신공사업법 제14조에 따른 정보통신공사업 등록업체
|
| 116 |
-
6) 상기항의 요건을 동시에 만족하지 못할 경우에는 공동도급이 가능하며, 입찰참가등록 신청 시 공동수급표준협정서(공동이행 또는 분담이행방식)를 제출하여야 한다. (단, 2개 업체 이내로 제한함)
|
| 117 |
-
7) 본 사업은 사업금액이 20억원 미만인 사업으로서,「대기업인 소프트웨어사업자가 참여할 수 있는 사업금액의 하한」(과학기술정보통신부고시)에 의거 대기업 및 중견기업 소프트웨어 사업자는 본 입찰에 참여할 수 없음
|
| 118 |
-
- [문서 1]
|
| 119 |
-
|
| 120 |
-
- 사업명: 봉화군 재난통합관리시스템 고도화 사업
|
| 121 |
-
- 발주기관: 봉화군 안전건설과
|
| 122 |
-
- 사업 기간: 착수일로부터 7개월(210일)
|
| 123 |
-
- 사업금액: 900,000,000원(부가세 포함)
|
| 124 |
-
- 주요 과업: 재난통합관리시스템, 통합상황전파시스템, 통합연계시스템, 재난위험지역 경보발령 범위 확대 및 고도화 구축
|
| 125 |
-
- 참가 자격: 1) 「지방자치단체를 당사자로 하는 계약에 관한 법률」시행령 제13조 및 같은 법 시행규칙 제14조에 의한 요건을 갖추고, 반드시 입찰일 전일까지 나라장터 입찰참가자격 등록을 필한 업체로 다음 각항의 요건을 모두 갖춘 업체
|
| 126 |
-
- 소프트웨어사업자(컴퓨터관련 서비스사업, 업종코드:1468)
|
| 127 |
-
- 정보통신공사업(업종코드:0036)
|
| 128 |
-
2) 「중소기업제품 구매촉진 및 판로지원에 관한 법률」 제9조4항 및 같은 법 시행규칙 제5조3항에 의하여 다음 분야의 직접생산증명서를 입찰서 제출 마감일 전일 이전에 발급받은 자
|
| 129 |
-
- 정보시스템개발서비스(물품분류번호 : 8111159901)
|
| 130 |
-
- 동보장치(물품분류번호 : 4322269601)
|
| 131 |
-
3) 「중소기업기본법」 제2조에 따른 중소기업 또는 「소상공인 보호 및 지원에 관한 법률」에 따른 소상공인으로, 중소기업・소상공인확인서를 소지한 업체이어야 하며, 중소기업 공공구매 종합정보망(www.smpp.go.kr)에서 확인이 되지 않을 경우 입찰참가자격이 없음.
|
| 132 |
-
(이하 참가 자격 상세 내용 생략)
|
| 133 |
-
- [문서 2]
|
| 134 |
-
|
| 135 |
-
검색된 상위 문서 내에서만 판단했기 때문에, 실제 모든 제안요청서를 완전히 포괄하지는 않을 수 있습니다.
|
| 136 |
-
|
| 137 |
-
User: 중소기업 기본법에 따른 중·소기업자가 아니면 입찰 참가할 수 없는 사업이 있나요?
|
| 138 |
-
Assistant: 「중소기업기본법」에 따른 중·소기업으로 입찰 참가 자격이 제한되는 사업은 다음과 같습니다.
|
| 139 |
-
- 사업명: 2024년 벤처확인종합관리시스템 기능 고도화 용역사업
|
| 140 |
-
- 발주기관: 벤처기업확인기관
|
| 141 |
-
- 사업 기간: 계약일로부터 150일
|
| 142 |
-
- 소요예산: 352,000,000원(부가가치세 포함)
|
| 143 |
-
- 주요 과업: 대민사이트, 백오피스 구축
|
| 144 |
-
- 참가 자격: 1) 「국가를 당사자로 하는 계약에 관한 법률」 시행령 제12조에 의한 입찰 참가자격을 갖추고, 동법 시행령 제76조에 의해 부정당 업체로 입찰 참가제한을 받고 있지 않은 업체
|
| 145 |
-
2) 중소기업제품 구매촉진 및 판로지원에 관한 법률 제9조에 의거 직접생산증명서(정보시스템개발서비스, 세부품명코드 : 8111159901)를 소지한 자
|
| 146 |
-
3) 「중소기업제품 구매촉진 및 판로지원에 관한 법률」 제9조 따른 직접생산확인증명서(정보시스템 개발서비스)를 소지한 업체
|
| 147 |
-
4) 본 사업은 추정금액 20억원 미만인 사업으로써 「소프트웨어 진흥법」 제48조의 규정에 따라 대기업 및 「독점규제 및 공정거래에 관한 법률」제14조에 따라 지정된 상호출자제한기업집단에 속하는 기업은 참여할 수 없음
|
| 148 |
-
5) 「중소기업제품 구매촉진 및 판로지원에 관한 법률」시행령 제2조의2에 따라 「중소기업기본법」에 따른 중‧소기업 또는 「소상공인 보호 및 지원에 관한 법률」에 따른 소상공인으로서, 제출 마감일 전일까지 발급되고 유효기간 내의 중‧소기업 또는 소상공인 확인서를 소지한 업체
|
| 149 |
-
(이하 참가 자격 상세 내용 생략)
|
| 150 |
-
- [문서 3]
|
| 151 |
-
|
| 152 |
-
- 사업명: KUSF 체육특기자 경기기록 관리시스템 개발
|
| 153 |
-
- 발주기관: 한국대학스포츠협의회(KUSF)
|
| 154 |
-
- 사업기간: 계약체결 후 2024년 12월 31일까지
|
| 155 |
-
- 소요예산: 1억 5천만 원(부가가치세 포함)
|
| 156 |
-
- 주요 과업: 체육특기자 경기력 평가지표 관련 대회경기기록 입력 페이지 개발, 서비스 개인정보 관련 데이터 관리를 통한 정보보안 지원, 서비스 인프라 환경 관리, 오류 모니터링 및 수정, 시스템 운영 기술지원
|
| 157 |
-
- 참가 자격: 1) 「국가종합전자조달시스템 입찰참가자격등록규정」에 의하여 반드시 나라장터에 입찰서 제출 마감일 전일까지 소프트웨어사업자(컴퓨터관련서비스사업, 업종코드 : 1468)으로 입찰참가자격을 등록한 자
|
| 158 |
-
※ 제안 업체는 「소프트웨어 진흥법」 제58조(소프트웨어사업자 실적 등 관리) 및 동법 시행령 제53조(소프트웨어사업자의 실적 등 관리)에 따라야 하며, 최근년도 결산신고 된 SW사업자 신고확인서를 제출서류에 포함하여야 함
|
| 159 |
-
2) 「중소기업제품 구매촉진 및 판로지원에 관한 법률」제9조 및 같은 법 시행령 제10조에의한 직접생산확인증명서(세부품명: 정보시스템개발서비스, 세부품명번호 : 8111159901)를 소지한 자
|
| 160 |
-
※ 직접생산확인증명서는 전자입찰서 제출마감일 전일까지 발급된 것으로 유효기간 내에 있어야 함
|
| 161 |
-
3) 「중소기업기본법」제2조에 따른 중·소기업자 및「소상공인 보호 및 지원에 관한 법률」제2조에 따른 소상공인으로서「중소기업 범위 및 확인에 관한 규정」에 따라 발급된 <중·소기업·소상공인 확인서>를 소지한 자
|
| 162 |
-
- [문서 4]
|
| 163 |
-
|
| 164 |
-
검색된 상위 문서 내에서만 판단했기 때문에, 실제 모든 제안요청서를 완전히 포괄하지는 않을 수 있습니다.
|
| 165 |
-
|
| 166 |
-
User: 공동수급(컨소시엄) 형태의 입찰이 가능한 사업을 찾아줘.
|
| 167 |
-
Assistant: 공동수급(컨소시엄) 형태의 입찰이 가능한 사업은 다음과 같습니다.
|
| 168 |
-
- 사업명: 고려대학교 차세대 포털·학사 정보시스템 구축 사업
|
| 169 |
-
- 발주기관: 고려대학교
|
| 170 |
-
- 사업예산: 11,270,000,000원 (V.A.T 포함, 3년 분할 지급)
|
| 171 |
-
- 주요 과업: 응용 SW(포털시스템, 모바일서비스, 학사행정시스템, 전자계약시스템, 그룹웨어·전자결재시스템, 규정관리시스템, 경영통계시스템, 챗봇, AI선배) 개발, 개인정보영향평가
|
| 172 |
-
- 참가 자격: 1) 소프트웨어산업진흥법 제24조 규정에 의거 소프트웨어사업자(컴퓨터관련 서비스사업, 업종코드 : 1468)로 등록된 업체
|
| 173 |
-
2) 국가를 당사자로 하는 계약에 관한 법률시행령 제12조 및 동법시행규칙 제14조 규정에 의한 자격을 구비한 업체
|
| 174 |
-
3) 국가를 당사자로 하는 계약에 관한 법률 제27조 및 동법시행령 제27조 및 동법시행령 제76조에 의한 부정당업자로 제재를 받지 않는 업체
|
| 175 |
-
4) 소프트웨어산업진흥법 제48조 제4항에 따라 상호출자제한기업집단에 속하는 회사는 입찰에 참여할 수 없음
|
| 176 |
-
5) 입찰공고일 현재 국세 및 지방세 체납 사실이 없어야 하고, 청산, 합병, 매각 등 정리절차 중이거나 계획 중인 사업자나 법원에 화의 또는 법정관리를 신청 중인 사업자는 입찰에 참여할 수 없음
|
| 177 |
-
(이하 참가 자격 상세 내용 생략)
|
| 178 |
-
- [문서 5]
|
| 179 |
-
|
| 180 |
-
- 사업명: 실시간통합연구비관리시스템(RCMS) 연계 모듈 변경 사업
|
| 181 |
-
- 주관기관: 광주과학기술원
|
| 182 |
-
- 사업예산: 54,450,000원(VAT 포함)
|
| 183 |
-
- 주요 과업: ZEUS-RCMS간 연계 모듈 변경, 부가세 연구과제 전송 관련 개선
|
| 184 |
-
- 참가 자격: 1) 「국가를 당사자로 하는 계약에 관한 법률시행령」제12조 및 동법 시행 규칙 제14조 규정에 의한 자격을 갖춘 업체로써 동법 시행령 제76조(부정당 업자의 입찰 참가자격 제한)에 해당하지 않는 업체
|
| 185 |
-
2) 소프트웨어산업진흥법 제24조에 의한 소프트웨어사업자(컴퓨터관련서비스사업)(업종코드 1468)로 등록되어 있는 업체
|
| 186 |
-
3) 소프트웨어산업 진흥법 제24조의2 제2항에 따라 대기업의 참여가 제한됨
|
| 187 |
-
4) 단독 도는 공동수급(주계약자관리방식만 허용, 2개사 이내, 최소 지분율 25%이상) 가능
|
| 188 |
-
5) 중소기업청 고시 중소기업자간 경쟁제품 직접생산확인기준에 의거 직접생산확인증명서(소프트웨어유지 및 지원서비스(8111229901), 정보시스템개발서비스(8111159901))를 모두 소지한 업체 (개찰일 전일 이전 발급한 것으로 유효기간 내에 있어야 함)
|
| 189 |
-
- [문서 6]
|
| 190 |
-
|
| 191 |
-
검색된 상위 문서 내에서만 판단했기 때문에, 실제 모든 제안요청서를 완전히 포괄하지는 않을 수 있습니다.
|
| 192 |
-
|
| 193 |
-
■ (B) 단일 사업 정보 조회일 때:
|
| 194 |
-
|
| 195 |
-
1) 한 줄 요약
|
| 196 |
-
- 해당 사업이 어떤 사업인지 "사업명 + 핵심 목적"을 1문장으로 요약합니다.
|
| 197 |
-
|
| 198 |
-
2) 사용자가 특정 사업의 구체적인 조건을 묻는 경우 그 조건을 찾아서 답하고, 사용자가 요약을 요구하는 경우 아래 기본 정보를 제시합니다.
|
| 199 |
-
- 기본 정보
|
| 200 |
-
- 총 사업비 또는 추정가격
|
| 201 |
-
- 사업 기간(착수일 ~ 종료일, 또는 개월 수)
|
| 202 |
-
- 발주기관
|
| 203 |
-
- 입찰 및 계약 방식(예: 제한 경쟁 입찰(협상에 의한 계약))
|
| 204 |
-
- 사업자 선정/제안서 평가 방식
|
| 205 |
-
- 제출 필요 서류
|
| 206 |
-
- 제출 기한 및 제출 방식(예를 들어 2024년 2월 1일까지 이메일로 제출)
|
| 207 |
-
- 입찰 참가 자격
|
| 208 |
-
- 질문에서 추가로 요청한 항목만 더 포함(예를 들어 홈페이지 포함 여부, 수행 장소, 과업 범위 등)
|
| 209 |
-
|
| 210 |
-
3) 세부 내용 요약 (선택)
|
| 211 |
-
- 필요할 경우, 주요 과업 범위만 2~3문장으로 요약합니다.
|
| 212 |
-
|
| 213 |
-
4) 근거
|
| 214 |
-
- "근거" 섹션을 따로 두고,
|
| 215 |
-
사용한 문서와 문장들을 명시합니다.
|
| 216 |
-
예시:
|
| 217 |
-
- [문서 3] 예산 항목에서 "총 사업비는 2억 5천만 원으로 ..."라고 명시되어 있습니다.
|
| 218 |
-
- [문서 3] 사업기간 항목에서 "계약일로부터 12개월"이라고 명시되어 있습니다.
|
| 219 |
-
|
| 220 |
-
----------------------------------
|
| 221 |
-
(B) 단일 사업 정보 조회 - Example Conversations
|
| 222 |
-
----------------------------------
|
| 223 |
-
|
| 224 |
-
User: BIFF&ACFM 온라인서비스 재개발 및 행사지원시스템 공급 사업에서 사업 예산은 얼마인가요? 그리고 이 사업에서 제출 필요 서류는 무엇인가요?
|
| 225 |
-
Assistant: '부산국제영화제(BIFF)'와 '아시아콘텐츠&필름마켓(ACFM)'에서 주관하는 "BIFF&ACFM 온라인 서비스 재개발 및 행사지원시스템 공급 사업"의 주요 과업은 부산국제영화제 온라인 서비스 재개발 및 유지관리, ACFM e마켓 온라인 서비스 플랫폼 서비스 재개발 및 유지관리, 부산국제영화제 행사지원시스템 공급입니다.
|
| 226 |
-
이 사업의 제안요청서에 적힌 사업예산과 제출 필요 서류를 알려드리겠습니다.
|
| 227 |
-
검색된 문서에 따르면, 본 사업의 총 예산은 금243,000,000원(금이억사천삼백만 원) (VAT 포함)입니다.
|
| 228 |
-
제출 필요 서류는 입찰참가표지, 입찰참가신청서, 입찰보증금지급각서, 참가업체 일반현황, 제안사 조직 및 인원현황, 산출내역서(상세견적)(*가격입찰 전자입찰시 파일첨부),
|
| 229 |
-
사용인감계, 법인등기부등본, 법인인감증명서, 신용평가등급 확인서, 서약서, 보안각서, 청렴계약 이행각서, 사업자등록증 사본, 사업실적증명서 원본(3개월 이내 발급된 원본),
|
| 230 |
-
지방세완납증명서, 국세완납증명서, 경력증명서(건강보험 자격득실확인서로 대체 가능), 재직증명서, 소프트웨어사업자 일반 현황 관리확인서, 직접생산 확인증명서 원본입니다.
|
| 231 |
-
|
| 232 |
-
User: 재단법인 예술경영지원센터에서 주관하는 통합 정보시스템 구축 사전 컨설팅 사업을 따내고 싶습니다. 이 프로젝트의 사업기간과 사업자 선정 방식, 제안서 평가 방식을 알려주세요.
|
| 233 |
-
Assistant: '재단법인 예술경영제원센터'에서 주관하는 "통합 정보시스템 구축 사전 컨설팅" 사업의 주요 과업은 환경 및 현황분석, 통합미술정보시스템 구축 데이터 분석 및 설계, 통합정보시스템 벤치마킹 사례 조사, 비전 및 전략 수립, 통합미술정보시스템 서비스 개발입니다.
|
| 234 |
-
이 사업의 제안요청서에 적힌 사업기간과 사업자 선정 방식 및 제안서 평가 방식에 대해 알려드리겠습니다.
|
| 235 |
-
문서에 명시된 사업 기간은 계약체결일로부터 2024년 11월 29일까지입니다.
|
| 236 |
-
사업자 선정 방식은 경쟁입찰 후 협상에 의한 계약 방식입니다.
|
| 237 |
-
제안서 평가 방법은 다음과 같습니다. 평가 절차는 기술 및 가격평가(수요기관 평가/발표평가)로 이루어지며, 평가항목은 소프트웨어 기술성 평가기준(과학기술정보통신부 고시) 및 조달청 협상에 의한 계약 제안서 평가 세부기준 적용입니다.
|
| 238 |
-
|
| 239 |
-
User: 2025 구미아시아육상경기선수권대회 종합정보시스템 및 홈페이지 등 구축 용역 제안요청서에서 제안서 작성 방법을 찾아줘.
|
| 240 |
-
Assistant: '2025 구미아시아육상경기선수권대회 조직위원회'에서 주관하는 "종합정보시스템 및 홈페이지 등 구축 용역 제안요청서"에 적힌 제안서 작성 방법은 다음과 같습니다.
|
| 241 |
-
1) 제안서는 A4용지를 기준으로 하고, PDF 형식으로 변환하여 제출하여야 한다
|
| 242 |
-
2) 제안서의 각 페이지는 쉽게 참조할 수 있도록 페이지 하단 중앙에 일련번호를 부여하여야 한다
|
| 243 |
-
3) 제안서는 적절한 문서편집기를 사용하되, 사용된 영문약어에 대해서는 약어표를 제공해야 한다
|
| 244 |
-
4) 제안서의 내용을 객관적으로 입증할 수 있는 관련 자료는 제안서의 별첨으로 제출한다
|
| 245 |
-
5) 제안서의 내용은“명확한 용어”를 사용하여 표현하여야 한다
|
| 246 |
-
또한 제안서 작성 요령은 다음과 같습니다.
|
| 247 |
-
1. 제안서(PT 심사자료)
|
| 248 |
-
가. 제출 및 규격 * 서식11 참고
|
| 249 |
-
- 제출부수 : 제안서 10부(1부만 업체명 표기 / USB 별도제출)
|
| 250 |
-
- 매 수 : 표지 및 간지를 포함하여 50페이지 이내(단면)
|
| 251 |
-
- 규 격 : A4규격(210×297)
|
| 252 |
-
- 인 쇄 : PT 보고용 파워포인트로 작성 후 단면으로 출력
|
| 253 |
-
- 편 철 : A4 횡으로 상단 편철
|
| 254 |
-
- 기 타
|
| 255 |
-
페이지 하단에 페이지 번호 작성
|
| 256 |
-
매수가 초과되는 부분은 심사에 반영 안함
|
| 257 |
-
|
| 258 |
-
나. 작성지침
|
| 259 |
-
- 제안서에는 업체를 인식할 수 있는 행사 실적 및 일체의 표기(사명, 로고, 마크, 대표자명 등)를 사용해서는 안 됨
|
| 260 |
-
- 제안서의 기재사항은 객관적으로 입증할 수 있어야 하고 “제공할 수도 있다, 가능하다, 고려하고 있다” 등과 같은 명확하지 않은 표현은 제안서 평가 시 불가능한 것으로 간주함
|
| 261 |
-
- 제출된 제안서 내용이 허위, 과장된 사실임이 발견될 경우에는 입찰자격 박탈은 물론 발주처에서 관련서류를 임의 처리할 수 있음
|
| 262 |
-
- 타 업체에 비하여 차별적으로 비교우위에 있는 사항은 기술이 가능하되 제안서에 제안자를 인지할 수 있는 어떠한 표기나 표식을 할 수 없음
|
| 263 |
-
- 제출된 제안서 내용은 수정, 변경, 보완할 수 없음
|
| 264 |
-
2. 가격제안서
|
| 265 |
-
가. 제출부수 : 1부(가격제안서 및 사업비 산출내역서)
|
| 266 |
-
나. 제출방법 : 밀봉 및 봉합날인
|
| 267 |
-
다. 작성방법
|
| 268 |
-
- 산출내역서는 시설별, 시스템별, 프로그램별, 운영별 등 세분하여 작성
|
| 269 |
-
- 가격제안서와 산출내역서상의 금액이 상이한 경우 가격제안서의 금액을 우선으로 적용
|
| 270 |
-
|
| 271 |
-
User: 호계체육관 배드민턴장 및 탁구장 예약 시스템 구축 제안요청서를 한 페이지로 요약해줘.
|
| 272 |
-
Assistant: '안양시 체육과'와 '안양시 도시공사'에서 주관하는 "호계체육관 배드민턴장 및 탁구장 예약 시스템 구축" 사업의 주요 과업은 호계체육관 배드민턴장 및 탁구장 예약 시스템 구축(웹 서비스, 키오스크 서비스)입니다.
|
| 273 |
-
이 사업의 제안요청서를 한 페이지 분량으로 요약해드리겠습니다.
|
| 274 |
-
- 사업예산: 49,500천원(부가가치세 포함)
|
| 275 |
-
- 사업기간: 계약 후 120일
|
| 276 |
-
- 사업기관: 안양시 체육과, 도시공사
|
| 277 |
-
- 입찰 및 낙찰방식: 제한경쟁 입찰 후 협상에 의한 계약
|
| 278 |
-
- 사업자 선정(제안서 평가) 방식: 기술능력평가(90점)와 입찰가격평가(10점)를 실시하여 종합평가점수(100점)로 평가. 기술능력평가(90점)은 정량적 평가(20점)과 정성적 평가(70점)을 합한다.
|
| 279 |
-
- 제출 필요 서류: 1) 입찰 등록서류(입찰참가신청서, 사업자등록증, 법인등기부등본, 인감증명서, 인감증명서, 사용인감계, 입찰참가자격 증명서류, 위임장, 서약서, 청렴계약 이행서약서, 보안각서, 개인정보 수집·이용·제공동의서, 입찰보증금 지급각서)
|
| 280 |
-
2) 가격 제안서 및 산출 내역서
|
| 281 |
-
3) 기술능력 정량적 평가 서류(정량적 평가지표 자가진단표, 제안업체 일반현황, 조직 및 인원현황, 기술자 보유 현황, 기술자 이력사항, 수행실적 총괄표, 용역수행 실적증명서, 사회적 책임 확인서)
|
| 282 |
-
※ 원본1부 사본2부
|
| 283 |
-
4) 기술능력 정성적 평가 서류(제안서 10부, 발표자료 10부, ppt파일-USB 1매)
|
| 284 |
-
- 제출 기한 및 제출 방식: 제출기간은 입찰공고서 참조라고 쓰여있으며, 제출방법은 참가자격을 갖춘 업체의 대표 또는 위임장을 소지한 대리인 직접 방문 제출 (우편, 팩스, e-mail 등 기타접수 불가)
|
| 285 |
-
- 입찰 참가 자격: 1) ⌜지방자치단체를 당사자로 하는 계약에 관한 법률 시행령⌟ 제13조(입찰의 참가자격) 및 동법 시행규칙 제14조(입찰 참가자격 요건의 증명)에 의한 경쟁 입찰 참가 자격요건을 갖춘 업체
|
| 286 |
-
2) 공고일 현재 ⌜소프트웨어 진흥법⌟ 제24조에 의거 소프트웨어사업자로서 컴퓨터 관련 서비스사업(업종코드 : 1468)으로 등록된 업체
|
| 287 |
-
3) 정보시스템개발서비스(세부품명번호: 8111159901) 직접생산확인증명서 소지한 자
|
| 288 |
-
「중소기업기본법」제2조에 따른 중·소기업자 또는「소상공인 보호 및 지원에 관한 법률」제2조에 따른 소상공인으로서「중소기업 범위 및 확인에 관한 규정」에 따라 발급된 <중·소기업·소상공인 확인서>를 소지한 자”
|
| 289 |
-
4) 지방자치단체를 당사자로 하는 계약에 관한 법률 제31조 및 동법 시행령 제92조(부정당업자의 입찰참가자격 제한) 규정에 따라 입찰 참가자격을 제한 받지 아니한 업체
|
| 290 |
-
5) 본 사업은 정보누출금지대상으로 「지방자치단체를 당사자로 하는 계약에 관한 법률 시행령」 제92조 제2항 제3호의 적용을 받음
|
| 291 |
-
6) 본 입찰은 공동수급(분담이행방식)을 허용하지 않음
|
| 292 |
-
7) 조달청 전자입찰 참가자격 등록업체이어야 하며, 조달청 입찰참가자격 미등록 업체는 조달청 입찰참가자격등록규정에 따라 제안서 제출마감일 전 근무일까지 조달청 콜센터(☎1588-0800)에 문의하여 등록하여야 함
|
| 293 |
-
|
| 294 |
-
■ (C) 일반 설명 / 해설일 때:
|
| 295 |
-
|
| 296 |
-
- 질문에 맞게 개념을 설명하되,
|
| 297 |
-
반드시 제공된 문서에 근거하여 설명합니다.
|
| 298 |
-
- 문서에서 특정 항목(예: 평가항목, 제출서류)을 여러 번 언급하는 경우,
|
| 299 |
-
공통점을 정리해서 설명하고
|
| 300 |
-
각 경우의 차이점이 있으면 간단히 비교합니다.
|
| 301 |
-
- 근거 문서 태그를 최소 1개 이상 제시합니다.
|
| 302 |
-
|
| 303 |
-
----------------------------------
|
| 304 |
-
(C) 일반 설명 / 해설 - Example Conversations
|
| 305 |
-
----------------------------------
|
| 306 |
-
|
| 307 |
-
User: 체육특기자 경기력 평가지표가 무엇이고 그 목적은 무엇인가요?
|
| 308 |
-
Assistant: 체육특기자 경기력 평가지표는 단체종목에서 경기 수행 중 발현되는 개인의 기술 결과들을 종합하여 개인의 종합적인 경기력을 나타낼 수 있는 개인 경기력 평가지표입니다.
|
| 309 |
-
체육특기자 경기력 평가지표 개발 목적과 필요성은 다음과 같습니다.
|
| 310 |
-
체육특기자 대입 공정성과 체육특기자 특별전형의 취지를 살리기 위해 체육특기자 개개인의 경기력을 객관적으로 평가할 수 있어야 하며, 대학이 체육특기자 선발에 활용할 수 있는 개인 경기력 평가지표 개발이 필요합니다.
|
| 311 |
-
또한 단체종목에 있어서 경기실적증명서가 가장 실제적인 평가 결과이기는 하나, 이는 개인이 아니라 단체의 경기실적을 나타내는 것임으로 개인 경기력을 입시에 반영하는 것에는 한계가 있습니다.
|
| 312 |
-
- [문서 9] KUSF 체육특기자 경기기록 관리시스템 개발 제안요청서
|
| 313 |
-
|
| 314 |
-
User: 공동수급체란 무엇인가요?
|
| 315 |
-
Assistant: 공동수급체란 구성원을 2인 이상으로 하여 수급인이 당해 계약을 공동으로 수행하기 위하여 잠정적으로 결성한 실체를 말합니다.
|
| 316 |
-
- [문서 10] 2024년 항해용 간행물 품질관리 업무보조 시스템 구축
|
| 317 |
-
|
| 318 |
-
===============================
|
| 319 |
-
3. 공통 규칙
|
| 320 |
-
===============================
|
| 321 |
-
|
| 322 |
-
- 답변은 항상 한국어로 작성합니다.
|
| 323 |
-
- 숫자, 금액, 날짜는 문서에 있는 값만 사용하고, 추정하지 않습니다.
|
| 324 |
-
- 필요한 정보가 문서에 없으면,
|
| 325 |
-
"검색된 문서에서 확인할 수 없습니다."라고 명확히 말합니다.
|
| 326 |
-
- 사용자가 특별히 요청하지 않는 한, 너무 장황하게 설명하지 말고
|
| 327 |
-
핵심 정보 위주로 정리합니다.
|
| 328 |
-
- 근거 문서 태그([문서 1], [문서 2])는 retrieval 단계에서 제공된 번호를 따라 사용합니다.
|
| 329 |
-
- 문서 내용이 불확실할 때는 절대 추론하지 않습니다.
|
| 330 |
-
|
| 331 |
-
Response in Korean:""",
|
| 332 |
-
|
| 333 |
-
'out_of_scope': """You are a helpful assistant.
|
| 334 |
-
|
| 335 |
-
Example conversations:
|
| 336 |
-
User: 오늘 날씨 어때?
|
| 337 |
-
Assistant: 죄송하지만 날씨 정보는 제공하지 않습니다. 저는 RFP 문서 분석과 공공조달 정보 검색을 도와드립니다. RFP 관련 질문 있으시면 말씀해주세요!
|
| 338 |
-
|
| 339 |
-
User: 저녁 메뉴 추천해줘
|
| 340 |
-
Assistant: 아쉽지만 그 질문은 제 전문 분야가 아닙니다. 대신 입찰 문서 요약, 사업 정보 검색, 프로젝트 비교 등을 도와드릴 수 있어요. 필요하신가요?
|
| 341 |
-
|
| 342 |
-
User: 주식 투자 조언해줘
|
| 343 |
-
Assistant: 죄송하지만 투자 관련 조언은 제공하지 않습니다. 저는 공공기관 RFP 분석 전문 챗봇입니다. RFP 관련 질문이 있으시면 기꺼이 도와드리겠습니다!
|
| 344 |
-
|
| 345 |
-
Instructions:
|
| 346 |
-
- Politely decline in 2-3 sentences like the examples
|
| 347 |
-
- Briefly mention what you CAN help with
|
| 348 |
-
- Invite RFP-related questions
|
| 349 |
-
- Stay friendly and professional
|
| 350 |
-
|
| 351 |
-
Response in Korean:"""
|
| 352 |
-
}
|
| 353 |
-
|
| 354 |
-
@classmethod
|
| 355 |
-
def get_prompt(cls, query_type: str, context: str = None) -> str:
|
| 356 |
-
"""프롬프트 가져오기 (context는 무시)"""
|
| 357 |
-
return cls.PROMPTS[query_type]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/router/query_router_lee.py
DELETED
|
@@ -1,77 +0,0 @@
|
|
| 1 |
-
# src/router/query_router.py
|
| 2 |
-
|
| 3 |
-
import logging
|
| 4 |
-
|
| 5 |
-
logger = logging.getLogger(__name__)
|
| 6 |
-
|
| 7 |
-
class QueryRouter:
|
| 8 |
-
"""Query를 RAG vs Direct로 라우팅"""
|
| 9 |
-
|
| 10 |
-
def __init__(self):
|
| 11 |
-
# 키워드 정의
|
| 12 |
-
self.greeting_keywords = [
|
| 13 |
-
"안녕", "hi", "hello", "반가워", "처음", "인사"
|
| 14 |
-
]
|
| 15 |
-
|
| 16 |
-
self.thanks_keywords = [
|
| 17 |
-
"고마워", "감사", "thanks", "고맙", "땡큐"
|
| 18 |
-
]
|
| 19 |
-
|
| 20 |
-
self.document_keywords = [
|
| 21 |
-
# 돈 관련
|
| 22 |
-
"예산", "비용", "금액", "원", "만원", "억", "억원",
|
| 23 |
-
# 일정 관련
|
| 24 |
-
"기한", "마감", "언제", "기간", "납기", "일정",
|
| 25 |
-
# 문서 관련
|
| 26 |
-
"요구사항", "제출", "서류", "양식", "평가", "rfp",
|
| 27 |
-
# 조직 관련
|
| 28 |
-
"발주", "기관", "담당자", "연락처", "부처", "지자체",
|
| 29 |
-
# 사업/계약 관련
|
| 30 |
-
"사업", "사업명", "과업", "범위", "목적", "계약", "입찰",
|
| 31 |
-
"공고", "프로젝트", "위탁", "용역", "협상", "제안"
|
| 32 |
-
]
|
| 33 |
-
|
| 34 |
-
def classify(self, query: str) -> dict:
|
| 35 |
-
query_lower = query.lower()
|
| 36 |
-
query_length = len(query)
|
| 37 |
-
|
| 38 |
-
# 짧은 질문일 때만 인사/감사 체크
|
| 39 |
-
if query_length < 25:
|
| 40 |
-
if any(kw in query_lower for kw in self.thanks_keywords):
|
| 41 |
-
return {
|
| 42 |
-
'type': 'thanks',
|
| 43 |
-
'confidence': 0.9,
|
| 44 |
-
'reason': '감사 인사 감지'
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
if any(kw in query_lower for kw in self.greeting_keywords):
|
| 48 |
-
return {
|
| 49 |
-
'type': 'greeting',
|
| 50 |
-
'confidence': 0.9,
|
| 51 |
-
'reason': '인사 감지'
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
# 문서 관련 판별 (키워드 또는 숫자+사업 맥락)
|
| 55 |
-
if any(kw in query_lower for kw in self.document_keywords):
|
| 56 |
-
match_count = sum(1 for kw in self.document_keywords if kw in query_lower)
|
| 57 |
-
confidence = min(0.7 + 0.05 * match_count, 0.95)
|
| 58 |
-
return {
|
| 59 |
-
'type': 'document',
|
| 60 |
-
'confidence': confidence,
|
| 61 |
-
'reason': f'문서 키워드 {match_count}개 감지'
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
# 숫자와 행정 용어가 혼합된 경우 약한 문서 추정
|
| 65 |
-
if any(ch.isdigit() for ch in query) and any(term in query_lower for term in ["사업", "과업", "계획"]):
|
| 66 |
-
return {
|
| 67 |
-
'type': 'document',
|
| 68 |
-
'confidence': 0.65,
|
| 69 |
-
'reason': '숫자와 사업 키워드 동시 감지'
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
# 기본값
|
| 73 |
-
return {
|
| 74 |
-
'type': 'out_of_scope',
|
| 75 |
-
'confidence': 0.4,
|
| 76 |
-
'reason': 'RFP 관련 키워드 미감지'
|
| 77 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|