Skip to content

Commit 41fb803

Browse files
committed
docs: 기능 강조 내용 변경
1 parent 5f8efd9 commit 41fb803

File tree

1 file changed

+105
-97
lines changed

1 file changed

+105
-97
lines changed

docs/overview/features.md

Lines changed: 105 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -13,148 +13,156 @@
1313

1414
## 1. 채팅 시스템
1515

16-
### 이중 프로토콜: WebSocket + HTTP REST API
16+
### HTTP-WebSocket Bridge 패턴: 비동기 장시간 작업 처리
1717

18-
**설명**: 실시간 WebSocket 통신과 HTTP REST API를 모두 지원하는 하이브리드 채팅 시스템
18+
**설명**: HTTP 요청으로 채팅을 시작하고 WebSocket으로 결과를 전송하는 비동기 아키텍처. 장시간 소요되는 LLM 처리를 논블로킹 방식으로 구현하여 클라이언트 타임아웃을 방지합니다.
19+
20+
**아키텍처 플로우**:
21+
```
22+
Client ──HTTP POST──→ ChatController ──Enqueue──→ Background Processing
23+
↑ ↓
24+
└──WebSocket Push────← WebSocketManager ←──Result────┘
25+
```
1926

2027
**구현 위치**:
21-
- **WebSocket 미들웨어**: [`ProjectVG.Api/Middleware/WebSocketMiddleware.cs`](../../ProjectVG.Api/Middleware/WebSocketMiddleware.cs)
22-
- **HTTP 채팅 컨트롤러**: [`ProjectVG.Api/Controllers/ChatController.cs`](../../ProjectVG.Api/Controllers/ChatController.cs)
23-
- **WebSocket 매니저**: [`ProjectVG.Application/Services/WebSocket/WebSocketManager.cs`](../../ProjectVG.Application/Services/WebSocket/WebSocketManager.cs)
28+
- **HTTP 진입점**: [`ProjectVG.Api/Controllers/ChatController.cs`](../../ProjectVG.Api/Controllers/ChatController.cs)
29+
- **WebSocket 결과 전송**: [`ProjectVG.Application/Services/WebSocket/WebSocketManager.cs`](../../ProjectVG.Application/Services/WebSocket/WebSocketManager.cs)
30+
- **비동기 처리 오케스트레이션**: [`ProjectVG.Application/Services/Chat/ChatService.cs`](../../ProjectVG.Application/Services/Chat/ChatService.cs)
2431

2532
**핵심 코드**:
2633
```csharp
27-
// WebSocket 연결 처리
28-
public async Task InvokeAsync(HttpContext context)
29-
{
30-
if (context.Request.Path == "/ws" && context.WebSockets.IsWebSocketRequest)
31-
{
32-
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
33-
var userId = await AuthenticateWebSocketAsync(context);
34-
35-
if (userId.HasValue)
36-
{
37-
await _webSocketManager.AddConnectionAsync(userId.Value, webSocket);
38-
await HandleWebSocketCommunication(webSocket, userId.Value);
39-
}
40-
}
41-
}
42-
43-
// HTTP 채팅 처리
34+
// HTTP로 요청 접수 (즉시 응답)
4435
[HttpPost]
4536
[JwtAuthentication]
4637
public async Task<IActionResult> ProcessChat([FromBody] ChatRequest request)
4738
{
48-
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
49-
var command = new ChatRequestCommand(userGuid, request.CharacterId, request.Message);
50-
var result = await _chatService.EnqueueChatRequestAsync(command);
51-
return Ok(result);
52-
}
53-
```
54-
55-
### 메시지 관리: User/Assistant/System 역할 기반 대화
56-
57-
**설명**: 대화의 각 메시지를 User, Assistant, System 역할로 구분하여 관리하는 시스템
39+
var command = new ChatRequestCommand(userId, request.CharacterId, request.Message);
5840

59-
**구현 위치**:
60-
- **대화 엔티티**: [`ProjectVG.Domain/Entities/Conversation/ConversationHistory.cs`](../../ProjectVG.Domain/Entities/Conversation/ConversationHistory.cs)
61-
- **채팅 역할**: [`ProjectVG.Domain/Entities/Conversation/ChatRole.cs`](../../ProjectVG.Domain/Entities/Conversation/ChatRole.cs)
62-
- **대화 서비스**: [`ProjectVG.Application/Services/Conversation/ConversationService.cs`](../../ProjectVG.Application/Services/Conversation/ConversationService.cs)
63-
64-
**핵심 코드**:
65-
```csharp
66-
public static class ChatRole
67-
{
68-
public const string User = "User";
69-
public const string Assistant = "Assistant";
70-
public const string System = "System";
41+
// 백그라운드 처리 시작 (논블로킹)
42+
var result = await _chatService.EnqueueChatRequestAsync(command);
7143

72-
public static bool IsValid(string role) =>
73-
role == User || role == Assistant || role == System;
44+
// 즉시 Accepted 응답 반환
45+
return Ok(new { status = "ACCEPTED", sessionId = result.SessionId });
7446
}
7547

76-
// 대화 히스토리 엔티티
77-
public class ConversationHistory : BaseEntity
48+
// 백그라운드에서 비동기 처리 후 WebSocket으로 결과 전송
49+
public async Task<ChatRequestResult> EnqueueChatRequestAsync(ChatRequestCommand command)
7850
{
79-
[Required]
80-
[RegularExpression("^(User|Assistant|System)$")]
81-
public string Role { get; set; } = string.Empty;
51+
await _validator.ValidateAsync(command);
52+
var context = await PrepareChatRequestAsync(command);
8253

83-
[Required]
84-
[StringLength(10000)]
85-
public string Content { get; set; } = string.Empty;
54+
// 백그라운드 태스크로 장시간 작업 실행
55+
_ = Task.Run(async () => await ProcessChatRequestInternalAsync(context));
8656

87-
public Guid? ConversationId { get; set; } // 대화 세션 그룹핑
57+
return ChatRequestResult.Accepted(command.Id.ToString(), command.UserId, command.CharacterId);
8858
}
8959
```
9060

91-
### 외부 서비스 연동: LLM, Memory, TTS 서비스 통합
61+
### ChatService 오케스트레이션 패턴: 복합 서비스 조율
62+
63+
**설명**: ChatService는 Facade 패턴을 구현하여 채팅 처리에 필요한 여러 서비스들을 단일 인터페이스로 추상화합니다. 비용 추적 데코레이터를 통해 LLM과 TTS 비용을 자동으로 추적합니다.
9264

93-
**설명**: LLM(언어모델), Memory(벡터 메모리), TTS(음성 합성) 외부 서비스와의 통합
65+
**실제 처리 플로우**:
66+
```
67+
1. 전처리 단계:
68+
- Validation: ChatRequestValidator
69+
- Input Analysis: UserInputAnalysisProcessor (+ Cost Tracking)
70+
- Action Processing: UserInputActionProcessor
71+
- Memory Context: MemoryContextPreprocessor
72+
- Character/Conversation Info
73+
74+
2. 백그라운드 처리:
75+
- LLM Processing: ChatLLMProcessor (+ Cost Tracking)
76+
- TTS Processing: ChatTTSProcessor (+ Cost Tracking)
77+
- Success Handling: ChatSuccessHandler
78+
- Result Persistence: ChatResultProcessor
79+
```
9480

9581
**구현 위치**:
96-
- **LLM 클라이언트**: [`ProjectVG.Infrastructure/Integrations/LLMClient/`](../../ProjectVG.Infrastructure/Integrations/LLMClient/)
97-
- **Memory 클라이언트**: [`ProjectVG.Infrastructure/Integrations/MemoryClient/VectorMemoryClient.cs`](../../ProjectVG.Infrastructure/Integrations/MemoryClient/VectorMemoryClient.cs)
98-
- **TTS 클라이언트**: [`ProjectVG.Infrastructure/Integrations/TextToSpeechClient/`](../../ProjectVG.Infrastructure/Integrations/TextToSpeechClient/)
82+
- **메인 오케스트레이터**: [`ProjectVG.Application/Services/Chat/ChatService.cs`](../../ProjectVG.Application/Services/Chat/ChatService.cs)
83+
- **비용 추적 데코레이터**: [`ProjectVG.Application/Services/Chat/CostTracking/`](../../ProjectVG.Application/Services/Chat/CostTracking/)
9984

10085
**핵심 코드**:
10186
```csharp
102-
// LLM 서비스 호출
103-
public async Task<LLMResponse> CreateTextResponseAsync(string systemMessage, string userMessage, List<History> conversationHistory)
87+
// Facade 패턴으로 채팅 처리 추상화
88+
public async Task<ChatRequestResult> EnqueueChatRequestAsync(ChatRequestCommand command)
10489
{
105-
var request = new LLMRequest
106-
{
107-
SystemMessage = systemMessage,
108-
UserMessage = userMessage,
109-
ConversationHistory = conversationHistory,
110-
Model = "gpt-4o-mini",
111-
MaxTokens = 1000
112-
};
90+
// 1. 즉시 검증
91+
await _validator.ValidateAsync(command);
92+
93+
// 2. 전처리 (context 준비)
94+
var preprocessContext = await PrepareChatRequestAsync(command);
95+
96+
// 3. 백그라운드에서 비동기 처리
97+
_ = Task.Run(async () => {
98+
await ProcessChatRequestInternalAsync(preprocessContext);
99+
});
113100

114-
var response = await _httpClient.PostAsJsonAsync("/api/v1/chat", request);
115-
return await response.Content.ReadFromJsonAsync<LLMResponse>();
101+
// 4. 즉시 Accepted 응답
102+
return ChatRequestResult.Accepted(command.Id.ToString(), command.UserId, command.CharacterId);
116103
}
117104

118-
// Memory 서비스 연동
119-
public async Task<MemoryInsertResponse> InsertAutoAsync(MemoryInsertRequest request)
105+
// 실제 백그라운드 처리
106+
private async Task ProcessChatRequestInternalAsync(ChatProcessContext context)
120107
{
121-
var response = await _httpClient.PostAsync("/api/memory", content);
122-
return MapInsertResponse(response);
108+
try {
109+
await _llmProcessor.ProcessAsync(context); // 비용 추적 있음
110+
await _ttsProcessor.ProcessAsync(context); // 비용 추적 있음
111+
112+
var successHandler = scope.ServiceProvider.GetRequiredService<ChatSuccessHandler>();
113+
var resultProcessor = scope.ServiceProvider.GetRequiredService<ChatResultProcessor>();
114+
115+
await successHandler.HandleAsync(context);
116+
await resultProcessor.PersistResultsAsync(context);
117+
}
118+
catch (Exception) {
119+
var failureHandler = scope.ServiceProvider.GetRequiredService<ChatFailureHandler>();
120+
await failureHandler.HandleAsync(context);
121+
}
122+
finally {
123+
_metricsService.EndChatMetrics();
124+
}
123125
}
124126
```
125127

