철도안전 지식 AI 솔루션 — 규정·법령 기반 RAG로 시나리오·보고서를 생성하는 온프레미스 아키텍처

FTA·보타이 시뮬레이터(정적 지식베이스)를 벡터DB 기반 검색증강생성(RAG) 언어모델과 결합하여, 내규·지침·법령을 근거로 위험 시나리오와 평가·보고서를 자동 생성하는 솔루션 설계 인사이트  |  기준일 2026-08-27

온프레미스 · Ollama + Gemma3 pgvector 하이브리드 검색 LangGraph 오케스트레이션 근거 인용 · RAGAS 평가 망분리 · 데이터 반출 0
3계층
지식베이스 (규범·구조·수치)
1,902
FTA/보타이 승인 매핑 (구조지식)
2
핵심 생성 기능 (시나리오·보고서)
100%
사내 처리 (외부 반출 없음)
근거 필수
모든 답변에 출처 조·항 인용

1. 문제 정의 — "정적 시뮬레이터"에서 "살아있는 지식 AI"로

현재 프로젝트는 FTA·보타이 시뮬레이터와 링크테이블(Trigger 156 · BOM 291 · Driver 87 · 승인매핑 1,902건)로 위험사건의 구조를 코드화하는 데 성공했습니다. 그러나 이 구조지식은 "무엇이 무엇과 연결되는가"는 알려주지만, "그 사건이 어떤 내규·지침·법령의 어느 조항에 걸리는가", "실제 상황에서 어떤 시나리오로 전개되는가", "그 결과를 어떤 보고서로 정리해야 하는가"는 담지 못합니다. 이 간극을 메우는 것이 언어모델 + 벡터DB(RAG) 계층입니다.

지금까지 (정적 구조지식)

· 링크테이블·마스터코드로 FTA/보타이 자동 생성
· 위험도 산정 = 결정적 집계 연산
· 사람이 규정을 찾아보고, 시나리오를 쓰고, 보고서를 손으로 작성
· 규정 개정 시 반영 지연, 담당자별 품질 편차

제안 솔루션 (검색증강 생성지식)

· 내규·지침·법령을 벡터DB에 임베딩해 의미검색
· 위험사건 코드 → 관련 규범 조항 자동 연결
· 상황 입력 → 시나리오·평가·보고서 초안 자동 생성
· 모든 문장에 출처 조·항 인용 → 검토자 신뢰 확보

설계 관점 한 줄: 링크테이블이 "뼈대(구조)"라면, RAG 지식베이스는 "살(규범·맥락)"입니다. 두 계층을 결합하면 코드(TRG/BOM/DRV)로 검색범위를 좁히고, 규범 텍스트로 근거를 채우는 하이브리드 지식엔진이 됩니다.

2. 지식베이스 설계 — 3계층 지식을 하나의 검색공간으로

RAG의 품질은 "무엇을 넣느냐"가 8할입니다. 본 솔루션은 성격이 다른 3계층을 각기 다른 방식으로 색인해 하나의 검색 오케스트레이션으로 묶습니다.

📜
계층 A — 규범지식 (내규·지침·법령) 철도안전법·시행령·시행규칙, 국토부 고시·지침, 사내 안전관리규정·작업내규·매뉴얼. 비정형 장문 → 조/항/호 단위 구조인식 청킹 후 임베딩
Vector (pgvector)
🧩
계층 B — 구조지식 (FTA·보타이 링크테이블) Trigger·BOM·Driver 마스터 + 1,902 승인매핑 + 보타이 예방/완화 배리어. 정형 코드 → RDB 그대로 + 각 노드 설명문을 임베딩하여 코드↔의미 양방향 검색
RDB + Vector
📈
계층 C — 수치지식 (고장이력·RAMS) 고장이력 4-테이블, 운용이력, λ·MTBF·가용도 산정값. 정형 수치 → RDB 집계/함수 호출(Tool)로 실시간 조회. 생성 시점에 최신 통계 주입
RDB Tool
핵심: 규범(A)은 의미검색, 구조(B)는 코드조인 + 의미검색, 수치(C)는 함수호출로 가져옵니다. 세 경로를 한 프롬프트에 합치는 것이 이 솔루션의 엔지니어링 난이도이자 차별점입니다.

