본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 09. 00:53

하나의 객체, 세 개의 인터페이스: ExoModel을 사용한 자연어 칸반 보드 구축하기

요약

ExoModel을 활용하여 CLI, Telegram, MCP라는 세 가지 인터페이스를 중복 로직 없이 통합한 자연어 칸반 보드 구축 사례를 소개합니다. 스키마 자체가 프롬프트가 되어 LLM이 직접 객체를 생성하고 업데이트하는 아키텍처를 다룹니다.

핵심 포인트

  • ExoModel을 통해 스키마를 프롬프트로 활용하여 파서 유지보수 비용 절감
  • SQLModel과 LLM 통신을 결합한 ExoSQLModel 기반의 객체 지향 설계
  • 인터페이스와 비즈니스 로직을 분리하여 확장성 있는 아키텍처 구축
  • LLM이 딕셔너리가 아닌 검증된 인스턴스를 직접 반환하는 워크플로우

AI 기반 앱에 인터페이스를 추가할 때마다 비용(tax)이 발생합니다. 해당 인터페이스를 위해 수동으로 작성된 의도 라우터(intent router)가 필요하며, 이는 다른 인터페이스들이 이미 처리하고 있는 동일한 명령들을 다시 파싱해야 합니다. CLI를 위해 하나, Telegram을 위해 또 다른 하나, MCP를 위해 세 번째가 필요합니다. 세 번째 인터페이스에 도달하면, 당신은 제품을 만드는 것이 아니라 파서(parser)를 유지보수하고 있는 셈입니다.

ExoKanban은 CLI, Telegram 봇, MCP 서버라는 세 가지 인터페이스가 중복된 라우팅 로직 없이 동일한 객체를 공유하는 개인용 칸반 보드입니다. 이것이 어떻게 구축되었는지, 그리고 왜 이러한 아키텍처를 갖게 되었는지 소개합니다.

기초: 활성화된 카드 (active Card)

ExoKanban의 모든 작업은 Card입니다. 필드 자체는 특별할 것이 없습니다:

class Card(ExoSQLModel, table=True):
    title: "str = \"\""
    description: "str = \"\""
...

차이점은 베이스 클래스(base class)에 있습니다. ExoSQLModelExoModel을 상속받으며 SQLModel 영속성(persistence)을 추가합니다. 따라서 SQLite와 통신하는 동일한 객체가 LLM과도 통신하게 됩니다.

자연어를 통한 카드 생성:

card = await Card.create("schedule annual medical checkup, high priority, due next Friday")
# title="Annual medical checkup", priority="high", due_date="2026-06-13", ...

파서도, 양식(form)도, if "priority" in text: extract_priority(text)와 같은 코드도 필요 없습니다. 스키마(schema) 자체가 프롬프트(prompt)입니다. ExoModel은 필드 타입과 이름을 사용하여 무엇을 채워야 할지 추론합니다.

기존 카드를 업데이트하는 것도 동일한 패턴입니다:

await card.update_object("change priority to critical and add a tag for health")
# card.priority == "critical", card.tag == "health"

객체가 스스로 자신의 필드를 업데이트합니다. LLM은 당신이 수동으로 매핑해야 하는 딕셔너리(dict)를 반환하는 것이 아니라, 검증된 인스턴스(instance)를 반환합니다.

다른 모든 것을 단순하게 만드는 아키텍처 결정

ExoKanban에는 카드 생성, 카드 이동, 카드 검색, 기한 만료 항목 목록화, CSV 내보내기 등 모든 보드 작업을 래핑(wrap)하는 KanbanService 클래스가 있습니다. 이 서비스는 사용자가 보드와 어떻게 통신하는지에 대해 전혀 알지 못합니다.

class KanbanService:
    async def new_card(self, prompt: str) -> Card:
        card = await Card.create(prompt)
...

세 개의 인터페이스가 동일한 서비스를 호출합니다. 그중 어느 것도 의도 라우팅 (intent routing)을 구현하지 않습니다.

인터페이스 1: CLI

CLI는 가공되지 않은 사용자 입력을 읽어 KanbanService에 전달합니다. 어댑터 계층 (adapter layer)은 설계상 매우 얇습니다.

/new schedule annual medical checkup
/move Today
/update change priority to critical
...

