본문 바로가기
AI 관련 자료

사내 서버에 로컬 LLM 띄워서 대시보드에 연동해본 후기

by Jay Son 아기 냥이 해린 짱💖 2026. 5. 23.
반응형

Ollama + Qwen으로 팀 업무 대시보드에 AI 분석 붙이기

회사에서 팀원들의 일감 현황을 보여주는 대시보드를 운영하고 있다.
기존에는 “누가 몇 개의 일감을 들고 있는지”, “마감이 언제인지” 같은 정보를 숫자와 그래프로만 보여줬다.

그런데 막상 화면을 보다 보면 결국 사람이 직접 판단해야 하는 부분이 있었다.

  • 지금 누가 가장 바쁜가?
  • 일정이 위험해 보이는 사람은 누구인가?
  • 특정 업무가 몰린 팀원이 있는가?
  • 일정 지연 가능성이 있는가?

그래서 이번에 사내 서버에 로컬 LLM(Local LLM)을 직접 띄우고, 대시보드가 AI에게 자연어 분석을 요청하는 기능을 붙여봤다.

핵심은 다음 두 가지였다.

  • 외부 API(OpenAI 등)를 사용하지 않는다.
  • 모든 데이터와 AI 처리를 사내 서버 내부에서만 수행한다.

비슷한 구조를 만들어보려는 분들이 전체 흐름을 이해할 수 있도록, 실제로 겪었던 삽질까지 포함해서 정리해본다.

무엇을 만들었나

한 줄로 요약하면 다음과 같다.

“대시보드에서 버튼을 누르면 AI가 팀원의 업무 상태를 한국어로 분석해주는 기능”

기존 사용자 카드에 AI 분석 버튼을 추가했다.

버튼을 누르면 해당 사용자의 일감 데이터를 기반으로 AI가 아래 같은 형태의 자연어 코멘트를 생성한다.

  • 현재 업무량 상태
  • 일정 위험도
  • 업무 집중도
  • 우선순위 조정 필요 여부
  • 특이사항

추가로 헤더에는 AI 리포트 버튼도 만들어 팀 전체 현황을 한 번에 요약할 수 있도록 구성했다.

예를 들면 이런 식이다.

A 개발자는 현재 긴급 업무 비중이 높고 일정 여유가 부족합니다.

B 개발자는 작업량은 많지만 마감 분산이 안정적입니다.”

숫자 기반 대시보드에 “설명”이 생긴 느낌이었다.

하드웨어 스펙

VM 환경

CPU: 2.10GHz 4코어 할당

Memory: 8GB 할당

저장공간: 30GB 할당

그래픽 카드: 없음

어떤 도구를 사용했나

1. Ollama 🦙

가장 핵심이었던 도구는 Ollama 였다.

로컬 환경에서 LLM을 매우 쉽게 실행할 수 있게 도와주는 도구다.

개발자 입장에서 체감은 거의 이런 느낌이다.

“모델 다운로드 → 실행 → 바로 REST API 사용 가능”

Docker처럼 생각하면 이해가 쉽다.

ollama run qwen2.5:3b
 

이 한 줄이면 모델이 내려받아지고 API 서버까지 같이 뜬다.

별도 추론 서버를 복잡하게 구성할 필요가 없어서 개인적으로 상당히 편했다.

설치중 간단한 에러는 문제는 ChatGPT 가이드 받아 쉽게 해결했다.

2. Qwen 2.5 3B

실제 답변 생성에는 Qwen 모델을 사용했다.

현재 서버 하드웨어 스펙이 사용 가능한 gamma4 e4b, qwen 2.5 3b 

3B(30억 파라미터) 모델이라 최신 대형 모델에 비하면 작은 편이지만, 사내 서버 환경에서는 오히려 현실적인 선택이었다.

특히 좋았던 점은 다음과 같다.

  • 서버 자원을 과하게 먹지 않음
  • 한국어 요약 성능이 생각보다 괜찮음
  • 설치 및 실행이 단순함
  • 업무 요약/리포트 용도로 테스트용으로 충분함

“작은 모델은 못 쓴다”는 편견이 있었는데, 실제 업무 자동화에서는 의외로 테스트용으로 실용적이었다.

Claude 분석 자료

백엔드 구성

백엔드는 Python 기반으로 구성했다.

