[AI 스터디] 로컬 문서 기반 AI 업무 자동화 에이전트 설계

이번 프로젝트에서는 Anthropic의 최신 통신 규격인 MCP(Model Context Protocol)를 활용하여,

내 컴퓨터의 파일을 스스로 읽고 요약하여 마크다운 보고서까지 작성하는 AI 에이전트를 구현했다.

 

전체 시스템 구조

사용자의 명령이 실제 하드웨어 자원을 제어하기까지 총 5개의 계층을 거치도록 설계했다.

  1. User Prompt: 사용자의 자연어 명령
  2. LangChain Agent: 전체 워크플로우를 관리하는 오케스트레이터
  3. Claude 3.5 Sonnet: 명령의 의도를 파악하고 적절한 도구를 선택하는 두뇌
  4. MCP (Client & Server): LLM과 로컬 리소스 사이의 표준화된 통신 규격
  5. OS / File System: 실제 데이터가 저장된 하드웨어 자원

에이전트 실행 흐름

에이전트는 ReAct(Reasoning + Acting) 프레임워크에 따라 다음과 같이 동작한다.

  1. Thinking: Claude가 현재 작업에 list_files와 read_text, write_md 도구가 필요함을 인식
  2. Action (1): list_files를 호출하여 폴더 내 파일들을 확인
  3. Action (2): 각 파일을 read_text로 읽어 내용을 파악하고 Claude가 내부적으로 요약
  4. Action (3): 요약된 텍스트를 write_md 도구에 전달하여 물리적인 파일로 저장
  5. Observation: 모든 작업이 완료되었음을 확인하고 사용자에게 최종 응답을 보냄

트러블슈팅

①  API 모델 권한 문제 (404 Not Found)

anthropic.NotFoundError: Error code: 404 - model: claude-3-5-sonnet-latest

Anthropic 계정의 Tier(등급)나 API 라이브러리 버전에 따라 특정 모델 식별자를 인식하지 못하는 문제였다.

최신 모델인 20241022 버전에서, 안정적인 호출이 보장되는 claude-3-haiku-20240307 모델로 전환했다.

 

②  비동기(Async) 처리와 결과 출력 누락

작업은 완료되었다고 뜨는데 결과값이 빈 리스트([])로 출력

MCP 통신은 비동기로 이루어지는데, 프로그램이 최종 답변이 나오기 전 마지막 도구 반환값만 출력한다는 문제였다.

astream 방식을 도입하여 에이전트의 생각과 도구 실행을 실시간으로 화면에 출력하도록 코드를 수정했다.

 

③ AI 응답 데이터 구조 불일치 (KeyError: 'text') 문제

AI가 텍스트 답변 없이 곧바로 도구를 호출할 때, 응답 리스트에 'text' 키가 없어 프로그램이 종료되는 문제였다.

last_msg.content 내부를 순회하며 type == "text"인 블록만 골라내어 결합하는 방어적 로직을 추가하였다.


주요 코드 상세 설명 및 기술 분석

①  비동기 컨텍스트 매니저 (async with)

async with stdio_client(server_params) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()

표준 입력/출력 을 통해 서버와 실시간으로 대화한다.

async with를 사용하여 서버와의 연결과 세션이 작업 종료 후 자동으로 안전하게 닫히도록 관리한다.

리소스 누수(Memory Leak)를 방지하는 현대 파이썬의 핵심 패턴이다.

 

  astream 기반의 실시간 로그 출력

async for chunk in agent.astream({"messages": [("user", query)]}, stream_mode="values"):
    if "messages" in chunk:
        last_msg = chunk["messages"][-1]
        # AI 생각 출력 및 도구 실행 결과 확인

ainvoke를 쓰면 모든 작업이 끝난 후의 최종 스냅샷만 보여준다.

이때 마지막 메시지가 AI의 대답이 아닌 도구 실행 결과일 경우 화면에 아무것도 안 뜨는([]) 문제가 있었다.

따라서 astream을 사용해 에이전트의 내부 상태 변화를 한 단계씩(Chunk) 받아온다.

 

last_msg.type == "ai"일 때는 AI가 무슨 도구를 쓸지 선택하는 내용을 보여준다.

last_msg.type == "tool"일 때는 실제 파일 시스템에서 일어난 일을 보여준다.

 

결과적으로 사용자는 에이전트가 수행하는 과정을 실시간으로 관찰할 수 있게 된다.


최종 실행 시나리오

시나리오 테스트를 위해 test_folder 폴더 안에 아래 두 파일을 새로 만들어 주었다.

 

[시나리오 1 테스트]

query = "'money.csv' 파일을 읽어서 지출 금액 평균을 내주고, 평소보다 너무 많이 쓴 '과소비' 항목이 있는지 찾아서 주의를 좀 줘."

 

 

[시나리오 2 테스트]

query = "폴더에 있는 'trip.txt' 파일을 읽고, 핵심 장소 2곳을 깔끔하게 요약해서 'tripPlan.md' 파일로 저장해줘."

 


마무리하며

왜 랭체인만으로는 부족할까?

기존 랭체인 방식은 새로운 도구가 추가될 때 마다 파이썬 코드를 직접 수정하고 에이전트 로직을 다시 짜야한다.

하지만 MCP(Model Context Protocol)를 도입하면, 도구를 제공하는 서버와 이를 사용하는 에이전트가 완전히 분리된다.

따라서 서버만 따로 업데이트하거나 다른 환경에서도 재사용할 수 있는 표준화된 확장성을 얻게 된다.

 

만약 이번 과제에서 랭체인만 사용했다면.

agent.py 안에 파일 읽기, CSV 분석, 마크다운 생성 로직을 @tool 데코레이터를 붙여서 다 작성했을 것이다.

즉, 도구가 늘어날수록 agent.py 코드는 길어지고 복잡해지게 된다,

 

MCP를 사용해 server.py에 도구들을 몰아넣고, 에이전트에서는 load_mcp_tools() 한 줄로 모든 기능을 가져온다.

에이전트와 서버가 분리되어 코드가 훨씬 간결하고 관리가 편하다는 것을 직접 느낄 수 있었다.

 

또한 CSV 분석 기능을 추가하거나 수정할 때, 에이전트의 메인 로직(agent.py)은 건드릴 필요가 없었다.

이를 통해 서버 쪽 기능만 업데이트하면 에이전트가 실행될 때 자동으로 새로운 기능을 인식하는 것을 알 수 있다.

대규모 시스템일수록 MCP 같은 표준 프로토콜이 유지보수에 유리하겠다는 생각이 들었다.