챗봇 v2 마이그레이션 — 어디살지 최적 계획

tenant 랜딩 챗봇 = AIAssistantBottomSheet 를 POC chat-v2 안정화 사이클 결과로 완전 대체. 중복 코드 0 원칙.
TL;DR — 결정 3줄
  1. backend/.../properties_agent/ 16,201 LOC + 본 어시스턴트 use_case 6,800 LOC = 23K LOC 폐기.
  2. POC poc-chat-search/src/{ports,application,adapters}/본 backend 의 domains/ai/ 로 흡수poc-chat-search/ 디렉토리 자체 삭제 (별도 docker-compose·api·tests 까지 전부).
  3. 프론트는 /v1/assistant/message 엔드포인트 유지 → 본 AIAssistantBottomSheet 그대로 사용, POC 전용 /chat-v2·/poc-v2 라우트 + PoCV2AIAssistantBottomSheet 삭제.

A. 현재 상태 매핑

대체 대상 — 본 backend 챗봇

분류경로LOC비고
SSE 진입backend/src/app/api/routers/assistant.py734POST /v1/assistant/message유지 (어댑터 교체)
Portcore/ports/search_assistant_port.py:97~100search_stream 시그니처 유지 (이벤트 매핑)
스레드 영속 APIapi/routers/ai_chat.py + domains/ai/use_cases/*~6,800스레드 CRUD·HMAC 검증 유지, 안쪽 비즈니스 로직만 폐기
폐기 대상adapters/properties_agent/**16,201assistant_adapter / pseudo_tool_agent / agent / slot_rules / persona_yeolmu / gates / response_diversity
기존 Qdrantadapters/qdrant/bootstrap.py~80collection properties_v1 1536d → properties_v2_3072 신규 (1536 collection 은 즉시 삭제)

대체 대상 — 본 프론트 tenant 랜딩

분류경로비고
tenant 랜딩 진입router.tsx:198SubdomainHomepages/tenant/Home.tsx:42AIAssistantBottomSheet 임베드 (모바일·로그인)
데스크톱 랜딩pages/tenant/landing/TenantLandingPhoneFrame.tsx:50동일 AIAssistantBottomSheet 임베드
핵심 컴포넌트components/chat/AIAssistantBottomSheet.tsx115 KB — 유지, SSE 이벤트 분기는 어댑터에서 호환
hooks/api/useAIChatSender.ts 등 4종유지
삭제 대상pages/poc-v2/**, PoCV2AIAssistantBottomSheet, usePoCChatV2, /chat-v2 라우트POC 직결 mirror — 흡수 후 잔재 0

이식 소스 — POC chat-v2 (안정화 사이클 결과, LLM Judge 4.59/5)

레이어경로LOC
Compositionpoc-chat-search/src/composition.py~200
Ports (14종)poc-chat-search/src/ports/*~600
Applicationapplication/{agent,chat_service,search_service,listing_registrar,tools}.py1,431
Adaptersadapters/{llm/groq, embedding/openai, vector/qdrant, reranker/cohere, bm25, pg/*}/**5,948
Eval (재사용)tests/{scenarios_65, llm_judge, eval_split}.py553

B. 대체 가능성

가능 — retrieval pipeline·tool calling·suggestion 4 카테고리 전부 호환. 단 3가지 차이가 어댑터 레이어에서 해소되어야 함.

#차이해소 방법
1DB·임베딩 차원: POC PgListingsAdapter 별도 PG + 3072d / 본 backend SQLAlchemy async + 1536dProperty 모델 read API 를 ListingsIndexPort 로 어댑팅 + Qdrant 신규 collection properties_v2_3072. 기존 1536 collection 폐기.
2인증·thread 영속: POC stateless / 본 get_current_user_optional + autosave + HMACcomposition root 에서 user_id 매핑 (member-{uid}/anon-{ip-hash}) + _autosave_conversation 은 그대로 유지.
3SSE 이벤트 스키마: POC 6종 vs 본 SearchStreamEvent 8종 (token/properties/suggestions/end 외 4종)신규 어댑터에서 POC SSE → SearchStreamEvent 매핑. 프론트 useAIChatSender 8종 분기 그대로 호환.

C. 마이그레이션 DAG (Layered ASCII)

Layer 0          Layer 1                  Layer 2                  Layer 3                  Layer 4         Layer 5         Layer 6
─────────        ─────────                ─────────                ─────────                ─────────       ─────────       ─────────
┌──────────┐    ┌──────────────┐         ┌──────────────────┐    ┌────────────────┐       ┌─────────┐    ┌──────────┐    ┌──────────┐
│N0.1* S ⏸│═══▶│N1.1* M ⏸    │════════▶│N2.1* L ⏸        │═══▶│N3.1* M ⏸     │══▶│N4.1 M ⏸│══▶│N6.1* L ⏸│══▶│N7.1* L ⏸│
│Qdrant   │    │Port 14종     │         │App: Agent/Chat/  │    │auth+tenant   │   │worker  │   │real DB   │   │폐기      │
│v2 3072d │    │이식 (ABC)    │         │Search 1,431 LOC │    │assistant.py  │   │인덱싱  │   │65+holdout│   │16K LOC   │
└──────────┘    └──────┬───────┘         └────────▲─────────┘    └──────┬───────┘   └─────┬──┘   └────┬─────┘   └──────────┘
                       │                          │                     │                 │           │
                       ▼                          │                     ▼                 │           │
                ┌──────────────┐                  │              ┌────────────────┐       │           │
                │N1.2 M ⏸     │──────────────────┤              │N3.2 S ⏸      │       │           │
                │LLM/Embed/Vec │                  │              │HMAC signer    │       │           │
                │/Rerank/BM25  │                  │              │통합           │       │           │
                └──────┬───────┘                  │              └────────────────┘       │           │
                       │                          │                                       │           │
                       ▼                          │              ┌────────────────┐       │           │
                ┌──────────────┐                  │              │N3.3 S ⏸      │       │           │
                │N1.3* L ⏸    │──────────────────┘              │thread autosave│       │           │
                │Listings 결합 │                                 │호환            │       │           │
                │(Property→idx)│                                 └────────────────┘       │           │
                └──────────────┘                                                          │           │
                                                                 ┌────────────────┐       │           │
                                                                 │N5.1 S ⏸      │───────┤           │
                                                                 │FE endpoint    │                   │
                                                                 │변경 0          │                   │
                                                                 └──────┬─────────┘                   │
                                                                        │                             │
                                                                        ▼                             │
                                                                 ┌────────────────┐                   │
                                                                 │N5.2 M ⏸      │───────────────────┘
                                                                 │PoCV2 라우트   │
                                                                 │+ 컴포넌트 제거│
                                                                 └────────────────┘

범례: ═ Critical Path / ─ non-CP / * CP / [S]≤50 [M]≤150 [L]≤300 LOC / ⏸ wait ▶ run ✓ done ✗ fail
노드경로변경 요지의존LOC위험
N0.1CPadapters/qdrant/bootstrap.pycollection properties_v2_3072 3072d 신규. 기존 properties_v1 1536d 는 N7.1 에서 drop.+30낮음
N1.1CPdomains/ai/ports/*.py 14종POC 14 port 그대로 이식. SearchAssistantPort 와 공존.N0.1+600낮음
N1.2adapters/{llm/groq,embedding/openai_3large,qdrant/v2_store,reranker/cohere,bm25/kiwi}/Groq/OpenAI/Qdrant/Cohere/BM25 adapter 이식 + httpx async 화.N1.1+1,400
N1.3CPdomains/properties/use_cases/index_for_chatv2.py + adapters/database/properties_repository.pyListingRegistrar 가 본 SQLAlchemy Property 모델 → 인덱싱 텍스트로 변환. tenant 격리 컬럼 (owner_id) payload.N1.1,N1.2+500높음
N2.1CPdomains/ai/use_cases/{chat_agent,chat_service_v2,search_service_v2,agent_tools,agent_config}.pyPOC Application 3종 + tools + config 이식 (723+106+103+376=1,431 LOC). domain → port 의존만.N1.1,N1.2+1,500높음
N2.2adapters/assistant_v2/assistant_adapter.pyAgent 를 SearchAssistantPort 로 래핑. POC SSE 6종 → SearchStreamEvent 8종 매핑.N2.1+400
N3.1CPapi/routers/assistant.pyDI 를 신규 어댑터로 완전 교체 (feature flag 없이 직접 교체 — 중복 코드 0 원칙). SearchContext.tenant_request_id 유지.N2.2~80
N3.2core/message_signer.pyPOC issue_signature 를 본 ai_message_signing_secret 으로 발행. ai_chat.py 검증 그대로.N3.1+50낮음
N3.3api/routers/assistant.py _autosave_conversationtools_used/property_ids/turn_count metadata 를 assistant_message.metadata 에 echo.N3.1+40낮음
N4.1workers/index_properties_chatv2.pyDramatiq actor — 신규 매물 → ListingRegistrar → Qdrant upsert + BM25 in-process rebuild.N1.3+250
N5.1frontend 변경 0/v1/assistant/message 가 새 어댑터로 향함 — useAIChatSender 8종 분기 그대로 작동.N3.10낮음
N5.2frontend/src/{pages/poc-v2,components/chat/PoCV2AIAssistantBottomSheet.tsx,hooks/usePoCChatV2.ts} 전부POC 전용 mirror 라우트·컴포넌트 완전 삭제. 신규 4 카테고리 suggestion chip / tool 진행 UI 가 본 AIAssistantBottomSheet 에 흡수.N5.1-300
N6.1CPbackend/tests/integration/chat_v2/eval_with_real_listings.pyPOC scenarios_65 + llm_judge + eval_split 재사용. 본 DB holdout 매물 fixture. LLM Judge ≥ 4.59 게이트.N4.1,N3.1+300
N7.1CPadapters/properties_agent/ + domains/ai/use_cases/* (구) + poc-chat-search/ 전체 + properties_v1 Qdrant collection + frontend /chat-v2 라우트중복 0 원칙 — 완전 폐기. 23K+ LOC + POC 디렉토리 + 1536d collection + mirror 라우트.N6.1 통과-23,000+높음

Critical Path: N0.1 → N1.1 → N1.3 → N2.1 → N3.1 → N6.1 → N7.1 (총 7개 CP 노드, 단일 PR/노드 = 7 PR 직렬)

D. 시나리오 테스트 계획

단계내용
1. 재사용poc-chat-search/tests/{scenarios_65,llm_judge,eval_split}.py 553 LOC 를 backend/tests/integration/chat_v2/ 로 그대로 이식 (이후 POC 디렉토리 제거 시 본 backend 가 단일 SSOT).
2. holdout 분할POC_EVAL_SEED=42 유지. holdout 매물 ID 는 production property allowlist (frozen) — 신규 매물 유입 영향 X.
3. 본 DB 매물 색인N4.1 Dramatiq worker → ListingRegistrar.register_many → properties UPSERT + embed + Qdrant properties_v2_3072 upsert + BM25 in-process rebuild.
4. 평가 흐름pytest fixture 에서 build_container()Agent.run(query) ×65 → LLMJudge.score() 4축 (relevance / persona_fit / anti_pattern / suggestions_quality).
5. 게이트train 60 평균 ≥ 4.59 + holdout 5 평균 ≥ POC 기준선 (overfitting 검출). 통과 시에만 N7.1 폐기.
6. CI 분리nightly cron = deep eval (judge budget $10) / PR-CI = smoke 10 시나리오만.

E. 중복 코드 0 보장 체크리스트

#제거 대상완료 조건
1backend/src/app/adapters/properties_agent/** (16,201 LOC)N7.1 PR 에서 rm -rf + import 그래프 0 검증
2backend/src/app/domains/ai/use_cases/* (구 ~6,800 LOC, 스레드 CRUD 제외)N7.1 PR 에서 신규 use_case 와 충돌 0 확인 후 폐기
3poc-chat-search/ 디렉토리 전체본 backend 이식 후 worktree 에서 디렉토리 삭제 + git rm
4frontend/src/pages/poc-v2/** + PoCV2AIAssistantBottomSheet.tsx + usePoCChatV2.ts + /chat-v2 라우트N5.2 PR 에서 전부 삭제
5Qdrant properties_v1 collection (1536d)N7.1 PR 에서 client.delete_collection("properties_v1") 호출 + bootstrap 코드 삭제
6중복 settings 키 (POC 의 POC_*)본 backend config/settings.py 의 일반 키로 통합 — POC prefix 제거

F. 위험 + 롤백

위험완화롤백
N3.1 어댑터 교체 직후 production traffic 회귀N6.1 통과 + alpha 환경 24h staginggit revert + Qdrant properties_v1 보존 유지
본 DB property 스키마 ↔ POC ListingsIndexPort 불일치N1.3 단독 PR 으로 read API 호환성 사전 검증
holdout 점수 회귀 (≥ 4.59 미만)N6.1 차단 — N7.1 진행 X이전 PR head 로 되돌림
16K LOC 폐기 후 의존 import 누락N7.1 직전 ruff --select F + pytest 전체 통과git revert N7.1

G. 단계별 PR 순서 (직렬 7 + 병렬 4)

PR-1 [N0.1]        Qdrant v2 collection 추가                    +30 LOC     1h
PR-2 [N1.1+N1.2]   Port 14종 + 5 adapter 이식 (병렬 작업)        +2,000 LOC  6h
PR-3 [N1.3]        Property → ListingsIndex 결합                +500 LOC    3h
PR-4 [N2.1+N2.2]   Application + SearchAssistantPort 어댑터     +1,900 LOC  8h
PR-5 [N3.1+3.2+3.3] auth router DI 교체 + signer + autosave    +170 LOC    2h
PR-6 [N4.1]        Dramatiq 인덱싱 worker                       +250 LOC    3h
PR-7 [N5.1+N5.2]   frontend mirror 라우트·컴포넌트 삭제          -300 LOC    2h
PR-8 [N6.1]        65 시나리오 + holdout 통합 평가              +300 LOC    4h
                   (게이트 통과 시 PR-9 진행)
PR-9 [N7.1]        properties_agent 16K + poc-chat-search/      -23,000+    1h
                   + Qdrant v1 collection + use_case (구) 전부 폐기

총 예상 LOC: +5,180 / -23,300 = 순 −18,120 LOC. wall-time ≈ 30h (직렬, 단독 작업 기준).