o2o-negosium-original/agent/docs/완전자율에이전트_변경정리.md
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

15 KiB
Raw Blame History

완전 자율 협상 에이전트 — 처음 대비 변경 정리

기준: 협상카드 + 룰 엔진 시절(처음) → 완전 자율 에이전트 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.py available_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.py 41항목, 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%" 표시 버그 후보