포스트

AI 앱 아키텍처, 2026년 4월의 정답은 “분리(Decouple) + 상태(State) + 거버넌스(Governance)”다

AI 앱 아키텍처, 2026년 4월의 정답은 “분리(Decouple) + 상태(State) + 거버넌스(Governance)”다

들어가며

2024~2025년의 AI 앱은 “LLM API 호출 + RAG”만 붙여도 제품이 됐습니다. 하지만 2026년 4월 기준, 현업에서 부딪히는 병목은 달라졌습니다.

  • 기능이 늘수록 프롬프트가 비대해져 monolithic agent가 되고, 변경/테스트/롤백이 어려워짐
  • 도구(tool)·데이터 소스가 늘면서 통합이 파편화되고, 권한/감사/보안이 아키텍처 레벨 문제로 튀어나옴
  • 멀티스텝(계획→실행→검증) 워크플로우가 기본이 되며, “LLM 한 번 호출” 모델로는 확장성(throughput)과 신뢰성을 맞추기 어려워짐

특히 도구 연결은 MCP(Model Context Protocol) 같은 표준을 중심으로 재편되는 흐름이 뚜렷하고, 동시에 agentic 보안 위협(Goal Hijack, Tool Misuse, Inter-agent comms 등)을 전제로 설계해야 합니다. (modelcontextprotocol.io)


🔧 핵심 개념

1) Orchestrator-Worker 패턴: “계획은 얇게, 실행은 분산으로”

요즘 확장 가능한 AI 앱은 Orchestrator(오케스트레이터) 가 작업을 쪼개고, Worker(워커) 들이 도메인 단위로 수행합니다. 장점은 명확합니다.

  • Orchestrator는 “무엇을/어떤 순서로”만 결정 → 프롬프트/정책/가드레일이 한 곳에 모여 통제 가능
  • Worker는 “한 가지 도구/책임”에 집중 → 병렬 실행, 재시도, 캐시가 쉬워 스케일링 유리
  • 워커 간 계약(contract)을 schema(JSON Schema/Pydantic/Zod) 로 고정하면, agent-to-agent 통신 품질이 급상승 (sitepoint.com)

핵심은 “LLM을 똑똑하게 만들기”보다, LLM이 지휘하는 실행 시스템을 클라우드 네이티브처럼 분해하는 겁니다.

2) Stateful Graph 패턴: “대화가 아니라 워크플로우를 모델링”

실무에서 “한 번 답변”보다 중요한 건 진행 상태입니다.

  • 입력 정규화 → 계획 수립 → 검색/도구 실행 → 검증/리라이트 → 감사 로그
  • 여기서 상태(state)는 단순 채팅 히스토리가 아니라, typed state(구조화된 실행 컨텍스트) 여야 합니다.
  • 이 상태가 있어야 idempotency(중복 실행 방지), 재시도, 부분 실패 격리, 관측성(트레이싱)이 됩니다.

Agentic RAG도 같은 방향으로 진화합니다. 단순 “retrieval 1회”가 아니라, 계획 기반으로 iterative retrieval / memory 관리 / tool-invocation 을 반복하는 구조가 정리되고 있습니다. (arxiv.org)

3) Tool Interface 표준화(MCP): “연결을 코드가 아니라 프로토콜로”

도구 통합을 SDK/어댑터로만 쌓으면 팀/서비스마다 연결 방식이 달라져 운영이 지옥이 됩니다. MCP는 이를 클라이언트-호스트-서버 구조로 표준화하고(JSON-RPC 기반), 한 앱(Host)이 여러 MCP Server(툴 제공자)에 연결하는 형태를 제시합니다. (modelcontextprotocol.io)

중요 포인트는 “도구를 붙인다”가 아니라:

  • 도구 목록/스키마/알림(notification) 등 컨텍스트 교환을 표준으로 다루고
  • 향후 transport scalability, agent communication, governance까지 로드맵에 포함되어 있다는 점입니다 (blog.modelcontextprotocol.io)

