Soonlife DOCS
API 레퍼런스

AI 챗봇 API

순라이프 AI 공지 챗봇 — 메시지 전송(NDJSON 스트리밍), 중단, 대화 기록, 답변 평가

개요

순라이프 AI는 학교 공지·학사일정 등을 근거로 답변하는 챗봇입니다. 자체 호스팅된 vLLM 서버(OpenAI 호환 /chat/completions API, stream=True)를 사용하며, 공지 검색·시맨틱 검색·학사일정 조회 등의 **도구 호출(tool calling)**을 통해 최신 정보를 찾아 답변합니다.

챗봇도 다른 포털 API와 동일하게 로그인이 필요합니다(sch_session 쿠키). 별도 공개 엔드포인트가 아닙니다.

엔드포인트 목록

메서드경로설명
POST/api/chat/메시지 전송 — NDJSON 스트리밍 응답
POST/api/chat/stop/진행 중인 답변 생성 중단
POST/api/chat/clear/대화 컨텍스트 초기화 (새 대화)
GET/api/chat/health/DB·LLM·시맨틱 검색 서버 상태 확인
GET/api/chat/history/저장된 대화 목록 조회
POST/api/chat/history/로컬(브라우저) 대화 기록을 서버로 이전
DELETE/api/chat/history/저장된 대화 기록 전체 삭제
GET/api/chat/history/{id}/특정 대화 상세 조회
DELETE/api/chat/history/{id}/특정 대화 삭제
POST/api/chat/feedback/답변 평가(좋아요/싫어요) 제출

모든 엔드포인트는 sch_session 쿠키 인증이 필요합니다.


대화 기록 저장 방식 — 로컬 저장 vs 클라우드 동기화

기본값은 로컬 저장입니다. 사용자가 설정에서 명시적으로 동의(클라우드 동기화 켬)하지 않으면 학번과 대화 내용은 서버 DB에 남지 않습니다.

모드저장 위치서버에 보내는 값
로컬 저장 (기본값)브라우저 localStorage (sch-chat-history 키, 최대 30개 대화)save_history: false + 최근 대화 맥락(local_turns)을 매 요청마다 함께 전송
클라우드 동기화 (동의 시)서버 DB (ChatConversation, 학번 기준)save_history: true + conversation_id

클라우드 동기화를 켜면 로컬 대화가 POST /api/chat/history/로 서버에 이전되고, 끄면 서버 대화가 다시 로컬로 내려온 뒤 DELETE /api/chat/history/로 서버 기록이 삭제됩니다.

저장 설정과 무관하게, 익명화된 문답 로그(ChatLog)는 답변 품질 검토를 위해 항상 남습니다. 학번 등 식별 정보는 포함하지 않으며, 같은 대화의 턴을 묶기 위한 키는 비밀키 HMAC으로 파생되어 원래 사용자로 역추적할 수 없습니다.


POST /api/chat/

메시지 1건을 전송하고 답변을 NDJSON(줄바꿈으로 구분된 JSON) 스트림으로 받습니다.

요청 본문

클라우드 동기화 모드 (save_history: true)

필드타입필수설명
messagestringO사용자 메시지 (최대 4000자)
high_qualityboolean아니오고성능 모드 여부 (아래 참조)
save_historybooleanOtrue
conversation_idnumber | null아니오이어서 대화할 서버 저장 대화 id. null/생략 시 새 대화
{
  "message": "이번 학기 기숙사 규정 뭐가 바뀌었어?",
  "high_quality": false,
  "save_history": true,
  "conversation_id": null
}

로컬 저장 모드 (save_history: false, 기본값)

필드타입필수설명
messagestringO사용자 메시지 (최대 4000자)
high_qualityboolean아니오고성능 모드 여부
save_historybooleanOfalse
local_keystring아니오브라우저에서 생성한 대화 식별 키 (예: "loc-<uuid>")
local_turnsobject[]아니오최근 대화 맥락 — [{"question": "...", "answer": "..."}] (최근 대화만, 항목당 텍스트 길이 제한 있음)
{
  "message": "이번 학기 기숙사 규정 뭐가 바뀌었어?",
  "high_quality": false,
  "save_history": false,
  "local_key": "loc-8f3a2b1c",
  "local_turns": [
    { "question": "안녕", "answer": "안녕하세요! 순라이프 AI입니다." }
  ]
}

서버는 사용자당 답변을 한 번에 하나만 처리합니다. 이전 답변이 아직 생성 중일 때 새 요청을 보내면 곧바로 {"type":"error","message":"이전 답변이 아직 생성 중이에요."}가 반환됩니다.

응답 — NDJSON 스트림

Content-Type: application/x-ndjson. 줄 단위로 아래 이벤트 타입이 순서대로 도착합니다.