2-1. 규범 문서의 "구조인식 청킹" — 왜 단순 분할로는 안 되는가

법령·규정은 조(條)–항(項)–호(號) 위계와 상호 참조("제12조제3항에 따라…")가 핵심입니다. 고정 길이(예: 500자)로 자르면 조항 경계가 깨지고 인용 정확도가 무너집니다. 조항 경계를 인식해 자르고, 각 청크에 메타데이터(법령명·조·항·시행일·개정이력)를 부착합니다.

ingest/chunk_regulation.py — 조·항 단위 구조인식 청킹 + 메타데이터 부착import re from dataclasses import dataclass # 제N조(제목) ... 형태의 조 경계와 ①②③ / 1.2.3. 항·호를 인식 ARTICLE = re.compile(r"제(\d+)조(?:의(\d+))?\s*\(([^)]*)\)") CLAUSE = re.compile(r"(?:^|\s)([①-⑮]|\d+\.)\s") @dataclass class RegChunk: law_name: str; effective_date: str # 법령명·시행일(개정 추적) article: str; clause: str; text: str ref_codes: list # 연계된 TRG/BOM/DRV 코드 def chunk_regulation(raw: str, law_name: str, effective_date: str): chunks, cur = [], None for line in raw.splitlines(): m = ARTICLE.search(line) if m: # 새 조(條) 시작 → 청크 확정 if cur: chunks.append(cur) art = f"제{m.group(1)}조" + (f"의{m.group(2)}" if m.group(2) else "") cur = RegChunk(law_name, effective_date, art, m.group(3), line, []) elif cur: cur.text += "\n" + line if cur: chunks.append(cur) return [c for c in chunks if len(c.text) > 20] # 너무 짧은 조 제거
효과: 청크 = 하나의 "조" 단위이므로, 검색 결과가 곧 인용 가능한 최소 단위가 됩니다. 생성 답변에 「철도안전법 제39조의3 제2항(시행 2026.x.x)」처럼 정확한 근거를 붙일 수 있습니다.

3. 아키텍처 — 온프레미스 RAG 파이프라인

망분리·데이터 반출 금지 환경을 전제로, 전 구성요소를 사내에서 구동합니다. 임베딩·생성 모두 로컬 모델을 사용해 외부 API 호출 0건을 보장합니다.

1수집·전처리PDF/HWP/DOCX 규정 → 텍스트화 → 조·항 구조인식 청킹 ETL
2임베딩bge-m3(다국어·한국어 강함) 또는 KoSimCSE로 벡터화 Ollama
3색인pgvector에 벡터+메타데이터 적재, HNSW 인덱스 pgvector
4검색코드필터 + 벡터유사도 + 키워드(BM25) 하이브리드 Retrieve
5생성Gemma3가 근거청크 기반 시나리오/보고서 작성 Gemma3
6검증·인용근거 인용 부착 + RAGAS 자동평가 + 검토승인 Guard

3-1. 스택 선택 근거 (온프레미스 전제)

