Ollama (qwen2.5:14b)를 활용한 자전거 온라인 쇼핑몰 AI 챗봇 시스템
┌─────────────────────────────────────┐
│ API Layer (Controllers, Hubs) │ ← 외부 인터페이스
├─────────────────────────────────────┤
│ Infrastructure (DB, HTTP Client) │ ← 외부 서비스 통합
├─────────────────────────────────────┤
│ Application (Services, DTOs) │ ← 비즈니스 로직
├─────────────────────────────────────┤
│ Domain (Entities, Enums) │ ← 핵심 도메인
└─────────────────────────────────────┘
backend/
├── BicycleShopChatbot.Domain/ # 도메인 계층
│ ├── Entities/
│ │ ├── ChatSession.cs # 채팅 세션
│ │ ├── ChatMessage.cs # 채팅 메시지
│ │ ├── Product.cs # 제품 정보
│ │ ├── Order.cs # 주문 정보
│ │ └── FAQ.cs # FAQ
│ └── Enums/
│ ├── MessageRole.cs # 메시지 역할 (User, Assistant, System)
│ ├── ChatCategory.cs # 채팅 카테고리
│ └── MessageStatus.cs # 메시지 상태
│
├── BicycleShopChatbot.Application/ # 애플리케이션 계층
│ ├── DTOs/
│ │ ├── ChatMessageDto.cs
│ │ ├── SendMessageRequest.cs
│ │ └── ChatSessionDto.cs
│ ├── Interfaces/
│ │ ├── Repositories/ # Repository 인터페이스
│ │ ├── IChatService.cs
│ │ ├── IOllamaService.cs
│ │ ├── IPromptService.cs
│ │ ├── IProductContextService.cs
│ │ └── IOrderContextService.cs
│ └── Services/
│ ├── ChatService.cs # 🔥 채팅 오케스트레이션
│ ├── OllamaService.cs # 🔥 Ollama API 통합
│ ├── PromptService.cs # 🔥 프롬프트 엔지니어링
│ ├── ProductContextService.cs # 제품 컨텍스트 관리
│ └── OrderContextService.cs # 주문 컨텍스트 관리
│
├── BicycleShopChatbot.Infrastructure/ # 인프라 계층
│ ├── Data/
│ │ ├── ApplicationDbContext.cs # EF Core DbContext
│ │ └── Configurations/ # Entity 설정
│ ├── Repositories/
│ │ └── Implementation/ # Repository 구현
│ └── Seed/
│ ├── ProductSeedData.cs # 제품 시드 데이터
│ ├── FaqSeedData.cs # FAQ 시드 데이터
│ ├── OrderSeedData.cs # 주문 시드 데이터
│ └── DatabaseSeeder.cs # 시드 오케스트레이터
│
└── BicycleShopChatbot.Api/ # API 계층
├── Hubs/
│ └── ChatHub.cs # 🔥 SignalR 허브
├── Controllers/
│ └── HealthController.cs # 헬스 체크
├── Program.cs # 앱 진입점 & DI 설정
└── appsettings.json # 설정 파일
frontend/
└── src/
└── app/
├── core/
│ ├── services/
│ │ ├── signalr.service.ts # SignalR 연결 관리
│ │ └── chat.service.ts # 채팅 상태 관리
│ └── models/
│ ├── chat-message.model.ts
│ └── chat-session.model.ts
├── features/chat/
│ └── components/
│ ├── chat-window/ # 메인 채팅 컨테이너
│ ├── message-list/ # 메시지 목록
│ ├── message-item/ # 개별 메시지
│ └── chat-input/ # 입력 창
└── shared/
└── components/
- Id (UUID, PK)
- SessionId (VARCHAR, UNIQUE)
- UserId (VARCHAR, NULL)
- UserName (VARCHAR, NULL)
- CreatedAt (TIMESTAMP)
- LastActivityAt (TIMESTAMP)
- IsActive (BOOLEAN)
- SessionMetadata (TEXT/JSON)
- TotalMessages (INT)
- Id (BIGINT, PK)
- ChatSessionId (UUID, FK)
- Role (VARCHAR) - 'User', 'Assistant', 'System'
- Content (TEXT)
- Timestamp (TIMESTAMP)
- Category (VARCHAR) - 'ProductSearch', 'FAQ', 'OrderStatus', 'General'
- IntentDetected (VARCHAR)
- ProductId (INT, FK, NULL)
- OrderId (INT, FK, NULL)
- Metadata (TEXT/JSON)
- TokensUsed (INT, NULL)
- ProcessingTimeMs (INT, NULL)
- Id (INT, PK)
- ProductCode (VARCHAR, UNIQUE)
- Name (VARCHAR)
- NameKorean (VARCHAR)
- Category (VARCHAR) - 'Road', 'Mountain', 'Hybrid', 'Electric', etc.
- Brand (VARCHAR)
- Price (DECIMAL)
- Description (TEXT)
- DescriptionKorean (TEXT)
- Specifications (TEXT/JSON)
- StockQuantity (INT)
- IsAvailable (BOOLEAN)
- ImageUrl (VARCHAR)
- CreatedAt, UpdatedAt (TIMESTAMP)
- Id (INT, PK)
- OrderNumber (VARCHAR, UNIQUE)
- CustomerEmail (VARCHAR)
- CustomerPhone (VARCHAR)
- Status (VARCHAR) - 'Pending', 'Processing', 'Shipped', 'Delivered'
- OrderDate (TIMESTAMP)
- TotalAmount (DECIMAL)
- ShippingAddress (VARCHAR)
- TrackingNumber (VARCHAR)
- EstimatedDelivery (DATE)
- UpdatedAt (TIMESTAMP)
- Id (INT, PK)
- Question (TEXT)
- QuestionKorean (TEXT)
- Answer (TEXT)
- AnswerKorean (TEXT)
- Category (VARCHAR)
- Keywords (VARCHAR)
- ViewCount (INT)
- IsActive (BOOLEAN)
- CreatedAt (TIMESTAMP)
PromptService.cs에서 사용자 메시지 분석:
public ChatCategory DetectIntent(string userMessage)
{
var lower = userMessage.ToLower();
if (lower.Contains("주문") || lower.Contains("배송") || lower.Contains("송장"))
return ChatCategory.OrderStatus;
if (lower.Contains("추천") || lower.Contains("찾아") || lower.Contains("자전거"))
return ChatCategory.ProductSearch;
if (lower.Contains("환불") || lower.Contains("교환") || lower.Contains("반품"))
return ChatCategory.FAQ;
return ChatCategory.General;
}
OllamaService.cs:
public async Task<string> GenerateResponseAsync(
string userMessage,
List<ChatMessageDto> conversationHistory,
string systemPrompt,
CancellationToken cancellationToken)
{
var messages = BuildMessagePayload(userMessage, conversationHistory, systemPrompt);
var request = new
{
model = "qwen2.5:14b",
messages = messages,
stream = false,
options = new { temperature = 0.7, top_p = 0.9, top_k = 40 }
};
var response = await _httpClient.PostAsJsonAsync("/api/chat", request, cancellationToken);
// ... 응답 처리
}
ChatHub.cs:
public class ChatHub : Hub
{
public async Task SendMessage(SendMessageRequest request)
{
var response = await _chatService.ProcessUserMessageAsync(request);
await Clients.Caller.SendAsync("ReceiveMessage", response);
}
public async Task JoinSession(string sessionId)
{
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId);
}
}
한국어 최적화 시스템 프롬프트:
당신은 자전거 온라인 쇼핑몰의 전문 AI 상담원입니다.
고객이 원하는 자전거를 찾도록 도와주는 것이 목표입니다.
현재 판매 중인 제품:
{products_json}
답변 규칙:
1. 항상 한국어로 친절하게 답변
2. 제품의 특징과 장점을 구체적으로 설명
3. 가격과 재고 상태를 정확히 전달
4. 고객의 용도와 예산을 고려하여 추천
5. 2~3개 제품을 비교하여 제시
# Ollama 설치 (Linux/Mac)
curl -fsSL https://ollama.com/install.sh | sh
# 모델 다운로드
ollama pull qwen2.5:14b
# 또는 더 빠른 모델 (권장)
ollama pull qwen2.5:7b
cd backend
dotnet restore
dotnet build
# API 서버 실행
cd src/BicycleShopChatbot.Api
dotnet run
서버가 http://localhost:5069에서 실행됩니다.
cd frontend
npm install
npm start
프론트엔드가 http://localhost:4200에서 실행됩니다.
GET /health - 헬스 체크 (데이터베이스 및 Ollama 상태)URL: /hubs/chat
메서드:
SendMessage(SendMessageRequest) - 메시지 전송JoinSession(string sessionId) - 세션 참여LeaveSession(string sessionId) - 세션 나가기클라이언트 이벤트:
ReceiveMessage(ChatMessageDto) - 메시지 수신{
"ConnectionStrings": {
"DefaultConnection": "Data Source=bicycleshop.db"
},
"Ollama": {
"BaseUrl": "http://localhost:11434",
"ModelName": "qwen2.5:14b",
"TimeoutSeconds": "120",
"MaxRetries": "3",
"DefaultTemperature": "0.7"
},
"ChatSettings": {
"MaxConversationHistory": "20",
"MaxMessageLength": "2000",
"SessionTimeoutMinutes": "60"
},
"Cors": {
"AllowedOrigins": "http://localhost:4200"
}
}
curl http://localhost:5069/health
예상 응답:
{
"status": "healthy",
"timestamp": "2026-01-02T...",
"database": {
"status": "connected",
"provider": "Microsoft.EntityFrameworkCore.Sqlite"
},
"ollama": {
"status": "available"
}
}
const signalR = require('@microsoft/signalr');
const connection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost:5069/hubs/chat')
.build();
connection.on('ReceiveMessage', (message) => {
console.log('받은 메시지:', message.content);
});
await connection.start();
await connection.invoke('SendMessage', {
sessionId: 'test-session',
message: '로드 바이크 추천해주세요',
userId: 'test-user'
});
qwen2.5:14b (9GB 모델):
qwen2.5:7b (권장, 4GB 모델):
더 작은 모델 사용:
ollama pull qwen2.5:7b
ollama pull llama3.2:3b
컨텍스트 축소:
스트리밍 활성화:
stream: true 옵션 사용타임아웃 증가 (임시 해결책):
"Ollama": {
"TimeoutSeconds": "300"
}
이 프로젝트는 자전거 온라인 쇼핑몰을 위한 AI 챗봇 시스템입니다.
MIT
Ollama 응답 시간: qwen2.5:14b 모델은 전체 제품 컨텍스트에서 120초 이상 소요
첫 요청 지연: Ollama 모델이 메모리에 로드되는 동안 긴 지연
SignalR 타임아웃: 긴 Ollama 응답 시 연결 끊김
프로젝트 관련 문의사항은 이슈를 생성해주세요.
Ollama (qwen2.5:14b)를 활용한 자전거 온라인 쇼핑몰 AI 챗봇 시스템
┌─────────────────────────────────────┐
│ API Layer (Controllers, Hubs) │ ← 외부 인터페이스
├─────────────────────────────────────┤
│ Infrastructure (DB, HTTP Client) │ ← 외부 서비스 통합
├─────────────────────────────────────┤
│ Application (Services, DTOs) │ ← 비즈니스 로직
├─────────────────────────────────────┤
│ Domain (Entities, Enums) │ ← 핵심 도메인
└─────────────────────────────────────┘
backend/
├── BicycleShopChatbot.Domain/ # 도메인 계층
│ ├── Entities/
│ │ ├── ChatSession.cs # 채팅 세션
│ │ ├── ChatMessage.cs # 채팅 메시지
│ │ ├── Product.cs # 제품 정보
│ │ ├── Order.cs # 주문 정보
│ │ └── FAQ.cs # FAQ
│ └── Enums/
│ ├── MessageRole.cs # 메시지 역할 (User, Assistant, System)
│ ├── ChatCategory.cs # 채팅 카테고리
│ └── MessageStatus.cs # 메시지 상태
│
├── BicycleShopChatbot.Application/ # 애플리케이션 계층
│ ├── DTOs/
│ │ ├── ChatMessageDto.cs
│ │ ├── SendMessageRequest.cs
│ │ └── ChatSessionDto.cs
│ ├── Interfaces/
│ │ ├── Repositories/ # Repository 인터페이스
│ │ ├── IChatService.cs
│ │ ├── IOllamaService.cs
│ │ ├── IPromptService.cs
│ │ ├── IProductContextService.cs
│ │ └── IOrderContextService.cs
│ └── Services/
│ ├── ChatService.cs # 🔥 채팅 오케스트레이션
│ ├── OllamaService.cs # 🔥 Ollama API 통합
│ ├── PromptService.cs # 🔥 프롬프트 엔지니어링
│ ├── ProductContextService.cs # 제품 컨텍스트 관리
│ └── OrderContextService.cs # 주문 컨텍스트 관리
│
├── BicycleShopChatbot.Infrastructure/ # 인프라 계층
│ ├── Data/
│ │ ├── ApplicationDbContext.cs # EF Core DbContext
│ │ └── Configurations/ # Entity 설정
│ ├── Repositories/
│ │ └── Implementation/ # Repository 구현
│ └── Seed/
│ ├── ProductSeedData.cs # 제품 시드 데이터
│ ├── FaqSeedData.cs # FAQ 시드 데이터
│ ├── OrderSeedData.cs # 주문 시드 데이터
│ └── DatabaseSeeder.cs # 시드 오케스트레이터
│
└── BicycleShopChatbot.Api/ # API 계층
├── Hubs/
│ └── ChatHub.cs # 🔥 SignalR 허브
├── Controllers/
│ └── HealthController.cs # 헬스 체크
├── Program.cs # 앱 진입점 & DI 설정
└── appsettings.json # 설정 파일
frontend/
└── src/
└── app/
├── core/
│ ├── services/
│ │ ├── signalr.service.ts # SignalR 연결 관리
│ │ └── chat.service.ts # 채팅 상태 관리
│ └── models/
│ ├── chat-message.model.ts
│ └── chat-session.model.ts
├── features/chat/
│ └── components/
│ ├── chat-window/ # 메인 채팅 컨테이너
│ ├── message-list/ # 메시지 목록
│ ├── message-item/ # 개별 메시지
│ └── chat-input/ # 입력 창
└── shared/
└── components/
- Id (UUID, PK)
- SessionId (VARCHAR, UNIQUE)
- UserId (VARCHAR, NULL)
- UserName (VARCHAR, NULL)
- CreatedAt (TIMESTAMP)
- LastActivityAt (TIMESTAMP)
- IsActive (BOOLEAN)
- SessionMetadata (TEXT/JSON)
- TotalMessages (INT)
- Id (BIGINT, PK)
- ChatSessionId (UUID, FK)
- Role (VARCHAR) - 'User', 'Assistant', 'System'
- Content (TEXT)
- Timestamp (TIMESTAMP)
- Category (VARCHAR) - 'ProductSearch', 'FAQ', 'OrderStatus', 'General'
- IntentDetected (VARCHAR)
- ProductId (INT, FK, NULL)
- OrderId (INT, FK, NULL)
- Metadata (TEXT/JSON)
- TokensUsed (INT, NULL)
- ProcessingTimeMs (INT, NULL)
- Id (INT, PK)
- ProductCode (VARCHAR, UNIQUE)
- Name (VARCHAR)
- NameKorean (VARCHAR)
- Category (VARCHAR) - 'Road', 'Mountain', 'Hybrid', 'Electric', etc.
- Brand (VARCHAR)
- Price (DECIMAL)
- Description (TEXT)
- DescriptionKorean (TEXT)
- Specifications (TEXT/JSON)
- StockQuantity (INT)
- IsAvailable (BOOLEAN)
- ImageUrl (VARCHAR)
- CreatedAt, UpdatedAt (TIMESTAMP)
- Id (INT, PK)
- OrderNumber (VARCHAR, UNIQUE)
- CustomerEmail (VARCHAR)
- CustomerPhone (VARCHAR)
- Status (VARCHAR) - 'Pending', 'Processing', 'Shipped', 'Delivered'
- OrderDate (TIMESTAMP)
- TotalAmount (DECIMAL)
- ShippingAddress (VARCHAR)
- TrackingNumber (VARCHAR)
- EstimatedDelivery (DATE)
- UpdatedAt (TIMESTAMP)
- Id (INT, PK)
- Question (TEXT)
- QuestionKorean (TEXT)
- Answer (TEXT)
- AnswerKorean (TEXT)
- Category (VARCHAR)
- Keywords (VARCHAR)
- ViewCount (INT)
- IsActive (BOOLEAN)
- CreatedAt (TIMESTAMP)
PromptService.cs에서 사용자 메시지 분석:
public ChatCategory DetectIntent(string userMessage)
{
var lower = userMessage.ToLower();
if (lower.Contains("주문") || lower.Contains("배송") || lower.Contains("송장"))
return ChatCategory.OrderStatus;
if (lower.Contains("추천") || lower.Contains("찾아") || lower.Contains("자전거"))
return ChatCategory.ProductSearch;
if (lower.Contains("환불") || lower.Contains("교환") || lower.Contains("반품"))
return ChatCategory.FAQ;
return ChatCategory.General;
}
OllamaService.cs:
public async Task<string> GenerateResponseAsync(
string userMessage,
List<ChatMessageDto> conversationHistory,
string systemPrompt,
CancellationToken cancellationToken)
{
var messages = BuildMessagePayload(userMessage, conversationHistory, systemPrompt);
var request = new
{
model = "qwen2.5:14b",
messages = messages,
stream = false,
options = new { temperature = 0.7, top_p = 0.9, top_k = 40 }
};
var response = await _httpClient.PostAsJsonAsync("/api/chat", request, cancellationToken);
// ... 응답 처리
}
ChatHub.cs:
public class ChatHub : Hub
{
public async Task SendMessage(SendMessageRequest request)
{
var response = await _chatService.ProcessUserMessageAsync(request);
await Clients.Caller.SendAsync("ReceiveMessage", response);
}
public async Task JoinSession(string sessionId)
{
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId);
}
}
한국어 최적화 시스템 프롬프트:
당신은 자전거 온라인 쇼핑몰의 전문 AI 상담원입니다.
고객이 원하는 자전거를 찾도록 도와주는 것이 목표입니다.
현재 판매 중인 제품:
{products_json}
답변 규칙:
1. 항상 한국어로 친절하게 답변
2. 제품의 특징과 장점을 구체적으로 설명
3. 가격과 재고 상태를 정확히 전달
4. 고객의 용도와 예산을 고려하여 추천
5. 2~3개 제품을 비교하여 제시
# Ollama 설치 (Linux/Mac)
curl -fsSL https://ollama.com/install.sh | sh
# 모델 다운로드
ollama pull qwen2.5:14b
# 또는 더 빠른 모델 (권장)
ollama pull qwen2.5:7b
cd backend
dotnet restore
dotnet build
# API 서버 실행
cd src/BicycleShopChatbot.Api
dotnet run
서버가 http://localhost:5069에서 실행됩니다.
cd frontend
npm install
npm start
프론트엔드가 http://localhost:4200에서 실행됩니다.
GET /health - 헬스 체크 (데이터베이스 및 Ollama 상태)URL: /hubs/chat
메서드:
SendMessage(SendMessageRequest) - 메시지 전송JoinSession(string sessionId) - 세션 참여LeaveSession(string sessionId) - 세션 나가기클라이언트 이벤트:
ReceiveMessage(ChatMessageDto) - 메시지 수신{
"ConnectionStrings": {
"DefaultConnection": "Data Source=bicycleshop.db"
},
"Ollama": {
"BaseUrl": "http://localhost:11434",
"ModelName": "qwen2.5:14b",
"TimeoutSeconds": "120",
"MaxRetries": "3",
"DefaultTemperature": "0.7"
},
"ChatSettings": {
"MaxConversationHistory": "20",
"MaxMessageLength": "2000",
"SessionTimeoutMinutes": "60"
},
"Cors": {
"AllowedOrigins": "http://localhost:4200"
}
}
curl http://localhost:5069/health
예상 응답:
{
"status": "healthy",
"timestamp": "2026-01-02T...",
"database": {
"status": "connected",
"provider": "Microsoft.EntityFrameworkCore.Sqlite"
},
"ollama": {
"status": "available"
}
}
const signalR = require('@microsoft/signalr');
const connection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost:5069/hubs/chat')
.build();
connection.on('ReceiveMessage', (message) => {
console.log('받은 메시지:', message.content);
});
await connection.start();
await connection.invoke('SendMessage', {
sessionId: 'test-session',
message: '로드 바이크 추천해주세요',
userId: 'test-user'
});
qwen2.5:14b (9GB 모델):
qwen2.5:7b (권장, 4GB 모델):
더 작은 모델 사용:
ollama pull qwen2.5:7b
ollama pull llama3.2:3b
컨텍스트 축소:
스트리밍 활성화:
stream: true 옵션 사용타임아웃 증가 (임시 해결책):
"Ollama": {
"TimeoutSeconds": "300"
}
이 프로젝트는 자전거 온라인 쇼핑몰을 위한 AI 챗봇 시스템입니다.
MIT
Ollama 응답 시간: qwen2.5:14b 모델은 전체 제품 컨텍스트에서 120초 이상 소요
첫 요청 지연: Ollama 모델이 메모리에 로드되는 동안 긴 지연
SignalR 타임아웃: 긴 Ollama 응답 시 연결 끊김
프로젝트 관련 문의사항은 이슈를 생성해주세요.