UserInteraction 클래스가 CLI 입출력 (I/O) 루프를 처리합니다. 이 클래스는 명령어를 파싱(parse)하지 않고, 접두사 (prefix)에 따라 적절한 서비스 메서드로 명령을 전달(dispatch)합니다. 이해 (understanding) 과정은 인터페이스가 아니라 Card.create()card.update_object() 내부에서 일어납니다.

인터페이스 2: Telegram 봇

Telegram 인터페이스는 수정 없이 KanbanService를 재사용합니다. 단일 봇 핸들러 (bot handler)는 다음과 같습니다.

@bot.message_handler(func=lambda m: m.text.startswith("/new"))
async def handle_new(message):
    prompt = message.text[4:].strip()
...

to_ui()Card 객체 자체의 메서드입니다. Telegram 핸들러는 응답을 포맷팅(format)하지 않습니다. 대신 객체가 이미 렌더링(render)할 줄 아는 내용을 렌더링할 뿐입니다.

CLI에서 작동하는 것과 동일한 명령어가 Telegram에서도 작동하는데, 이는 두 인터페이스 모두 동일한 서비스를 호출하고, 그 서비스가 동일한 객체를 호출하기 때문입니다.

인터페이스 3: MCP 서버

MCP 계층은 FastMCP를 사용하여 HTTP를 통해 11개의 도구 (tools)를 노출하며, 이를 통해 ExoKanban을 Claude Desktop 또는 Claude Code에서 직접 사용할 수 있게 합니다.

@mcp.tool()
async def create_card(prompt: str) -> dict:
    """자연어 설명을 사용하여 새로운 카드를 생성합니다."""
...

MCP 도구들은 얇은 래퍼 (wrapper)일 뿐입니다. 지능은 여전히 객체 내부에 있습니다. ExoModel의 네이티브 RAG (Retrieval-Augmented Generation)가 별도의 벡터 저장소 (vector store) 없이도 카드 콘텐츠 전반에 걸친 의미론적 검색 (semantic search)을 처리합니다.

서버 시작:

python mcp_server.py  # Bearer 인증과 함께 8000번 포트에서 실행됩니다

그 후, Claude Desktop은 자연스러운 대화를 통해 카드를 생성, 업데이트, 이동 및 검색할 수 있습니다. 이는 도구 시그니처 (tool signatures)가 단순히 prompt: str일 뿐이며, 객체 (object)가 해당 프롬프트를 어떻게 처리해야 할지 스스로 알고 있기 때문입니다.

이것이 실제로 보여주는 것

ExoKanban은 의도적으로 단순하게 설계된 프로젝트입니다. 컬럼 (columns), 카드 상태 (card state), 우선순위 (priorities)와 같은 보드 로직은 흥미로운 부분이 아닙니다. 정말 흥미로운 점은 통합 코드 (integration code)의 접점 (surface area)입니다.

자연어 입력과 의미론적 검색 (semantic search)을 지원하며 SQLite를 기반으로 하는 보드와 통신하는 세 가지 인터페이스 (CLI, Telegram, MCP)가 존재합니다. 전체 통합 코드는 얇은 래퍼 (thin wrappers)와 I/O 어댑터 (I/O adapters)로 구성됩니다. 의도 라우터 (intent router)는 존재하지 않는데, 그 이유는 라우터가 인터페이스가 아닌 객체 내부에 존재하기 때문입니다.

이것이 바로 ExoModel의 전제 조건입니다. 이해 (understanding) 능력을 객체 수준 (object level)에 배치하면, 그 위의 모든 인터페이스는 병렬 구현 (parallel implementation)이 아닌 얇은 어댑터 (thin adapter)가 됩니다.

직접 시도해보기

git clone https://github.com/exomodel-ai/exokanban
cd exokanban
pip install -r requirements.txt
...

Google Gemini (기본값), Anthropic Claude, 그리고 OpenAI를 지원합니다. 애플리케이션 코드를 수정하지 않고도 제공자 (providers)를 교체할 수 있습니다.

ExoModel 자체 설치:

pip install exomodel

GitHub: exomodel-ai/exomodel — 유용했다면 별 (star)을 눌러주세요.
ExoKanban: exomodel-ai/exokanban

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0