fabagent / agents /supervisor.py
hee_!J
feat(orchestrator): Supervisor agent - LLM-driven dynamic workflow routing
580db8f
Raw
History Blame Contribute Delete
4.15 kB
"""Supervisor Agent - LLM-driven dynamic workflow routing
Tier 2 ์›์ธ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ  ํ›„์† ํ–‰๋™์„ LLM์ด ๋™์ ์œผ๋กœ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
threshold ๊ธฐ๋ฐ˜ conditional edge(orchestrator)์™€ ๋‹ค๋ฅธ ์ :
- ์‚ฌ๋žŒ์ด ์ •์˜ํ•œ ์ž„๊ณ„์น˜๊ฐ€ ์•„๋‹ˆ๋ผ LLM์ด ๋งฅ๋ฝ์„ ๋ณด๊ณ  ํŒ๋‹จ
- ๊ธฐ์—ฌ๋„ยท์ฆ์ƒยท์˜ํ–ฅ lotยท๊ณต์ • critical ์—ฌ๋ถ€๋ฅผ ์ข…ํ•ฉ ๊ณ ๋ ค
๊ฐ€์šฉ action:
- proceed_full: ํ‘œ์ค€ ๊ฒฝ๋กœ (Tier 3 -> Tier 4)
- fast_track: ์›์ธ์ด ๋ช…ํ™•ํ•ด ์˜ํ–ฅ ํ‰๊ฐ€ skip, Tier 4 ์ง์ง„ (latencyยทcost ์ ˆ๊ฐ)
- escalate: ๊ณ ์œ„ํ—˜ - ์ •์ƒ ์ง„ํ–‰ + human review ํ”Œ๋ž˜๊ทธ
severity ๋ถ„๋ฅ˜: low / medium / high / critical
๋น„์šฉ ์ ˆ๊ฐ: ์˜์‚ฌ๊ฒฐ์ •๋งŒ ํ•˜๋Š” ์†Œ์ž‘์—…์ด๋ผ SUPERVISOR_MODEL = gpt-4o-mini
ํ™˜๊ฒฝ๋ณ€์ˆ˜ SUPERVISOR_ENABLED=false ๋ฉด ํ•ญ์ƒ proceed_full (Supervisor ํ˜ธ์ถœ ์ž์ฒด skip)
"""
import json
import os
from langsmith import traceable
from agents.llm import client
from core.schema import Tier1, Tier2
SUPERVISOR_MODEL = "gpt-4o-mini"
ACTION_SCHEMA = {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["proceed_full", "fast_track", "escalate"],
},
"severity": {
"type": "string",
"enum": ["low", "medium", "high", "critical"],
},
"reasoning": {"type": "string"},
},
"required": ["action", "severity", "reasoning"],
"additionalProperties": False,
}
SYSTEM_PROMPT = """๋‹น์‹ ์€ ๋ฐ˜๋„์ฒด fab์˜ incident response supervisor ์—์ด์ „ํŠธ์ž…๋‹ˆ๋‹ค.
์›์ธ ๋ถ„์„(Tier 2) ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ  ํ›„์† ๋‹จ๊ณ„ ์ง„ํ–‰ ๋ฐฉ์‹์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
[๊ฐ€์šฉ action]
- proceed_full: ํ‘œ์ค€ - Tier 3(์˜ํ–ฅํ‰๊ฐ€) โ†’ Tier 4(๋Œ€์‘๊ถŒ๊ณ ) ์ˆœ์ฐจ ์ง„ํ–‰ (๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ)
- fast_track: ์›์ธ์ด 1~2๊ฐœ๋กœ ๋ช…ํ™•ํ•˜๊ณ  ์ฆ‰์‹œ ์กฐ์น˜๋งŒ ํ•„์š” - Tier 3 skip, Tier 4 ์ง์ง„ (latencyยทcost ์ ˆ๊ฐ)
- escalate: ๊ณ ์œ„ํ—˜ - ์ •์ƒ ์ง„ํ–‰ํ•˜๋˜ human review ํ”Œ๋ž˜๊ทธ (์กฐ์น˜ ์ „ ์šด์˜์ž ์ถ”๊ฐ€ ๊ฒ€ํ†  ๊ฐ•์ œ)
[severity ๋ถ„๋ฅ˜]
- low: ์‚ฌ์†Œํ•œ ๋ณ€๋™, ์ž๋™ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
- medium: ํ‘œ์ค€ ๋Œ€์‘์œผ๋กœ ์ถฉ๋ถ„
- high: ์˜ํ–ฅ ํผ, ๋น ๋ฅธ ์กฐ์น˜ ํ•„์š”
- critical: ๋ผ์ธ ์ •์ง€ ๊ฒ€ํ†  ์ˆ˜์ค€
[ํŒ๋‹จ ๊ธฐ์ค€]
- ์›์ธ 1๊ฐœ๊ฐ€ ๊ธฐ์—ฌ๋„ 70%+ ๋‹จ๋… ์šฐ์„ธ + medium/low severity โ†’ fast_track ํ›„๋ณด
- ์›์ธ ๋‹ค์ˆ˜(3๊ฐœ+) ๋ถ„ํฌ ๋˜๋Š” high/critical severity โ†’ proceed_full
- critical severity, downstream ์˜ํ–ฅ์ด ํด ๊ฒƒ์œผ๋กœ ์ถ”์ • โ†’ escalate
- ์ผ๋ฐ˜์ ์œผ๋กœ proceed_full์ด ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’
[์ค‘์š”] reasoning์€ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ๊ฒฐ์ • ๊ทผ๊ฑฐ๋ฅผ ๋ช…์‹œ (์˜ˆ: "๋‹จ์ผ ์›์ธ 80% ๋‹จ๋… ์šฐ์„ธ, medium severity โ†’ fast_track")"""
def _build_user_prompt(alarm: dict, tier1: Tier1, tier2: Tier2) -> str:
causes = "\n".join(
f"- [{c['pct']}%] {c['name']}: {c['evidence'][:120]}"
for c in tier2["causes"]
)
return f"""## ์ด์ƒ ์•Œ๋žŒ
- ๊ณต์ •: {alarm['title']}
- lot: {alarm['lot_id']}
## Tier 1 ์ด์ƒ ํƒ์ง€
- ์ด์ƒ ์ ์ˆ˜: {tier1['score']}
## Tier 2 ์›์ธ ๋ถ„์„ ๊ฒฐ๊ณผ
{causes}
ํ›„์† actionยทseverityยทreasoning์„ ๊ฒฐ์ •ํ•ด ์ฃผ์„ธ์š”."""
def supervisor_enabled() -> bool:
return os.getenv("SUPERVISOR_ENABLED", "true").lower() not in ("false", "0", "no")
@traceable(name="Supervisor_Agent", run_type="chain")
def run_supervisor(alarm: dict, tier1: Tier1, tier2: Tier2) -> dict:
"""ํ›„์† workflow action์„ LLM์ด ๊ฒฐ์ •
๋ฐ˜ํ™˜: {"action": "proceed_full"|"fast_track"|"escalate",
"severity": "low"|"medium"|"high"|"critical",
"reasoning": str}
"""
if not supervisor_enabled():
return {"action": "proceed_full", "severity": "medium",
"reasoning": "(Supervisor ๋น„ํ™œ์„ฑ - ํ•ญ์ƒ ํ‘œ์ค€ ๊ฒฝ๋กœ)"}
resp = client().chat.completions.create(
model=SUPERVISOR_MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": _build_user_prompt(alarm, tier1, tier2)},
],
response_format={
"type": "json_schema",
"json_schema": {"name": "supervisor", "schema": ACTION_SCHEMA, "strict": True},
},
)
return json.loads(resp.choices[0].message.content)