여기서 일부러 신경 쓴 부분이 하나 있었다.

외부 패키지 최소화

보통은 requests 같은 라이브러리를 많이 쓰는데, 이번에는 Python 기본 내장 모듈인 urllib만 사용했다.

이유는 단순했다.

  • 의존성 최소화
  • 서버 배포 단순화
  • 운영 환경 안정성 확보

예시 코드는 이런 느낌이다.

import urllib.request
import json

data = {
    "model": "qwen2.5:3b",
    "prompt": prompt,
    "stream": False
}

req = urllib.request.Request(
    "http://localhost:11434/api/generate",
    data=json.dumps(data).encode("utf-8"),
    headers={"Content-Type": "application/json"}
)

response = urllib.request.urlopen(req)
 

생각보다 충분히 잘 동작했다.

전체 구조

전체 흐름은 다음과 같다.

브라우저
   ↓
백엔드 서버
   ↓
Ollama
   ↓
Qwen 모델
 

조금 더 자세히 보면:

  1. 사용자가 AI 분석 버튼 클릭
  2. 브라우저가 백엔드 API 호출
  3. 백엔드가 일감 데이터를 정리
  4. 프롬프트 생성
  5. Ollama REST API 호출
  6. Qwen 모델이 응답 생성
  7. 결과를 다시 화면에 표시

주요 API 구성

GET /ai/status

AI 사용 가능 여부 확인용 API다.

여기서 확인하는 것:

  • Ollama 실행 여부
  • 모델 존재 여부

프론트는 이 응답을 보고 AI 버튼 표시 여부를 결정한다.

즉:

  • 모델 살아있음 → 버튼 표시
  • 모델 꺼져있음 → 버튼 숨김

사용자 경험 측면에서 꽤 중요했다.

POST /ai/analyze

실제 분석 요청 API다.

여기서:

  • 사용자 업무 데이터 수집
  • 프롬프트 생성
  • Ollama 호출
  • 결과 반환

을 수행한다.

삽질 1 — AI 버튼이 안 보인다

배포 후 가장 먼저 만난 문제였다.

코드는 정상인데 화면에 버튼이 안 나타났다.

원인은 의외로 단순했다.

문제 원인

프론트 코드에서 이렇게 요청하고 있었다.

 
fetch("/ai/status")
 

그런데 서비스 구조가:

/redmine-dashboard/
 

같은 하위 경로(sub-path) 기반이었다.

즉 nginx 프록시 뒤에서 동작 중이었다.

여기서 /로 시작하는 절대 경로를 사용하면:

/ai/status
 

로 요청이 날아가버린다.

하지만 실제 주소는:

/redmine-dashboard/ai/status
 

였던 것.

결국 404 발생.

해결 방법

절대경로 대신 상대경로 사용.

 
fetch("ai/status")
 

이렇게 바꾸니 해결됐다.

여기서 배운 점

nginx 하위 경로 프록시 환경에서는:

fetch 경로를 상대경로로 쓰는 게 안전하다.

생각보다 자주 터지는 문제였다.

삽질 2 — Unexpected token '<'

이번에는 버튼은 보이는데 분석 요청 시 이런 에러가 발생했다.

Unexpected token '<'
 

초보 시절 정말 많이 보던 에러다.

보통 의미는 하나다.

“JSON을 기대했는데 HTML이 왔다”

원인

범인은 nginx timeout 설정이었다.

기본 설정:

proxy_read_timeout 30;
 

그런데 모델 응답이 30초를 넘기기 시작했다.

그러면 nginx가:

“응답 너무 늦는데?”

라고 판단하고 HTML 에러 페이지를 대신 반환한다.

브라우저는 JSON을 기대하고 있다가:

<html>...
 

의 < 문자를 만나며 파싱 실패.

해결

AI 경로 전용 nginx 설정 추가:

location /redmine-dashboard/ai/ {
    proxy_read_timeout 120s;
}
 

일단 이걸로 HTML 에러는 사라졌다.

삽질 3 — 한국어 답변에 중국어가 섞인다

Qwen 은 중국 계열 모델이다 보니 가끔 중국어나 한자를 섞었다.

업무 화면에서 이건 꽤 거슬렸다.

해결 방법 — 프롬프트 강화