4) 거버넌스-퍼스트: OWASP가 말하는 “AI가 무엇을 하게 둘 것인가”

2025 LLM 앱 보안은 Prompt Injection 등 “출력/프롬프트 중심”이 강했고, 2026 Agentic은 “AI가 실행하는 행위” 자체가 위협면입니다. (owasp.org)

그래서 아키텍처 패턴도 바뀝니다:

  • 최소 권한 도구권한(least privilege) + 동적 승인(consent)
  • 실행 전/후 정책 검사(policy check)
  • 툴 호출/외부 요청/데이터 접근에 대한 감사 추적(audit trail)

💻 실전 코드

아래 예시는 “Orchestrator-Worker + Structured Output + 정책 게이트”의 최소 구현입니다.
실제 LLM 호출은 공급자별로 다르니, 여기서는 LLM이 반환한다고 가정한 JSON을 검증/실행하는 쪽에 집중합니다(=아키텍처 뼈대).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# python 3.11+
# pip install pydantic

from __future__ import annotations
from pydantic import BaseModel, Field, ValidationError
from typing import Literal, List, Optional, Dict, Any
import time

# 1) Worker 계약(Contract): 에이전트/워커 사이 메시지를 스키마로 고정
class PlanStep(BaseModel):
    kind: Literal["tool", "retrieval", "final"]
    name: str = Field(..., description="tool name or step name")
    args: Dict[str, Any] = Field(default_factory=dict)
    risk: Literal["low", "medium", "high"] = "low"

class ExecutionPlan(BaseModel):
    goal: str
    steps: List[PlanStep]

# 2) Tool registry: MCP 같은 표준을 쓰더라도 결국 "서버/툴 카탈로그" 개념이 필요
def tool_send_email(to: str, subject: str, body: str) -> str:
    # 실제로는 SMTP/Provider API 호출
    return f"EMAIL_SENT(to={to}, subject={subject})"

def tool_sql_query(query: str) -> list[dict]:
    # 실제로는 read-only replica / sandbox에서 실행해야 함
    if "delete" in query.lower():
        raise PermissionError("Destructive query is blocked")
    return [{"id": 1, "name": "alice"}]

TOOLS = {
    "send_email": tool_send_email,
    "sql_query": tool_sql_query,
}

# 3) Policy gate: OWASP Agentic 관점에서 "도구 오용"을 아키텍처에서 차단
def policy_check(step: PlanStep) -> None:
    # 간단 예: high risk는 사람 승인 필요(여기선 예외로 차단)
    if step.risk == "high":
        raise PermissionError(f"Step '{step.name}' requires human approval")

    # 도구 allowlist
    if step.kind == "tool" and step.name not in TOOLS:
        raise PermissionError(f"Tool '{step.name}' is not allowed")

# 4) Orchestrator: 계획을 해석하고 워커를 실행(분산 실행은 큐/워크플로 엔진으로 확장)
def execute_plan(plan: ExecutionPlan) -> dict:
    trace = {"goal": plan.goal, "events": []}
    for i, step in enumerate(plan.steps, start=1):
        t0 = time.time()
        policy_check(step)

        if step.kind == "tool":
            fn = TOOLS[step.name]
            result = fn(**step.args)  # 스키마 기반 args라면 여기서 타입 안정성↑
        elif step.kind == "retrieval":
            # 예: vector search / keyword search 등을 붙이는 자리
            result = {"docs": ["doc1...", "doc2..."], "query": step.args.get("q", "")}
        else:  # final
            result = step.args.get("answer", "")

        trace["events"].append({
            "seq": i,
            "step": step.model_dump(),
            "result": result,
            "latency_ms": int((time.time() - t0) * 1000),
        })
    return trace

