25

Tool 테스트 전략: 현장 배포 전 필수 검증

Day 2: Tool 정의 & Function Calling

학습 목표

Tool을 현장 배포 전 체계적으로 테스트하는 방법을 안다 엣지 케이스와 에러 시나리오를 미리 식별한다 실제 MES/ERP 연동 시 주의사항을 이해한다

"데모에서는 됐는데..."

Agent를 만들고 데모를 한다. "CNC-001 상태 알려줘" - 잘 된다. "매뉴얼에서 E-4072 찾아줘" - 잘 된다.

박수를 받고 배포한다.

현장 반장: "cnc-001 알려줘"        -> Tool: "cnc-001을 찾을 수 없습니다" (대소문자!)
현장 반장: " CNC-001 "             -> Tool: " CNC-001 을 찾을 수 없습니다" (공백!)
현장 반장: "CNC-999 상태"          -> Tool: KeyError (존재하지 않는 설비!)
현장 반장: "전체 설비 상태 보여줘"  -> Tool: 어떤 Tool을 써야 할지 모름

데모에서 되는 것과 현장에서 되는 것은 다르다.


테스트 레벨 3단계

Level 1: 단위 테스트 (Tool 개별)

def test_get_equipment_status():
    """각 Tool의 기본 동작을 테스트합니다."""
    # 정상 케이스
    result = get_equipment_status("CNC-001")
    assert "CNC-001" in result
    assert "가동" in result

    # 대소문자 처리
    result = get_equipment_status("cnc-001")
    assert "CNC-001" in result  # 대문자로 변환되어야 함

    # 공백 처리
    result = get_equipment_status("  CNC-001  ")
    assert "CNC-001" in result  # 앞뒤 공백 제거되어야 함

    # 존재하지 않는 설비
    result = get_equipment_status("CNC-999")
    assert "찾을 수 없습니다" in result
    assert "CNC-001" in result  # 등록된 설비 목록 안내

    # 빈 문자열
    result = get_equipment_status("")
    assert "찾을 수 없습니다" in result

    print("모든 단위 테스트 통과!")

Level 2: 통합 테스트 (Agent + Tool)

def test_agent_integration():
    """Agent가 Tool을 올바르게 호출하는지 테스트합니다."""
    test_cases = [
        # (입력, 기대 호출 Tool, 기대 파라미터)
        ("CNC-001 상태 알려줘", "get_equipment_status", {"equipment_id": "CNC-001"}),
        ("E-4072 알람 원인이 뭐야?", "search_manual", {"query": "E-4072"}),
        ("PRESS-005 알람 이력 조회", "get_alarm_history", {"equipment_id": "PRESS-005"}),
        ("베어링 BRG-002 재고", "check_inventory", {"item_code": "BRG-002"}),
        ("오늘 생산 실적", "get_production_data", {}),
    ]

    for user_input, expected_tool, expected_args in test_cases:
        # Agent 호출 후 어떤 Tool이 호출되었는지 확인
        # (실제로는 mock을 사용하거나 로그를 분석)
        print(f"입력: {user_input}")
        print(f"기대: {expected_tool}({expected_args})")
        print()

Level 3: 현장 시나리오 테스트

# 현장 엔지니어들의 실제 질문 패턴
real_world_questions = [
    # 자연어 변형
    "CNC-003 어때?",          # 구어체
    "003번 설비 확인해봐",     # 번호만 사용
    "A라인 전체 괜찮아?",      # 라인 단위 질문

    # 복합 질문
    "CNC-003에 알람 떴는데 부품 재고 있어?",  # 2개 Tool 필요
    "어제 생산량이랑 불량률 비교해줘",          # 분석 요구

    # 모호한 질문
    "문제 있는 설비?",         # 어떤 기준?
    "긴급한 거 있어?",         # 무엇이 긴급?
    "아까 그거 다시 알려줘",   # 문맥 의존

    # 엣지 케이스
    "",                        # 빈 입력
    "ㅋㅋㅋ",                  # 의미 없는 입력
    "삭제해줘",               # 위험한 요청
]

실제 MES/ERP 연동 시 주의사항

┌─────────────────────────────────────────┐
│           실제 연동 체크리스트            │
├─────────────────────────────────────────┤
│ [ ] 인증: API 키 / OAuth 토큰 관리      │
│ [ ] 타임아웃: 3-5초 제한 (MES 느릴 수 있음) │
│ [ ] 재시도: 실패 시 1-2회 재시도          │
│ [ ] 캐싱: 동일 조회 5분간 캐시           │
│ [ ] 로깅: 모든 Tool 호출/결과 기록       │
│ [ ] 권한: 읽기 전용 계정 사용            │
│ [ ] 속도제한: API 호출 횟수 제한         │
│ [ ] 데이터 형식: 날짜/숫자 변환 처리     │
│ [ ] 개인정보: 작업자 이름 등 마스킹       │
│ [ ] 모니터링: 에러율, 응답시간 추적       │
└─────────────────────────────────────────┘
# 실제 연동 패턴 (프로덕션 수준)
import time
import logging
from functools import lru_cache

logger = logging.getLogger("agent_tools")

@lru_cache(maxsize=100)
def _cached_equipment_status(equipment_id: str, _cache_key: int) -> dict:
    """5분 캐시되는 설비 상태 조회 (내부용)"""
    return mes_api.get_status(equipment_id)

def get_equipment_status(equipment_id: str) -> str:
    """설비 상태 조회 (프로덕션 버전)"""
    equipment_id = equipment_id.upper().strip()
    cache_key = int(time.time() // 300)  # 5분 단위 캐시

    try:
        start = time.time()
        data = _cached_equipment_status(equipment_id, cache_key)
        elapsed = time.time() - start

        logger.info(f"Tool called: get_equipment_status({equipment_id}) - {elapsed:.2f}s")

        if not data:
            return f"설비 '{equipment_id}'를 찾을 수 없습니다."

        return format_equipment_status(data)

    except ConnectionError:
        logger.error(f"MES connection failed for {equipment_id}")
        return "MES 시스템에 연결할 수 없습니다. 네트워크를 확인해주세요."
    except TimeoutError:
        logger.error(f"MES timeout for {equipment_id}")
        return "MES 응답 시간이 초과되었습니다. 잠시 후 다시 시도해주세요."
    except Exception as e:
        logger.error(f"Unexpected error for {equipment_id}: {e}")
        return "설비 상태 조회 중 오류가 발생했습니다. 관리자에게 문의해주세요."

핵심: 에러 메시지는 사용자가 이해할 수 있는 한국어로 반환한다. 스택 트레이스나 영어 에러 메시지를 절대 노출하지 않는다.

AI로 학습하기 — 꿀팁
🤖Tool 테스트 체크리스트 자동 생성AI 학습 팁

제조 현장 배포 전 Tool 검증 체크리스트를 구조화하면 팀 전체가 재사용 가능한 QA 템플릿이 됩니다.

제조 현장에 배포할 설비 제어 Tool(예: set_equipment_speed, trigger_maintenance_request)의 현장 배포 전 테스트 체크리스트를 만들어줘. 정상 입력, 경계값, 잘못된 타입, 권한 없는 명령, 설비 오프라인 상태, 동시 호출 충돌 등 엣지 케이스를 포함하고, 각 테스트 케이스에 입력값·예상 출력·판정 기준을 표로 정리해줘.
이 팁이 도움이 됐나요?