hee_!J commited on
Commit
28e3341
·
1 Parent(s): 25f728a

feat: Tier 4 대응 권고 LLM+RAG 구현

Browse files
Files changed (1) hide show
  1. agents/response.py +121 -5
agents/response.py CHANGED
@@ -1,11 +1,127 @@
1
- """Tier 4 대응 권고 에이전트 (M2에서 구현)
2
 
3
- 입력: 알람 컨텍스트 + Tier 1·2·3 결과
4
- 출력: core.schema.Tier4 (즉시 조치 + 중장기 조치 + 근거 자료)
5
- 모델: GPT-5 (오케스트레이터급) + RAG, SOP/표준 절차 문서 검색
 
 
 
6
  """
 
 
 
 
7
  from core.schema import Tier1, Tier2, Tier3, Tier4
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  def run_response(alarm: dict, tier1: Tier1, tier2: Tier2, tier3: Tier3) -> Tier4:
11
- raise NotImplementedError("M2: Tier 4 대응 권고 에이전트 구현 예정")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tier 4 대응 권고 에이전트
2
 
3
+ 알람 + Tier 1/2/3 결과와 RAG 지식을 바탕으로
4
+ - immediate: 즉시 조치 목록 (LLM)
5
+ - longterm: 중장기 조치 목록 (LLM)
6
+ - refs: 근거 자료 (RAG로 검색된 문서 ID와 제목, 결정론적)
7
+
8
+ 모델: GPT-5 mini (agents.llm.SUBAGENT_MODEL)
9
  """
10
+ import json
11
+
12
+ from agents.llm import SUBAGENT_MODEL, client
13
+ from agents.rag.store import load_document, search
14
  from core.schema import Tier1, Tier2, Tier3, Tier4
15
 
16
+ TOP_K_DOCS = 4
17
+
18
+ # LLM이 채울 부분만 스키마로, refs는 검색 결과에서 결정론적으로 구성
19
+ LLM_PART_SCHEMA = {
20
+ "type": "object",
21
+ "properties": {
22
+ "immediate": {
23
+ "type": "array",
24
+ "items": {
25
+ "type": "object",
26
+ "properties": {
27
+ "text": {"type": "string"},
28
+ "meta": {"type": ["string", "null"]},
29
+ },
30
+ "required": ["text", "meta"],
31
+ "additionalProperties": False,
32
+ },
33
+ },
34
+ "longterm": {
35
+ "type": "array",
36
+ "items": {
37
+ "type": "object",
38
+ "properties": {
39
+ "text": {"type": "string"},
40
+ "meta": {"type": ["string", "null"]},
41
+ },
42
+ "required": ["text", "meta"],
43
+ "additionalProperties": False,
44
+ },
45
+ },
46
+ },
47
+ "required": ["immediate", "longterm"],
48
+ "additionalProperties": False,
49
+ }
50
+
51
+ SYSTEM_PROMPT = """당신은 반도체 공정 대응 권고 전문가입니다.
52
+ 이상 알람과 그동안의 분석(탐지·원인·영향)을 종합하여 구체적인 조치를 권고합니다.
53
+
54
+ 산출물:
55
+ 1. immediate: 즉시 조치 (시간 단위 안에 수행, 예: PM 투입, 후공정 hold, 일정 재조정)
56
+ 2. longterm: 중장기 조치 (재발 방지, PM 주기 조정, 모니터링 강화, 절차 개정)
57
+
58
+ 각 조치는 text(권고 본문)와 meta(부가 정보, 예: "예상 2시간", "Etch hold", "PPC 협조")로 구성합니다.
59
+ meta가 필요 없으면 null로 둡니다. 제공된 지식 문서를 근거로 작성하고, 근거가 약한 권고는 포함하지 않습니다."""
60
+
61
+
62
+ def _doc_description(doc_id: str) -> str:
63
+ """문서 첫 줄(# 제목)에서 ID 다음 부분을 desc로 추출"""
64
+ text = load_document(doc_id)
65
+ if not text:
66
+ return doc_id
67
+ first_line = text.split("\n", 1)[0].lstrip("# ").strip()
68
+ for sep in (" — ", " - "):
69
+ if sep in first_line:
70
+ return first_line.split(sep, 1)[1].strip()
71
+ return first_line
72
+
73
+
74
+ def _build_query(alarm: dict, tier2: Tier2) -> str:
75
+ causes = " ".join(c["name"] for c in tier2["causes"])
76
+ return f"{alarm['title']} 대응 PM 조치 보류 재조정 모니터링 {causes}"
77
+
78
 
79
  def run_response(alarm: dict, tier1: Tier1, tier2: Tier2, tier3: Tier3) -> Tier4:
80
+ doc_ids = search(_build_query(alarm, tier2), top_k=TOP_K_DOCS)
81
+ knowledge = "\n\n".join(f"[{d}]\n{load_document(d)}" for d in doc_ids)
82
+
83
+ cause_lines = "\n".join(
84
+ f"- {c['name']} ({c['pct']}%)" for c in tier2["causes"]
85
+ )
86
+ impact_lots_text = ", ".join(
87
+ f"{l['label']} {l['lots']}lot/{l['wafers']}장" for l in tier3["impact_lots"]
88
+ )
89
+ user_prompt = f"""## 이상 알람
90
+ - 공정: {alarm['title']}
91
+ - lot: {alarm['lot_id']}
92
+
93
+ ## Tier 1 이상 탐지
94
+ - 이상 점수: {tier1['score']}
95
+
96
+ ## Tier 2 원인 (기여도 순)
97
+ {cause_lines}
98
+
99
+ ## Tier 3 영향
100
+ - 예상 수율 손실: {tier3['yield_loss']} %p
101
+ - 영향 WIP: {impact_lots_text}
102
+
103
+ ## 사내 지식 문서
104
+ {knowledge}
105
+
106
+ 위 분석을 종합해 immediate와 longterm 조치를 권고해 주세요."""
107
+
108
+ resp = client().chat.completions.create(
109
+ model=SUBAGENT_MODEL,
110
+ messages=[
111
+ {"role": "system", "content": SYSTEM_PROMPT},
112
+ {"role": "user", "content": user_prompt},
113
+ ],
114
+ response_format={
115
+ "type": "json_schema",
116
+ "json_schema": {"name": "tier4_part", "schema": LLM_PART_SCHEMA, "strict": True},
117
+ },
118
+ )
119
+ llm_out = json.loads(resp.choices[0].message.content)
120
+
121
+ refs = [{"id": d, "desc": _doc_description(d)} for d in doc_ids]
122
+
123
+ return {
124
+ "immediate": llm_out["immediate"],
125
+ "longterm": llm_out["longterm"],
126
+ "refs": refs,
127
+ }