hee_!J commited on
Commit
c4e7eab
·
1 Parent(s): f630bf1

feat: Tier 2 원인 분석 LLM+RAG 구현

Browse files
agents/cause.py CHANGED
@@ -1,11 +1,81 @@
1
- """Tier 2 원인 분석 에이전트 (M2에서 구현)
2
 
3
- 입력: 알람 컨텍스트 + Tier 1 결과
4
- 출력: core.schema.Tier2 (추정 원인 + 기여도 % + 근거 + RAG citation)
5
- 모델: GPT-5 mini + RAG (agents.rag.store), 과거 인시던트/FMEA 문서 검색
6
  """
 
 
 
 
7
  from core.schema import Tier1, Tier2
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  def run_cause(alarm: dict, tier1: Tier1) -> Tier2:
11
- raise NotImplementedError("M2: Tier 2 원인 분석 에이전트 구현 예정")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tier 2 원인 분석 에이전트
2
 
3
+ 알람 컨텍스트 + Tier 1 결과 + RAG 지식 문서를 근거로
4
+ 추정 원인을 기여도(%)와 근거, citation과 함께 산출
5
+ 모델: GPT-5 mini (agents.llm.SUBAGENT_MODEL)
6
  """
7
+ import json
8
+
9
+ from agents.llm import SUBAGENT_MODEL, client
10
+ from agents.rag.store import load_document, search
11
  from core.schema import Tier1, Tier2
12
 
13
+ TOP_K_DOCS = 3
14
+
15
+ # structured output용 Tier2 JSON 스키마
16
+ TIER2_SCHEMA = {
17
+ "type": "object",
18
+ "properties": {
19
+ "causes": {
20
+ "type": "array",
21
+ "items": {
22
+ "type": "object",
23
+ "properties": {
24
+ "name": {"type": "string"},
25
+ "pct": {"type": "integer"},
26
+ "evidence": {"type": "string"},
27
+ "citations": {"type": "array", "items": {"type": "string"}},
28
+ },
29
+ "required": ["name", "pct", "evidence", "citations"],
30
+ "additionalProperties": False,
31
+ },
32
+ }
33
+ },
34
+ "required": ["causes"],
35
+ "additionalProperties": False,
36
+ }
37
+
38
+ SYSTEM_PROMPT = """당신은 반도체 Photo 공정의 원인 분석 전문가입니다.
39
+ 주어진 이상 알람과 탐지 결과, 사내 지식 문서를 근거로 가장 가능성 높은 원인을
40
+ 2~3개 추정합니다. 각 원인은 기여도(pct, %)를 가지며 합이 100에 가깝도록 합니다.
41
+ 근거(evidence)는 제공된 문서 내용에 기반해 구체적으로 작성하고, citations에는
42
+ 근거가 된 문서 ID만 정확히 기입합니다. 제공되지 않은 문서는 인용하지 않습니다.
43
+ 기여도가 높은 원인부터 순서대로 제시합니다."""
44
+
45
+
46
+ def _build_query(alarm: dict, tier1: Tier1) -> str:
47
+ feature = alarm.get("feature") or ""
48
+ return f"{alarm['title']} {feature} 원인 CD 산포 렌즈 노광 진동 표면 결함"
49
+
50
 
51
  def run_cause(alarm: dict, tier1: Tier1) -> Tier2:
52
+ doc_ids = search(_build_query(alarm, tier1), top_k=TOP_K_DOCS)
53
+ knowledge = "\n\n".join(f"[{d}]\n{load_document(d)}" for d in doc_ids)
54
+
55
+ sensors = ", ".join(f["name"] for f in tier1["features"])
56
+ user_prompt = f"""## 이상 알람
57
+ - 공정: {alarm['title']}
58
+ - lot: {alarm['lot_id']}
59
+ - 이상 피처: {alarm.get('feature')} {alarm.get('feature_arrow') or ''}
60
+
61
+ ## Tier 1 이상 탐지 결과
62
+ - 이상 점수: {tier1['score']}
63
+ - 기여 센서(Top): {sensors}
64
+
65
+ ## 사내 지식 문서
66
+ {knowledge}
67
+
68
+ 위 정보를 근거로 원인을 분석해 주세요."""
69
+
70
+ resp = client().chat.completions.create(
71
+ model=SUBAGENT_MODEL,
72
+ messages=[
73
+ {"role": "system", "content": SYSTEM_PROMPT},
74
+ {"role": "user", "content": user_prompt},
75
+ ],
76
+ response_format={
77
+ "type": "json_schema",
78
+ "json_schema": {"name": "tier2", "schema": TIER2_SCHEMA, "strict": True},
79
+ },
80
+ )
81
+ return json.loads(resp.choices[0].message.content)
agents/llm.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """OpenAI 클라이언트와 모델 설정
2
+
3
+ 서브에이전트(cause/impact/response) = SUBAGENT_MODEL
4
+ 오케스트레이터 = ORCHESTRATOR_MODEL
5
+ 모델명을 여기 한 곳에서 관리해 교체를 쉽게 함
6
+ """
7
+ from functools import lru_cache
8
+
9
+ from dotenv import load_dotenv
10
+ from openai import OpenAI
11
+
12
+ load_dotenv()
13
+
14
+ SUBAGENT_MODEL = "gpt-5-mini"
15
+ ORCHESTRATOR_MODEL = "gpt-5"
16
+
17
+
18
+ @lru_cache(maxsize=1)
19
+ def client() -> OpenAI:
20
+ """OpenAI 클라이언트, 첫 호출 시 1회만 생성"""
21
+ return OpenAI()
agents/rag/knowledge/FMEA-PH-007.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FMEA-PH-007 — Photo 공정 실패 모드 분석
2
+
3
+ ## 대상
4
+
5
+ Photo / Lithography 공정의 주요 실패 모드와 원인, 영향, 검출 방법을 정리합니다.
6
+
7
+ ## 실패 모드
8
+
9
+ ### 1. CD 산포 증가
10
+
11
+ - 잠재 원인: 투영 렌즈 오염, 노광 에너지 드리프트, 스테이지 진동
12
+ - 영향: 후공정 Etch 단계의 트렌치 깊이 편차 확대, 수율 손실
13
+ - 검출: SPC 기반 CD-X 산포 모니터링, 헤이즈 센서
14
+
15
+ ### 2. 오버레이 오차
16
+
17
+ - 잠재 원인: 스테이지 진동, 정렬 마크 손상
18
+ - 영향: 패턴 정합 불량
19
+ - 검출: 오버레이 계측, 진동 센서
20
+
21
+ ### 3. 포커스 편차
22
+
23
+ - 잠재 원인: 웨이퍼 평탄도 불량, 척 오염
24
+ - 영향: 패턴 해상도 저하
25
+ - 검출: 포커스 계측
26
+
27
+ ## 우선순위
28
+
29
+ CD 산포 증가는 발생 빈도와 후공정 영향이 모두 커 최우선 관리 대상입니다. 원인
30
+ 중 렌즈 오염은 PM 주기와 강한 상관이 있어 정비 이력 확인이 우선입니다.
agents/rag/knowledge/INC-2024-0289.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # INC-2024-0289 — 스테이지 진동에 의한 노광 위치 오차
2
+
3
+ ## 분류
4
+
5
+ - 유형: 공정 인시던트
6
+ - 공정: Photo / Lithography
7
+ - 사업부: 메모리 2동
8
+ - 발생일: 2024-02-28
9
+
10
+ ## 증상
11
+
12
+ 특정 시간대에 처리된 로트에서 오버레이와 CD 산포가 동시에 악화되었습니다.
13
+ 이상이 간헐적으로 발생하여 초기 원인 규명에 어려움이 있었습니다.
14
+
15
+ ## 원인
16
+
17
+ 웨이퍼 스테이지의 진동이 원인으로 확인되었습니다. 인접 설비의 정비 작업 중
18
+ 발생한 진동이 전달된 것으로, 진동 센서에서 동일 시간대 이상치가 검출되었습니다.
19
+
20
+ ## 조치
21
+
22
+ - 인접 설비 정비 일정 조정
23
+ - 진동 센서 모니터링 구간 확대
24
+
25
+ ## 참고
26
+
27
+ 렌즈 오염과 달리 진동 원인은 발생 시간대와의 상관관계가 뚜렷합니다. 원인 구분
28
+ 시 시간대 패턴을 함께 확인하는 것이 유효합니다.
agents/rag/knowledge/INC-2024-0312.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # INC-2024-0312 — Photo 렌즈 오염으로 인한 CD 산포 증가
2
+
3
+ ## 분류
4
+
5
+ - 유형: 공정 인시던트
6
+ - 공정: Photo / Lithography
7
+ - 사업부: 메모리 1동
8
+ - 발생일: 2024-03-12
9
+
10
+ ## 증상
11
+
12
+ 노광 공정에서 CD-X 산포가 관리 한계를 초과하였습니다. 동일 스캐너에서 처리된
13
+ 연속 로트에서 CD 균일도가 점진적으로 악화되는 양상이 관측되었습니다.
14
+
15
+ ## 원인
16
+
17
+ 투영 렌즈 표면 오염으로 확인되었습니다. 직전 PM(예방 정비) 이후 14일이 경과한
18
+ 시점이었으며, 헤이즈 센서 측정값이 평소 대비 18% 상승해 있었습니다.
19
+
20
+ ## 조치
21
+
22
+ - 렌즈 긴급 PM 투입 (약 2시간 소요)
23
+ - 영향 로트 후공정 진입 보류
24
+ - PM 주기를 30일에서 21일로 단축 권고
25
+
26
+ ## 재발 방지
27
+
28
+ 헤이즈 센서 임계값을 신설하고, 임계 초과 시 자동 알람이 발생하도록 모니터링을
29
+ 강화하였습니다.
agents/rag/knowledge/SOP-PH-LENS-002.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SOP-PH-LENS-002 — 렌즈 PM 표준 절차
2
+
3
+ ## 목적
4
+
5
+ Photo 스캐너 투영 렌즈의 예방 정비(PM) 절차를 표준화합니다.
6
+
7
+ ## 적용 범위
8
+
9
+ 전 사업부 Photo / Lithography 스캐너 설비.
10
+
11
+ ## PM 주기
12
+
13
+ - 표준 주기: 30일
14
+ - CD 산포 악화 또는 헤이즈 센서 상승 시: 21일로 단축
15
+
16
+ ## 절차
17
+
18
+ 1. 설비 정지 및 로트 진입 차단
19
+ 2. 렌즈 표면 오염도 측정 (헤이즈 센서)
20
+ 3. 세정 작업 수행
21
+ 4. 세정 후 오염도 재측정 및 기준 충족 확인
22
+ 5. 시험 노광으로 CD 산포 정상 범위 확인
23
+ 6. 설비 재가동 및 로트 진입 재개
24
+
25
+ ## 소요 시간
26
+
27
+ 표준 약 2시간. 오염도가 높을 경우 추가 세정으로 연장될 수 있습니다.
28
+
29
+ ## 기록
30
+
31
+ PM 수행 이력은 인시던트 DB에 기록하며, 직전 PM 경과일은 원인 분석 시 주요 참고
32
+ 지표로 활용합니다.
agents/rag/store.py CHANGED
@@ -1,22 +1,41 @@
1
- """RAG 도메인 지식 검색 (M2에서 구현)
2
 
