Skip to content

v1.2.0#69

Merged
rettooo merged 12 commits into
mainfrom
hotfix/HSC-422
Mar 31, 2026
Merged

v1.2.0#69
rettooo merged 12 commits into
mainfrom
hotfix/HSC-422

Conversation

@rettooo
Copy link
Copy Markdown
Contributor

@rettooo rettooo commented Mar 31, 2026

📝작업 내용

  1. Retrieval (DB)
    MAIN_PRODUCT_TYPES별 벡터 검색을 순차 5회 쿼리 → 단일 윈도우 SQL (ROW_NUMBER() OVER (PARTITION BY product_type …))로 통합해 왕복 횟수를 줄였습니다.
    구현 위치: app/services/recommendation/constants.py (SEARCH_SIMILAR_MAIN_TYPES_WINDOW_SQL), app/services/recommendation/retrieval.py.

  2. Kafka
    app/infra/kafka/recommendation_producer.py: 앱 lifespan에서 추천용 AIOKafkaProducer 시작/종료 (bootstrap 미설정·시작 실패 시 스킵/로그).
    app/realtime/main.py: lifespan에 producer start/stop 연동.
    kafka_publish: 공유 producer가 있으면 send_and_wait만 호출; 없으면 기존처럼 일회성 producer로 폴백 (스크립트·로컬 호환).
    페이로드에 traceId 옵션 (publish_recommendation_to_kafka(..., trace_id=...)), 기본 빈 문자열.

  3. OpenAI
    app/infra/openai/app_client.py: OPENAI_API_KEY가 있을 때 공유 AsyncOpenAI 1개 생성, shutdown 시 close.
    app/realtime/main.py: application.state.openai_client에도 참조 저장.
    get_recommendation: 공유 클라이언트 우선, 없으면 기존처럼 요청 단위 생성.

  4. 구조 리팩터
    app/services/recommendation/ 패키지로 분리:
    constants, utils, weights, context_loader, retrieval, llm_recommendation, kafka_publish, service, init.py
    app/services/recommendation_service.py: 위 패키지를 re-export하는 얇은 래퍼 (외부 import 유지).

  5. 기타
    chore: 로깅 traceId 추가 커밋 포함 (추적용 로깅 보강).
    retrieval_query_builder.py: 소규모 포맷 정리만 포함 (이 PR diff 기준).


👀변경 사항


🎫 Jira Ticket

  • Jira Ticket: HCR-422

#️⃣관련 이슈

@rettooo rettooo added 🏷️ release 릴리즈 준비/버전 태깅/릴리즈 노트/릴리즈 브랜치 작업 🗂️ area: BE 백엔드 영역 🚑 hotfix 프로덕션 긴급 수정(우회/긴급 패치 포함) ☁️ area: INFRA 인프라/운영/배포 영역 release:minor 버전 minor bump: X.Y.0 🚀 deploy 배포/런칭 작업(배포 스크립트, 환경 배포, 롤백 포함) 🔥 priority: P0 즉시 처리 필요(서비스/데모 블로커) deploy:intelligence-server labels Mar 31, 2026
@rettooo rettooo changed the title Hotfix/hsc 422 v1.2.0 Mar 31, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the recommendation service into a modular package and introduces centralized lifecycle management for Kafka and OpenAI clients. Key changes include the reorganization of recommendation logic into specialized modules and the integration of client initialization into the application's lifespan. Review feedback points out a potential resource leak regarding unclosed OpenAI clients and suggests improving code consistency and safety by using local variables and safe attribute access for model configurations.

