티스토리 뷰

반응형
DEVOPS · LOCAL LLM · HANDS-ON GUIDE

폐쇄망에서 로컬 AI Agent 구축하기

Gemma 4 + Ollama로 엑셀 자동화까지

외부 인터넷이 완전히 차단된 환경에서 로컬 LLM을 설치하고,
Python AI Agent로 엑셀 문서를 자동 정리하는 실전 가이드

# 로컬LLM # AI_Agent # 폐쇄망AI # Gemma사용법 # 엑셀자동화
01

개요

요즘 팀 내에서 AI 도구를 쓰고 싶은데 막히는 경우가 생각보다 많다.
이유는 단순하다. 망이 막혀 있기 때문이다.

금융권, 공공기관, 제조업 현장, 군 관련 SI 프로젝트... 이런 환경에서 일하는 엔지니어라면 ChatGPT나 Claude를 브라우저에서 열었다가 차단 페이지를 마주한 경험이 한 번쯤은 있을 것이다. 당연히 API 호출도 막혀 있다.

그렇다고 AI 도구를 아예 포기해야 할까? 아니다. 로컬에서 직접 LLM을 돌리면 된다.

이 글에서 다루는 내용

Gemma 4 모델을 오프라인으로 설치
Ollama로 로컬 LLM 서버 구동
Python Agent를 붙여서 엑셀 문서 자동 정리
💡 이 글은 이미 구축된 환경에 대한 설명이 아니라, 인터넷이 되는 개인 PC에서 준비 → 폐쇄망 서버에 이식하는 실제 흐름을 따른다.
02

전체 아키텍처 설명

먼저 전체 그림을 머릿속에 그려두고 시작하자.

┌─────────────────────────────────────────────────────────────┐
│                     폐쇄망 서버 (Air-gapped)                  │
│                                                              │
│  ┌──────────────┐    REST API    ┌──────────────────────┐   │
│  │  Python Agent│ ─────────────▶│   Ollama Server       │   │
│  │  (LangChain) │ ◀─────────────│   (localhost:11434)   │   │
│  └──────┬───────┘                └──────────┬───────────┘   │
│         │                                   │                │
│         │ 파일 R/W                           │ 모델 로드       │
│         ▼                                   ▼                │
│  ┌──────────────┐                ┌──────────────────────┐   │
│  │  Excel 파일   │                │  Gemma 4 모델 파일    │   │
│  │  (.xlsx)     │                │  (GGUF / Ollama)     │   │
│  └──────────────┘                └──────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

구성 요소별 역할

컴포넌트 역할 비고
Ollama 로컬 LLM 실행 엔진 (OpenAI 호환 API 제공) GPU/CPU 모두 지원
Gemma 4 실제 추론을 수행하는 LLM 모델 Google 오픈소스
Python Agent 사용자 요청 → LLM → 도구 실행 오케스트레이션 LangChain 기반
openpyxl / pandas 엑셀 파일 R/W 처리 도구 순수 로컬

데이터 흐름

1 사용자가 엑셀 파일 경로와 작업 지시를 Python 스크립트에 전달
2 Agent가 파일을 읽고, LLM에게 데이터 분석/변환 요청
3 Ollama가 Gemma 4 모델로 추론 결과 반환
4 Agent가 결과를 해석해 엑셀 파일 수정 또는 리포트 생성
03

사용 기술 스택

핵심 기술 선택 이유

기술 버전 선택 이유
Gemma 4 27B (Q4_K_M) Google 오픈소스, 한국어 성능 양호, 상업 이용 허용
Ollama 0.6.x 단일 바이너리, OpenAI 호환 API, 모델 관리 용이
Python 3.11+ 폐쇄망 환경 wheel 설치 가능
LangChain 0.3.x Agent/Tool 추상화, Ollama 연동 공식 지원
Docker 선택적 격리 환경이 필요한 경우 사용

왜 Gemma 4인가?

2025년 4월 기준으로 폐쇄망에서 사용할 수 있는 오픈소스 LLM 중 Gemma 4는 몇 가지 면에서 실용적이다.

라이선스
Gemma Terms of Use
상업적 사용 가능, 기업 내부 OK
한국어 성능
27B 모델 기준으로
실무 문서 처리에 충분한 수준
리소스
Q4 양자화 기준 VRAM 16~20GB
CPU-only도 가능 (단, 느림)
Ollama 지원
별도 변환 없이
바로 사용 가능

💡 Tip — 모델 크기 선택

GPU 없이 CPU만 있는 서버라면 gemma4:2b (2B 파라미터) 모델도 고려하자. 속도는 느리지만 간단한 작업은 충분히 처리한다. gemma4:12b (~8GB)가 속도와 품질의 중간 타협점으로 가장 인기가 많다.

04

설치 가이드 (STEP BY STEP)

폐쇄망 설치의 핵심은 "반입"이다. 인터넷이 되는 PC에서 아래를 모두 준비해 USB나 내부 파일 서버에 담아가야 한다.

사전 준비 체크리스트 (인터넷 PC에서)

Ollama 바이너리 (Linux x86_64)
Gemma 4 모델 파일 (.gguf 또는 Ollama manifest)
Python 3.11 인스톨러 또는 Conda 오프라인 설치본
pip wheel 파일들 (langchain, ollama, pandas, openpyxl)
Docker 이미지 (선택, tar 파일로 export)
1

Ollama 바이너리 다운로드 (인터넷 PC에서)

bash
# Linux x86_64용 다운로드
curl -L https://ollama.com/download/ollama-linux-amd64 -o ollama

# 또는 GitHub Releases에서 직접
# https://github.com/ollama/ollama/releases
2

Gemma 4 모델 파일 준비 (인터넷 PC에서)

Ollama 모델은 로컬 캐시 디렉토리에 저장된다. 인터넷이 되는 PC에서 먼저 pull한 뒤 그 디렉토리째로 복사하면 된다.

bash
# 인터넷 PC에서 모델 pull
ollama pull gemma4:27b

# 모델이 저장된 위치 확인
ls ~/.ollama/models/
# blobs/   manifests/

# 모델 디렉토리 압축 (반입용)
tar -czvf ollama_models.tar.gz ~/.ollama/models/

💡 Tip — 모델 크기 미리 확인

gemma4:27b (Q4_K_M 기본값 기준)는 약 17GB 내외다. USB가 부족하면 gemma4:12b (~8GB)나 gemma4:2b (~2GB)를 선택하자. 정확한 용량은 ollama show gemma4:27b --verbose로 확인 가능하다.

3

Python 패키지 wheel 준비 (인터넷 PC에서)

bash
# 필요한 패키지를 wheel 파일로 다운로드 (설치하지 않음)
pip download \
  langchain \
  langchain-ollama \
  pandas \
  openpyxl \
  -d ./wheels/

# wheels/ 디렉토리를 반입용으로 압축
tar -czvf python_wheels.tar.gz ./wheels/
4

폐쇄망 서버에 설치

bash — 폐쇄망 서버
# 1. Ollama 바이너리 설치
chmod +x ollama
sudo mv ollama /usr/local/bin/ollama

# 2. 모델 파일 복원
tar -xzvf ollama_models.tar.gz -C ~/
# ~/.ollama/models/ 에 풀림

# 3. Ollama 서버 시작 (백그라운드)
OLLAMA_HOST=0.0.0.0:11434 ollama serve &

# 모델이 인식되는지 확인
ollama list
# NAME            ID              SIZE    MODIFIED
# gemma4:27b      ...             17GB    ...

# 4. Python 패키지 오프라인 설치
tar -xzvf python_wheels.tar.gz
pip install --no-index --find-links=./wheels/ \
  langchain langchain-ollama pandas openpyxl

🚨 주의사항 — Ollama 서비스 등록

장기 운영 환경이라면 systemd 서비스로 등록해두자. 서버 재부팅 후 Ollama가 자동으로 뜨지 않으면 Agent가 동작하지 않는다.

/etc/systemd/system/ollama.service
[Unit]
Description=Ollama LLM Server
After=network.target

[Service]
ExecStart=/usr/local/bin/ollama serve
Environment="OLLAMA_HOST=0.0.0.0:11434"
Restart=always
User=ollama

[Install]
WantedBy=multi-user.target
sudo systemctl enable ollama
sudo systemctl start ollama
5

동작 확인

bash
# Ollama API 헬스체크
curl http://localhost:11434/api/tags

# 간단한 추론 테스트
curl http://localhost:11434/api/generate \
  -d '{"model":"gemma4:27b","prompt":"안녕하세요","stream":false}'
✓ 정상 응답
{
  "model": "gemma4:27b",
  "response": "안녕하세요! 무엇을 도와드릴까요?",
  "done": true
}
05

Gemma 4 연동 방법

Python에서 Ollama 연결

Ollama는 OpenAI 호환 REST API를 제공하므로 langchain-ollama 또는 직접 requests로 연결할 수 있다.

python — langchain-ollama 사용 (권장)
from langchain_ollama import OllamaLLM

llm = OllamaLLM(
    model="gemma4:27b",
    base_url="http://localhost:11434",  # 폐쇄망 내부 IP도 가능
    temperature=0.1,  # 문서 처리는 낮게
)

response = llm.invoke("다음 데이터에서 결측값이 있는 행을 찾아줘: ...")
print(response)

OpenAI 호환 방식 (기존 코드 재사용 시)

이미 OpenAI API를 쓰던 코드가 있다면 베이스 URL만 바꾸면 된다.

python — OpenAI 호환 방식
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",  # 아무 값이나 OK (Ollama는 키 검증 안 함)
)

response = client.chat.completions.create(
    model="gemma4:27b",
    messages=[{"role": "user", "content": "엑셀 데이터를 분석해줘"}],
)
print(response.choices[0].message.content)

💡 Tip — 모델 파라미터 튜닝

문서 처리 작업은 창의성보다 정확성이 중요하다. temperature=0.0~0.1로 설정하면 LLM이 일관된 출력을 낸다. num_ctx(컨텍스트 길이)는 기본 2048인데, 큰 엑셀 데이터를 처리하려면 8192 이상으로 올려주는 게 좋다.

llm = OllamaLLM(
    model="gemma4:27b",
    base_url="http://localhost:11434",
    temperature=0.0,
    num_ctx=8192,
)
06

엑셀 문서 정리 자동화 활용

이제 실제로 쓸 수 있는 시나리오로 들어간다. 업무에서 자주 마주치는 상황들이다.

시나리오 1 컬럼명 정리 및 표준화

원본 엑셀에 컬럼명이 중구난방으로 되어 있을 때

원본 컬럼명 표준화 결과
부서명  (공백 포함) department
사원번호(ID) employee_id
입사일자 hire_date
연봉(만원) annual_salary_10k
시나리오 2 이상 데이터 탐지
날짜 컬럼에 "2025년 13월" 같은 존재하지 않는 날짜
숫자 컬럼에 "-", "N/A", "미정" 같은 텍스트 혼입
중복 행 감지
시나리오 3 데이터 요약 리포트 자동 생성

수백 행의 판매 데이터를 넣으면 LLM이 핵심 지표를 텍스트로 요약해주는 시나리오. 회의 전에 빠르게 현황을 파악하거나, 정기 리포트 작성을 자동화할 때 유용하다.

입력/출력 예시

INPUT — 원본 엑셀 데이터

사원ID,부서,입사일,연봉
E001,개발팀,2021-03-01,5500
E002,기획팀,2022-07월,4800    ← 날짜 형식 오류
E003,개발팀,,6200             ← 결측값
E001,개발팀,2021-03-01,5500   ← 중복 행

OUTPUT — LLM 분석 결과 (JSON)

{
  "format_errors": [
    {"row": 3, "col": "입사일", "value": "2022-07월", "issue": "날짜 형식 오류"}
  ],
  "missing_values": [
    {"row": 4, "col": "입사일", "issue": "결측값"}
  ],
  "duplicates": [
    {"rows": [2, 5], "key": "E001"}
  ]
}
07

실제 사용 예제 (코드 포함)

완성된 Python 스크립트다. 폐쇄망에서 그대로 실행 가능하다.

excel_agent.py
Python 3.11+
# excel_agent.py
import json
import pandas as pd
from langchain_ollama import OllamaLLM
from pathlib import Path


class ExcelAgent:
    def __init__(self, model: str = "gemma4:27b", host: str = "http://localhost:11434"):
        self.llm = OllamaLLM(
            model=model,
            base_url=host,
            temperature=0.0,
            num_ctx=8192,
        )

    def load_excel(self, path: str, sheet: str = None) -> pd.DataFrame:
        df = pd.read_excel(path, sheet_name=sheet or 0)
        return df

    def analyze_quality(self, df: pd.DataFrame) -> dict:
        """데이터 품질 이슈 탐지"""
        # 데이터 샘플을 CSV 문자열로 변환 (최대 200행)
        sample = df.head(200).to_csv(index=False)

        prompt = f"""아래 CSV 데이터를 분석하고, 다음 항목을 JSON으로 반환하세요:
1. "format_errors": 형식이 잘못된 셀 목록 (row_index, column, value, issue)
2. "missing_values": 결측값이 있는 셀 목록 (row_index, column)
3. "duplicates": 중복 행 그룹 (row_indices 배열)
4. "summary": 전체 데이터 요약 (한국어, 3문장 이내)

[데이터]
{sample}

반드시 유효한 JSON만 반환하세요. 다른 텍스트는 포함하지 마세요."""

        response = self.llm.invoke(prompt)

        # JSON 파싱 (LLM이 마크다운 코드블록 붙이는 경우 처리)
        raw = response.strip()
        if raw.startswith("```"):
            raw = raw.split("```")[1]
            if raw.startswith("json"):
                raw = raw[4:]
        return json.loads(raw.strip())

    def standardize_columns(self, df: pd.DataFrame) -> pd.DataFrame:
        """컬럼명 표준화"""
        col_list = json.dumps(list(df.columns), ensure_ascii=False)
        prompt = f"""아래 컬럼명 리스트를 snake_case 영어로 변환하세요.
입력: {col_list}
출력: 반드시 같은 길이의 JSON 배열만 반환 (예: ["col1", "col2"])"""

        response = self.llm.invoke(prompt)
        raw = response.strip()
        if raw.startswith("```"):
            raw = raw.split("```")[1].lstrip("json").strip()

        new_columns = json.loads(raw)
        df.columns = new_columns
        return df

    def save_report(self, analysis: dict, output_path: str):
        """분석 결과를 텍스트 파일로 저장"""
        with open(output_path, "w", encoding="utf-8") as f:
            f.write("=== 데이터 품질 분석 리포트 ===\n\n")
            f.write(f"[요약]\n{analysis.get('summary', '')}\n\n")

            if analysis.get("format_errors"):
                f.write("[형식 오류]\n")
                for e in analysis["format_errors"]:
                    f.write(f"  - 행 {e['row_index']}, 컬럼 '{e['column']}': "
                            f"{e['value']} → {e['issue']}\n")

            if analysis.get("missing_values"):
                f.write("\n[결측값]\n")
                for m in analysis["missing_values"]:
                    f.write(f"  - 행 {m['row_index']}, 컬럼 '{m['column']}'\n")

            if analysis.get("duplicates"):
                f.write("\n[중복 행]\n")
                for d in analysis["duplicates"]:
                    f.write(f"  - 중복 행 인덱스: {d['row_indices']}\n")


# ── 실행 진입점 ─────────────────────────────────────────────────
if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print("사용법: python excel_agent.py <엑셀파일경로>")
        sys.exit(1)

    input_file  = sys.argv[1]
    output_report = Path(input_file).stem + "_report.txt"
    output_excel  = Path(input_file).stem + "_cleaned.xlsx"

    agent = ExcelAgent()

    print(f"[1/3] 파일 로드 중: {input_file}")
    df = agent.load_excel(input_file)

    print(f"[2/3] 데이터 품질 분석 중... (LLM 추론, 30초~수분 소요)")
    analysis = agent.analyze_quality(df)

    print(f"[3/3] 컬럼 표준화 및 결과 저장")
    df_clean = agent.standardize_columns(df.copy())
    df_clean.to_excel(output_excel, index=False)
    agent.save_report(analysis, output_report)

    print(f"\n완료!")
    print(f"  정리된 엑셀: {output_excel}")
    print(f"  분석 리포트: {output_report}")
    print(f"\n[요약] {analysis.get('summary', '')}")

Before / After

BEFORE — 실행 전 (원본)

사원ID   부서명     입사일자     연봉(만원)
E001     개발팀     2021-03-01   5500
E002     기획팀     2022-07월    4800
E003     개발팀                  6200
E001     개발팀     2021-03-01   5500

AFTER — 실행 후 (_cleaned.xlsx)

employee_id  department  hire_date    annual_salary_10k
E001         개발팀       2021-03-01   5500
E002         기획팀       2022-07월    4800
E003         개발팀                    6200
E001         개발팀       2021-03-01   5500

REPORT — _report.txt