3
  knowledge/ 의 마크다운 문서(INC-*, FMEA-*, SOP-* 등)를 검색해
4
  원인 분석(Tier 2)·대응 권고(Tier 4) 에이전트에 근거를 제공
5
 
6
- 문서가 5~6개뿐이라 벡터DB는 오버스펙
7
- citation ID로 직접 로드하거나 간단한 키워드 매칭이면 충분
8
  """
 
9
  from pathlib import Path
10
 
11
  KNOWLEDGE_DIR = Path(__file__).parent / "knowledge"
12
 
13
 
14
  def load_document(doc_id: str) -> str:
15
- """citation ID(예: 'INC-2024-0312')로 knowledge 문서 본문을 로드, 없으면 빈 문자열"""
16
  path = KNOWLEDGE_DIR / f"{doc_id}.md"
17
  return path.read_text(encoding="utf-8") if path.exists() else ""
18
 
19
 
 
 
 
 
 
 
 
 
 
 
20
  def search(query: str, top_k: int = 3) -> list[str]:
21
- """쿼리와 관련된 문서 ID를 반환 (M2: 키워드 매칭 기반)"""
22
- raise NotImplementedError("M2: RAG 검색 구현 예정")
 
 
 
 
 
 
 
 
 
1
+ """RAG 도메인 지식 검색
2
 
