o2o-negosium-original/agent
2026-06-24 14:32:38 +09:00
..
bootstrap Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
common config.local.toml 설정 단일화 . 2026-06-17 11:26:20 +09:00
config config.local.toml 설정 단일화 . 2026-06-17 11:26:20 +09:00
eval_harness Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
negotiation [fix] 재협상 가격협상 흐름 정비 — 무한루프·카드 연출·요약카드 2026-06-24 11:52:34 +09:00
router [fix] 재협상 가격협상 흐름 정비 — 무한루프·카드 연출·요약카드 2026-06-24 11:52:34 +09:00
services [fix] 재협상 가격협상 흐름 정비 — 무한루프·카드 연출·요약카드 2026-06-24 11:52:34 +09:00
tenancy cors 문제 해결 . 2026-06-18 17:02:12 +09:00
tenants [fix] 재협상 가격협상 흐름 정비 — 무한루프·카드 연출·요약카드 2026-06-24 11:52:34 +09:00
tests cors 문제 해결 . 2026-06-18 17:02:12 +09:00
tools Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
.dockerignore Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
.gitignore Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
conftest.py Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
Dockerfile Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
pytest.ini Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
README.md Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
requirements.txt Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
web_main.py Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00

Negosium Agent

범용 멀티테넌트 협상 솔루션 PoC. 여러 회사(ktcommerce·imarketkorea 등)가 각자 데이터로 분기 학습하는 협상 카드 선택 에이전트. Q-Learning(UCB) 기반 Chat_server(단일 테넌트)를 참고해 신규 구축한다.

PoC 목표 두 가지:

  1. 학습 알고리즘이 실제 협상 성과를 개선하는지 정량 검증 (Q-Table vs LinUCB vs Offline RL 비교 하네스).
  2. 멀티테넌트 분기 학습 + 기존 14개 API/step 체계 무손실 보존.

o2o-negosium 서버군 내 위치

agent 는 서버군의 한 서비스다. backend 와 같은 컨벤션·같은 negosium_db 를 공유한다.

서비스 포트 DB 역할
backend 9300 negosium_db 회사/공급사/견적/카드 도메인 + 인증
negodata/backend 9400 negodata_db 데이터 도구
agent 9500 negosium_db (learning 스키마 + 기존 스키마 읽기/쓰기) 협상 챗봇·학습

통합 토폴로지 (확정: 하이브리드)

  • agent 는 독립 FastAPI 서비스(자체 포트)로 뜨되, backend 의 구조적 컨벤션을 그대로 채택한다:
    • MVC: router/(컨트롤러) → services/(로직, 추가 예정) → crud/(쿼리, 추가 예정).
    • 람다 DB: common/database/db_session_manager.pyexecute_lambda / execute_lambda_run, Read/Write 엔진 분리.
    • Protocol: common/models/gmodel.pyWebPacketProtocol/Req_*/Res_*/result: ErrorInfo.
    • config: config/config.<APP_ENV>.toml (APP_ENV 로 선택).
  • 협상 엔진 내부는 헥사고날 구조(negotiation/ 패키지)로 유지한다 — 도메인은 tenant-agnostic, config 주입.
  • backend 가 /chat 을 agent 로 위임하거나, 프런트가 agent 를 직접 호출(인증 토큰은 backend 발급분 검증).

장단점

  • (+) 무거운 RL 의존성(torch/d3rlpy/obp)이 인증 backend 를 오염시키지 않음 — 격리 + 독립 스케일.
  • (+) backend 컨벤션·negosium_db 단일 소스 재사용 → 학습 곡선·중복 제거.
  • (+) PoC 알고리즘 실험의 잦은 재학습이 운영 backend 배포에 묶이지 않음.
  • () common/ 을 backend 와 별도 유지(현재는 복제) → 추후 공유 라이브러리화 검토.
  • () 서비스 간 Protocol 버전 정합 관리 필요.

테넌트 = company_id

계획서의 문자열 tenant_id 는 이 레포에서 company.companies.company_id(uuid) 로 매핑한다(이미 1급 시민). 공유 베이스 정책은 예약 식별자(_base)로 표현한다. 모든 학습 테이블은 company_id 로 논리 격리한다.

RL 학습 자산 저장 위치 = learning 스키마

