diff --git a/.env.example b/.env.example index c955e38..f216d96 100644 --- a/.env.example +++ b/.env.example @@ -83,9 +83,10 @@ ALIMTALK_TEMPLATE_CODE= # 묶어야 한다. 빌더에서 코드를 받아 채널에 한 번 입력하는 절차다. # ★ 이 값이 비면 연결 화면이 아예 안 뜬다 — 어디에 코드를 칠지 말해 줄 수 없는데 # 코드만 발급하면 사장님에게는 고장난 화면이다. -# ★ 사장님 대화창(에이전트). 기본 꺼짐 — 카카오톡 채널이 준비되기 전에는 띄우지 않는다. -# 코드는 다 있지만 채널 없이 열어 두면 사장님에게는 어디에도 닿지 않는 입구다. -AGENT_CHAT_ENABLED=0 +# 사장님 대화창(에이전트). 1=사용, 0=감춤. +# ★ 1 이어도 LLM 키가 없으면 안 열린다 — 키 없는 환경에서 켜 둔 채 잊어도 +# "눌러도 안 되는 입구" 가 생기지 않는다. +AGENT_CHAT_ENABLED=1 KAKAO_CHANNEL_PUBLIC_ID= KAKAO_LINK_CODE_TTL_MIN=10 KAKAO_LINK_MAX_ATTEMPTS=5 diff --git a/AGENTS.md b/AGENTS.md index 1778e25..c199620 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -169,9 +169,11 @@ `services/*` 를 통과한다. `collect_service.store_facts` 가 크롤러에 걸어 둔 그 문이다. - **카카오 채널 발화자는 우리 `user_id` 가 아니다** — 채널 단위 익명 키다. `owner_kakao_links` 매핑 없이 발화자를 믿으면 **채널 진입점만 소유자 범위 밖**에 놓인다. -- **에이전트 화면은 2026-09-21 기준 감춰져 있다** — `AGENT_CHAT_ENABLED=0`(기본) · - `KAKAO_CHANNEL_PUBLIC_ID` 빔. 카카오톡 채널이 보류돼서이고 **코드는 멀쩡하다**. - "기능이 없다" 고 판단해 지우지 않는다([AGENT.md](docs/AGENT.md)). +- **에이전트 대화창은 스위치와 LLM 키를 둘 다 본다**(`AGENT_CHAT_ENABLED`, 기본 `1`). + 키만 보면 "잠시 닫아 두기" 가 키를 지우는 일이 되어 소개문·사진분류까지 꺼지고, + 스위치만 보면 키 없는 환경에 **눌러도 안 되는 입구**가 생긴다. + 카카오 연결 카드는 `KAKAO_CHANNEL_PUBLIC_ID` 가 비면 감춰진다 — + 웹훅(4단계)이 없어 코드를 보내도 연결이 완성되지 않기 때문이다([AGENT.md](docs/AGENT.md)). - **에이전트 등급을 모델이 정하게 두지 않는다** — 확인이 필요한 행위인지는 `services/agent/tools.py` 레지스트리가 못 박는다. 응답 스키마에 그 칸을 만들면 프롬프트에 끼어든 한 줄이 확인 절차를 건너뛴다. - **실행 결과 문구를 LLM 이 쓰게 두지 않는다** — 모델은 **하지 않은 일을 했다고 말할 수 있고**, diff --git a/docs/AGENT.md b/docs/AGENT.md index a3228a0..b498361 100644 --- a/docs/AGENT.md +++ b/docs/AGENT.md @@ -7,22 +7,24 @@ 지금까지 만든 것은 **1단계(신원 연결)** 와 **2단계(도구·런타임·빌더 채팅창)** 다. 카카오 채널 웹훅은 아직 없다. -## ★ 지금은 화면에서 감춰져 있다 (2026-09-21 보류) +## 화면 스위치 -카카오톡 채널 개설이 **법인폰 본인인증**에 걸려 보류됐다. 채널이 없으면 이 기능은 -사장님에게 **어디에도 닿지 않는 입구**다 — 열어 두면 "되는 기능" 으로 오해한다. +| 화면 | 여는 조건 | 지금 | +|---|---|---| +| 대화창(`AgentChatDock`) | `AGENT_CHAT_ENABLED=1`(기본) **그리고** LLM 키 | 열림 | +| 연결 카드(`KakaoChannelCard`) | `KAKAO_CHANNEL_PUBLIC_ID` 가 채워짐 | 채널 ID 미설정 | -| 화면 | 감추는 조건 | -|---|---| -| 대화창(`AgentChatDock`) | `AGENT_CHAT_ENABLED=0` (기본값) | -| 연결 카드(`KakaoChannelCard`) | `KAKAO_CHANNEL_PUBLIC_ID` 가 빔 (기본값) | +★ 스위치와 키를 **둘 다** 본다(`runtime.is_configured`). 키만 보면 "잠시 닫아 두기" 를 키를 +지워서 해야 하고 그러면 소개문·사진분류까지 같이 꺼진다. 스위치만 보면 키 없는 환경에서 +**눌러도 안 되는 입구**가 생긴다. -**코드는 그대로 두고 설정으로만 닫았다.** 채널이 준비되면 값 둘을 채우고 다시 띄우면 된다 — -되돌릴 때 커밋을 되짚지 않는다. 서버도 함께 닫힌다(`runtime.is_configured()` 가 스위치를 -보므로, 화면을 우회해 API 를 직접 불러도 `AGENT_NOT_CONFIGURED` 다). +★ 2026-09-21 에 카카오 채널 개설이 법인폰 본인인증에 걸려 한 번 닫았고, +인증이 끝나 2026-09-22 에 다시 열었다. **그때도 코드는 지우지 않고 값만 바꿨다** — +닫고 여는 일이 커밋을 되짚는 일이 되면 안 된다. -★ Threads 카드는 반대로 '자리는 두고 버튼만 죽이는' 쪽이다. 저쪽은 사장님이 **곧 쓸 수 있는** -기능이라 존재를 알려야 했고, 이쪽은 언제 열릴지 말해 줄 수 없다. 판단이 갈린 이유가 그것이다. +★ 연결 카드를 '감추는' 쪽으로 둔 것은 Threads 카드('자리는 두고 버튼만 죽인다')와 반대 +판단인데 의도한 차이다. 저쪽은 사장님이 곧 쓸 수 있는 기능이라 존재를 알려야 했고, +이쪽은 웹훅(4단계)이 없어 아직 연결이 **완성되지 않는다**. ## 왜 신원 연결이 먼저인가 diff --git a/docs/DEVLOG.md b/docs/DEVLOG.md index 01ceb00..e8fbce8 100644 --- a/docs/DEVLOG.md +++ b/docs/DEVLOG.md @@ -1,5 +1,20 @@ # 개발 일지 +## 2026-09-22 — 에이전트 대화창 다시 염(기본 켜짐) + +카카오톡 채널의 통신사 인증이 끝나 보류를 푼다(사장님 지시). `AGENT_CHAT_ENABLED` 기본값을 +`0` → `1` 로 돌렸다. **코드는 어제도 오늘도 그대로다** — 닫고 여는 일이 커밋을 되짚는 일이 +되면 안 된다는 어제 판단이 하루 만에 값을 쳤다. + +★ 기본을 켜도 **LLM 키가 없으면 안 열린다**(`runtime.is_configured` 가 스위치와 키를 둘 다 +본다). 키 없는 환경에서 켜 둔 채 잊어도 "눌러도 안 되는 입구" 가 생기지 않는다. + +★ 카카오 연결 카드는 아직 감춰져 있다 — `KAKAO_CHANNEL_PUBLIC_ID` 미설정. +채우면 코드는 발급되지만 **소비할 웹훅(4단계)이 없어 연결이 완성되지 않는다.** +채널 '채팅' 과 '챗봇(오픈빌더 스킬)' 은 다른 기능이고, 웹훅이 붙는 쪽은 후자다. + +**검증** — `test_agent_runtime`(스위치 테스트를 새 기본값에 맞춰 갱신)·`test_kakao_link` 34 passed. + ## 2026-09-21 — 에이전트 화면 보류: 설정으로 닫는다(코드는 그대로) 카카오톡 채널 개설이 **법인폰 본인인증**에 걸려 보류됐다(사장님 지시: "이 작업은 여기서 딱 diff --git a/solution/backend/config/agent_config.py b/solution/backend/config/agent_config.py index 7d13cd9..eca935d 100644 --- a/solution/backend/config/agent_config.py +++ b/solution/backend/config/agent_config.py @@ -23,10 +23,12 @@ class AgentConfig(BaseSettings): # 코드가 짧아서(사람이 손으로 친다) 무차별 대입이 가능하다. 시도 수로 끊는다. KAKAO_LINK_MAX_ATTEMPTS: int = 5 - # ★ 기본 꺼짐. 카카오톡 채널 개설이 법인폰 본인인증에 걸려 보류됐고(2026-09-21), - # 채널 없이 대화창만 띄우면 사장님에게는 **어디에도 닿지 않는 입구**가 된다. - # 코드는 그대로 두고 이 값으로만 연다 — 되돌릴 때 커밋을 되짚지 않아도 된다. - AGENT_CHAT_ENABLED: str = "0" + # 빌더 화면의 대화창. 2026-09-21 에 한 번 닫았다가(카카오 채널 보류) 채널 인증이 + # 끝나 다시 열었다(2026-09-22). + # ★ 이 값이 "1" 이어도 **LLM 키가 없으면 안 열린다**(runtime.is_configured 가 둘 다 본다) — + # 키 없는 환경에서 켜 둔 채 잊어도 "눌러도 안 되는 입구" 가 생기지 않는다. + # 다시 닫을 일이 생기면 이 값만 "0" 으로 되돌린다. 코드를 되짚지 않는다. + AGENT_CHAT_ENABLED: str = "1" def get(name, default=""): diff --git a/solution/backend/services/agent/runtime.py b/solution/backend/services/agent/runtime.py index 1212698..1d53f68 100644 --- a/solution/backend/services/agent/runtime.py +++ b/solution/backend/services/agent/runtime.py @@ -45,11 +45,10 @@ class AgentError(RuntimeError): def is_configured() -> bool: """대화창을 열 수 있나 — 스위치와 LLM 키를 **둘 다** 본다. - ★ `AGENT_CHAT_ENABLED` 가 기본 꺼짐이다(config/agent_config). 카카오톡 채널이 준비되기 - 전에는 대화창을 띄우지 않는다 — 코드는 다 있지만 사장님 입장에서는 어디에도 닿지 않는 - 입구이고, 열려 있으면 "되는 기능" 으로 오해한다. - 이건 Threads 카드처럼 '자리는 두고 버튼만 죽이는' 경우와 다르다. 저쪽은 사장님이 - **곧 쓸 수 있는** 기능이라 존재를 알려야 했고, 이쪽은 아직 제품이 아니다.""" + ★ 스위치(`AGENT_CHAT_ENABLED`)와 키를 **둘 다** 보는 이유: 키만 보면 "잠시 닫아 두기" 를 + 키를 지워서 해야 하는데 그러면 소개문·사진분류까지 같이 꺼진다. 스위치만 보면 + 키 없는 환경에서 **눌러도 안 되는 입구**가 생긴다. + 실제로 2026-09-21 에 카카오 채널 보류로 한 번 닫았고, 채널 인증이 끝나 다시 열었다.""" return config.chat_enabled() and provider.active().is_configured() diff --git a/solution/backend/tests/test_agent_runtime.py b/solution/backend/tests/test_agent_runtime.py index e60298a..2ef663f 100644 --- a/solution/backend/tests/test_agent_runtime.py +++ b/solution/backend/tests/test_agent_runtime.py @@ -227,20 +227,22 @@ def test_읽기_도구는_확인을_요구하지_않는다(): # ── 보류 스위치 ───────────────────────────────────────────────────────── -def test_스위치가_꺼져_있으면_키가_있어도_안_열린다(monkeypatch): - """★ 기본이 꺼짐이다. 카카오톡 채널이 준비되기 전에는 대화창을 띄우지 않는다 — - 코드는 다 있지만 사장님에게는 어디에도 닿지 않는 입구다.""" +def test_스위치와_키를_둘_다_본다(monkeypatch): + """★ 키만 보면 '잠시 닫아 두기' 를 키를 지워서 해야 하고, 그러면 소개문·사진분류까지 + 같이 꺼진다. 스위치만 보면 키 없는 환경에서 **눌러도 안 되는 입구**가 생긴다.""" monkeypatch.setattr(runtime.provider, "active", lambda: SimpleNamespace(is_configured=lambda: True)) monkeypatch.delenv("AGENT_CHAT_ENABLED", raising=False) - assert runtime.is_configured() is False - monkeypatch.setenv("AGENT_CHAT_ENABLED", "1") - assert runtime.is_configured() is True + assert runtime.is_configured() is True # 기본은 켜짐(2026-09-22) monkeypatch.setenv("AGENT_CHAT_ENABLED", "0") - assert runtime.is_configured() is False + assert runtime.is_configured() is False # 스위치로 닫을 수 있다 + + monkeypatch.setenv("AGENT_CHAT_ENABLED", "1") + monkeypatch.setattr(runtime.provider, "active", lambda: SimpleNamespace(is_configured=lambda: False)) + assert runtime.is_configured() is False # 켜 뒀어도 키가 없으면 안 열린다 -async def test_꺼진_동안_대화_요청은_거절된다(client, auth_headers, monkeypatch, db_engine): - monkeypatch.delenv("AGENT_CHAT_ENABLED", raising=False) +async def test_닫아_두면_대화_요청은_거절된다(client, auth_headers, monkeypatch, db_engine): + monkeypatch.setenv("AGENT_CHAT_ENABLED", "0") h, pid = await seed(client, auth_headers) res = await client.post(f"/v1/agent/chat/{pid}", headers=h, json={"message": "안녕"}) assert res.status_code == 409