Skip to content

Repository files navigation

🎼 Orchestrion

출처에 기반해 안전하게 답하는, 통제된 LLM 에이전트 환경.

서비스에 LLM을 붙일 때, 가장 중요한 건 뭘까요? 유창한 오답보다는 믿을 수 있는 답변이 필요할 때가 있습니다. 근거 없이 그럴듯하게 지어낸 한 문장이, 그대로 사고가 되기도 하니까요.

보통의 단일 에이전트는 모델 하나가 알아서 판단하고, 도구를 부르고, 답까지 씁니다. 그리고 지어내지 않았기를 바랄 수밖에 없죠. Orchestrion은 이 구조를 뒤집었습니다. 순서는 코드가 정하고, 모델은 각 단계에서 자기 판단만 내립니다. 그래서 모든 답변은 입력 게이트를 지나고, 문장마다 근거를 달고, 출력 검증까지 통과한 뒤에야 화면에 도달합니다.

Claude Code와 몇 가지 의존성만 있으면 바로 돌려볼 수 있는 샘플 구조입니다. 통제된 에이전트 라우팅, 가드레일, 지식베이스(KB) 기반 응답이 하나의 시나리오로 묶여 있습니다. 화면은 한국어이고, 예시로 화학 안전 문서를 넣어 뒀지만 엔진은 도메인을 가리지 않아 어떤 마크다운 문서 묶음에도 그대로 붙습니다.