구성요소채택선택 근거
LLM(생성)Gemma3 (Ollama 서빙)사내 GPU에서 구동, 한국어 성능·라이선스 양호. 무거운 보고서 생성은 대형 파라미터, 실시간 태깅은 경량 모델로 모델 라우팅
임베딩bge-m3 / KoSimCSE한국어·법령체 문어에 강함, 1024차원. Ollama로 동일 인프라에서 서빙해 운영 단순화
벡터스토어PostgreSQL + pgvector기존 RDB(고장이력·RAMS)와 동일 DB 안에 벡터 저장 → 코드조인·수치조회·의미검색을 한 트랜잭션에서. 별도 벡터DB 운영부담 제거
오케스트레이션LangGraph (StateGraph)검색→생성→검증→재검색 순환·분기 워크플로를 상태그래프로 명시. 다단계 보고서 생성에 적합(멀티에이전트 확장 용이)
평가RAGASfaithfulness(근거충실도)·answer relevancy·context precision을 정량화 → 규정 오인용 여부를 자동 감지
왜 pgvector인가: 이 솔루션의 본질은 "코드(구조) + 벡터(규범) + 수치(통계)"의 결합입니다. 세 가지를 서로 다른 저장소에 두면 조인이 애플리케이션 레벨로 새어나가 복잡해집니다. 한 PostgreSQL 안에 두면 WHERE trg_code = '...' AND embedding <=> query < θ 한 쿼리로 끝납니다.

3-2. 벡터 스키마 & 적재 (pgvector)

db/schema.sql — 규범 청크 벡터 테이블 (기존 RDB와 동일 DB)CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE reg_chunk ( id bigserial PRIMARY KEY, law_name text, -- 철도안전법 / 안전관리규정 … article text, -- 제39조의3 clause text, -- 제2항 effective_date date, -- 시행일(개정 추적) doc_type text, -- law | notice | internal ref_codes text[], -- {TRG-0001, BOM-...} 연계코드 content text, embedding vector(1024) -- bge-m3 차원 ); -- 의미검색용 HNSW + 코드/유형 필터용 B-tree·GIN 병행 CREATE INDEX ON reg_chunk USING hnsw (embedding vector_cosine_ops); CREATE INDEX ON reg_chunk USING gin (ref_codes); CREATE INDEX ON reg_chunk (doc_type, effective_date);
ingest/embed_load.py — Ollama 임베딩 → pgvector 적재import ollama, psycopg from pgvector.psycopg import register_vector def embed(text: str) -> list: # 온프레미스 Ollama 임베딩 (외부 호출 없음) r = ollama.embed(model="bge-m3", input=text) return r["embeddings"][0] def load_chunks(chunks, dsn: str): with psycopg.connect(dsn) as conn: register_vector(conn) for c in chunks: conn.execute( """INSERT INTO reg_chunk (law_name, article, clause, effective_date, doc_type, ref_codes, content, embedding) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)""", (c.law_name, c.article, c.clause, c.effective_date, c.doc_type, c.ref_codes, c.text, embed(c.text))) conn.commit()

3-3. 하이브리드 검색 — 코드로 좁히고, 의미로 찾고, 키워드로 보정

위험사건 코드가 주어지면 ①연계코드 필터로 후보를 좁힌 뒤 ②벡터 유사도로 정렬하고, 법조문 특유의 정확 용어(예: "정거장", "선로전환기")는 ③키워드 매칭으로 보정합니다. 개정 추적을 위해 시행일 최신본만 필터링합니다.

retrieve/hybrid_search.py — 코드필터 + 벡터 + 키워드 하이브리드def retrieve(conn, query: str, ref_codes: list = None, k: int = 6): qvec = embed(query) sql = """ SELECT law_name, article, clause, content, 1 - (embedding <=> %(qv)s) AS sim, -- 코사인 유사도 ts_rank(to_tsvector('simple', content), plainto_tsquery('simple', %(q)s)) AS kw -- 키워드 점수 FROM reg_chunk WHERE effective_date = (SELECT max(effective_date) -- 최신 시행본 FROM reg_chunk r2 WHERE r2.article = reg_chunk.article) {code_filter} ORDER BY (0.75 * (1 - (embedding <=> %(qv)s)) + 0.25 * ...) DESC -- 가중 결합 LIMIT %(k)s""" code_filter = "AND ref_codes && %(codes)s" if ref_codes else "" return conn.execute(sql.format(code_filter=code_filter), {"qv": qvec, "q": query, "codes": ref_codes, "k": k}).fetchall()
주의 — 규범 도메인의 함정: 일반 RAG와 달리 법령은 "틀린 조항 인용"이 곧 규정 위반 리스크입니다. ① 최신 시행본만 검색(개정 반영), ② 유사도 임계값 미달 시 "관련 규정 없음"을 명시(억지 인용 금지), ③ 생성 후 인용된 조항이 실제 검색결과에 있었는지 역검증 — 3중 안전장치가 필수입니다.