Comment on lines +125 to +144
client = get_openai_client()
if client is None:
client = AsyncOpenAI(api_key=api_key)
service = RecommendationService(settings=settings, client=client)
try:
return await service.recommend_for_member(member_id)
except Exception as e:
logger.exception(
"get_recommendation: OpenAI 또는 추천 파이프라인 예외 member_id=%s error_type=%s error=%s",
member_id,
type(e).__name__,
e,
)
return RecommendationResponse(
segment=Segment.normal,
cached_llm_recommendation="[일시 오류] 추천을 생성하지 못했습니다. 잠시 후 다시 시도해 주세요.(openai)",
recommended_products=[],
source="LIVE",
updated_at=utc_now_iso(),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

get_openai_client()None을 반환할 때 생성되는 AsyncOpenAI 인스턴스가 명시적으로 닫히지 않아 리소스 누수가 발생할 수 있습니다. owned 플래그와 finally 블록을 사용하여 생성된 클라이언트를 안전하게 닫아주어야 합니다.

    client = get_openai_client()
    owned = False
    if client is None:
        client = AsyncOpenAI(api_key=api_key)
        owned = True
    try:
        service = RecommendationService(settings=settings, client=client)
        return await service.recommend_for_member(member_id)
    except Exception as e:
        logger.exception(
            "get_recommendation: OpenAI 또는 추천 파이프라인 예외 member_id=%s error_type=%s error=%s",
            member_id,
            type(e).__name__,
            e,
        )
        return RecommendationResponse(
            segment=Segment.normal,
            cached_llm_recommendation="[일시 오류] 추천을 생성하지 못했습니다. 잠시 후 다시 시도해 주세요.(openai)",
            recommended_products=[],
            source="LIVE",
            updated_at=utc_now_iso(),
        )
    finally:
        if owned:
            await client.close()


try:
emb_resp = await client.embeddings.create(
model=settings.openai_embedding_model,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

135번 라인에서 getattr을 통해 안전하게 가져온 emb_model 변수가 있음에도 불구하고, 여기서는 settings.openai_embedding_model에 직접 접근하고 있습니다. 일관성을 유지하고 settings 객체의 타입 불확실성에 따른 잠재적인 AttributeError를 방지하기 위해 emb_model 변수를 사용하는 것이 좋습니다.

Suggested change
model=settings.openai_embedding_model,
model=emb_model,

)
try:
resp = await client.chat.completions.create(
model=settings.openai_chat_model,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

325번 라인에서 정의한 chat_model 변수를 사용하는 대신 settings.openai_chat_model에 직접 접근하고 있습니다. 코드의 일관성과 안정성을 위해 chat_model 변수를 사용하도록 수정하는 것이 좋습니다.

Suggested change
model=settings.openai_chat_model,
model=chat_model,

(DEFAULT_RETRIEVAL_QUERY or "")[:60] + ("..." if len(DEFAULT_RETRIEVAL_QUERY or "") > 60 else ""),
)
emb_resp = await client.embeddings.create(
model=settings.openai_embedding_model,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

464번 라인에서 정의한 emb_model 변수를 사용하는 대신 settings.openai_embedding_model에 직접 접근하고 있습니다. 일관성을 위해 emb_model을 사용하십시오.

Suggested change
model=settings.openai_embedding_model,
model=emb_model,

logger.info("recommendation: 폴백 LLM reason 생성 요청 상품=%d", len(summaries))
reasons = await generate_recommendation_reasons(
client,
settings.openai_chat_model,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

settings.openai_chat_model에 직접 접근하는 대신 getattr을 사용하여 안전하게 속성을 가져오는 것이 좋습니다. RecommendationService에서 settingsobject 타입으로 다루고 있으므로 방어적인 코딩이 필요합니다.

Suggested change
settings.openai_chat_model,
getattr(settings, "openai_chat_model", ""),

@rettooo rettooo merged commit 5e3c24d into main Mar 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🗂️ area: BE 백엔드 영역 ☁️ area: INFRA 인프라/운영/배포 영역 deploy:intelligence-server 🚀 deploy 배포/런칭 작업(배포 스크립트, 환경 배포, 롤백 포함) 🚑 hotfix 프로덕션 긴급 수정(우회/긴급 패치 포함) 🔥 priority: P0 즉시 처리 필요(서비스/데모 블로커) release:minor 버전 minor bump: X.Y.0 🏷️ release 릴리즈 준비/버전 태깅/릴리즈 노트/릴리즈 브랜치 작업

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant