Conversation
vcz-Chan
added a commit
that referenced
this pull request
Nov 16, 2025
* 📝 주석 추가 (#22) * ✨ ASK 세션 도입 및 중복 질문 처리 구현 (#23) * 📝 구현 계획 업데이트 * 📝 디비 마이그레이션 추가 * 🔧 db 헬퍼 추가 * ✨ 세션 관련 레포지토리 구현 * ✨ 세션 api 구현 * ✨ ask api 수정 * ✨ 맥락 전달 구현(2턴) * ✨ 유사 질문 히트시 기존 답변 재사용 * ✨ 임베딩시 해당 유저의 모든 재사용용 관계 테이블 삭제 * 🐛 open ai 입력에 맞게 수정 * 🐛 db에 청크 단위로 파싱해 저장 * 📝 api 명세 문서 * 📝 개선 계획 * 🔨 마이그레이션 파일 생성 * ✨ 중복 질문 레포지토리로 수정 * 🚚 경로 수정 * ✨ 세션 히스토리 서비스 수정 * ✨ 중복 질문에 이전 맥락 추가 * ✨말투 대체 서비스 구현 * ⚡️디버그 유틸 고도화 * 📝 문서화
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
요약
/ai/(v1|v2)/ask에 세션 개념을 도입해 질문/답변 히스토리를 DB에 영속화하고, LLM 호출 시 최근 대화 맥락을 재사용할 수 있게 합니다./ai/v2/sessions계열 REST API와 무한 스크롤 메시지 API로 대화 목록/이력을 조회할 수 있게 합니다.Context
/ai/ask,/ai/v2/ask는 stateless 구조라 세션/대화 이력이 없고, 클라이언트가 질문마다 새 요청을 보내는 단발형 UX만 가능했습니다.user_id를 본문에서 받아 세션/소유자/요청자 개념이 섞여 있었고, JWT 기반 권한/접근제어와 캐시 무효화(블로그 소유자 기준)를 동시에 처리하기 어려운 구조였습니다.user_id(requester)와owner_user_id(챗봇/블로그 주인)를 엄격히 구분하며,Changes
DB 스키마 및 마이그레이션
ask_session테이블 추가id,requester_user_id,owner_user_id,title,metadata,last_question_at,created_at,updated_at.(requester_user_id, created_at DESC),(owner_user_id, created_at DESC),last_question_at DESC등 세션 리스트/정렬 최적화.ask_message테이블 추가id,session_id(FK),role(user|assistant),content,search_plan,retrieval_meta,created_at.(session_id, created_at DESC, id DESC)로 세션별 페이지네이션 지원.ask_message_embedding→ask_question_cache로 리네임speech_tone_id integer NOT NULL DEFAULT -1컬럼 추가.message_id(PK/FK),owner_user_id,requester_user_id,category_id,post_id,answer_message_id,embedding(vector(1536)),speech_tone_id,created_at,updated_at.owner_user_id,(owner_user_id, category_id),(owner_user_id, post_id),requester_user_id,IVFFlat(embedding vector_cosine_ops)등 + 필요 시(owner_user_id, requester_user_id, speech_tone_id).pgvector확장 설치를 전제하고, 마이그레이션/롤백 절차를docs/migrations에 문서화.세션/메시지/질문 캐시 레포지토리 및 서비스
ask-session.repository.tsask-message.repository.tscreated_at + id커서 기반 페이지네이션 쿼리 구현.ask-question-cache.repository.ts(구ask-message-embedding.repository.ts)speechToneId추가,upsertEmbedding가 tone ID를 함께 저장.findSimilarEmbeddings에서 유사도 +speechToneId를 반환해 tone-aware 캐시 선택이 가능하도록 변경.session-history.service.tspersistConversation에speechTone?: number인자를 추가하고, 세션/메시지/질문 캐시를 한 번에 영속화.findCachedAnswer가speechToneId를 포함한 후보 배열을 반환하도록 확장.ASK 엔드포인트 세션/권한 처리
/ai/v1/ask,/ai/v2/ask공통 Request Body 정리question,user_id(= owner_user_id),session_id,category_id,post_id,speech_tone,llm구조 정리.user_id를requester_user_id로 사용하고, Body의user_id를owner_user_id로 매핑.session_id가 없거나 null인 경우에만 새 세션 생성(이때owner_user_id필수).session_id가 있는 경우 DB의owner_user_id와 Body의user_id가 불일치하면 400/409로 즉시 거부.ask_session레코드 생성 + 첫 질문 텍스트 기반 기본title설정.event: session을 보내{ session_id, owner_user_id, requester_user_id }전달, 응답 헤더session-id도 세팅.event: session_saved(성공) 또는event: session_error(실패)를 보내 히스토리 영속화 상태를 클라이언트가 알 수 있게 함.히스토리 로딩 및 프롬프트 구성
answerStream/answerStreamV2에서 세션 최근 2턴(user ↔ assistant)을 로드해 LLM 메시지 배열에 prepend.buildSearchPlanPrompt등에 “이전 대화” 섹션을 옵션으로 추가해 follow-up 판단에 활용 가능하도록 확장.무한 스크롤 메시지 API
GET /ai/v2/sessions/:sessionId/messagescursor(선택, base64 'created_at|id'),direction(backward|forward, 기본 backward),limit(기본 20, 최대 50).session_id,messages[{ id, role, content, search_plan, retrieval_meta, created_at }],paging{ direction, has_more, next_cursor }.created_at/id조합으로 앞/뒤 페이지 모두 지원, API 레이어에서 시간순 정렬을 정규화.세션 REST API
GET /ai/v2/sessionsowner_user_id필터, 커서 기반 페이징(cursor,limit).GET /ai/v2/sessions/:idGET /ai/v2/sessions/:id/messagesPATCH /ai/v2/sessions/:idtitle,metadata(JSON object)수정.owner_user_id는 변경 불가.DELETE /ai/v2/sessions/:idask_message/ask_question_cache는 ON DELETE CASCADE로 함께 제거.중복 질문 판별 개선
[Q-2]\n[Q-1]\n[Q-now]를 하나의 문자열로 임베딩.캐시 응답 말투 정합성
ask_question_cache에speech_tone_id를 영속화해 캐시 후보마다 tone 정보를 보존.speechToneId === 요청 speech_tone인 항목이 있으면 이를 그대로 재생.replace-tone.service.ts)으로 재작성 후 응답.-1)는 tone 일치 후보에서 제외하고, 필요 시 재작성 대상만으로 사용.replace-tone.service.tsrewriteTone(answer, { speechToneId, speechTonePrompt, llm? }).search_plan/context이벤트를 그대로 재생하고,answer이벤트 payload만 tone 반영 후 텍스트 사용.session_saved이벤트에cached,tone_rewritten등의 플래그를 추가해 프론트에서 구분 가능하도록 확장 가능.임베딩/캐시 파이프라인
ask_question_cache)에 재사용.owner_user_id,requester_user_id,post_id/category_id가 현재 요청과 일치하는 레코드만 후보로 제한.search_plan/retrieval_meta/answer를 그대로 재생.ask_message(user) → ask_message(assistant) → ask_question_cache순서로 INSERT/UPDATE.블로그 임베딩 재계산 시 캐시 무효화
queue-consumer.ts)에서 특정owner_user_id의 포스트를 재임베딩하면, 같은 owner의ask_question_cache레코드를 일괄 삭제.Breaking Changes
ask_session/ask_message/ask_question_cache테이블 및 인덱스 추가,pgvector확장 필수.ask_message_embedding을 리네임/초기화하므로, 이전 캐시는 그대로 사용할 수 없습니다./ai/(v1|v2)/ask계약 변경user_id는 이제 “챗봇/블로그 주인(owner_user_id)”로만 사용되며, 실제 요청자 식별은 JWTuser_id기준으로 동작합니다.session_id+user_id조합이 기존과 다르게 엄격히 검증되며, owner 불일치 시 400/409가 발생합니다.session,session_saved,session_error.speech_tone_id를 기준으로 tone까지 포함해 정합성을 보장하므로, 과거 캐시와 응답 패턴이 일부 달라질 수 있습니다.