한국 채용 플랫폼(사람인, 원티드)의 채용 공고를 수집·추출하고, 구직 활동 그룹별로 저장·관리하는 FastAPI 백엔드 서비스입니다.
프로덕션
- SwaggerUI: https://career-os.fastapicloud.dev/v1/docs
- ReDoc: https://career-os.fastapicloud.dev/v1/redoc
| 역할 | 링크 |
|---|---|
| 프로덕트 매니저 | SoEun99 |
| 소프트웨어 엔지니어 | dongju93 |
| 항목 | 링크 |
|---|---|
| 이슈 트래킹 | Linear - CAR |
| 문서 | Confluence - CareerOS |
| 항목 | 링크 |
|---|---|
| 데이터베이스 관리 | Neon - career-os |
| 백엔드 배포 관리 | FastAPI Cloud - career-os |
| 에러 모니터링 | Sentry - career-os-backend |
- Python
≥ 3.14+uv - PostgreSQL (로컬 또는 Neon 등 외부)
- Google OAuth 클라이언트 ID/Secret
- OpenAI API 키
uv sync루트에 .env 파일을 생성합니다.
DATABASE_URL=postgresql://user:pass@localhost:5432/career_os
OPENAI_API_KEY=sk-...
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
SECRET_KEY=<충분히 긴 임의 문자열>
# 로컬 개발 시 — DEV=true 하나로 redirect_uri와 frontend_url을 함께 오버라이드
DEV=true
# 또는 개별 설정
# REDIRECT_URI=http://localhost:8000/v1/auth/google/callback
# 선택 — 미설정 시 사용량 제한 비활성화(fail-open)
REDIS_URL=redis://localhost:6379/0
# 선택 — ChatKit AI 어시스턴트 (기본값으로 동작)
CHATKIT_ENABLED=true # false 시 /v1/chatkit 404 반환
CHATKIT_MODEL= # 미설정 시 OPENAI_MODEL 사용
CHATKIT_MAX_THREADS_PER_USER=50
CHATKIT_HISTORY_ITEM_LIMIT=20
# 선택 — Application Strategist 에이전트 (기본값 비활성화)
STRATEGIST_AGENT_ENABLED=false # true 시 /v1/agent/plan, /v1/agent/artifact 활성화
STRATEGIST_MODEL= # 미설정 시 OPENAI_MODEL 사용
STRATEGIST_PLAN_POSTING_LIMIT=20uv run fastapi devAPI 문서: http://localhost:8000/v1/docs
| 라이브러리 | 용도 | 선정 이유 |
|---|---|---|
| FastAPI | ASGI 웹 프레임워크 | 타입 힌트 기반 검증, async 지원, OpenAPI 자동 문서화가 기본 제공되어 API 서버 개발 생산성과 계약 명확성이 높음. Flask보다 보일러플레이트가 적고 Django보다 경량 |
| psycopg 3 + psycopg-pool | PostgreSQL 드라이버 | PostgreSQL에 직접 접근하면서 async I/O와 커넥션 풀을 함께 제공. 단순한 데이터 접근 계층에서는 ORM보다 의존성과 추상화 비용이 낮고 SQL 제어권을 유지하기 쉬움 |
| Pydantic v2 + pydantic-settings | 스키마 검증·설정 관리 | 런타임 검증, 직렬화, JSON Schema 생성을 타입 힌트 중심으로 통합. 설정값도 같은 검증 모델로 관리할 수 있어 환경 변수 누락이나 타입 오류를 초기에 발견하기 좋음 |
| Authlib | Google OAuth 클라이언트 | OAuth 2.0·OpenID Connect처럼 보안 민감도가 높은 표준 플로우를 검증된 라이브러리에 맡길 수 있음. 직접 구현 대비 인증 취약점과 유지보수 부담을 줄임 |
| python-jose | JWT 발급·검증 | JWT/JWS 처리를 위한 가벼운 JOSE 구현체. 내부 access token 서명·검증 요구사항에는 충분하면서, OAuth 클라이언트 라이브러리와 토큰 책임을 분리하기 쉬움 |
| Redis (redis-py + hiredis) | 사용량 제한 저장소 | 슬라이딩 윈도우(Lua 스크립트, 원자적)와 고정 윈도우 두 전략을 조합. REDIS_URL 미설정 시 fail-open으로 동작해 Redis 없이도 서버가 기동됨 |
| Beautiful Soup 4 | HTML 파싱 | 실제 웹 페이지처럼 구조가 일정하지 않은 HTML에서도 탐색·검색 API가 안정적이고 단순함. 브라우저 자동화나 무거운 파서 없이 서버 사이드 텍스트 추출 요구를 충족 |
| OpenAI Python SDK | 구조화 데이터 추출 | OpenAI API의 공식 SDK라 모델·파라미터 변화에 대한 호환성이 가장 높음. async client, 멀티모달 입력, 구조화 출력 지원을 직접 HTTP 래퍼로 관리하지 않아도 됨 |
| openai-chatkit + openai-agents | AI 어시스턴트 대화 | openai-agents의 Runner, Agent, 스트리밍을 openai-chatkit의 스레드·이력 관리 프로토콜로 감싼 구조. HTTP SSE 스트리밍과 PostgreSQL 대화 이력 저장을 최소 코드로 연결 |
| Ruff | 린터·포매터 | 린트, 포맷, import 정리, pyupgrade 계열 규칙을 단일 Rust 기반 도구로 처리해 빠르고 설정이 단순함. 여러 Python 품질 도구를 조합하는 비용을 줄임 |
| Pyrefly | 타입 검사 | 빠른 정적 타입 검사와 언어 서버 기능을 제공해 피드백 루프가 짧음. Python 타입 적용 범위를 점진적으로 넓히기 좋고, CI와 IDE 양쪽에서 같은 타입 품질 기준을 유지하기 쉬움 |
| pytest + pytest-asyncio + pytest-cov | 테스트 | pytest의 fixture·plugin 생태계가 넓어 API, 서비스, DB 경계 테스트를 확장하기 좋음. async 테스트와 커버리지 측정을 표준 플러그인으로 붙일 수 있어 별도 테스트 프레임워크가 불필요 |
저장된 공고 데이터를 문맥으로 활용하는 한국어 구직 활동 도우미입니다.
- 실시간 스트리밍 — HTTP SSE(
text/event-stream)로 응답을 토큰 단위로 전송; 지연 없이 대화 흐름 유지 - 대화 이력 영속화 — 스레드·메시지를 PostgreSQL JSONB로 저장; 세션을 넘어 맥락 유지
- 저장 공고 검색 도구 —
search_saved_job_postings/get_saved_job_posting_detail두 함수 도구를 에이전트에 연결해 사용자가 저장한 공고에 대해 직접 질의 가능 - 테넌트 격리 — 도구가 받는
user_id는 인증된 세션에서만 유입; 모델이 다른 사용자 데이터에 접근할 수 없는 구조 - 사용량 제한 — 분당 30회·일별 500회; Redis 슬라이딩 윈도우(Lua 원자 스크립트)
저장된 공고 전체와 커리어 프로필을 입력으로 받아 두 단계 분석을 수행하는 목표 지향 에이전트입니다.
Phase 1 — 전략 플랜 (POST /agent/plan)
get_career_profile/list_postings_with_status두 읽기 전용 도구로 사용자 데이터를 조회한 뒤output_type=ApplicationPlan으로 우선순위가 매겨진 지원 전략을 구조화 출력- 모델 출력은 Pydantic 검증 + 소유권 재검증(
job_id·target_group_id)을 거친 뒤에만 응답에 포함 - 최대 8 turns; 분당 5회·일별 30회 제한
Phase 2 — 공고별 맞춤 지원 자료 (POST /agent/artifact)
- 단일 공고 + 커리어 프로필을 서버가 직접 조합해 입력 구성 (모델이 데이터를 요청·수집하지 않음)
artifact_type∈resume_bullets/cover_letter/interview_prep세 유형 선택- 응답의
job_id·artifact_type은 요청값으로 고정(모델 에코 신뢰 안 함);content_markdown≤ 12 000자 하드캡 - 최대 2 turns; 분당 5회·일별 20회 제한
두 엔드포인트 모두
STRATEGIST_AGENT_ENABLED=true설정 시 활성화됩니다(기본값false).
- Google OAuth 로그인 — 브라우저용 세션 쿠키 인증, 서버 발급 Bearer 토큰 fallback 지원
- Google Cross-Account Protection — RISC Security Event Token 검증·기록, 세션/토큰 폐기 이벤트 반영
- 구직 활동 그룹 관리 — 구직 라운드 생성·조회·수정·종료·삭제, 진행/종료 상태 필터링, 최초 로그인 시 기본 그룹 자동 생성
- 채용 공고 추출 — 사람인·원티드 URL을 입력하면 OpenAI로 구조화된 데이터 반환
- 공고 저장·관리 — 공고를
group_id에 연결해 저장, 그룹별 목록 필터링, 동일 공고의 다른 그룹별 별도 저장 지원 - 공고 상태·메모 관리 — 지원 상태(
saved/applied/interviewing/offer/rejected/withdrawn) 변경, 다른 그룹으로 이동, 메모 작성을 부분 수정으로 지원 - 커리어 프로필 — 직무·경력·기술·희망 근무지 등 7개 필드를 전체 교체(upsert) 방식으로 관리
- 사용량 제한 — Redis 슬라이딩 윈도우(분당)와 고정 윈도우(일·월별) 조합; Redis 미설정 시 fail-open
- 지원 플랫폼 —
saramin.co.kr,wanted.co.kr
OAuth 로그인으로 저장되는 사용자 계정 정보와 Google Cross-Account Protection(RISC) 보안 이벤트 정보는 Neon 데이터 마스킹 기능을 적용해 관리합니다.
- Google 계정 식별자, 이메일, 표시 이름처럼 사용자를 직접 식별할 수 있는 정보
- RISC 보안 이벤트에 포함되는 이벤트 식별자, 대상 계정 식별자, 원본 이벤트 페이로드
운영 환경에서는 위 정보가 관리자 화면이나 데이터베이스 조회 과정에서 원문 그대로 노출되지 않도록 익명화·대체된 값으로 표시합니다. 인증 또는 보안 이벤트에 새로운 개인정보가 추가되면, 프로덕션 반영 전 Neon 마스킹 규칙도 함께 갱신합니다.
모든 엔드포인트는 /v1 접두사를 사용합니다. 보호된 엔드포인트는 브라우저 세션 쿠키와 X-Career-OS-Client: web 헤더 또는 Authorization: Bearer <token> 헤더가 필요합니다.
| 메서드 | 경로 | 인증 | 설명 |
|---|---|---|---|
GET |
/ |
헬스체크 | |
GET |
/health/db |
DB 연결 확인 | |
GET |
/auth/google |
Google 로그인 시작 (?callback_url= 지원) |
|
GET |
/auth/google/callback |
OAuth 콜백, 세션 발급 + 1회용 login_code 리다이렉트 |
|
POST |
/auth/token |
login_code를 Bearer 토큰으로 교환 (세션 쿠키가 차단된 브라우저용 fallback) |
|
POST |
/auth/google/risc |
Google RISC 보안 이벤트 수신 | |
GET |
/auth/me |
✓ | 현재 사용자 조회 |
PATCH |
/auth/me |
✓ | 사용자 이름 수정 |
POST |
/auth/logout |
✓ | 로그아웃 |
POST |
/chatkit |
✓ | AI 어시스턴트 채팅; 스트리밍 SSE 또는 JSON; 분당 30회·일별 500회 제한; CHATKIT_ENABLED=false 시 404 |
GET |
/job-postings |
✓ | 저장된 공고 목록 (offset, limit, 선택 group_id) |
GET |
/job-postings/extraction?url= |
✓ | URL에서 공고 추출 (저장 안 함) |
POST |
/job-postings |
✓ | 추출된 공고를 그룹에 저장 (선택 group_id, 201 신규 / 200 갱신) |
GET |
/job-postings/{id} |
✓ | 저장된 공고 상세 조회 |
PATCH |
/job-postings/{id} |
✓ | 공고 부분 수정 (지원 상태·그룹 이동·메모); 빈 본문 422, 대상 그룹 없음 404, 대상 그룹에 중복 시 409 |
GET |
/job-search-groups |
✓ | 구직 활동 그룹 목록 (status, offset, limit) |
POST |
/job-search-groups |
✓ | 구직 활동 그룹 생성 |
GET |
/job-search-groups/{id} |
✓ | 구직 활동 그룹 상세 조회 |
PATCH |
/job-search-groups/{id} |
✓ | 구직 활동 그룹 이름·기간·메모 수정 |
DELETE |
/job-search-groups/{id} |
✓ | 구직 활동 그룹 삭제 (마지막 그룹 삭제 불가) |
GET |
/profile |
✓ | 커리어 프로필 조회; 없으면 404 |
PUT |
/profile |
✓ | 커리어 프로필 전체 교체 (upsert); 최초 생성 시 201, 교체 시 200 |
POST |
/agent/plan |
✓ | 지원 전략 플랜 생성; 분당 5회·일별 30회 제한; STRATEGIST_AGENT_ENABLED=false 시 404 |
POST |
/agent/artifact |
✓ | 공고별 맞춤 지원 자료 생성 (resume_bullets/cover_letter/interview_prep); 분당 5회·일별 20회 제한; STRATEGIST_AGENT_ENABLED=false 시 404 |
uv run pytest # 전체 테스트
uv run pytest --cov=career_os_api # 커버리지 포함
uvx ruff check --fix . # 린트
uvx ruff format . # 포매팅
uvx pyrefly check # 타입 검사career_os_api/service/job_posting/platform.py—Platformenum에 값 추가,PLATFORM_REGISTRY에PlatformAdapter(domain, base_url, extract_id)항목 추가 (PLATFORM_BASE_URLS는 자동 계산됨)career_os_api/service/job_posting/<platform>.py— 플랫폼 전용 fetch 함수 구현career_os_api/service/job_posting/fetch.py— 디스패치 분기 추가career_os_api/database/ddl.py—CHECK (platform IN (...))제약 확장