4. 두 가지 핵심 생성 기능

4-1. 위험 시나리오 생성 Scenario

위험사건 코드(TRG) 또는 상황 설명을 입력하면, FTA 원인경로(구조지식) + 관련 규범(규범지식) + 고장통계(수치지식)를 결합해 "어떻게 전개되고, 어디서 막을 수 있으며, 어떤 규정 위반이 되는가"를 서사형 시나리오로 생성합니다.

입력TRG-0001(제동장치 고장) + 차종/노선 선택
결합FTA 6대 경로 · 보타이 배리어 · 관련 조항 · λ/MTBF
출력사건전개 서사 · 악화/완화 요인 · 위반 우려 조항 · 예방 배리어 점검항목
활용: 안전교육 사례, 위험성평가 워크숍 시드, 비상대응 시나리오 초안, 신규자 훈련 자료.

4-2. 평가·보고서 생성 Report

고장/장애 데이터 또는 사건 개요를 입력하면 위험도평가서·사고조사 보고서·안전점검 결과보고서 등 규정 양식에 맞춰 초안을 생성합니다. 수치는 RDB에서 실시간 조회(할루시네이션 차단), 서술·판단 근거는 규범 청크에서 인용합니다.

입력사건 개요 / 고장이력 레코드 / 기간·설비 범위
주입λ·MTBF·가용도(Tool 실측) · 5×5 위험도 · 근거조항
출력규정 양식 보고서 초안(.docx) · 각 판단에 출처 인용 · 검토체크리스트
활용: 위험도평가서, RAMS 결과보고, 안전관리 정기보고, 개선요구·시정조치 문서.

4-3. LangGraph 오케스트레이션 — 검색→생성→자기검증 순환

보고서 생성은 단발 프롬프트가 아니라 계획→검색→작성→인용검증→(부족 시)재검색의 상태그래프로 구현합니다. 근거가 부족하면 스스로 재검색하고, 인용이 실제 검색결과와 불일치하면 반려하는 자기검증 루프가 신뢰성의 핵심입니다.

graph/report_agent.py — LangGraph 상태그래프 (검색·생성·검증 루프)from langgraph.graph import StateGraph, END from typing import TypedDict class S(TypedDict): query: str; codes: list; ctx: list # 검색된 근거청크 stats: dict; draft: str; verified: bool; retries: int def plan(s): # 어떤 규범·통계가 필요한지 계획 s["codes"] = link_table_lookup(s["query"]); return s def search(s): # 하이브리드 검색 + RDB 통계 Tool 호출 s["ctx"] = retrieve(conn, s["query"], s["codes"]) s["stats"] = rams_stats(s["codes"]); return s def write(s): # Gemma3: 근거청크·통계로만 작성, 문장마다 [출처] 태그 s["draft"] = gemma_generate(REPORT_TMPL, s["ctx"], s["stats"]); return s def verify(s): # 인용 조항이 실제 ctx에 존재? faithfulness 통과? s["verified"] = check_citations(s["draft"], s["ctx"]); return s def route(s): # 검증 실패 & 재시도 여력 → 재검색, 아니면 종료 return "search" if (not s["verified"] and s["retries"] < 2) else END g = StateGraph(S) for n, fn in [("plan",plan),("search",search),("write",write),("verify",verify)]: g.add_node(n, fn) g.set_entry_point("plan") g.add_edge("plan","search"); g.add_edge("search","write"); g.add_edge("write","verify") g.add_conditional_edges("verify", route) # 자기검증 순환 report_agent = g.compile()
이 구조의 가치: 생성물이 근거를 스스로 검증하고 부족하면 스스로 재검색합니다. 규범 도메인에서 "그럴듯하지만 틀린 조항"을 최종 산출물에서 걸러내는 것이 사람 검토자의 부담을 실질적으로 줄이는 지점입니다.