3
  knowledge/ 의 마크다운 문서(INC-*, FMEA-*, SOP-* 등)를 검색해
4
  원인 분석(Tier 2)·대응 권고(Tier 4) 에이전트에 근거를 제공
5
 
6
+ 문서가 소수라 벡터DB는 오버스펙, 키워드 매칭으로 충분
7
+ 파일명이 곧 citation ID (예: INC-2024-0312.md -> "INC-2024-0312")
8
  """
9
+ import re
10
  from pathlib import Path
11
 
12
  KNOWLEDGE_DIR = Path(__file__).parent / "knowledge"
13
 
14
 
15
  def load_document(doc_id: str) -> str:
16
+ """citation ID로 knowledge 문서 본문을 로드, 없으면 빈 문자열"""
17
  path = KNOWLEDGE_DIR / f"{doc_id}.md"
18
  return path.read_text(encoding="utf-8") if path.exists() else ""
19
 
20
 
21
+ def _knowledge_docs() -> dict[str, str]:
22
+ """knowledge/ 의 모든 문서를 {doc_id: 본문}으로 로드, README는 제외"""
23
+ docs = {}
24
+ for path in KNOWLEDGE_DIR.glob("*.md"):
25
+ if path.stem == "README":
26
+ continue
27
+ docs[path.stem] = path.read_text(encoding="utf-8")
28
+ return docs
29
+
30
+
31
  def search(query: str, top_k: int = 3) -> list[str]:
32
+ """쿼리와 관련된 문서 ID를 키워드 매칭으로 반환, 관련도 내림차순"""
33
+ keywords = [w for w in re.split(r"\W+", query.lower()) if len(w) >= 2]
34
+ scored = []
35
+ for doc_id, text in _knowledge_docs().items():
36
+ low = text.lower()
37
+ hits = sum(low.count(kw) for kw in keywords)
38
+ if hits > 0:
39
+ scored.append((doc_id, hits))
40
+ scored.sort(key=lambda x: -x[1])
41
+ return [doc_id for doc_id, _ in scored[:top_k]]