Q-Table·q_values·visit_counts·experience_logs(propensity/turn 포함)는 negosium_db 안의 신설 learning 스키마(7번째)에 둔다. 기존 company/card/negotiation/quotation 스키마는 재사용한다.

저작권 분리 (클린룸) — CLEANROOM.md

플랫폼은 특정 고객사 독점 자산과 분리해 독립 설계한다. 참고 엔진(Chat_server)은 기능 구조(아이디어·방법) 이해용으로만 열람하고, 독점 표현물(협상 스크립트·고유 카드 코드·verbatim 라벨·튜닝값)은 반입하지 않는다. 레포의 테넌트 프로파일은 합성/중립 데모값이며, 실제 운영값은 테넌트 비공개 소스(YAML/DB)에서 런타임 주입한다. 카드 코드는 우리 중립 스킴(NGC-*). 상세는 CLEANROOM.md.

디렉토리 구조

agent/
├── web_main.py                 # 엔트리포인트 (uvicorn, 포트 9500)
├── config/                     # APP_ENV 별 toml (backend 컨벤션)
├── common/                     # logger·enums·singleton·gmodel·gtime·db_session_manager (backend 이식)
├── router/                     # FastAPI app + 미들웨어 + v1 라우터
│   ├── router.py               #   app + lifespan + TenantMiddleware
│   ├── middleware/tenant_middleware.py  # X-Tenant-ID → request.state.tenant_id (P4 완성)
│   └── v1/health/health.py
├── negotiation/                # 협상 엔진 (Chat_server 이식, 헥사고날)
│   ├── chat/service/           #   chat_engine·step 핸들러 (P7/P8)
│   ├── orchestrator/           #   negotiation_orchestrator (P4 팩토리화)
│   ├── policy/  policies/      #   UCBPolicy + 상위 NegotiationPolicy 추상 (H0)
│   ├── qtable/                 #   state·q_table·reward·usecase (P2 config 주입)
│   ├── cards/                  #   CardSourcePort + card.* 어댑터 (P6)
│   └── profiling/              # ← 구 N-profiling 개명 (동적 import 해킹 제거) ✅ P0 완료
├── tenancy/                    # TenantConfig·로더·레지스트리 (P1/P4)
├── eval_harness/               # 알고리즘 비교 하네스 (PoC 본체, H1~H7)
├── tools/                      # init_base·train·export 스크립트
└── tests/

실행 / 테스트

cd agent
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx     # 테스트 도구

# config 는 config.local.toml 하나만 사용(.gitignore — DB 비번·OpenAI 키 포함).
# 없으면 직접 생성(DB·OpenAI 값 입력). test/docker 도 이 파일로 폴백된다.

python web_main.py                          # APP_ENV 기본 local, http://localhost:9500/docs
APP_ENV=test python -m pytest               # 테스트 (config.local.toml 사용)

브라우저 데모 (스크립트 기반 협상 채팅)

서버를 띄우고 http://localhost:9500/demo 접속 (tests/negotiation_demo.html, /v1/chat 구동). 테넌트/유형을 고르면 서비스안내→담당자확인→협상품목안내→가격협상→와일드카드→협상완료 대화가 스크립트로 진행되고, 버튼/가격 입력이 step 에 따라 동적 렌더링. 가격협상 턴에서 UCB Q-Table 이 카드를 선택하고 학습(card_id·Q·visit 표시), 종료 시 성공/실패 보상 반영. 스크립트 브랜드는 테넌트별 치환(데모상사 A/B).

콘솔 테스트 (프론트 없이)

현재 구현된 부분을 콘솔로 확인하는 방법:

1) 의사결정 루프 데모 — 테넌트별 config 주입·상태분류·보상·DB 격리를 눈으로 확인:

APP_ENV=local python -m tools.console_demo --tenant ktcommerce      # 기본 3턴 시나리오
APP_ENV=local python -m tools.console_demo --tenant imarketkorea    # 다른 테넌트(다른 카드셋·임계값)
APP_ENV=local python -m tools.console_demo --tenant ktcommerce --interactive   # 직접 입력
APP_ENV=local python -m tools.console_demo --tenant ktcommerce --no-db          # DB 없이

