"""ScriptModifier — LLM 으로 협상 스크립트 어조/내용을 다듬되 구조·치환자·스타일 보존. openai SDK 직접 호출(langchain 미사용). 프롬프트 문구는 우리 자체 작성(CLEANROOM.md). LLM 미설정/실패 시 원본을 그대로 반환(안전 폴백). """ import json import logging from typing import Optional from negotiation.profiling.config import LlmCredentials logger = logging.getLogger(__name__) _SYSTEM = """역할: 협상 스크립트 문장 다듬기 도우미. 입력은 JSON 블록 리스트다. 각 블록의 text 만 다듬고 그 외 모든 것은 그대로 둔다. 규칙: - 출력은 입력과 동일 구조의 유효한 JSON 이어야 한다. {"script": [...]} 형태로 반환한다. - {price} 같은 치환자는 추가/삭제/변경하지 않는다. 입력에 있던 것만 유지한다. - color/bold 등 스타일 키와 children 배열 구조는 변경하지 않는다. - text 값만 요청된 어조/맥락에 맞게 자연스럽게 다시 쓴다(한국어). - 새로운 키를 추가하지 않는다.""" class ScriptModifier: def __init__(self, creds: Optional[LlmCredentials] = None): self._creds = creds def is_available(self) -> bool: creds = self._creds or LlmCredentials.from_config() return creds.is_configured() def modify_script(self, original_script: list, context: dict = None) -> list: """context(예: {'tone': 'polite'})에 맞춰 스크립트 텍스트 수정. 실패 시 원본 반환.""" if not original_script or not isinstance(original_script, list): return original_script try: from negotiation.profiling.infra.llm_adapter import chat_json messages = [ {"role": "system", "content": _SYSTEM}, {"role": "user", "content": "원본 스크립트:\n" + json.dumps(original_script, ensure_ascii=False) + "\n\n맥락/지시:\n" + json.dumps(context or {}, ensure_ascii=False) + "\n\n위 규칙대로 다듬어 {\"script\": [...]} 로 출력."}, ] result = chat_json(messages, creds=self._creds) modified = result.get("script", result) if isinstance(result, dict) else result return modified if isinstance(modified, list) else original_script except Exception as e: logger.error(f"Failed to modify script via LLM: {e}") return original_script