5. 신뢰성·거버넌스 — 규범 AI가 반드시 갖춰야 할 안전장치

① 근거 강제(Grounding)

모든 판단 문장에 「법령명 제○조제○항」 인용을 강제. 근거 없는 문장은 생성 규칙에서 차단. 검색결과에 없는 조항 인용 시 자동 반려(역검증).

② 개정 추적

시행일 메타데이터로 최신본만 검색. 규정 개정 시 재임베딩 파이프라인이 구본을 만료 처리 → "지난 규정으로 판단" 리스크 제거.

③ 정량 자동평가

RAGAS로 faithfulness·context precision 상시 측정. 임계 미달 답변은 검토큐로 라우팅. 회귀 테스트셋으로 모델 교체 영향 검증.

④ 망분리·데이터 주권

임베딩·생성 전부 온프레미스(Ollama). 규정·고장데이터 외부 반출 0건. 공공기관 보안·데이터 거버넌스 요건 충족.

⑤ 사람-검토 워크플로

생성물은 "초안"으로만 취급, 승인 전 배포 불가. FTA/보타이 승인체계(APPROVED)와 동일한 거버넌스 계승.

⑥ 모름의 명시

근거 부족 시 억지 생성 대신 "관련 규정 미확인" 표기. FTA의 UNKNOWN(D9xxx) 철학을 생성 계층에도 일관 적용.

가장 중요한 설계 원칙: 이 솔루션은 판단을 대체하지 않고 근거를 조립합니다. 법령 해석·최종 판단의 책임은 사람에게 있으며, AI는 "관련 조항을 빠짐없이·정확히·출처와 함께" 모아주는 역할에 한정하는 것이 규제·감사 대응 측면에서 안전합니다.

6. 착수 로드맵 — 우선순위 순

#단계내용산출물
1규범 코퍼스 구축철도안전법 3단(법·령·규칙) + 핵심 사내 규정 5~10종을 텍스트화·구조인식 청킹. 파일럿 범위는 제동·신호 등 1~2개 설비군으로 한정해 품질 검증청킹 코퍼스 · 메타데이터 스키마
2pgvector 색인 & 검색 PoC기존 RDB에 pgvector 확장, bge-m3 임베딩 적재. 코드필터+벡터 하이브리드 검색의 Top-k 정확도(정답 조항 포함율) 측정검색 모듈 · 정확도 리포트
3링크테이블 ↔ 규범 연계 매핑TRG/BOM/Driver 코드에 관련 조항을 ref_codes로 연결. 최다빈도 Driver(D601·D602·D9002 등)부터 착수해 커버리지 조기 확보코드-규범 매핑테이블
4시나리오 생성 프로토타입TRG 입력 → FTA경로+규범+통계 결합 시나리오 생성. 안전교육/워크숍 1건에 실제 투입해 현업 피드백 수집시나리오 생성기 · 사용성 평가
5보고서 생성 + LangGraph 검증루프위험도평가서 1종을 대상으로 자기검증 그래프 구현. RAGAS 기준선 설정, 근거 역검증 통과율을 배포 게이트로 지정보고서 에이전트 · 평가 대시보드
6거버넌스·운영 정착규정 개정 시 재임베딩 자동화, 검토승인 워크플로, 접근권한·감사로그. FTA 승인체계와 통합 운영운영 SOP · 재색인 파이프라인
Quick Win: 이미 보유한 온프레미스 RAG 자산(Ollama·Gemma3·pgvector 경험)FTA/보타이 구조지식(1,902 매핑)이 있으므로, 1~2단계는 신규 구축이 아니라 기존 자산의 결합입니다. 좁은 설비군 1개로 시나리오 생성 데모를 먼저 성공시켜 조직 내 설득력을 확보하는 전략을 권장합니다.