판정 룰(앵커타결/와일드존/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>
15 KiB
완전 자율 협상 에이전트 — 처음 대비 변경 정리
기준: 협상카드 + 룰 엔진 시절(처음) → 완전 자율 에이전트 v3.2 + LLM 멘트 (2026-07-10 현재) 롤백:
docker-compose.yml의AUTONOMY_MODE=0하나로 룰 엔진 즉시 복귀 (재빌드 불필요)
1. 한눈에 보기 — 무엇이 바뀌었나
| 영역 | 처음 (룰 + 카드) | 지금 (자율 에이전트) |
|---|---|---|
| 협상 판정 | 하드코딩 룰 (앵커 이하 타결 / 와일드카드 존 / 3라운드 결렬) | RL 정책이 매 턴 행동을 직접 선택 |
| 발화 선택 | DB 협상카드(NGC-001~011)를 UCB/Q-table 로 선택 | 카드 없음 — 행동 30개 중 신경망이 선택 |
| 역제안 금액 | 카드에 박힌 고정값 | 앵커~목표가 6단 사다리에서 정책이 선택 |
| 와일드카드 | 사람이 등록한 카드(WC-01~05) 발동 | 최종제안·역제시 타이밍을 정책+봉투가 자율 수행 |
| 멘트 | 고정 템플릿 | Gemini LLM 생성 + 할루시네이션 가드 (실패 시 템플릿 폴백) |
| 입력 상태 | 가격 스냅샷 9차원 | 21차원 (마감·협력사 이력·인터넷최저가·에피소드 기억 추가) |
| 학습 | Q-table 온라인 갱신 | 시뮬레이터 DQN 학습 → 프로브 게이트 → npz 번들 배포 |
2. 의사결정 — 행동 공간 30개
ACCEPT 수락 (협상완료, 제시가 타결)
WALK 결렬 의사 → 최종제안 1회 보장 후 종료
COUNTER 역제안: 금액 위치 6단 {-5%, 0, 25, 50, 75, 100% of (목표가-앵커가)} × 화법 4종
PRESS 압박(설득): 화법 4종
행동의 실체는 Action(kind, counter_q, strategy) — (무엇을, 얼마에, 어떤 말투로) 좌표 3개짜리 데이터다
(policies/autonomy_actions.py, DB 아님). 에이전트는 매 턴 30개 중 조합 1개를 고르고,
원화 환산(앵커 + q×스팬)과 문장(LLM)은 선택 이후의 실행 단계.
역제안 사다리 6단 (실스케일 앵커 418,966/목표 423,198 기준): q=−0.05→418,754 / 0→418,966(앵커) / 0.25→420,024 / 0.5→421,082 / 0.75→422,140 / 1.0→423,198(목표가). 비율(q)이라 견적 스케일과 무관하게 같은 행동 공간이 재사용된다.
화법 4종은 기존 카드 전략 분류를 그대로 승계: 경쟁 압박 / 수용 공감 / 기준 고수 / 협력 파트너. 톤 선택도 학습 결과 — 라이브에서 초반 경쟁(1)→중반 수용(2)→교착 협력(4)으로 국면별 전환 관측.
설계 출처: 화법 4종·금액 범위(앵커~목표가)는 제품 승계, 수락·결렬 포함은 완전 자율 정의의 필연,
격자 6단만 설계 재량(COUNTER_GRID 수정+재학습으로 변경 가능). 알려진 한계: 부를 수 있는
금액이 격자 6지점뿐 — 연속 금액 미세조정은 불가(필요 시 격자 확장이 현실적).
- 모델: action-as-feature DQN (ScoreNet MLP — 상태 21 + 행동특징 9 → 점수 1개)
- 서빙: numpy 전용 (
autonomy_serving.npz) — 컨테이너에 PyTorch 불필요 - 현재 서빙본: v3.5 (백업
autonomy_v35.npz/ 반려본 v3.4 / 이전 v3.2)
3. 입력 상태 — 9차원 → 21차원
"완전한 에이전트에는 다 들어가야 한다" 요구로 확장:
| 그룹 | 차원 | 내용 | 출처 |
|---|---|---|---|
| 기본 | 9 | 매출액·유통코드·협력사수·수락률·제시가·앵커가·목표가·라운드 등 | 기존 스냅샷 |
| 테넌트 | 5 | 보상 설정 특징 | reward config |
| 에피소드 기억 | 2 | 직전 역제안 유무·위치 | ctx autonomy_last |
| 마감 | 1 | 마감 잔여율 | quotations start/end_time |
| 협력사 이력 | 3 | 과거 협상 횟수·성공률·평균 타결비율 | experience_logs ⨝ sessions |
| 시장가 | 1 | 인터넷 최저가 갭 | items.internet_lowest_price |
- 소스가 없으면 중립값(0.5/0) — 학습 시뮬의 '미상' 표현과 동일
- 상대 발화 내용 파싱은 보류 (사용자 결정 — 프론트 입력 UI 변경 필요)
4. 행동 봉투 — 실전 테스트에서 잡은 결함의 구조적 방지
룰과 다름: 룰은 결과를 정하고, 봉투는 행동만 금지한다. 나머지(타이밍·속도·금액)는 전부 정책 학습.
| # | 봉투 | 막는 결함 (실제 발생 사례) | 성격 |
|---|---|---|---|
| ① | 목표가 초과 제시가는 수락 불가 | v3.1 이 보상 구멍을 착취해 목표가+14% 매입 | 안전 (영구) |
| ② | 직전 역제안보다 낮은 금액 재제시 금지 (단조 양보) | 423,198 → 420,024 제안 철회 사건 | 안전 (영구) |
| ③ | 역제시는 설득 ≥2회 후 해금 (AUTONOMY_MIN_PRESS) |
첫 턴부터 역제시 — 옛 의미론(일반카드=설득, 와일드카드만 역제시) 복원 | 예절 (해제 후보) |
| ④ | 목표가 0.5% 이내 마무리 국면에선 압박 금지 | 802원 차이에 "재검토 부탁" 반복하던 푼돈 흥정 | 예절 (해제 후보) |
| ⑤ | 첫 역제안은 앵커가 이하만 (q ≤ 0) | 사다리 꼭대기 근처(422,140)에서 개시해 올라갈 계단이 없던 문제 | 예절 (해제 후보) |
| ⑥ | 같은 금액 반복·결렬 의사 → 자율_최종제안 1회 보장, 금액은 목표가 | 확인 없이 결렬 / 직전 금액을 "최종"으로 반복해 승인 여지를 남긴 채 종료하던 문제 | 안전 (영구) |
| ⑦ | 결렬(walk)도 해금 전 금지 — 설득 ≥2회 전에는 설득만 가능 | 설득 0회에 walk 선택 시 최종제안 보장(⑥)과 결합해 "첫 턴 목표가 통보"가 됨 (v3.4 라이브 결함) | 예절 (해제 후보) |
- 구현: 서빙
policy/autonomy_store.py후보 마스크 + 학습tools/train_full_autonomy.pyavailable_actions양쪽 동일 - 예절 봉투(③④⑤)는 실로그가 쌓이면
AUTONOMY_MIN_PRESS=0등으로 해제 실험 가능
5. 멘트 — 템플릿 → LLM + 가드레일
역할 분리(안전 설계): 무엇을 말할지(금액/전략/수락/결렬)는 RL 이 결정, LLM 은 표현만 담당.
설정: agent/config/config.local.toml [OpenAIConfig]
모델: gemini-2.5-flash-lite (OpenAI 호환 base_url)
· 2.5-flash → thinking 지연으로 백엔드 10초 한도 초과 ("협상 응답 지연" 토스트 원인)
· 2.0-flash → 은퇴(404)
시간: LLM_TIMEOUT_S=6 초과 시 템플릿 폴백 (검증 최대 응답 2.9초)
끄기: AUTONOMY_LLM=0
할루시네이션 가드 (하나라도 걸리면 템플릿 폴백, 협상은 계속):
| 가드 | 내용 |
|---|---|
| 숫자 화이트리스트 | 프롬프트로 준 금액(제시가·제안가·직전제안가·양보폭) 외 숫자 = 즉시 폐기 |
| 목표가 비공개 | 압박 프롬프트에 목표가 미포함 + 화이트리스트에서도 제외 — 노출 사고 재발 방지 |
| 금지어 | 보장/물량/독점/최저가/시장가/%/계약기간/법적 등 승인 안 된 전술·커밋 |
| 문장 완결 | thinking 토큰 소진으로 잘린 문장 폐기 (max_tokens 2048) |
| 제안가 포함 | 역제안·최종제안 멘트에 제안 금액 필수 |
추가 기능:
- 인터넷 최저가 인용 (구 NGC-008 자율판): 수집돼 있고 제시가 > 최저가일 때만 근거 인용 허용 — 그 턴에만 '최저가' 금지어 해제, 수치는 화이트리스트 검증
- 대화 기억: 직전 제안 거절 사실·양보폭을 멘트에 반영("직전 제안에서 5원 상향한…") + 직전 멘트와 같은 문장구조 반복 금지 — "멘트가 다 똑같다" 해결. temperature 0.9
6. 학습 시뮬레이터 버전 이력 — 실패 2건 포함
| 버전 | 변경 | 결과 |
|---|---|---|
| v1 | 최초 학습 | 한 방 큰 컷 + 같은 숫자 반복 → "이게 협상이야??" |
| v2 | 에피소드 기억·컷 특징·협력사 반복 짜증/이탈 | 개선되나 지형 불일치 잔존 |
| v3 | 상태 21차원 확장 | — |
| v3.1 | 지형 정합: 앵커율 0.8~6% 샘플링 (실제 ~1% vs 시뮬 20%) | ⚠️ 보상 구멍 착취 — 목표가+14% 매입 학습 → 봉투 ① 신설 |
| v3.2 | 컷 반발·반복 짜증·양보 상호성(우리가 올리면 상대도 내림)·floor ≤ 첫제시가×0.98 | ✅ 현재 서빙본 (목표가 초과 타결 0/30) |
| v3.3 | 단조·상호성 반영 재학습 | ❌ "무조건 목표가 즉시 지르기"로 퇴화 → 프로브 게이트 반려 (full_autonomy.pt 만 보관, 미서빙) |
| v3.4 | 봉투 ①~⑤ 정합 + 보상 수정(목표가 초과 타결 = 결렬 취급) 재학습 | ❌ 반려 — 초기 게이트(실스케일 단일 지형) 통과 후 라이브에서 퇴화 발견: 소액 지형에서 첫 턴 walk→목표가 통보 / walk 잠금 후엔 압박 12연발·최종제안 생략·화법 단조(전부 전략3). 게이트를 2개 지형으로 확장해 재판정 → v3.2 우위 확인, v3.2 복원 (autonomy_v34_rejected.npz 보관) |
| v3.5 | v3.4 + 관측성 마스크: 마감 40% 미관측(0.5 고정)·15% 완전 미상 에피소드 — 서빙 중립값 상태를 시뮬 분포에 혼입 (v3.4 퇴화 원인 해소) | ✅ 현재 서빙본 — 게이트 78/78, 사다리 3단 사용, 협조 케이스 목표가 대비 -2,116원 타결. 게이트가 이 과정에서 철회 실버그 발견(아래) |
교훈 1 — 보상 = 유일한 스펙: 룰을 제거하면 보상 함수의 구멍이 곧 행동이 된다 (v3.1). 교훈 2 — 프로브 게이트: 재학습은 퇴화할 수 있다. 배포 전 반드시 실스케일 제시가별 행동표(
tools/probe_serving_dqn.py)로 비교 검증 (v3.3). 교훈 3 — 지형 일반화: 한 지형의 게이트 통과가 다른 지형을 보증하지 않는다 (v3.4 — 실스케일 통과, 소액 퇴화). 교훈 4 — 시뮬은 관측까지 닮아야 한다: 세계뿐 아니라 '무엇을 모르는지'도 서빙과 같아야 한다. 마감·이력 미상(중립값) 상태가 시뮬에 없으면 그 상태가 분포 밖이 된다 (v3.4 원인 → v3.5 해소).
철회 실버그 (게이트가 발견, 2026-07-10 수정): 단조 봉투의 기준 autonomy_last가 '마지막 행동'이라 counter→press→counter 순서에서 설득이 역제안 기억을 덮어써 봉투가 뚫렸다(9,975 제안 후 9,900 재제안). 역제안 기억을 autonomy_last_counter로 별도 보존하도록 수정 — 시뮬(역제안만 추적)과도 일치. v3.2는 이 패턴을 쓰지 않아 드러나지 않았을 뿐 프로덕션에 실존하던 구멍.
7. 현재 협상 흐름 (검증 완료)
협력사 제시
│
▼
설득(압박) ≥2회 ── 인터넷최저가 근거 인용 가능, 목표가 절대 비공개
│
▼
역제안 해금 ── 첫 제안은 앵커가 이하로 개시 (봉투⑤)
│
▼
단조 상향 사다리 ── 후퇴 금지(봉투②), 양보폭·속도는 정책이 결정
│
▼
목표가 0.5% 이내 ── 압박 중단, 클로징만 (봉투④)
│
├─ 제시가 ≤ 목표가 → 수락 → 협상완료
├─ 같은 금액 반복 / 결렬 의사 → 자율_최종제안 1회, 금액=목표가 (봉투⑥)
│ ├─ 예 → 협상완료 └─ 아니오 → 협상실패
└─ 12턴 초과(엔지니어링 캡) → 최종제안(목표가) 1회 거쳐 종료 — 캡도 봉투⑥을 우회하지 않음
8. 운영 스위치 & 파이프라인
| 스위치 (docker-compose agent env) | 값 | 의미 |
|---|---|---|
AUTONOMY_MODE |
1 | 자율 모드 (0 = 룰 엔진 복귀) |
DQN_SERVING |
1 | 카드 선택 DQN (0 = UCB Q-table) |
AUTONOMY_LLM |
1(기본) | LLM 멘트 (0 = 템플릿만) |
AUTONOMY_MIN_PRESS |
2(기본) | 역제시 해금에 필요한 설득 횟수 |
LLM_TIMEOUT_S |
6(기본) | LLM 시간 상한, 초과 시 템플릿 폴백 |
학습→배포 파이프라인:
tools/train_full_autonomy (시뮬 15k ep, 룰 베이스라인 비교)
→ tools/export_autonomy_serving (artifacts/autonomy_serving.npz, .prev 자동 백업)
→ tools/probe_serving_dqn (실스케일 행동표 — 눈으로 보는 진단)
→ tools/test_autonomy_defects (결함 회귀 게이트 — 지형 2종×시나리오 3종 + 단위·멘트가드 검사,
자동 합격/불합격. 단, v3.4 사례처럼 게이트 통과 ≠ 품질 보증:
궤적 자체도 눈으로 비교할 것)
→ docker compose build agent (npz 는 이미지에 베이크)
로깅: 자율 행동도 experience_logs 에 기록 (card_id = AUT|종류|위치|전략, 진행 row + 종결 row). 카드 재학습(retrain_from_logs)은 AUT 세션 자동 제외.
9. 변경 파일 지도
| 파일 | 역할 |
|---|---|
negotiation/policy/autonomy_store.py |
신규 — 자율 정책 numpy 서빙 + 봉투 ①~⑤ 마스크 |
negotiation/policies/autonomy_actions.py |
신규 — 행동 30개·특징 인코딩 (학습/서빙 공유) |
negotiation/chat/service/ment_generator.py |
신규 — LLM 멘트 생성 + 가드레일 |
negotiation/chat/service/chat_engine.py |
자율 스텝(자율_역제안/최종제안/압박_1~4) + _autonomy_next 봉투⑥ |
services/chat_service.py |
decider 주입·행동 로깅·대화기억 ctx 관리 |
negotiation/chat/infra/repository/nego_context_crud.py |
인터넷최저가·견적기간·협력사이력 조회 |
negotiation/chat/service/negotiation_context_loader.py |
확장 컨텍스트 로드 (company_id) |
tools/train_full_autonomy.py |
신규 — 시뮬레이터(현실화 협력사 모델) + DQN 학습 |
tools/export_autonomy_serving.py / probe_serving_dqn.py |
신규 — 번들 내보내기 / 프로브 게이트 |
tools/test_autonomy_defects.py |
신규 — 결함 회귀 게이트: 실전에서 발견된 결함 41항목을 시나리오·단위·멘트가드 검사로 자동 재생 (서빙 실물 코드 구동, DB/LLM 불필요) |
config/config.local.toml |
Gemini 접속 정보 (gitignore, 이미지에 베이크) |
docker-compose.yml |
AUTONOMY_MODE / DQN_SERVING 플래그 |
10. 남은 일
- 결함 회귀 게이트 구축 —
test_autonomy_defects.py41항목, v3.2 전항목 통과 확인 (2026-07-10) - 보상 수정 — 목표가 초과 타결은 학습 보상에서 결렬 취급 (v3.1 구멍을 유인 수준에서 차단, 봉투 ①과 이중 방어)
- ⚠️ Gemini API 키 재발급 — 채팅에 노출된 키, 테스트 종료 후 반드시 교체 (config.local.toml + 이미지 리빌드)
- 봉투 정합 재학습 — v3.4 반려(소액 지형 퇴화) → 원인 규명(관측성 불일치) → v3.5 관측성 마스크로 해소, 배포 완료 (2026-07-10)
- 철회 실버그 수정 — counter→press→counter 에서 단조 봉투 뚫림 →
autonomy_last_counter별도 보존 - 턴캡 최종제안 보장 — 캡 종료도 "끝내기 전 한 번 더"를 거침
- 상대 발화 LLM 파싱 (보류 중 — 프론트 입력 UI 변경 필요)
- 실로그 축적 후: 예절 봉투(③④⑤) 해제 실험 → LLM 협력사 셀프플레이 (집컴 GPU 단계)
- (소소) negodata 프론트 "목표 마진율 1000%" 표시 버그 후보