Orchestrion 프로세스 대시보드 — 질문이 노드 그래프를 통과하며 [S#]·[DB#] 근거를 인용하고 출력 검증까지 통과하는 화면

질문 하나가 입력 게이트 → 계획 → 근거 수집 → 검수 → 출력 검증 6단계를 지나며, 답변의 모든 사실 문장이 문서 청크([S#])와 정형 팩트([DB#])에 걸려 있습니다. 오른쪽 패널에는 실행된 SQL, 조회된 팩트 표, 도구 호출, 가드레일 판정이 실시간으로 쌓입니다.


핵심 원칙

  • 지휘는 모델이 아니라 브리지가 한다. 각 노드는 딱 한 가지 일만 하는 LLM 호출 하나입니다(게이트 → 라우팅 → 계획 → 작업 → 검수 → 검증). 다음에 어느 노드가 돌지는 모델이 아니라 파이썬 브리지가 규칙대로 정합니다. 제어 흐름이 긴 대화 속에서 우연히 결정되는 게 아니라, 언제든 따라 읽고 재현할 수 있는 코드로 남습니다.
  • 근거가 검증돼야 나간다. 작업 노드는 사실을 담은 문장마다 근거를 답니다 — 문서에서 왔으면 [S#], 정형 데이터에서 왔으면 [DB#]. 받쳐 줄 근거가 없으면 그럴듯하게 지어내는 대신 "제공 자료에 없음" 이라고 분명히 밝힙니다. 그리고 별도의 검증 노드가 초안을 근거와 한 번 더 맞춰 봅니다.
  • 도구 게이트는 단 하나, 빠짐없이 지난다. 모든 도구 호출은 예외 없이 하나의 정책 훅을 지납니다. 이 훅은 규칙 저장소를 호출할 때마다 새로 읽기 때문에, 화면이나 자연어로 규칙을 켜고 끄면 바로 다음 호출부터 적용됩니다.
  • 무엇이 빠져도 멈추지 않고 물러선다. 임베딩 모델이 없으면 검색은 BM25만으로 돌아가고, 지식이 아예 없어도 그래프는 그대로 돌면서 솔직하게 모른다고 답합니다. 어느 지점에서도 소리 없이 무너지지 않습니다.
  • 정체성은 코드가 아니라 콘텐츠다. 하나의 에이전트는 scenario.md + knowledge/raw/ + data/demo.json + 가드레일 정책으로 정의되고, 실행할 때마다 이 파일들을 새로 읽습니다. 그래서 새 업무를 만드는 일은 엔진을 다시 짜는 게 아니라 콘텐츠를 갈아 끼우는 일입니다.

아키텍처

실행 흐름

질문 하나는 이 노드 그래프를 따라 흐릅니다. 갈림길과 순서는 브리지가 규칙대로 정하고, 각 노드는 모델을 한 번 불러 자기 몫의 판단만 내립니다.

flowchart LR
    Q([질문]) --> RMI["입력 게이트<br/>rm_input"]
    RMI -. REJECT .-> DECL([정중한 거절])
    RMI -- ALLOW --> RT{"라우팅<br/>route"}
    RT -- 단순 --> SA["즉답<br/>simple_answer"]
    RT -- 업무 --> PL["계획<br/>plan"]
    PL --> WK["근거 수집·초안<br/>worker"]
    WK --> RV["검수<br/>review"]
    RV -. "REDO ≤ 2" .-> WK
    RV --> RMO["출력 검증<br/>rm_output"]
    SA --> RMO
    RMO --> ANS([검증 통과 답변])

    classDef rm fill:#fde2e2,stroke:#c0392b,color:#7b241c;
    classDef hyp fill:#e2ecfd,stroke:#2c5aa0,color:#1a3c6e;
    classDef wrk fill:#e6f6ea,stroke:#27ae60,color:#186a3b;
    class RMI,RMO rm;
    class RT,SA,PL,RV hyp;
    class WK wrk;
Loading

노드 — 무엇을, 어떤 모델로

노드 레인 역할 모델
rm_input 위험관리 질문이 업무 범위 안인지 판정 — 벗어나면 정중히 거절 haiku
route 총괄 단순 질문(즉답)인지 실제 업무인지 분기 haiku
simple_answer 총괄 인사·안내 같은 단순 질문에 짧게 즉답 sonnet
plan 총괄 업무 방식 절차에 따라 검색 계획 수립 sonnet
worker 실무 지식 도구로 근거를 모아 [S#]/[DB#] 인용 초안 작성 sonnet
review 총괄 초안이 질문에 답하고 인용을 갖췄는지 검수 — 미흡하면 REDO(최대 2회) haiku
rm_output 위험관리 초안을 근거와 대조해 사실·톤 검증, JSON 판정 산출 haiku

레인(위험관리 / 총괄 / 실무)은 대시보드에서 색으로 구분되어, 지금 어떤 성격의 노드가 돌고 있는지 한눈에 들어옵니다.

검증 계약 — [S#][DB#]

이 프로젝트에서 가장 공들인 부분입니다. 작업 노드가 인용할 수 있는 근거는 딱 두 종류입니다.

  • [S#] — 문서에서 잘라낸 청크. 하이브리드 검색으로 찾은 원문이고, 화면에는 주황색으로 나옵니다.
  • [DB#] — 정형 팩트 테이블의 한 행. 정확한 수치나 속성을 SQL로 조회한 것이고, 노란색으로 표시하면서 실행한 쿼리와 결과 표까지 함께 보여 줍니다.

근거가 없는 문장은 애초에 쓸 수 없고, 그럴 때는 "제공 자료에 없음"이라고 답합니다. 마지막으로 rm_output 노드가 초안의 사실 문장을 하나하나 근거와 다시 맞춰 보고, 여기까지 통과한 답변만 사용자에게 전달됩니다.

지식 파이프라인

knowledge/raw/*.md
      │  ingest (make ingest)
      ▼
  청크 분할 ─→ FTS5 색인(BM25) + 임베딩(bge-m3) + LLM 팩트 추출
      ▼
  SQLite (docs · chunks · chunks_fts · embeddings · facts)
      ▲
      │  worker의 4개 읽기 전용 도구만 접근 (MCP)
      │  search_knowledge · read_doc · query_facts · list_sources

검색은 BM25와 코사인 유사도를 상호 순위 융합(RRF)으로 섞어 씁니다. 임베딩 모델이 없으면 자동으로 BM25만 쓰기 때문에, 어떤 환경에서도 데모가 멈추지 않습니다.

구성 요소

파일 역할
bridge/graph.py 노드 그래프 + 모델 티어별 웜 세션 풀(런 간 유지, 노드마다 새 세션 id로 히스토리 격리)
bridge/hooks.py · bridge/policy.py 유일한 도구 게이트 + 편집 가능한 first-match 규칙 저장소
bridge/api.py · bridge/app.py REST API + WebSocket 이벤트 피드, 정적 프런트 서빙
bridge/scenario.py scenario.md를 파싱해 그래프 프롬프트에 런타임 주입
core/db.py · core/search.py · core/embed.py SQLite 스키마 · 하이브리드 검색 · 임베딩(강등 지원)
ingest/ 마크다운 → 청크 → FTS → 임베딩 → 팩트 적재
mcpserver/server.py FastMCP — worker에게 노출되는 지식 도구 4종
frontend/ 그래프가 실시간으로 켜지는 과정을 보여 주는 React 대시보드

구성 요소별 자세한 설명과 지켜야 할 불변식(invariant)은 CLAUDE.md에 정리해 뒀습니다.


빠른 시작

준비물: uv(Python 3.11), Node 18+, Claude 구독 또는 API 키, 그리고 임베딩을 쓰려면 ollama + bge-m3(선택).

cp .env.example .env          # 선택: ANTHROPIC_API_KEY 설정, 없으면 구독 인증
make setup                    # uv sync
make ingest                   # knowledge/raw/ → kb.sqlite
./start.sh                    # FastMCP + 브리지, 프런트 빌드, 브라우저 자동 열림

화면 접속

./start.sh는 프런트엔드를 빌드해서 브리지가 같이 띄워 줍니다. 그래서 브라우저에서 http://localhost:8787 한 곳으로 대시보드에 들어갑니다(자동으로 열리고, 원치 않으면 ./start.sh --no-open). 위쪽 탭으로 세 화면을 오갑니다.

화면 무엇을 보나
프로세스 질문을 넣으면 노드 그래프가 실시간으로 켜지고, 인용된 근거·검증 판정이 나타나는 메인 화면
지식체계 적재된 문서·청크·팩트를 훑어보고 하이브리드 검색을 직접 던져 보는 화면
가드레일 도구 정책 규칙을 켜고 끄고, 지금까지의 허용·차단 로그를 보는 화면

프런트엔드만 따로 만질 때는 HMR이 붙은 개발 서버가 편합니다. ./start.sh --dev를 쓰면 Vite가 http://localhost:5173 에 뜨고, /ws는 브리지로 프록시됩니다.

대시보드에서 질문을 넣으면 그래프가 도는 모습을 볼 수 있습니다. 화면 없이 확인하고 싶다면:

python scripts/graph_probe.py "물질 A의 1일 최대 취급량과 인화점은?"
curl -s localhost:8787/api/kb/stats

내 지식으로 바꾸기

  1. knowledge/raw/에 잘 정리된 .md 파일을 넣습니다. (PDF 파서는 일부러 넣지 않았으니, 먼저 마크다운으로 바꿔 주세요.)
  2. scenario.md를 다시 씁니다(요약/대상/금지 + 업무 방식: 불릿). 이 내용이 실행할 때 그래프 프롬프트로 들어갑니다.
  3. make ingest 후 다시 시작합니다.

이렇게 만든 번들은 scenarios/<이름>/에 스냅샷으로 저장해 두고 필요할 때 갈아 끼울 수 있습니다. 기본으로 들어 있는 scenarios/chemical/이 그 예시입니다.

라이선스

MIT — LICENSE 참고.

About

Deterministic multi-LLM orchestration with grounded, verified answers, an editable guardrail gate, and a live process dashboard.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages