AI 서비스
이 페이지는 대화 상태를 저장하고, 채팅 요청을 chatbot-svc로
프록시하며, 읽기 전용 SQL을 검증하고, monolith 내부에서 확인된 도구를
실행하는 테넌트 범위 모듈 lumie-backend/modules/ai의 레퍼런스입니다.
소스 경로
| 경로 | 역할 |
|---|---|
lumie-backend/modules/ai/src/main/java/com/lumie/ai/adapter/in/web/{ChatController,ConversationController,InternalChatbotController}.java | 공개 chat 엔드포인트, conversation 조회, 워커 콜백 인터페이스 |
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 추가 |
공개 인터페이스
| 엔드포인트 | 목적 |
|---|---|
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 내부에서 읽기 전용 SQL을 실행하기 위한 워커 콜백 |
POST /internal/chatbot/tools/{toolName} | 확인된 tool 실행을 위한 워커 콜백 |
GET /internal/chatbot/schema | schema 설명을 가져오기 위한 워커 콜백 |
GET /internal/chatbot/history | 최근 conversation history를 가져오기 위한 워커 콜백 |
POST /internal/chatbot/pending-action, POST /internal/chatbot/save-message, POST /internal/chatbot/conversations | pending action, message, 신규 conversation을 저장하기 위한 워커 콜백 엔드포인트 |
모든 공개 chat 및 conversation 엔드포인트는 Role.STUDENT를 거부합니다.
즉, 이 인터페이스는 staff 전용입니다.
내부 인터페이스와 의존성
| 인터페이스 | 역할 |
|---|---|
chatbot-svc | 그래프 오케스트레이션과 모델 상호작용을 소유하며, monolith는 더 이상 로컬 LLM 대체 경로를 실행하지 않음 |
/internal/chatbot/** | SQL, write, persistence를 monolith 안에 유지하는 HMAC-protected 콜백 인터페이스 |
SqlValidator | non-SELECT SQL, 세미콜론, DDL/DML 키워드, catalog 접근을 거부 |
ToolRegistry | 워커를 대신해 실행되는 confirmed write tool의 런타임 registry |
TenantService.listActiveTenants() | ScheduledTaskExecutor가 기한이 된 task를 실행할 때 쓰는 cross-테넌트 source |
집계와 테이블
| 집계 | 참고 |
|---|---|
Conversation | 소유 사용자와 active flag를 가진 conversation shell |
ChatMessage | 저장된 assistant, user, tool-call, pending-action message record |
ScheduledTask | 테넌트 범위 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 인증에 성공한 워커 요청이라도, 요청한 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입니다. 워커가 후속 pending action을 반환하면
ChatbotClient.toReply(...)는 그 워커 객체를 중첩 객체가 아니라 JSON
문자열로 직렬화합니다.
내부 도구 실행 페이로드
워커 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는 워커가 기본 JDK HTTP/2 협상 경로와 호환되지 않기 때문에 backend-to-워커 HTTP를 HTTP/1.1로 고정합니다.chat/confirm은 선택적Idempotency-Key를 지원하지만, 일반chat과chat/stream은 idempotency layer를 사용하지 않습니다.SqlValidator는 어떤 query도 데이터베이스에 도달하기 전에 write SQL, 다중 statement,pg_*,information_schema접근을 차단합니다.InternalChatbotController는 모든 워커 콜백마다 tenant와 user context를 다시 세팅하여, RLS와 사용자 인지형 tool 로직이 monolith 내부에서 실행되도록 합니다.ScheduledTaskExecutor는 활성 tenant를 순회하며, 각 due task를 테넌트 컨텍스트 안에서 실행하고 실패한 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만 허용하고, 워커 tool schema가phoneNumber,toolName같은 camelCase key를 계속 사용합니다.