"""B-4 KB 자동 업데이트 에이전트 테스트""" import sys, ast, asyncio, os os.environ.setdefault("GUARDIA_SECRET_KEY", "test-b4-secret-key-32bytes-padded!") os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./test_b4.db") ok = True print("=== 1. 구문 검사 ===") files = ["core/kb_agent.py", "routers/kb_agent.py", "main.py"] for f in files: try: with open(f, encoding="utf-8") as fh: src = fh.read() ast.parse(src) print(f" OK {f}") except SyntaxError as e: print(f" ERR {f}: {e}") ok = False print("\n=== 2. models.py KBDocument 확장 컬럼 확인 ===") with open("models.py", encoding="utf-8") as f: models_src = f.read() checks = [ ("source_sr_id", "source_sr_id 컬럼 (KB-SR 연결)"), ("author", "author 컬럼 (kb-agent)"), ("updated_at", "updated_at 컬럼"), ] for sym, desc in checks: # models.py의 KBDocument 섹션 안에 있는지 확인 kb_start = models_src.find("class KBDocument(Base):") kb_end = models_src.find("\n\nclass ", kb_start + 1) kb_section = models_src[kb_start:kb_end] if kb_end > 0 else models_src[kb_start:] status = "OK" if sym in kb_section else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 3. core/kb_agent.py 함수 확인 ===") with open("core/kb_agent.py", encoding="utf-8") as f: kb_src = f.read() fn_checks = [ ("def classify_category(", "카테고리 분류 함수"), ("def extract_tags_rule(", "태그 추출 함수"), ("async def extract_kb_with_llm(", "LLM KB 추출"), ("def extract_kb_rule(", "규칙 기반 KB 추출"), ("def compute_similarity(", "유사도 계산"), ("async def find_similar_kb(", "유사 KB 검색"), ("async def auto_create_kb_from_sr(", "SR→KB 자동 생성"), ("async def run_kb_agent_batch(", "일괄 처리"), ("doc_id_val", "doc_id 생성 로직"), ("_CATEGORY_KEYWORDS", "카테고리 키워드 맵"), ("OLLAMA_URL", "Ollama URL"), ] for sym, desc in fn_checks: status = "OK" if sym in kb_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 4. routers/kb_agent.py 엔드포인트 확인 ===") with open("routers/kb_agent.py", encoding="utf-8") as f: router_src = f.read() endpoint_checks = [ ('@router.post("/run")', "POST /api/kb-agent/run"), ('@router.post("/analyze/{sr_id}")', "POST /api/kb-agent/analyze/{sr_id}"), ('@router.get("/candidates")', "GET /api/kb-agent/candidates"), ('@router.get("/stats")', "GET /api/kb-agent/stats"), ('@router.post("/kb"', "POST /api/kb-agent/kb (수동 생성)"), ('@router.put("/kb/{kb_id}"', "PUT /api/kb-agent/kb/{id}"), ("run_kb_agent_batch", "배치 실행 함수 호출"), ("auto_create_kb_from_sr", "SR 분석 함수 호출"), ] for sym, desc in endpoint_checks: status = "OK" if sym in router_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 5. main.py 등록 확인 ===") with open("main.py", encoding="utf-8") as f: main_src = f.read() main_checks = [ ("kb_agent", "kb_agent 라우터 임포트"), ("kb_agent.router", "kb_agent 라우터 등록"), ] for sym, desc in main_checks: status = "OK" if sym in main_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 6. classify_category 테스트 ===") try: import importlib.util spec = importlib.util.spec_from_file_location("kb_mod", "core/kb_agent.py") kb_mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(kb_mod) cases = [ ("서버 CPU 과부하", "서버 CPU 메모리 문제입니다", "서버 운영"), ("톰캣 배포 오류", "tomcat WAS 배포 실패", "배포"), ("오라클 DB 연결 실패", "database connection pool 소진", "DB"), ("SSL 인증서 만료", "HTTPS 보안 SSL 오류", "네트워크"), ] for title, desc, expected in cases: cat = kb_mod.classify_category(title, desc) status = "OK" if cat == expected else f"WARN(got {cat}, expected {expected})" print(f" {status} '{title}' → {cat}") except Exception as e: print(f" ERR classify_category 오류: {type(e).__name__}: {e}") ok = False print("\n=== 7. extract_tags_rule 테스트 ===") try: cases2 = [ ("톰캣 재기동", "tomcat restart java WAS", "", ["java", "restart", "tomcat"]), ("MySQL DB 연결", "mysql database connection", "", ["mysql"]), ("Nginx SSL 설정", "nginx ssl tls 설정", "", ["nginx", "ssl"]), ] for title, desc, solution, expected_tags in cases2: tags = kb_mod.extract_tags_rule(title, desc, solution) # 기대 태그 중 하나라도 있으면 OK found = [t for t in expected_tags if t in tags] status = "OK" if found else f"WARN(got {tags}, expected subset of {expected_tags})" print(f" {status} '{title}' → tags={tags}") except Exception as e: print(f" ERR extract_tags_rule 오류: {type(e).__name__}: {e}") ok = False print("\n=== 8. compute_similarity 테스트 ===") try: cases3 = [ ("서버 CPU 과부하 장애", "서버 CPU 과부하 장애", 1.0), # 동일 ("서버 CPU 장애", "서버 메모리 문제", 0.1), # 낮은 유사도 (일부 겹침) ("완전 다른 텍스트 xyz", "전혀 관련 없음 abc def", 0.0), # 매우 낮음 ] for t1, t2, min_expected in cases3: sim = kb_mod.compute_similarity(t1, t2) status = "OK" if sim >= min_expected else f"WARN(got {sim:.2f}, min={min_expected})" print(f" {status} 유사도({t1[:15]}|{t2[:15]}) = {sim:.2f}") except Exception as e: print(f" ERR compute_similarity 오류: {type(e).__name__}: {e}") ok = False print("\n=== 9. extract_kb_rule 테스트 ===") try: result = kb_mod.extract_kb_rule( title="서버 CPU 과부하 장애", description="CPU 사용률이 95%를 초과하여 서비스가 중단됨", work_log="$ top -bn1 | head -20\n$ kill -9 12345", sr_type="OTHER", ) assert "title" in result, "title 없음" assert "category" in result, "category 없음" assert "symptom" in result, "symptom 없음" assert "solution" in result, "solution 없음" assert "tags" in result, "tags 없음" assert len(result["commands"]) > 0, f"commands 없음: {result['commands']}" print(f" OK KB 추출: title='{result['title'][:40]}', category={result['category']}") print(f" OK commands 추출: {result['commands']}") except AssertionError as e: print(f" ERR extract_kb_rule 오류: {e}") ok = False except Exception as e: print(f" ERR extract_kb_rule 예외: {type(e).__name__}: {e}") ok = False print("\n=== 10. Ollama 폴백 테스트 ===") async def test_llm_fallback(): try: result = await kb_mod.extract_kb_with_llm( title="서버 오류", description="CPU 과부하", work_log="재기동 완료", sr_type="OTHER", model="llama3", timeout=2, ) assert result is None or isinstance(result, dict), "폴백 타입 오류" print(f" OK LLM 폴백 (None 반환): {type(result).__name__}") except Exception as e: print(f" ERR LLM 폴백: {type(e).__name__}: {e}") asyncio.run(test_llm_fallback()) print("\n=== B-4 KB 자동 업데이트 에이전트 테스트 완료 ===") if ok: print("모든 검사 통과") else: sys.exit(1)