o2o-negosium-original/agent/tools/export_autonomy_serving.py
jwkim 51b3820cc9 [feat] agent: 완전 자율 협상 모드 (AUTONOMY_MODE) + LLM 멘트
판정 룰(앵커타결/와일드존/3라운드결렬)과 카드 선택을 학습 정책으로 대체:
수락/역제안 금액/압박 화법/결렬 전부 행동 30개(수락1+결렬1+역제안 6단x화법4+압박4)에서 선택.

- autonomy_actions: 행동 공간·특징 인코딩 (학습/서빙 공유)
- autonomy_store: numpy 서빙 + 행동 봉투 7개(수락<=목표가 / 역제안 단조 / 역제시·결렬은
  설득 2회 후 해금 / 마무리국면 압박 금지 / 첫 역제안 앵커 이하 / 최종제안 1회 보장)
- chat_engine: 자율 스텝(역제안/최종제안/압박1~4), 최종제안 금액=목표가, 턴캡 12
- ment_generator: Gemini 멘트 생성 + 가드(숫자 화이트리스트·목표가 비공개·금지어·문장완결,
  실패시 템플릿 폴백, 6초 컷), 인터넷최저가 근거 인용(수집됨+제시가 초과시만), 대화 기억
- chat_service: 자율 행동 experience_logs 로깅(AUT|종류|위치|전략), 대화기억 ctx 관리
- context loader/CRUD: 인터넷최저가·견적기간·협력사 이력 로드 (v3 상태 21차원)
- train_full_autonomy: 시뮬 15k ep — 앵커율 0.8~6% 정합, 협력사 현실화(컷반발·반복짜증·
  양보 상호성), 관측성 마스크(마감 40% 미관측·15% 전부미상 — 서빙 중립값 분포 정합),
  보상 수정(목표가 초과 타결=결렬 취급)
- 서빙 v3.5 (v3.3 목표가 즉시지르기 퇴화, v3.4 소액지형 첫턴 통보 퇴화 — 게이트 반려 이력 보관)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:57:38 +09:00

43 lines
1.6 KiB
Python

"""full_autonomy 체크포인트(.pt) → 서빙 번들(autonomy_serving.npz) export.
dqn_serving 과 동일 패턴: ScoreNet 가중치만 numpy 로 묶어 PyTorch 없이 서빙한다.
행동 특징은 코드(autonomy_actions)가 런타임 생성하므로 번들에는 가중치만 담는다.
실행(호스트, torch 필요): APP_ENV=local python -m tools.export_autonomy_serving
"""
import os
import numpy as np
import torch
from negotiation.policies.autonomy_actions import ACTION_DIM, EXTRA_STATE_DIM
from negotiation.qtable.domain.service.feature_builder import STATE_FEATURE_DIM, TENANT_FEATURE_DIM
_HERE = os.path.dirname(os.path.abspath(__file__))
CKPT_PATH = os.path.join(_HERE, "..", "artifacts", "full_autonomy.pt")
OUT_PATH = os.path.join(_HERE, "..", "artifacts", "autonomy_serving.npz")
STATE_DIM = STATE_FEATURE_DIM + TENANT_FEATURE_DIM + EXTRA_STATE_DIM
def main():
sd = torch.load(CKPT_PATH, map_location="cpu")
W0, b0 = sd["net.0.weight"].numpy(), sd["net.0.bias"].numpy()
W1, b1 = sd["net.2.weight"].numpy(), sd["net.2.bias"].numpy()
W2, b2 = sd["net.4.weight"].numpy(), sd["net.4.bias"].numpy()
assert W0.shape[1] == STATE_DIM + ACTION_DIM, f"입력 차원 불일치: {W0.shape[1]}"
tmp = OUT_PATH + ".tmp"
with open(tmp, "wb") as f:
np.savez(f, W0=W0, b0=b0, W1=W1, b1=b1, W2=W2, b2=b2,
state_dim=STATE_DIM, action_dim=ACTION_DIM)
if os.path.exists(OUT_PATH):
os.replace(OUT_PATH, OUT_PATH + ".prev")
os.replace(tmp, OUT_PATH)
print(f"[저장] {os.path.abspath(OUT_PATH)} (state {STATE_DIM} + action {ACTION_DIM})")
if __name__ == "__main__":
main()