feat(nanoclaude): 도메인 키워드 자동 라우팅 + 서고 확인 안내 문구
- _pre_route: 산업안전/ASME/법령 등 도메인 키워드로 "문서 찾아줘" 없이도 자동으로 document tool 라우팅 (도메인 진입 시 ask +1 보너스) - EXAONE classifier: document.ask/search_full 도구 + 예시 추가 - worker: document tool 호출 전 "서고를 확인하는 중입니다" 안내 문구 전송 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,9 @@ CLASSIFIER_PROMPT = """\
|
|||||||
- calendar.create_confirmed() — pending_draft가 있을 때, 사용자가 "확인/예/yes" 한 경우만 사용
|
- calendar.create_confirmed() — pending_draft가 있을 때, 사용자가 "확인/예/yes" 한 경우만 사용
|
||||||
- email.search(query, days) — 메일 검색. days 기본 7
|
- email.search(query, days) — 메일 검색. days 기본 7
|
||||||
- email.read(uid) — 메일 본문 조회
|
- email.read(uid) — 메일 본문 조회
|
||||||
- document.search(query) — 문서 검색
|
- document.ask(query) — 문서 기반 AI 답변. 법령/규정/기술 내용을 물어볼 때 사용. 시간 걸림
|
||||||
|
- document.search_full(query) — 문서 고급 검색 (리랭킹 포함). 문서 목록이 필요할 때 사용
|
||||||
|
- document.search(query) — 문서 간단 검색
|
||||||
- document.read(doc_id) — 문서 조회
|
- document.read(doc_id) — 문서 조회
|
||||||
- infra.status(host) — 서버 Docker 컨테이너 상태. host: gpu (기본), nas-company
|
- infra.status(host) — 서버 Docker 컨테이너 상태. host: gpu (기본), nas-company
|
||||||
- infra.health(service) — 서비스 헬스체크. service 생략 시 전체 체크. 개별: document-server, mlx, ollama-gpu
|
- infra.health(service) — 서비스 헬스체크. service 생략 시 전체 체크. 개별: document-server, mlx, ollama-gpu
|
||||||
@@ -51,7 +53,10 @@ JSON 형식:
|
|||||||
- "이번주 일정" → tools: calendar.search(이번주 월~일)
|
- "이번주 일정" → tools: calendar.search(이번주 월~일)
|
||||||
- "내일 3시 회의" → tools: calendar.create_draft(...)
|
- "내일 3시 회의" → tools: calendar.create_draft(...)
|
||||||
- "최근 메일" → tools: email.search("", 7)
|
- "최근 메일" → tools: email.search("", 7)
|
||||||
- "문서 찾아줘" → tools: document.search(...)
|
- "문서 찾아줘" → tools: document.search_full(...)
|
||||||
|
- "산업안전보건법 제6장 내용이 뭐야" → tools: document.ask("산업안전보건법 제6장")
|
||||||
|
- "ASME Section VIII 관련 자료" → tools: document.search_full("ASME Section VIII")
|
||||||
|
- "위험성평가 절차 알려줘" → tools: document.ask("위험성평가 절차")
|
||||||
- "GPU 서버 상태" → tools: infra.status("gpu")
|
- "GPU 서버 상태" → tools: infra.status("gpu")
|
||||||
- "서비스 살아있어?" → tools: infra.health()
|
- "서비스 살아있어?" → tools: infra.health()
|
||||||
- "디스크 용량" → tools: infra.disk()
|
- "디스크 용량" → tools: infra.disk()
|
||||||
|
|||||||
@@ -183,18 +183,36 @@ def _pre_route(message: str) -> dict | None:
|
|||||||
# 문서 키워드 — 질문형/탐색형 점수 기반 분기
|
# 문서 키워드 — 질문형/탐색형 점수 기반 분기
|
||||||
doc_entry = any(k in msg for k in ["문서", "도큐먼트", "자료", "파일"])
|
doc_entry = any(k in msg for k in ["문서", "도큐먼트", "자료", "파일"])
|
||||||
doc_action = any(k in msg for k in ["찾아", "검색", "확인", "알려", "설명", "뭐야"])
|
doc_action = any(k in msg for k in ["찾아", "검색", "확인", "알려", "설명", "뭐야"])
|
||||||
if doc_entry and doc_action:
|
|
||||||
|
# 도메인 키워드 — "문서 찾아줘" 없이도 전문 분야 질문이면 자동 라우팅
|
||||||
|
domain_keywords = [
|
||||||
|
"산업안전", "산안법", "안전보건", "위험성평가", "중대재해",
|
||||||
|
"asme", "압력용기", "배관", "용접", "기계안전",
|
||||||
|
"화학물질", "유해물질", "msds", "ghs",
|
||||||
|
"법령", "시행령", "시행규칙", "고시", "규정",
|
||||||
|
"산업안전기사", "건설안전", "전기안전",
|
||||||
|
]
|
||||||
|
domain_hit = any(k in msg.lower() for k in domain_keywords)
|
||||||
|
|
||||||
|
if (doc_entry and doc_action) or domain_hit:
|
||||||
query = msg
|
query = msg
|
||||||
for rm in ["문서", "도큐먼트", "자료", "파일", "찾아줘", "찾아", "검색", "확인", "해줘", "줘", "좀"]:
|
# 명시적 문서 키워드가 있으면 제거
|
||||||
query = query.replace(rm, "")
|
if doc_entry:
|
||||||
|
for rm in ["문서", "도큐먼트", "자료", "파일", "찾아줘", "찾아", "검색", "확인", "해줘", "줘", "좀"]:
|
||||||
|
query = query.replace(rm, "")
|
||||||
query = query.strip()
|
query = query.strip()
|
||||||
if query:
|
if query:
|
||||||
# 질문형 강신호
|
# 질문형 강신호
|
||||||
ask_signals = ["알려줘", "설명해", "차이", "비교", "절차", "요건", "무엇", "왜", "어떻게", "뭐야", "뭔가"]
|
ask_signals = ["알려줘", "설명해", "차이", "비교", "절차", "요건", "무엇", "왜", "어떻게", "뭐야", "뭔가", "뭐지", "내용"]
|
||||||
# 탐색형 강신호
|
# 탐색형 강신호
|
||||||
search_signals = ["찾아", "검색", "목록", "최근", "업로드", "리스트"]
|
search_signals = ["찾아", "검색", "목록", "최근", "업로드", "리스트"]
|
||||||
ask_score = sum(1 for s in ask_signals if s in msg)
|
ask_score = sum(1 for s in ask_signals if s in msg)
|
||||||
search_score = sum(1 for s in search_signals if s in msg)
|
search_score = sum(1 for s in search_signals if s in msg)
|
||||||
|
|
||||||
|
# 도메인 키워드만으로 진입한 경우 질문형으로 간주 (+1 보너스)
|
||||||
|
if domain_hit and not doc_entry:
|
||||||
|
ask_score += 1
|
||||||
|
|
||||||
# 초기 운영 가드: ask는 강신호 2개 이상일 때만
|
# 초기 운영 가드: ask는 강신호 2개 이상일 때만
|
||||||
if ask_score >= 2 and ask_score > search_score:
|
if ask_score >= 2 and ask_score > search_score:
|
||||||
operation = "ask"
|
operation = "ask"
|
||||||
@@ -357,6 +375,11 @@ async def run(job: Job) -> None:
|
|||||||
await state_stream.push(job.id, "result", {"content": response})
|
await state_stream.push(job.id, "result", {"content": response})
|
||||||
await conversation_store.add(user_id, "assistant", response)
|
await conversation_store.add(user_id, "assistant", response)
|
||||||
else:
|
else:
|
||||||
|
# 문서 도구 호출 시 안내 문구
|
||||||
|
if tool_name == "document" and job.callback == "synology":
|
||||||
|
notice = "서고를 확인하는 중입니다..." if operation == "ask" else "문서를 검색하는 중입니다..."
|
||||||
|
await send_to_synology(notice, raw=True)
|
||||||
|
|
||||||
# 일반 도구 실행 (document.ask는 긴 timeout)
|
# 일반 도구 실행 (document.ask는 긴 timeout)
|
||||||
timeout = DOCUMENT_ASK_TIMEOUT if (tool_name == "document" and operation == "ask") else TOOL_TIMEOUT
|
timeout = DOCUMENT_ASK_TIMEOUT if (tool_name == "document" and operation == "ask") else TOOL_TIMEOUT
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user