테스트

This commit is contained in:
2026-01-28 21:24:22 +09:00
parent 5f828601ce
commit b2a8634fde
3 changed files with 61 additions and 41 deletions

11
app.py
View File

@@ -105,11 +105,8 @@ def query_select_summarize_stream(results, query, ai, min_similarity: float = 0.
else : else :
print('일반', results.get('documents')) print('일반', results.get('documents'))
context_parts = [f'검색된 사용자 수는 {len(results.get('ids'))}'] context_parts = [f'검색된 사용자 수는 {len(results.get('ids'))}']
docs = [f"{d[d.find('[이름]'): d.find('[', d.find('[이름]')+1)]} {d[d.find('[부서]'): d.find('[', d.find('[부서]')+1)]}" for d in results.get('documents')] docs = [f"[{d[d.find('[이름]'): d.find('[', d.find('[이름]')+1)]} {d[d.find('[부서]'): d.find('[', d.find('[부서]')+1)]}]" for d in results.get('documents')]
context = ",".join(context_parts + docs)
context = "\n".join(context_parts + docs)
print(context) print(context)
# 모델 로드 # 모델 로드
@@ -148,7 +145,7 @@ def query_select_summarize_stream(results, query, ai, min_similarity: float = 0.
generation_kwargs = dict( generation_kwargs = dict(
**model_inputs, **model_inputs,
streamer=streamer, streamer=streamer,
max_new_tokens=3000, max_new_tokens=600,
do_sample=True, do_sample=True,
temperature=0.3, temperature=0.3,
top_p=0.9, top_p=0.9,
@@ -213,7 +210,7 @@ def query_summarize_simple(query: str) :
# conduct text completion # conduct text completion
generated_ids = model.generate( generated_ids = model.generate(
**model_inputs, **model_inputs,
max_new_tokens=300, max_new_tokens=600,
do_sample=True, # ✅ 샘플링 활성화 do_sample=True, # ✅ 샘플링 활성화
temperature=0.3, temperature=0.3,
top_p=0.9, top_p=0.9,

View File

@@ -2,26 +2,28 @@ import json
from typing import List, Union from typing import List, Union
import chromadb import chromadb
from chromadb.utils import embedding_functions from sentence_transformers import SentenceTransformer # ✅ 임베딩 전용 라이브러리
# === 경로 설정 (모두 로컬) === # === 경로 설정 (모두 로컬) ===
EMBEDDING_MODEL_PATH = "./models/ko-sroberta-multitask" EMBEDDING_MODEL_PATH = "./models/ko-sroberta-multitask"
# 1. 임베딩 모델 로드 (앱 시작 시 1회만 실행 권장)
# NOTE: 실제 프로덕션에서는 전역 변수나 싱글톤으로 관리
embedding_model = SentenceTransformer(
EMBEDDING_MODEL_PATH,
device="cpu"
)
# 속도 최적화: 조직도 데이터는 짧으므로 max_seq_length 줄임
embedding_model.max_seq_length = 128
# 2. 벡터 DB 설정 # 2. 벡터 DB 설정
persist_directory = "./chroma_db" persist_directory = "./chroma_db"
chroma_client = chromadb.PersistentClient(path=persist_directory) chroma_client = chromadb.PersistentClient(path=persist_directory)
# 임베딩 함수 설정 # 컬렉션 생성 (임베딩 함수는 None으로 설정 - 수동 전달할 예정)
embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name=EMBEDDING_MODEL_PATH, # 로컬 폴더 경로 가능
device="cpu",
normalize_embeddings=True
)
# 컬렉션 생성 또는 가져오기
collection = chroma_client.get_or_create_collection( collection = chroma_client.get_or_create_collection(
name="orgchart", name="orgchart",
embedding_function=embedding_fn, embedding_function=None, # ✅ 수동 임베딩 전달 시 None
metadata={"hnsw:space": "cosine"} metadata={"hnsw:space": "cosine"}
) )
@@ -36,21 +38,24 @@ def init(sessionId: str, data: List[Union[str, dict]]):
""" """
print(f'{sessionId} init start') print(f'{sessionId} init start')
if not data:
print(f'{sessionId} init skipped: no data')
return
# 문서 ID 생성 # 문서 ID 생성
doc_ids = [f"{sessionId}_{i}" for i in range(len(data))] doc_ids = [f"{sessionId}_{i}" for i in range(len(data))]
# 데이터 처리: 문자열이면 그대로, 객체면 JSON 문자열로 변환 # 데이터 처리: 간결하고 검색 친화적인 텍스트 생성
documents = []
doc_list = [] doc_list = []
metadatas = []
for q in data: for q in data:
# 필드 안전하게 추출 (None 방어) # 필드 안전 추출
name = q.get('name') or "" name = q.get('name') or ""
dept = q.get('deptNm') or "" dept = q.get('deptNm') or ""
grade = q.get('gradeNm') or "" grade = q.get('gradeNm') or ""
position = q.get('ptsnNm') or "" position = q.get('ptsnNm') or ""
office_phone = q.get('ofcePhn') or "" office_phone = q.get('ofcePhn') or ""
mobile_phone = q.get('mblPhn') or ""
chief_name = q.get('chiefNm') or "" chief_name = q.get('chiefNm') or ""
state_code = q.get('state') or "" state_code = q.get('state') or ""
@@ -58,30 +63,48 @@ def init(sessionId: str, data: List[Union[str, dict]]):
state_map = {'C': '재직', 'T': '퇴사', 'H': '휴직'} state_map = {'C': '재직', 'T': '퇴사', 'H': '휴직'}
status = state_map.get(state_code, "정보없음") status = state_map.get(state_code, "정보없음")
# [핵심] 검색 엔진이 좋아할만한 서술형 문장 생성 # ✅ 개선: 짧고 핵심적인 문장 (토큰 수 ↓ → 속도 ↑, 정확도 ↑)
# 부서명과 이름을 앞부분에 배치하여 가중치 유도 if name:
if name == '': # 이름 있는 경우: "이름 직급 직위, 부서 소속, 상태"
doc = f"[이름]:{name} [직급]:{grade} [직위]:{position} [부서]:{dept} [상태]: {status}"
if office_phone:
doc += f", 사내번호:{office_phone}"
else :
# 부서장 정보만 있는 경우
doc = ( doc = (
f"부서: {dept}. " f"부서: {dept}. 해당 {dept}의 [부서장][팀장]은 {chief_name}입니다."
f"해당 {dept}의 부서장은 {chief_name}입니다."
)
else:
doc = (
f" [이름]:{name}"
f" [부서]:{dept}"
f" [소속]:{dept}"
f" [직급]:{grade}"
f" [직위]:{position}"
f" 현재 {status} 중입니다. "
f" 사내 전화번호(사선)는 {office_phone}입니다."
) )
doc_list.append(doc) doc_list.append(doc)
# 벡터 DB에 추가 # 메타데이터는 원본 값 유지 (검색 필터링용)
metadatas.append({
"sessionId": sessionId,
"deptCd": q.get('deptCd') or "",
"gradeCd": q.get('gradeCd') or "",
"name": name,
"deptNm": dept,
"state": state_code
})
# ✅ 임베딩 직접 계산 (Chroma 내부 중복 계산 방지)
try:
embeddings = embedding_model.encode(
doc_list,
normalize_embeddings=True, # cosine 유사도 사용 시 필수
show_progress_bar=False,
batch_size=32 # 메모리 절약
).tolist()
except Exception as e:
print(f"임베딩 생성 실패: {e}")
raise
# ✅ 벡터 DB에 추가 (임베딩 직접 전달)
collection.add( collection.add(
documents=doc_list, documents=doc_list,
ids=doc_ids, ids=doc_ids,
metadatas=[{"sessionId": sessionId, 'deptCd': d.get('deptCd') or "", 'gradeCd': d.get('gradeCd') or ""} for d in data] embeddings=embeddings, # ← 핵심: 수동 전달
metadatas=metadatas
) )
print(f'{sessionId} init end') print(f'{sessionId} init end (총 {len(data)}건)')

View File

@@ -1,5 +1,5 @@
# 테스트용 스크립트 # 테스트용 스크립트
d='[이름]:이준원[부서]:토건부[소속]:토건부[직급]:2직급[직위]:현재 재직 중입니다.' d='[이름]:박현우 [직급]:3직급 [직위]: [소속]:저탄장옥내화부 상태: 재직, 사내번호:1589'
print(f'{d[d.find('[이름]'): d.find('[', d.find('[이름]')+1)]}') print(f'{d[d.find('[이름]'): d.find('[', d.find('[이름]')+1)]}')