# ---- demo ----
if __name__ == "__main__":
    # LLM이 반환했다고 가정한 JSON(Structured Output)
    llm_json = {
        "goal": "고객 목록을 조회하고 요약 메일 발송",
        "steps": [
            {"kind": "tool", "name": "sql_query", "args": {"query": "select id, name from customers limit 10"}, "risk": "low"},
            {"kind": "tool", "name": "send_email", "args": {"to": "ops@company.com", "subject": "요약", "body": "고객 10명 조회 완료"}, "risk": "medium"},
            {"kind": "final", "name": "respond", "args": {"answer": "완료했습니다."}, "risk": "low"},
        ]
    }

    try:
        plan = ExecutionPlan.model_validate(llm_json)  # 스키마 검증(실패하면 재프롬프트/수정 루프)
        trace = execute_plan(plan)
        print(trace)
    except ValidationError as e:
        print("PLAN_SCHEMA_INVALID:", e)
    except Exception as e:
        print("EXECUTION_FAILED:", e)

이 코드의 요지는 “LLM이 곧 코드”가 아니라, LLM은 계획(데이터)을 내고 시스템이 실행한다는 점입니다. 이 구조가 되어야 스케일링(큐/병렬/재시도/관측성)과 보안(정책 게이트)이 들어갑니다.


⚡ 실전 팁

1) Compute / State / Governance를 물리적으로 분리

  • Compute: 모델 호출, 워커 실행
  • State: 대화/작업 상태(typed state), 장기 메모리, 벡터 인덱스
  • Governance: 정책/권한/감사/키관리
    이 분리가 되어야 “모델 바꾸기”, “툴 추가”, “권한 강화”가 서로 발목을 안 잡습니다. (멀티에이전트 스케일링 글들이 반복해서 강조하는 지점도 결국 이 분리입니다.) (nexaitech.com)

2) Structured Output을 ‘옵션’이 아니라 ‘기본 계약’으로

  • agent-to-agent / agent-to-tool 모두 스키마로 고정
  • 실패 시: (a) 자동 재요청 (b) 부분 수정 (c) fallback 모델
    멀티에이전트에서 “한 에이전트 출력이 다른 에이전트 입력”이 되면, 포맷 흔들림이 곧 장애입니다. (androidcentral.com)

3) RAG는 ‘검색’이 아니라 ‘제어 루프’로 설계 Agentic RAG 흐름에서는

  • 불확실하면 더 찾고
  • 충돌하면 재질의하고
  • 메모리를 갱신/폐기하는 루프가 핵심입니다. 단발 retrieval로는 비용만 늘고 품질이 불안정해집니다. (arxiv.org)

4) MCP 도입 시, “연결”보다 “신뢰 경계(trust boundary)”를 먼저 그리기 MCP는 통합을 가속하지만, 도구/서버가 늘수록 공격면도 커집니다. MCP 자체 공격면을 체계화한 위협 분류(MCP-38)처럼, 프로토콜/툴 계층의 위협을 별도로 모델링하세요. (arxiv.org)

5) OWASP Top 10을 아키텍처 체크리스트로 “매핑” LLM 앱(2025)과 Agentic 앱(2026)은 포커스가 다릅니다. “무슨 말을 하게 할 것인가”에서 “무슨 행동을 하게 둘 것인가”로 중심이 이동했습니다.

  • 도구 오용/권한 남용/에이전트 목표 하이재킹을 전제로
  • 실행 전 정책 게이트, 인간 승인, 감사 로그를 설계에 포함하세요 (owasp.org)

🚀 마무리

2026년 4월의 AI 애플리케이션 아키텍처 설계 패턴을 한 줄로 요약하면:

  • 오케스트레이션은 중앙에서 얇게(Orchestrator), 실행은 도메인 단위로 두껍게(Worker)
  • 상태는 typed state로, 워크플로우는 graph/plan으로
  • 툴 통합은 표준(MCP)로, 보안은 OWASP Agentic 관점으로 거버넌스-퍼스트

다음 학습으로는 (1) MCP 기반 툴 카탈로그/권한 모델 설계, (2) agentic RAG의 planning+retrieval 루프 구현, (3) OWASP LLM(2025)·Agentic(2026) Top 10을 자사 아키텍처에 매핑한 보안 설계 문서화를 추천합니다. (blog.modelcontextprotocol.io)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.