이벤트필드설명
metaconversation_id새 서버 저장 대화가 처음 생성된 시점에 1회 전송 — 이후 요청부터 이 값을 conversation_id로 사용
tracemessage진행 상황 안내 문구 (예: "공지사항을 찾는 중이에요.") — 내부 도구명은 노출되지 않음
deltatext최종 답변 텍스트 조각 (6자 단위, 초당 약 500자 — 타자기 효과)
ping15초 이상 이벤트가 없을 때 보내는 keepalive (프록시 idle timeout 방지)
donestopped(선택)턴 종료. 사용자가 중단한 경우 stopped: true
errormessage오류 발생

delta는 LLM 토큰을 실시간으로 그대로 흘리는 것이 아닙니다. 답변을 전부 생성하고 검증까지 마친 뒤 확정된 텍스트를 잘게 나눠 스트리밍하는 방식입니다.

스트림 예시

{"type":"trace","message":"질문을 살펴보는 중이에요."}
{"type":"meta","conversation_id":42}
{"type":"trace","message":"공지사항을 찾는 중이에요."}
{"type":"delta","text":"이번 학"}
{"type":"delta","text":"기부터 "}
{"type":"ping"}
{"type":"delta","text":"바뀐 규정은..."}
{"type":"done"}

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료
EMPTY_MESSAGE400message가 빈 문자열
MESSAGE_TOO_LONG400message가 4000자 초과
INVALID_CONVERSATION_ID400conversation_id가 정수로 변환 불가

스트림 도중 발생하는 오류(대화를 찾지 못함, LLM 호출 실패 등)는 200 응답 안에서 {"type":"error", ...} 이벤트로 전달됩니다.


고성능 모드 (high_quality)

첨부문서(공지 첨부 PDF/HWP/DOCX 등)를 근거로 답변할 때, 일부 페이지만 보고 답하다 보면 중간에 있는 내용을 놓칠 수 있습니다. 고성능 모드는 첨부 전체 페이지를 배치로 전사(轉寫)해 근거 누락을 줄입니다. 그만큼 응답 시간이 길어집니다.

항목일반 모드고성능 모드
첨부 페이지 처리처음 3페이지 + 마지막 페이지만전체 페이지
이미지 처리 한도최대 8장최대 32장 (8장씩 배치 전사)

"규정 변경"을 묻는 질문은 high_quality 값과 무관하게 서버가 자동으로 전체 페이지 조회 모드로 전환합니다. 또한 특정 문구(기숙사 규정 변경 질문)를 그대로 보내면 서버가 고성능 모드를 강제로 켭니다 — 프론트엔드도 동일한 문구를 감지해 "고성능 모드로 자동 전환했어요" 안내를 보여줍니다.


POST /api/chat/stop/

진행 중인 답변 생성을 즉시 중단합니다. 이미 생성된 부분까지는 스트림으로 전달되었을 수 있으나, 해당 턴은 대화 기록에 저장되지 않습니다.

요청

Body 없음.

응답

204 No Content

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료

클라이언트가 fetch 연결을 그냥 끊어도(페이지 이탈, 브라우저 탭 닫기 등) 서버가 이를 감지해 동일하게 중단 처리합니다 — 반드시 이 엔드포인트를 호출해야만 중단되는 것은 아닙니다.


POST /api/chat/clear/

서버 메모리에 유지 중인 대화 컨텍스트를 초기화합니다(새 대화 시작). 진행 중인 답변 생성이 있다면 함께 중단됩니다.

요청

Body 없음.

응답

204 No Content

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료

GET /api/chat/history/

내 대화 기록 목록을 최신순으로 반환합니다. (클라우드 동기화로 서버에 저장된 대화만 해당 — 로컬 저장 모드의 대화는 브라우저에만 있어 이 API로 조회되지 않습니다.)

쿼리 파라미터

파라미터타입필수설명
fullstring아니오"1"이면 각 대화의 turns(전체 문답 내용) 포함

응답 — 200 OK

{
  "conversations": [
    {
      "id": 42,
      "title": "이번 학기 기숙사 규정 뭐가 바뀌었어?",
      "updated_at": "2026-07-20T15:30:00+09:00"
    }
  ]
}

full=1이면 각 항목에 turns 배열([{question, answer, trace}])이 추가됩니다.

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료

POST /api/chat/history/

브라우저(로컬 저장)에 있던 대화 기록을 서버로 이전합니다. 클라우드 동기화를 켤 때 호출됩니다.

멱등(idempotent) 처리됩니다. 각 로컬 대화의 local_id를 기준으로 이미 이전된 대화는 다시 만들지 않고 기존 서버 id를 그대로 반환합니다 — 이전 도중 실패 후 재시도해도 중복 저장되지 않습니다.

요청 본문

필드타입필수설명
conversationsobject[]O이전할 로컬 대화 목록
conversations[].local_idstring아니오브라우저의 로컬 대화 id — 멱등 처리 키
conversations[].titlestring아니오대화 제목. 생략 시 첫 질문으로 자동 생성
conversations[].turnsobject[]O[{question, answer}] 형식의 문답 목록
{
  "conversations": [
    {
      "local_id": "loc-8f3a2b1c",
      "title": "기숙사 규정 문의",
      "turns": [
        { "question": "이번 학기 기숙사 규정 뭐가 바뀌었어?", "answer": "..." }
      ]
    }
  ]
}

응답 — 200 OK

{
  "mapping": {
    "loc-8f3a2b1c": 42
  }
}

mapping은 로컬 대화 id → 서버에 생성/재사용된 대화 id의 매핑입니다.

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료
INVALID_PAYLOAD400conversations가 배열이 아님

DELETE /api/chat/history/

내 대화 기록을 서버에서 전부 삭제합니다. 클라우드 동기화를 끌 때 호출됩니다.

응답

204 No Content

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료

GET /api/chat/history/{id}/

특정 대화의 전체 내용을 조회합니다.

응답 — 200 OK

{
  "id": 42,
  "title": "이번 학기 기숙사 규정 뭐가 바뀌었어?",
  "turns": [
    { "question": "...", "answer": "...", "trace": ["..."] }
  ],
  "updated_at": "2026-07-20T15:30:00+09:00"
}

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료
NOT_FOUND404해당 id의 대화가 없거나 내 대화가 아님

DELETE /api/chat/history/{id}/

특정 대화를 삭제합니다.

응답

204 No Content

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료

POST /api/chat/feedback/

답변에 대한 좋아요/싫어요 평가를 제출합니다. 추후 응답 품질 개선(RLHF 학습 데이터)에 활용됩니다.

로그인한 사용자만 제출할 수 있지만, 저장되는 데이터에는 학번 등 식별 정보가 포함되지 않습니다.

요청 본문

필드타입필수설명
ratingstringO"up" 또는 "down"
questionstringO평가 대상 질문
answerstringO평가 대상 답변
commentstring아니오사용자 의견 (최대 2000자)
contextobject[]아니오직전 문맥 — [{question, answer}] 최대 3쌍
high_qualityboolean아니오고성능 모드로 생성된 답변인지 여부
{
  "rating": "up",
  "question": "이번 학기 기숙사 규정 뭐가 바뀌었어?",
  "answer": "...",
  "comment": "표로 정리해줘서 좋았어요",
  "context": [],
  "high_quality": true
}

응답

201 Created

에러 응답

에러 코드HTTP설명
UNAUTHORIZED401세션 없음 또는 만료
INVALID_RATING400rating"up"/"down"이 아님
EMPTY_TURN400question 또는 answer가 빈 문자열

GET /api/chat/health/

챗봇 관련 인프라(DB, LLM, 시맨틱 검색 서버) 상태를 확인합니다. 로그인 여부와 무관하게 호출 가능합니다.

응답 — 200 OK

{
  "ok": true,
  "db": "connected",
  "model": "soonlife-qwen",
  "embed_search": "connected (12480건 인덱싱됨)"
}
필드타입설명
okbooleanDB 연결 정상 여부
dbstring"connected" 또는 에러 메시지
modelstring현재 사용 중인 vLLM 모델명
embed_searchstring시맨틱 검색 서버 상태 — "not_configured" / "connected (N건 인덱싱됨)" / "unhealthy" / "unreachable (...)"

기숙사 규정 변경 질문 — 고정 답변

특정 조건에 맞는 질문(기숙사 관련 단어 + "바뀌다/변경/개정" 등 변경 관련 단어가 모두 포함되고, 보조 판별 모델이 "2026-2학기 기숙사 규정 변경 문의"로 확인한 경우)은 LLM 생성 과정을 완전히 건너뛰고 사람이 직접 검증한 고정 답변을 즉시 반환합니다. 모델이 첨부 문서의 비교표를 잘못 옮겨 적는 문제를 막기 위한 안전장치입니다.

이 경로로 응답되는 경우 도구 호출, 답변 검증 단계가 모두 생략되며, trace 이벤트도 발생하지 않고 곧바로 delta/done이 이어집니다.


챗봇 설정 (참고 — 서버 환경변수)

배포 환경에 따라 아래 값들이 챗봇 동작에 영향을 줍니다. (API 사용자 입장에서 직접 제어하는 값은 아니며, 서버 운영 참고용입니다.)

환경변수기본값설명
VLLM_BASE_URLhttp://localhost:8000/v1vLLM 서버 주소 (OpenAI 호환 API)
VLLM_MODELsoonlife-qwen사용할 모델명
VLLM_CONTEXT_LEN0 (자동 감지)컨텍스트 길이
AGENT_MAX_TURNS10한 답변 내 도구 호출 루프 최대 반복 횟수
VLLM_TIMEOUT180vLLM 요청 타임아웃(초)
VLLM_TEMPERATURE0.3샘플링 온도
VLLM_MAX_TOKENS4096응답 최대 토큰
ENABLE_THINKINGtrue모델 reasoning(사고) 모드 사용 여부
EMBED_SEARCH_URL"" (미설정)시맨틱 검색 서버 주소 — 미설정 시 관련 도구는 비활성화
EMBED_SEARCH_TIMEOUT20시맨틱 검색 요청 타임아웃(초)

On this page