126-
### 페이지네이션: 대화 기록 조회
128+
### WebSocket 세션 관리: 실시간 결과 전송
127129

128-
**설명**: 대화 기록의 효율적인 페이지네이션 조회 시스템
130+
**설명**: WebSocket을 통한 실시간 채팅 결과 전송 시스템. 클라이언트가 `/ws` 엔드포인트로 연결하면 채팅 처리 완료 시 결과를 자동으로 수신합니다.
129131

130132
**구현 위치**:
131-
- **대화 리포지토리**: [`ProjectVG.Infrastructure/Persistence/Repositories/Conversation/SqlServerConversationRepository.cs`](../../ProjectVG.Infrastructure/Persistence/Repositories/Conversation/SqlServerConversationRepository.cs)
132-
- **대화 컨트롤러**: [`ProjectVG.Api/Controllers/ConversationController.cs`](../../ProjectVG.Api/Controllers/ConversationController.cs)
133+
- **WebSocket 미들웨어**: [`ProjectVG.Api/Middleware/WebSocketMiddleware.cs`](../../ProjectVG.Api/Middleware/WebSocketMiddleware.cs)
134+
- **WebSocket 매니저**: [`ProjectVG.Application/Services/WebSocket/WebSocketManager.cs`](../../ProjectVG.Application/Services/WebSocket/WebSocketManager.cs)
135+
- **연결 관리**: [`ProjectVG.Infrastructure/Realtime/WebSocketConnection/WebSocketClientConnection.cs`](../../ProjectVG.Infrastructure/Realtime/WebSocketConnection/WebSocketClientConnection.cs)
133136

134137
**핵심 코드**:
135138
```csharp
136-
// 페이지네이션 조회
137-
public async Task<IEnumerable<ConversationHistory>> GetConversationHistoryAsync(
138-
Guid userId, Guid characterId, int page = 1, int pageSize = 10)
139+
// WebSocket 연결 처리 (미들웨어)
140+
if (context.Request.Path == "/ws" && context.WebSockets.IsWebSocketRequest)
139141
{
140-
return await _context.ConversationHistories
141-
.Where(c => c.UserId == userId && c.CharacterId == characterId)
142-
.OrderByDescending(c => c.CreatedAt)
143-
.Skip((page - 1) * pageSize)
144-
.Take(pageSize)
145-
.ToListAsync();
142+
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
143+
var userId = await AuthenticateWebSocketAsync(context);
144+
145+
if (userId.HasValue)
146+
{
147+
await _webSocketManager.AddConnectionAsync(userId.Value, webSocket);
148+
await HandleWebSocketCommunication(webSocket, userId.Value);
149+
}
146150
}
147151

148-
// API 엔드포인트
149-
[HttpGet("{characterId}")]
150-
[JwtAuthentication]
151-
public async Task<ActionResult<ConversationHistoryListResponse>> GetConversationHistory(
152-
Guid characterId, [FromQuery] GetConversationHistoryRequest request)
152+
// 채팅 결과 WebSocket으로 전송
153+
public async Task SendChatResultAsync(Guid userId, ChatResult result)
153154
{
154-
var conversations = await _conversationService.GetConversationHistoryAsync(
155-
userId.Value, characterId, request.Page, request.PageSize);
155+
var connection = _connections.GetValueOrDefault(userId);
156+
if (connection != null)
157+
{
158+
var message = JsonSerializer.Serialize(new WebSocketMessage
159+
{
160+
Type = "chat_result",
161+
Data = result
162+
});
156163

157-
return Ok(new ConversationHistoryListResponse { Conversations = conversations });
164+
await connection.SendAsync(message);
165+
}
158166
}
159167
```
160168

0 commit comments

Comments
 (0)