hee_!J commited on
Commit
085208f
ยท
unverified ยท
2 Parent(s): 25f728a28e3341

Merge pull request #4 from FabAgentGroup/feat/tier4-response

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
+ }