=== 데이터 품질 분석 리포트 ===

[요약]
총 4건의 데이터 중 1건의 날짜 형식 오류, 1건의 결측값, 1건의 중복 행이 발견되었습니다.
데이터 정제 후 실제 유효 레코드는 3건으로 추정됩니다.

[형식 오류]
  - 행 2, 컬럼 '입사일자': 2022-07월 → 날짜 형식 오류 (YYYY-MM 또는 YYYY-MM-DD 필요)

[결측값]
  - 행 3, 컬럼 '입사일자'

[중복 행]
  - 중복 행 인덱스: [1, 4]
08

장점 / 단점

폐쇄망 환경에서의 장점

항목 내용
보안 데이터가 외부로 한 바이트도 나가지 않음. 기밀 문서 처리에 적합
비용 API 과금 없음. 서버 전력비만 발생
가용성 외부 서비스 장애와 무관하게 24/7 운영 가능
커스터마이징 모델 fine-tuning, 시스템 프롬프트 완전 제어 가능
규정 준수 개인정보보호법, 망 분리 의무 등 내부 규정 충족

한계 및 주의사항

항목 내용
성능 GPT-4o, Claude 3.7 대비 추론 품질 낮음 (특히 복잡한 논리 처리)
속도 GPU 없을 경우 응답에 1~5분 소요 가능
한국어 27B 이하 모델은 한국어 명령 이해가 불안정할 수 있음
컨텍스트 한계 수천 행의 엑셀을 한 번에 처리하기 어려움. 청크 분할 필요
유지보수 모델 업데이트 시 반입 절차 재수행 필요

💡 Tip — 대용량 엑셀 처리 전략

엑셀이 수만 행이라면 LLM에 전체를 한 번에 넣는 건 불가능하다. pandas로 500~1000행씩 청크로 나눠서 처리하고 결과를 합산하는 방식을 쓰자.

chunk_size = 500
results = []
for i in range(0, len(df), chunk_size):
    chunk = df.iloc[i:i+chunk_size]
    result = agent.analyze_quality(chunk)
    results.append(result)
# 결과 병합 로직 추가...
09

결론

어떤 조직에 적합한가?

🏦
금융 / 보험사
고객 데이터, 계약 문서를 외부 AI로 보낼 수 없는 규정이 있는 곳
🏛️
공공기관 / 정부
망 분리 의무화 환경에서 업무 자동화가 필요한 팀
🏭
제조업 MES 환경
공장 내부망에서 생산 데이터 분석 자동화
🛡️
방산 / 군 관련 SI
보안 등급 문서를 다루는 개발 환경

앞으로의 확장 가능성

현재 상태
엑셀 파일 처리 (데이터 품질 검사, 컬럼 정리)
1단계 확장
PDF, Word, 사내 DB 연동 (LangChain Tool 추가)
2단계 확장
RAG (내부 문서 벡터 DB 구축) → 사내 문서 기반 Q&A
3단계 확장
멀티 Agent (기획서 작성 / 검토 / 승인 Agent 분리)
4단계 확장
사내 포털 연동 (Confluence, 그룹웨어 API)

마치며

로컬 LLM의 성능은 빠르게 올라오고 있다. 지금 당장 GPT-4 수준은 아니지만, 단순 반복 작업, 데이터 품질 검사, 문서 요약 정도는 이미 실무에서 충분히 쓸 수 있는 수준이다.

한 번 구축해두면 API 비용 걱정 없이, 망 분리 위반 걱정 없이, 팀 전체가 쓸 수 있는 내부 AI 인프라가 된다. 이 글에 담긴 내용은 실제로 폐쇄망 환경에서 Ollama를 설치하고 엑셀 자동화 파이프라인을 구축하면서 겪은 과정을 정리한 것이다. 막히는 부분이 있으면 댓글로 남겨주시면 같이 해결해보겠다.

이 글이 도움이 됐다면 공유해 주세요 🙏  |  질문은 댓글로

'기타' 카테고리의 다른 글

자유에 관하여  (0) 2026.05.21
석유 왕 록펠러, 시장을 어떻게 손에 넣었나  (0) 2026.04.21
YEBISU BEER — 역사와 전통, 그리고 맛의 계보  (0) 2026.04.14
럭비 가이드  (1) 2026.03.29
Github vscode by PAT 연동 25.03  (0) 2025.03.02