⚠️ 카드선택은 임시 placeholder 정책(실제 UCB Q-Table 은 H1/P5). 학습은 아직 일어나지 않는다.

2) 서버 헬스/테넌트 라우팅 — 서버를 띄우고 curl:

APP_ENV=local python web_main.py            # 다른 터미널에서:
curl localhost:9500/healthz                 # 200
curl localhost:9500/v1/health               # {"status":"ok",...}
curl localhost:9500/v1/foo                  # 400 TENANT_HEADER_MISSING (헤더 없음)
curl localhost:9500/v1/foo -H 'X-Tenant-ID: nonexistent'   # 404 TENANT_NOT_REGISTERED
curl localhost:9500/v1/foo -H 'X-Tenant-ID: ktcommerce'    # 통과(라우트 미존재라 404 Not Found)

2-1) 협상 한 라운드 (HTTP 프리뷰)POST /v1/negotiation/step (Swagger: http://localhost:9500/docs):

curl -s -X POST localhost:9500/v1/negotiation/step \
  -H 'X-Tenant-ID: ktcommerce' -H 'Content-Type: application/json' \
  -d '{"revenue_amount":20000000,"distribution_code":"A","partner_count":1,
       "acceptance_ratio":0.11,"input_price":990,"anchor_price":800,"target_price":1000,
       "round_number":3,"outcome":"success"}'
# → state_index / card_id(NGC-A*) / reward / logged. 테넌트를 imarketkorea 로 바꾸면 다른 상태·카드(NGC-B*).

⚠️ 카드선택은 임시 placeholder. 실제 대화 /chat·step 체계·학습형 정책은 P7/H1.

3) DB 격리 확인 — 콘솔 데모 실행 후 company_id 별 로그 건수 조회:

APP_ENV=local python -m tools.show_logs        # learning.experience_logs 를 company_id 별로 집계

진행 상황 (Phase)

  • P0 스캐폴딩: 디렉토리/config/common/람다DB/Protocol 골격, profiling 개명(동적 import 제거), 앱 순환 import 없이 로드 + 테넌트 미들웨어 골격. (tests/test_p0_scaffold.py 5/5)
  • P1 TenantConfig & 로더: pydantic config + YAML/_base deep-merge + TTL 캐시, 클린룸 적용(중립 데모값·NGC-* 카드). (tests/test_p1_tenant_config.py 7/7)
  • P2 State/Reward/Mapper config 주입: build_state(IntEnum→주입, mixed-radix index), RewardCalculator(자체 공식), ActionCardMapper(+중복방지 마스킹). 결정론·주입효과 검증. (tests/test_p2_* 8/8)
  • P3 learning 스키마: postgres-init/02-learning-schema.sql(company_id 격리·복합유니크·propensity/turn), ORM 모델, LearningRepository(company_id 생성자 박기·리셋 스코프). 실DB 검증: 2테넌트 version_name 공존, reset_all 타테넌트 무영향(파괴 테스트), company_id 위조방지. (tests/test_p3_* 5/5)
  • P4 Registry/Factory & 미들웨어: 전역 싱글톤 제거 → TenantEngineRegistry(테넌트별 지연생성+lock 캐시), EngineFactory, episode_state 외부화(EpisodeState 요청스코프 — 동시성 오염 방지), 미들웨어 미등록 404. 검증: 두 테넌트 다른 엔진/카드, 동시요청 1회조립, 헤더 400·미등록 404. (tests/test_p4_* 7/7)
  • H0 Policy 추상 + H1 UCB Q-Table 정책: NegotiationPolicy(ABC), QTable(numpy, Q-learning), UCBQTablePolicy(UCB 선택+propensity 근사+마스킹), QTablePolicyStore(learning 스키마 로드/영속). /v1/negotiation/step 이 실제 학습형으로 교체 — 반복 호출 시 UCB 탐색 + Q 갱신 + DB 누적 + 테넌트 격리. (tests/test_h1_* 7/7)
  • 대화 스크립트 + ScriptRepository: Chat_server 스크립트 구조 이식(재협상/재견적/와일드카드/step·변수맵), KT 브랜드→{company_name}/{service_name} 변수화 + 표현 중립 재작성(클린룸). (tests/test_scripts_* 7/7)
  • P7 슬라이스 /v1/chat: 인메모리 세션 + ChatEngine(step 전이·조건분기·와일드카드 진입) + 가격협상 턴 UCB 카드선택·학습 + 종료보상 역전파. 브라우저 채팅 UI. (tests/test_p7_chat.py 5/5)
  • H5 학습검증 하네스 (PoC 본체): 카드별 효과가 다른 시뮬 구매자(eval_harness/) → 정책 비교. 학습형(qtable_ucb)이 random/static 대비 평균보상 우위(95%CI 분리)·좋은카드 적중 0.9 vs 0.32 → "학습하면 성과가 오른다" 정량 입증. python -m eval_harness.runner --config configs/exp_default.yaml --tenant ktcommerce. (tests/test_h5_* 5/5)
  • P7 14개 API: chat / q-table(versions·switch·current) / experience-logs / reset-learning · reset-all(타테넌트 무영향) / invalidate-session / train(오프라인 Q-learning) / verification-report / card-update · card-search. 전부 X-Tenant-ID 격리. (tests/test_p7_apis.py 5/5)
  • P5 베이스 warm-start / cold-start 3단: warm_start_from_base(차원 호환 시 base Q값/방문수 복제, visit 감쇠·base_version_id 추적), cold-start 3단(warm-start→차원불일치 휴리스틱 폴백→첫 버전), tools/init_base.py(시뮬레이터로 베이스 시드). 신규 테넌트 첫 협상 → v000_warmstart_from_base. (tests/test_p5_* 5/5)
  • P8-A 세션 상태 DB화: learning.chat_sessions + ChatSessionRepository(인메모리 제거). 서버 재시작/멀티워커에도 협상 진행 상태 복원(라이브 검증: kill 후 재기동→같은 session_id 이어짐). (tests/test_p8_* 3/3)
  • H3 LinUCB / H4 OPE(IPS/DR/SNIPS) / H6 CQL+FQE / P8-B 채팅로그(negotiation.chats) 적재.

