Skip to content

Repository files navigation

CareerOS API

FastAPI FastAPI Cloud Python PostgreSQL OpenAI Pyrefly Google OAuth codecov Sentry

한국 채용 플랫폼(사람인, 원티드)의 채용 공고를 수집·추출하고, 구직 활동 그룹별로 저장·관리하는 FastAPI 백엔드 서비스입니다.

프로덕션

프로젝트 정보

참여자

역할 링크
프로덕트 매니저 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=20

실행

uv run fastapi dev

API 문서: 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-agentsRunner, 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 테스트와 커버리지 측정을 표준 플러그인으로 붙일 수 있어 별도 테스트 프레임워크가 불필요

AI 기능

AI 구직 어시스턴트 (ChatKit)

저장된 공고 데이터를 문맥으로 활용하는 한국어 구직 활동 도우미입니다.

  • 실시간 스트리밍 — HTTP SSE(text/event-stream)로 응답을 토큰 단위로 전송; 지연 없이 대화 흐름 유지
  • 대화 이력 영속화 — 스레드·메시지를 PostgreSQL JSONB로 저장; 세션을 넘어 맥락 유지
  • 저장 공고 검색 도구search_saved_job_postings / get_saved_job_posting_detail 두 함수 도구를 에이전트에 연결해 사용자가 저장한 공고에 대해 직접 질의 가능
  • 테넌트 격리 — 도구가 받는 user_id는 인증된 세션에서만 유입; 모델이 다른 사용자 데이터에 접근할 수 없는 구조
  • 사용량 제한 — 분당 30회·일별 500회; Redis 슬라이딩 윈도우(Lua 원자 스크립트)

AI 지원 전략가 (Application Strategist)

저장된 공고 전체와 커리어 프로필을 입력으로 받아 두 단계 분석을 수행하는 목표 지향 에이전트입니다.

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_typeresume_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 마스킹 규칙도 함께 갱신합니다.


API 엔드포인트

모든 엔드포인트는 /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                    # 타입 검사

새 플랫폼 추가

  1. career_os_api/service/job_posting/platform.pyPlatform enum에 값 추가, PLATFORM_REGISTRYPlatformAdapter(domain, base_url, extract_id) 항목 추가 (PLATFORM_BASE_URLS는 자동 계산됨)
  2. career_os_api/service/job_posting/<platform>.py — 플랫폼 전용 fetch 함수 구현
  3. career_os_api/service/job_posting/fetch.py — 디스패치 분기 추가
  4. career_os_api/database/ddl.pyCHECK (platform IN (...)) 제약 확장

About

사람인 그리고 원티드 채용 공고를 AI로 추출, 저장 및 관리하는 커리어 관리 서비스

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages