AI 서비스
이 페이지는 conversation state를 저장하고, chat request를 chatbot-svc로
프록시하며, read-only SQL을 검증하고, monolith 내부에서 확인된 tool을
실행하는 tenant 범위 모듈 lumie-backend/modules/ai의 레퍼 런스입니다.
소스 경로
| Path | 역할 |
|---|---|
lumie-backend/modules/ai/src/main/java/com/lumie/ai/adapter/in/web/{ChatController,ConversationController,InternalChatbotController}.java | 공개 chat endpoint, conversation 조회, worker callback surface |
lumie-backend/modules/ai/src/main/java/com/lumie/ai/adapter/out/external/ChatbotClient.java | SSE passthrough를 포함한 monolith에서 chatbot-svc로의 HTTP 프록시 |
lumie-backend/modules/ai/src/main/java/com/lumie/ai/application/service/{ChatService,ConversationQueryService,SchemaDiscoveryService,SqlValidator,ScheduledTaskExecutor}.java | persistence, schema discovery, SQL safety, scheduled-task execution |
lumie-backend/modules/ai/src/main/java/com/lumie/ai/domain/entity/{Conversation,ChatMessage,ScheduledTask}.java | AI conversation, message, scheduled-task aggregate |
lumie-backend/app/src/main/java/com/lumie/app/config/internal/InternalHmacAuthFilter.java | /internal/chatbot/**용 HMAC 보호 |
lumie-backend/app/src/main/resources/db/migration/public/{V18__rls_baseline,V27__langgraph_schema}.sql | 기본 AI table과 LangGraph 관련 schema 추가 |
공개 인터페이스
| Endpoint | 목적 |
|---|---|
POST /v1/chat | chatbot-svc로 프록시되는 비스트리밍 chat 요청 |
POST /v1/chat/stream | chatbot-svc로 프록시되는 SSE chat stream |
POST /v1/chat/confirm | 사용자 확인 후 pending tool/action 재개 |
GET /v1/conversations, GET /v1/conversations/{id}, DELETE /v1/conversations/{id} | staff 전용 conversation 목록, 상세, soft-delete 작업 |
POST /internal/chatbot/query | monolith 내부에서 read-only SQL을 실행하기 위한 worker callback |
POST /internal/chatbot/tools/{toolName} | 확인된 tool 실행을 위한 worker callback |
GET /internal/chatbot/schema | schema 설명을 가져오기 위한 worker callback |
GET /internal/chatbot/history | 최근 conversation history를 가져오기 위한 worker callback |
POST /internal/chatbot/pending-action, POST /internal/chatbot/save-message, POST /internal/chatbot/conversations | pending action, message, 신규 conversation을 저장하기 위한 worker callback endpoint |
모든 공개 chat 및 conversation endpoint는 Role.STUDENT를 거부합니다.
즉, 이 surface는 staff 전용입니다.
내부 인터페이스와 의존성
| Surface | 역할 |
|---|---|
chatbot-svc | graph orchestration과 model interaction을 소유하며, monolith는 더 이상 local LLM fallback을 실행하지 않음 |
/internal/chatbot/** | SQL, write, persistence를 monolith 안에 유지하는 HMAC-protected callback surface |
SqlValidator | non-SELECT SQL, 세미콜론, DDL/DML 키워드, catalog 접근을 거부 |
ToolRegistry | worker를 대신해 실행되는 confirmed write tool의 런타임 registry |
TenantService.listActiveTenants() | ScheduledTaskExecutor가 기한이 된 task를 실행할 때 쓰는 cross-tenant source |
집계와 테이블
| Aggregate | 참고 |
|---|---|
Conversation | 소유 사용자와 active flag를 가진 conversation shell |
ChatMessage | 저장된 assistant, user, tool-call, pending-action message record |
ScheduledTask | tenant 범위 scheduled tool execution 상태 |
런타임 흐름
계약 참고 사항
확인된 write tool은 내부 컨트롤러 경계에서 명시적인 allowlist로 제한됩니다.
// lumie-backend/modules/ai/src/main/java/com/lumie/ai/adapter/in/web/InternalChatbotController.java
private static final Set<String> ALLOWED_TOOLS = Set.of(
"create_announcement", "send_sms", "send_telegram", "schedule_task");
HMAC 인증에 성공한 worker 요청이라도, 요청한 tool이 이 집합 밖에 있거나
ToolRegistry에 등록되어 있지 않으면 거부됩니다.
예시 계약
이 예시는 ChatController, ChatReplyResponse, ChatbotClient,
InternalChatbotController, chatbot-svc/src/graph/tools.py에서 직접
가져왔습니다.
대기 중인 채팅 작업 확인
POST /v1/chat/confirm
Idempotency-Key: confirm-99
Content-Type: application/json
{
"messageId": 99,
"confirmed": true
}
HTTP/1.1 200 OK
{
"conversationId": 10,
"message": "Announcement created.",
"pendingAction": null
}
놓치기 쉬운 세부사항 하나가 있습니다. ChatReplyResponse.pendingAction은
String입니다. worker가 후속 pending action을 반환하면
ChatbotClient.toReply(...)는 그 worker 객체를 중첩 객체가 아니라 JSON
문자열로 직렬화합니다.
내부 도구 실행 페이로드
worker tool schema는 camelCase argument key를 사용하며,
InternalChatbotController.executeTool(...)는 arguments 안에서 그 key가
바뀌지 않은 상태로 들어오기를 기대합니다.
POST /internal/chatbot/tools/send_sms
Content-Type: application/json
{
"tenantSlug": "acme",
"tenantId": 15,
"userId": 7,
"arguments": {
"phoneNumber": "01012345678",
"message": "Tomorrow's class starts at 10:00."
}
}
HTTP/1.1 200 OK
{
"success": true,
"data": "<tool-result-json>",
"error": null
}
실패, 재시도, 관측성
ChatbotClient는 worker가 기본 JDK HTTP/2 협상 경로와 호환되지 않기 때문에 backend-to-worker HTTP를 HTTP/1.1로 고정합니다.chat/confirm은 선택적Idempotency-Key를 지원하지만, 일반chat과chat/stream은 idempotency layer를 사용하지 않습니다.SqlValidator는 어떤 query도 데이터베이스에 도달하기 전에 write SQL, 다중 statement,pg_*,information_schema접근을 차단합니다.InternalChatbotController는 모든 worker callback마다 tenant와 user context를 다시 세팅하여, RLS와 사용자 인지형 tool 로직이 monolith 내부에서 실행되도록 합니다.ScheduledTaskExecutor는 활성 tenant를 순회하며, 각 due task를 tenant context 안에서 실행하고 실패한 task는FAILED로 표시합니다.ChatService는 persistence 전용입니다. 코드 주석은 예전의 monolith 내부 Spring AI 경로가 Phase 5 전환에서 제거되었다고 명시합니다.
검증
cd lumie-backend
./gradlew :modules:ai:test
./gradlew :modules:ai:test --tests '*Chat*'
./gradlew :modules:ai:test --tests '*ScheduledTask*'
예상 성공 신호:
- Gradle이
BUILD SUCCESSFUL로 종료되고, AI 모듈 테스트가 여전히 chat, confirm, scheduled-task 흐름을 다룹니다. InternalChatbotController가 여전히create_announcement,send_sms,send_telegram,schedule_task만 허용하고, worker tool schema가phoneNumber,toolName같은 camelCase key를 계속 사용합니다.