누적 테스트 73/73 (cd agent && APP_ENV=test python -m pytest). DB 테스트는 로컬 postgres 필요(미가용 시 skip).

협상 경제 모델 (KT 구매자 관점)

  • KT커머스/아이마켓코리아 = 구매자(갑). 목표 = 싸게 매입. 협력사(판매자)가 제시가를 낸다.
  • 앵커링가(anchor) < 목표가(target). 앵커링값은 갑이 직접 입력(UI 기본 제안 = target×(10.01), 편집 가능).
  • 협력사 제시가 ≤ 앵커가 → 우선협상(타결), 더 낮을수록 KT 보상↑. 앵커가 초과 → 카드로 인하 협상, 설정 카드 모두 소진 시 결렬.
  • 와일드카드: 앵커가 살짝 초과 구간에서 1% 인하/목표가 매칭 압박.

PoC 본체 결과 (H5, 위 경제모델 기준 / target=10000·anchor=8000 시나리오)

python -m eval_harness.runner --config configs/exp_default.yaml --tenant ktcommerce
policy        success  settled/tgt  turns  mean_rwd   ±95%CI  good_hit
random          0.988      0.904    2.02    1.1176    0.0206    0.350
static          1.000      0.923    2.56    0.9995    0.0020    0.000
qtable_ucb      0.998      0.881    1.46    1.2686    0.0088    1.000   ← 학습형(최저 매입가)
판정 ✅ PASS: 학습형이 baseline 대비 평균보상 우위(95%CI 분리) + 좋은카드 적중 우위 → 학습 루프 유효
   (settled/tgt 낮을수록 = 더 싸게 매입 = KT 이득. 학습형이 가장 낮음)

학습 한계(정직): 현재 보상은 snapshot 만으로 산출돼 어떤 카드를 골랐는지에 무관하다. 즉 UCB 탐색·Q갱신·영속·격리 '머신'은 실동작하지만, "어떤 카드가 더 좋은가"를 학습하려면 행동-의존 보상이 필요하다 → H5(LLM 구매자 시뮬레이터) 또는 실 협상결과 로그. 그게 PoC 본체의 다음 핵심.

  • 트랙2(하네스): H0 로깅 보강 → H1 QTable 어댑터 → … → H7 비교표/리포트.

상세 계획·검증 기준은 실행 계획서 참조.