diff --git a/backend/src/main/java/com/zioinfo/mes/config/SecurityConfig.java b/backend/src/main/java/com/zioinfo/mes/config/SecurityConfig.java
index bf3a2bd..359ad49 100644
--- a/backend/src/main/java/com/zioinfo/mes/config/SecurityConfig.java
+++ b/backend/src/main/java/com/zioinfo/mes/config/SecurityConfig.java
@@ -57,6 +57,9 @@ public class SecurityConfig {
.requestMatchers("/api/admin/audit").hasAnyRole("SUPERADMIN", "MANAGER")
.requestMatchers("/api/admin/**").hasRole("SUPERADMIN")
+ // RAG 기법 토글 변경 — MANAGER 이상(운영자 전용). 분석 트리거(POST)는 아래 WORKER+ 규칙 적용.
+ .requestMatchers(HttpMethod.PUT, "/api/mes/rag/toggles/**").hasAnyRole("SUPERADMIN", "MANAGER")
+
// 변경(실적·검사·입출고·재고이동) — WORKER 이상 (기준정보/마감/승인은 서비스에서 MANAGER+ 가드)
.requestMatchers(HttpMethod.POST, "/api/mes/**").hasAnyRole("SUPERADMIN", "MANAGER", "WORKER")
.requestMatchers(HttpMethod.PUT, "/api/mes/**").hasAnyRole("SUPERADMIN", "MANAGER", "WORKER")
diff --git a/backend/src/main/java/com/zioinfo/mes/rag/RagClient.java b/backend/src/main/java/com/zioinfo/mes/rag/RagClient.java
new file mode 100644
index 0000000..2fbb5fe
--- /dev/null
+++ b/backend/src/main/java/com/zioinfo/mes/rag/RagClient.java
@@ -0,0 +1,193 @@
+package com.zioinfo.mes.rag;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import java.time.Duration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 얇은 중앙 guardia-rag REST 클라이언트 (MES 전용).
+ *
+ *
핵심 설계: MES 는 LangChain·검색·에이전틱 로직을 Java 에 재구현하지 않는다. 중앙 Python
+ * guardia-rag 의 검색·에이전틱·구조화·검증을 REST 로 호출하기만 한다. 미가용/타임아웃/RAM 부족 시
+ * 절대 예외를 전파하지 않고 {@code degraded:true} 가 표시된 빈/폴백 결과를 돌려준다(서비스 계층이
+ * MES 결정론 로컬 폴백 수행).
+ *
+ *
POST /rag/answer — 검색(retrieval_mode)+근거생성+인용+guardrail
+ *
POST /rag/agent — 에이전틱 tool-use(멀티스텝 추론, step 상한)
+ *
POST /rag/structured — format:json 결정론 JSON(스키마 강제)
+ *
POST /rag/feedback — 👍/👎 피드백(solution=mes 격리)
+ *
GET /rag/trust/settings — 가용성 프로브
+ *
+ */
+@Slf4j
+@Component
+public class RagClient {
+
+ private final WebClient.Builder builder;
+ private final RagProperties props;
+
+ public RagClient(WebClient.Builder builder, RagProperties props) {
+ this.builder = builder;
+ this.props = props;
+ }
+
+ /** 중앙 RAG 사용 가능 여부(설정 enabled + trust/settings 도달). */
+ public boolean available() {
+ if (!props.isEnabled()) return false;
+ try {
+ builder.baseUrl(props.getBaseUrl()).build()
+ .get().uri("/rag/trust/settings?solution=" + props.getSolution())
+ .retrieve().bodyToMono(String.class)
+ .timeout(Duration.ofSeconds(3)).block();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * POST /rag/structured — 스키마 강제 결정론 JSON.
+ *
+ * @return {@code {data:{...}, valid, fallback_used, degraded}} 형태. 미가용 시 {@code degraded:true}.
+ */
+ public Map structured(String prompt, Map schema,
+ String model, double temperature) {
+ if (!props.isEnabled()) return degraded("rag_disabled");
+ try {
+ Map body = new LinkedHashMap<>();
+ body.put("solution", props.getSolution());
+ body.put("prompt", prompt);
+ if (schema != null) body.put("schema", schema);
+ if (model != null) body.put("model", model);
+ body.put("temperature", temperature);
+ body.put("max_retries", 1);
+ Map res = post("/rag/structured", body);
+ return res != null ? res : degraded("rag_no_response");
+ } catch (Exception e) {
+ log.warn("RAG /structured 일시 불가 — MES 폴백: {}", summarize(e));
+ return degraded("rag_unavailable");
+ }
+ }
+
+ /**
+ * POST /rag/answer — 검색(retrieval_mode)+생성+검증+guardrail.
+ *
+ * @return {@code {answer, grounded, faithfulness, citations[], guardrail{...}, sources[], answer_id, degraded}}.
+ * 미가용 시 {@code degraded:true}.
+ */
+ public Map answer(String query, String retrievalMode, boolean rerank,
+ int topK, String model, boolean verify) {
+ if (!props.isEnabled()) return degraded("rag_disabled");
+ try {
+ Map options = new LinkedHashMap<>();
+ options.put("rerank", rerank);
+ options.put("stream", false);
+ options.put("verify", verify);
+ if (model != null) options.put("model", model);
+
+ Map body = new LinkedHashMap<>();
+ body.put("solution", props.getSolution());
+ body.put("query", query);
+ body.put("retrieval_mode", retrievalMode == null ? "vector" : retrievalMode);
+ body.put("top_k", topK <= 0 ? 5 : topK);
+ body.put("options", options);
+ Map res = post("/rag/answer", body);
+ return res != null ? res : degraded("rag_no_response");
+ } catch (Exception e) {
+ log.warn("RAG /answer 일시 불가 — MES 폴백: {}", summarize(e));
+ return degraded("rag_unavailable");
+ }
+ }
+
+ /**
+ * POST /rag/agent — 에이전틱 tool-use(멀티스텝 추론). MES 도메인 조회 매퍼를 도구로 노출해
+ * 조회→집계→해석을 수행한다. {@code maxSteps} 상한으로 폭주/RAM 위협 차단.
+ *
+ * @param tools 읽기전용 조회 도구 정의 목록(이름·설명·파라미터 스키마). 쓰기/SSH 도구 금지.
+ * @return {@code {answer, steps[], tool_calls[], structured?, answer_id, degraded}}. 미가용 시 {@code degraded:true}.
+ */
+ public Map agent(String task, Object tools, String retrievalMode,
+ int maxSteps, String model, Map structuredSchema) {
+ if (!props.isEnabled()) return degraded("rag_disabled");
+ try {
+ Map options = new LinkedHashMap<>();
+ options.put("max_steps", maxSteps <= 0 ? 4 : Math.min(maxSteps, 8)); // 상한 강제
+ options.put("stream", false);
+ if (model != null) options.put("model", model);
+ if (retrievalMode != null) options.put("retrieval_mode", retrievalMode);
+ if (structuredSchema != null) options.put("schema", structuredSchema); // tool-use + 구조화 결합
+
+ Map body = new LinkedHashMap<>();
+ body.put("solution", props.getSolution());
+ body.put("task", task);
+ if (tools != null) body.put("tools", tools);
+ body.put("options", options);
+ Map res = post("/rag/agent", body);
+ return res != null ? res : degraded("rag_no_response");
+ } catch (Exception e) {
+ log.warn("RAG /agent 일시 불가 — MES 폴백: {}", summarize(e));
+ return degraded("rag_unavailable");
+ }
+ }
+
+ /**
+ * POST /rag/feedback — 👍/👎 피드백(solution=mes 격리).
+ *
+ * @return {@code {feedback_id, stored}} 또는 미가용 시 {@code {stored:false, degraded:true}}.
+ */
+ public Map feedback(String answerId, String query, String answer,
+ String verdict, String correction, String userRef) {
+ if (!props.isEnabled()) return Map.of("stored", false, "degraded", true);
+ try {
+ Map body = new LinkedHashMap<>();
+ body.put("solution", props.getSolution());
+ if (answerId != null) body.put("answer_id", answerId);
+ if (query != null) body.put("query", query);
+ if (answer != null) body.put("answer", answer);
+ body.put("verdict", verdict);
+ if (correction != null) body.put("correction", correction);
+ if (userRef != null) body.put("user_ref", userRef);
+ Map res = post("/rag/feedback", body);
+ return res != null ? res : Map.of("stored", false, "degraded", true);
+ } catch (Exception e) {
+ log.warn("RAG /feedback 일시 불가: {}", summarize(e));
+ return Map.of("stored", false, "degraded", true);
+ }
+ }
+
+ // ── helpers ───────────────────────────────────────────────────────────
+ @SuppressWarnings("unchecked")
+ private Map post(String path, Map body) {
+ return builder.baseUrl(props.getBaseUrl()).build()
+ .post().uri(path)
+ .header("X-Solution-Key", props.getSolution())
+ .bodyValue(body)
+ .retrieve()
+ .bodyToMono(Map.class)
+ .timeout(Duration.ofMillis(props.getTimeoutMs()))
+ .map(m -> (Map) m)
+ .block();
+ }
+
+ private Map degraded(String reason) {
+ Map out = new LinkedHashMap<>();
+ out.put("degraded", true);
+ out.put("degraded_reason", reason);
+ return out;
+ }
+
+ /** 스택트레이스 미노출: 메시지 1줄만 요약. */
+ private String summarize(Exception e) {
+ String m = e.getMessage();
+ if (m == null) return e.getClass().getSimpleName();
+ return m.length() > 160 ? m.substring(0, 160) : m;
+ }
+}
diff --git a/backend/src/main/java/com/zioinfo/mes/rag/RagController.java b/backend/src/main/java/com/zioinfo/mes/rag/RagController.java
new file mode 100644
index 0000000..6d1ef79
--- /dev/null
+++ b/backend/src/main/java/com/zioinfo/mes/rag/RagController.java
@@ -0,0 +1,71 @@
+package com.zioinfo.mes.rag;
+
+import com.zioinfo.mes.admin.SettingService;
+import com.zioinfo.mes.common.ApiResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * MES 최신 AI 기법(중앙 guardia-rag) 배선 API — 별개 레이어.
+ *
+ *
기존 {@code /api/mes/ai/*}(AiController, 8개 제조 AI)는 불변. 본 컨트롤러는 중앙 경유
+ * 신규 경로만 추가한다. RBAC 은 {@code SecurityConfig} 가 통제(POST=Worker+, 토글 PUT=Manager+).
+ *
+ *
+ *
POST /api/mes/rag/defect-analysis — 불량 RCA + SPC 이상감지(/agent+/structured, SPC 수치는 결정론)
+ *
POST /api/mes/rag/predict-analysis — 설비 예지보전 + 수요/생산 예측(/agent, 수치는 결정론 베이스라인)
+ *
POST /api/mes/rag/feedback — 👍/👎 피드백(/rag/feedback, solution=mes 격리)
+ *
GET /api/mes/rag/toggles — 현재 기법 토글 스냅샷
+ *
PUT /api/mes/rag/toggles/{key} — 토글 변경(Manager+, mes_setting 격리 저장)
+ *
+ */
+@RestController
+@RequestMapping("/api/mes/rag")
+@RequiredArgsConstructor
+public class RagController {
+
+ private final RagMesService service;
+ private final SettingService settingService;
+
+ @PostMapping("/defect-analysis")
+ public ApiResponse