흥미로웠던 건:

  • 한국어 지시보다
  • 영어 지시가 더 잘 먹혔다는 점

최종적으로는 이렇게 넣었다.

IMPORTANT:
Reply ONLY in Korean.
Do NOT use Chinese characters.
 

추가로 temperature도 조정했다.

기존:

0.3
 

변경:

0.2
 

temperature를 낮추니:

  • 답변 일관성 증가
  • 이상한 문장 감소
  • 언어 혼용 감소

효과가 있었다.

가장 중요한 개선 — 비동기 Job 구조

사실 timeout을 120초로 늘린 건 임시방편이었다.

상황에 따라 모델 응답이:

  • 2분
  • 5분
  • 그 이상

걸릴 수도 있었다.

근본 문제는:

“사용자가 계속 기다려야 하는 구조”

였다.

동기 방식의 문제

기존 방식:

버튼 클릭
→ 응답 올 때까지 대기
→ 결과 반환
 

모델이 느리면:

  • 브라우저 timeout
  • nginx timeout
  • 사용자 피로
  • UX 악화

전부 발생했다.

비동기 Job 방식으로 변경

구조를 완전히 바꿨다.

버튼 클릭
→ 서버가 접수번호(job_id) 즉시 반환
→ 백그라운드에서 AI 처리
→ 브라우저가 주기적으로 상태 확인
→ 완료 시 결과 표시
 

식당 진동벨 구조와 비슷하다.

실제 API 구조

POST /ai/analyze

즉시 응답:

{
  "job_id": "abc123"
}
 

HTTP 상태:

202 Accepted
 

GET /ai/result/{job_id}

현재 상태 반환:

  • pending
  • running
  • done

error

프론트 처리 방식

브라우저는:

3초마다 상태 확인
 

을 수행한다.

예:

분석 중... 24초 경과
 

같은 표시 출력.

완료되면 결과 렌더링.

캐시가 있으면 즉시 표시.

결과적으로 좋아진 점

이 구조로 바꾸고 나니:

  • nginx timeout 문제 제거
  • 브라우저 timeout 문제 제거
  • 긴 작업 안정화
  • 사용자 UX 개선

이 모두 해결됐다.

핵심은 결국 이것이었다.

“오래 기다리게 만들지 말고, 기다리지 않는 구조로 바꿔라”

UI 개선

처음에는 헤더에:

AI 분석
 

버튼 하나만 두고 전체 팀 분석만 제공했다.

그런데 실제 사용 패턴은 달랐다.

사람들은:

  • 특정 팀원만 확인
  • 일부만 재분석
  • 문제 있는 사람만 보기

를 더 자주 했다.

그래서 바꾼 UI

  • 사용자 카드별 AI 분석 버튼 추가
  • 완료 후 새로고침 버튼으로 변경
  • 헤더에는 AI 리포트만 유지

이 구조가 훨씬 사용성이 좋았다.

배포하면서 자주 놓치는 부분

백엔드는 Gunicorn 기반이었다.

여기서 많이 헷갈리는 부분.

정적 파일 vs Python 코드

HTML/CSS/JS:

새로고침 → 바로 반영
 

Python 코드:

재시작 필요
 

즉:

systemctl restart gunicorn
 

같은 과정이 필요하다.

이걸 잊으면:

“왜 수정했는데 안 바뀌지?”

상태에 빠지기 쉽다.

마무리

이번 작업은 단순히 AI 기능 하나 붙인 정도가 아니었다.

오히려 더 많이 배운 건 주변 인프라였다.

  • nginx 프록시 구조
  • timeout 처리
  • 동기/비동기 설계
  • 캐시 전략
  • 프롬프트 제어
  • 모델 운영 방식

특히 가장 크게 체감한 건 이것이다.

긴 작업은 timeout으로 버티는 게 아니라, 애초에 비동기 구조로 설계하는 게 맞다.

로컬 LLM은 생각보다 훨씬 실용적이었다.

특히:

  • 외부 API 사용이 어려운 환경
  • 사내 데이터 보안이 중요한 환경
  • 간단한 요약/분석 자동화

같은 곳에서는 꽤 현실적인 선택지가 될 수 있다고 느꼈다.

비슷한 구조를 고민하는 분들에게 조금이라도 참고가 되었으면 좋겠다.

반응형