Skip to content
GitLab
Projects Groups Topics Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in
  • I intern-homework-was
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributor statistics
    • Graph
    • Compare revisions
    • Locked files
  • Issues 2
    • Issues 2
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 1
    • Merge requests 1
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Terraform modules
  • Monitor
    • Monitor
    • Metrics
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Code review
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • mingyeong.cha
  • intern-homework-was
  • Issues
  • #9
Closed
Open
Issue created Sep 23, 2026 by mingyeong.cha@mingyeong.chaOwner

[feat] LLM 채팅 스트리밍 API 및 호출 기록

name: Feature Request
labels: 'Type : Feature'

기능 요약

LLM 채팅 스트리밍 API(SSE)와 LLM 호출 기록을 추가한다.

상세 설명

채팅 API

  • 메시지 전송 POST /api/chat → SSE 스트리밍
    • 요청: {conversationId, message}
    • 프론트는 이 엔드포인트 하나만 호출하고, 단계(stage)는 보내지 않는다 — 라우팅은 서버가 정한다
    • 대화를 소유하지 않으면 스트림을 열기 전에 404
  • 이벤트 4종을 순서대로 흘린다 (route → token* → done, 오류 시 error)
    이벤트 내용 횟수
    route {stage, confidence, fallback} 1회. 분류기가 아직 없어 항상 fallback
    token {text} 토큰마다
    done {messageId, seq, usage} 정상 종료 시 1회
    error {traceId, code, detail} 오류 시
  • 오류도 HTTP 200 안에서 error 이벤트로 내려간다. 스트림이 열린 뒤에는 상태 코드를 바꿀 수 없다
  • EventSource가 아니라 fetch() + ReadableStream을 전제로 한다 — POST여야 하고, EventSource의 자동 재연결이 생성을 다시 트리거하면 안 되기 때문

스레드·트랜잭션 경계

  • 소유 검증과 사용자 메시지 저장은 스트림을 열기 전에 요청 스레드에서 끝낸다 (스트림이 열린 뒤 실패하면 오류가 SSE 안에 묻힌다)
  • JPA는 블로킹이므로 DB 작업은 Schedulers.boundedElastic()으로 분리한다
  • SecurityContext는 스레드를 따라가지 않으므로 주체를 값으로 넘긴다 (AclContextOverride.callAs). 빠뜨리면 RLS의 msg_insert 정책이 전부 막는다
  • 어시스턴트 응답은 스트림이 끝난 뒤 done 단계에서 메시지로 저장한다

LLM 호출 기록 (observability)

  • 모든 LLM 호출을 llm_call_logs에 1행 남긴다 — 단순 로그가 아니라 라우팅 정확도 평가용 데이터셋
  • 컬럼: traceId, user, purpose, model, almStage, tokensIn/Out, latencyMs, status
  • CallPurpose: CLASSIFY_L1 / GENERATE / EMBED
  • CallStatus: OK / ERROR / TIMEOUT
  • REQUIRES_NEW 트랜잭션이다 — 호출이 실패해 바깥이 롤백돼도 실패 기록은 남아야 한다
  • 모델명은 설정값이 아니라 응답 메타데이터의 실제 모델명을 쓴다 (없을 때만 설정값)
  • 비용(costUsd)은 단가 테이블이 없어 비워둔다

설정 (application.yml)

  • slexn.chat.memory-messages — 프롬프트에 실을 최근 메시지 수 (기본 20)
  • slexn.chat.timeout — LLM 응답 지연 허용 (기본 60s, 초과 시 error 이벤트)
  • slexn.chat.model — 로그에 남길 모델명 대체값
  • slexn.chat.system-prompt — ALM 맥락 지시

참고 자료

  • @ManyToOne(fetch = FetchType.LAZY)이 뭘까?
  • Server-sent events
  • SSE란 무엇이며, WebScoket과는 어떤 차이가 있는가?
  • SSE vs 스트리밍 차이점 알아보니..
  • Chat Model API
  • [JPA] @Embedded, @Embeddable란 무엇이며 언제 사용할까?
  • [Hibernate] Hibernate6.2 부터 변경된 @Enumerated(EnumType.STRING) 매핑방식
  • application.yml은 어떻게 관리해야할까?
Edited Sep 23, 2026 by mingyeong.cha
Assignee
Assign to
Time tracking