[chore] solution/backend: 에이전트 대화창 다시 염 — AGENT_CHAT_ENABLED 기본 1

카카오톡 채널의 통신사 인증이 끝나 어제 걸어 둔 보류를 푼다. 코드는 어제도 오늘도
그대로고 값만 바꿨다 — 닫고 여는 일이 커밋을 되짚는 일이 되면 안 된다는 어제
판단이 하루 만에 값을 쳤다.

- config/agent_config: 기본값 0 → 1
- ★ 켜도 LLM 키가 없으면 안 열린다(runtime.is_configured 가 스위치와 키를 둘 다
  본다). 키 없는 환경에서 켜 둔 채 잊어도 "눌러도 안 되는 입구" 가 안 생긴다
- 스위치 테스트를 새 기본값에 맞춰 갱신 — 키가 없을 때도 안 열리는 것을 함께 검사

카카오 연결 카드는 아직 감춰져 있다(KAKAO_CHANNEL_PUBLIC_ID 미설정). 채우면 코드는
발급되지만 소비할 웹훅(4단계)이 없어 연결이 완성되지 않는다.

test_agent_runtime·test_kakao_link 34 passed. npm run lint 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hbyang 2026-09-22 14:17:48 +09:00
parent acd5383b0f
commit 5b54daac10
7 changed files with 59 additions and 36 deletions

View File

@ -83,9 +83,10 @@ ALIMTALK_TEMPLATE_CODE=
# 묶어야 한다. 빌더에서 코드를 받아 채널에 한 번 입력하는 절차다. # 묶어야 한다. 빌더에서 코드를 받아 채널에 한 번 입력하는 절차다.
# ★ 이 값이 비면 연결 화면이 아예 안 뜬다 — 어디에 코드를 칠지 말해 줄 수 없는데 # ★ 이 값이 비면 연결 화면이 아예 안 뜬다 — 어디에 코드를 칠지 말해 줄 수 없는데
# 코드만 발급하면 사장님에게는 고장난 화면이다. # 코드만 발급하면 사장님에게는 고장난 화면이다.
# ★ 사장님 대화창(에이전트). 기본 꺼짐 — 카카오톡 채널이 준비되기 전에는 띄우지 않는다. # 사장님 대화창(에이전트). 1=사용, 0=감춤.
# 코드는 다 있지만 채널 없이 열어 두면 사장님에게는 어디에도 닿지 않는 입구다. # ★ 1 이어도 LLM 키가 없으면 안 열린다 — 키 없는 환경에서 켜 둔 채 잊어도
AGENT_CHAT_ENABLED=0 # "눌러도 안 되는 입구" 가 생기지 않는다.
AGENT_CHAT_ENABLED=1
KAKAO_CHANNEL_PUBLIC_ID= KAKAO_CHANNEL_PUBLIC_ID=
KAKAO_LINK_CODE_TTL_MIN=10 KAKAO_LINK_CODE_TTL_MIN=10
KAKAO_LINK_MAX_ATTEMPTS=5 KAKAO_LINK_MAX_ATTEMPTS=5

View File

@ -169,9 +169,11 @@
`services/*` 를 통과한다. `collect_service.store_facts` 가 크롤러에 걸어 둔 그 문이다. `services/*` 를 통과한다. `collect_service.store_facts` 가 크롤러에 걸어 둔 그 문이다.
- **카카오 채널 발화자는 우리 `user_id` 가 아니다** — 채널 단위 익명 키다. - **카카오 채널 발화자는 우리 `user_id` 가 아니다** — 채널 단위 익명 키다.
`owner_kakao_links` 매핑 없이 발화자를 믿으면 **채널 진입점만 소유자 범위 밖**에 놓인다. `owner_kakao_links` 매핑 없이 발화자를 믿으면 **채널 진입점만 소유자 범위 밖**에 놓인다.
- **에이전트 화면은 2026-09-21 기준 감춰져 있다**`AGENT_CHAT_ENABLED=0`(기본) · - **에이전트 대화창은 스위치와 LLM 키를 둘 다 본다**(`AGENT_CHAT_ENABLED`, 기본 `1`).
`KAKAO_CHANNEL_PUBLIC_ID` 빔. 카카오톡 채널이 보류돼서이고 **코드는 멀쩡하다**. 키만 보면 "잠시 닫아 두기" 가 키를 지우는 일이 되어 소개문·사진분류까지 꺼지고,
"기능이 없다" 고 판단해 지우지 않는다([AGENT.md](docs/AGENT.md)). 스위치만 보면 키 없는 환경에 **눌러도 안 되는 입구**가 생긴다.
카카오 연결 카드는 `KAKAO_CHANNEL_PUBLIC_ID` 가 비면 감춰진다 —
웹훅(4단계)이 없어 코드를 보내도 연결이 완성되지 않기 때문이다([AGENT.md](docs/AGENT.md)).
- **에이전트 등급을 모델이 정하게 두지 않는다** — 확인이 필요한 행위인지는 `services/agent/tools.py` - **에이전트 등급을 모델이 정하게 두지 않는다** — 확인이 필요한 행위인지는 `services/agent/tools.py`
레지스트리가 못 박는다. 응답 스키마에 그 칸을 만들면 프롬프트에 끼어든 한 줄이 확인 절차를 건너뛴다. 레지스트리가 못 박는다. 응답 스키마에 그 칸을 만들면 프롬프트에 끼어든 한 줄이 확인 절차를 건너뛴다.
- **실행 결과 문구를 LLM 이 쓰게 두지 않는다** — 모델은 **하지 않은 일을 했다고 말할 수 있고**, - **실행 결과 문구를 LLM 이 쓰게 두지 않는다** — 모델은 **하지 않은 일을 했다고 말할 수 있고**,

View File

@ -7,22 +7,24 @@
지금까지 만든 것은 **1단계(신원 연결)****2단계(도구·런타임·빌더 채팅창)** 다. 지금까지 만든 것은 **1단계(신원 연결)****2단계(도구·런타임·빌더 채팅창)** 다.
카카오 채널 웹훅은 아직 없다. 카카오 채널 웹훅은 아직 없다.
## ★ 지금은 화면에서 감춰져 있다 (2026-09-21 보류) ## 화면 스위치
카카오톡 채널 개설이 **법인폰 본인인증**에 걸려 보류됐다. 채널이 없으면 이 기능은 | 화면 | 여는 조건 | 지금 |
사장님에게 **어디에도 닿지 않는 입구**다 — 열어 두면 "되는 기능" 으로 오해한다. |---|---|---|
| 대화창(`AgentChatDock`) | `AGENT_CHAT_ENABLED=1`(기본) **그리고** LLM 키 | 열림 |
| 연결 카드(`KakaoChannelCard`) | `KAKAO_CHANNEL_PUBLIC_ID` 가 채워짐 | 채널 ID 미설정 |
| 화면 | 감추는 조건 | ★ 스위치와 키를 **둘 다** 본다(`runtime.is_configured`). 키만 보면 "잠시 닫아 두기" 를 키를
|---|---| 지워서 해야 하고 그러면 소개문·사진분류까지 같이 꺼진다. 스위치만 보면 키 없는 환경에서
| 대화창(`AgentChatDock`) | `AGENT_CHAT_ENABLED=0` (기본값) | **눌러도 안 되는 입구**가 생긴다.
| 연결 카드(`KakaoChannelCard`) | `KAKAO_CHANNEL_PUBLIC_ID` 가 빔 (기본값) |
**코드는 그대로 두고 설정으로만 닫았다.** 채널이 준비되면 값 둘을 채우고 다시 띄우면 된다 — ★ 2026-09-21 에 카카오 채널 개설이 법인폰 본인인증에 걸려 한 번 닫았고,
되돌릴 때 커밋을 되짚지 않는다. 서버도 함께 닫힌다(`runtime.is_configured()` 가 스위치를 인증이 끝나 2026-09-22 에 다시 열었다. **그때도 코드는 지우지 않고 값만 바꿨다**
보므로, 화면을 우회해 API 를 직접 불러도 `AGENT_NOT_CONFIGURED` 다). 닫고 여는 일이 커밋을 되짚는 일이 되면 안 된다.
★ Threads 카드는 반대로 '자리는 두고 버튼만 죽이는' 쪽이다. 저쪽은 사장님이 **곧 쓸 수 있는** ★ 연결 카드를 '감추는' 쪽으로 둔 것은 Threads 카드('자리는 두고 버튼만 죽인다')와 반대
기능이라 존재를 알려야 했고, 이쪽은 언제 열릴지 말해 줄 수 없다. 판단이 갈린 이유가 그것이다. 판단인데 의도한 차이다. 저쪽은 사장님이 곧 쓸 수 있는 기능이라 존재를 알려야 했고,
이쪽은 웹훅(4단계)이 없어 아직 연결이 **완성되지 않는다**.
## 왜 신원 연결이 먼저인가 ## 왜 신원 연결이 먼저인가

View File

@ -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 — 에이전트 화면 보류: 설정으로 닫는다(코드는 그대로) ## 2026-09-21 — 에이전트 화면 보류: 설정으로 닫는다(코드는 그대로)
카카오톡 채널 개설이 **법인폰 본인인증**에 걸려 보류됐다(사장님 지시: "이 작업은 여기서 딱 카카오톡 채널 개설이 **법인폰 본인인증**에 걸려 보류됐다(사장님 지시: "이 작업은 여기서 딱

View File

@ -23,10 +23,12 @@ class AgentConfig(BaseSettings):
# 코드가 짧아서(사람이 손으로 친다) 무차별 대입이 가능하다. 시도 수로 끊는다. # 코드가 짧아서(사람이 손으로 친다) 무차별 대입이 가능하다. 시도 수로 끊는다.
KAKAO_LINK_MAX_ATTEMPTS: int = 5 KAKAO_LINK_MAX_ATTEMPTS: int = 5
# ★ 기본 꺼짐. 카카오톡 채널 개설이 법인폰 본인인증에 걸려 보류됐고(2026-09-21), # 빌더 화면의 대화창. 2026-09-21 에 한 번 닫았다가(카카오 채널 보류) 채널 인증이
# 채널 없이 대화창만 띄우면 사장님에게는 **어디에도 닿지 않는 입구**가 된다. # 끝나 다시 열었다(2026-09-22).
# 코드는 그대로 두고 이 값으로만 연다 — 되돌릴 때 커밋을 되짚지 않아도 된다. # ★ 이 값이 "1" 이어도 **LLM 키가 없으면 안 열린다**(runtime.is_configured 가 둘 다 본다) —
AGENT_CHAT_ENABLED: str = "0" # 키 없는 환경에서 켜 둔 채 잊어도 "눌러도 안 되는 입구" 가 생기지 않는다.
# 다시 닫을 일이 생기면 이 값만 "0" 으로 되돌린다. 코드를 되짚지 않는다.
AGENT_CHAT_ENABLED: str = "1"
def get(name, default=""): def get(name, default=""):

View File

@ -45,11 +45,10 @@ class AgentError(RuntimeError):
def is_configured() -> bool: def is_configured() -> bool:
"""대화창을 열 수 있나 — 스위치와 LLM 키를 **둘 다** 본다. """대화창을 열 수 있나 — 스위치와 LLM 키를 **둘 다** 본다.
`AGENT_CHAT_ENABLED` 기본 꺼짐이다(config/agent_config). 카카오톡 채널이 준비되기 스위치(`AGENT_CHAT_ENABLED`) 키를 ** ** 보는 이유: 키만 보면 "잠시 닫아 두기"
전에는 대화창을 띄우지 않는다 코드는 있지만 사장님 입장에서는 어디에도 닿지 않는 키를 지워서 해야 하는데 그러면 소개문·사진분류까지 같이 꺼진다. 스위치만 보면
입구이고, 열려 있으면 "되는 기능" 으로 오해한다. 없는 환경에서 **눌러도 되는 입구** 생긴다.
이건 Threads 카드처럼 '자리는 두고 버튼만 죽이는' 경우와 다르다. 저쪽은 사장님이 실제로 2026-09-21 카카오 채널 보류로 닫았고, 채널 인증이 끝나 다시 열었다."""
** 있는** 기능이라 존재를 알려야 했고, 이쪽은 아직 제품이 아니다."""
return config.chat_enabled() and provider.active().is_configured() return config.chat_enabled() and provider.active().is_configured()

View File

@ -227,20 +227,22 @@ def test_읽기_도구는_확인을_요구하지_않는다():
# ── 보류 스위치 ───────────────────────────────────────────────────────── # ── 보류 스위치 ─────────────────────────────────────────────────────────
def test_스위치가_꺼져_있으면_키가_있어도_안_열린(monkeypatch): def test_스위치와_키를_둘_다_본(monkeypatch):
"""기본이 꺼짐이다. 카카오톡 채널이 준비되기 전에는 대화창을 띄우지 않는다 — """키만 보면 '잠시 닫아 두기' 를 키를 지워서 해야 하고, 그러면 소개문·사진분류까지
코드는 있지만 사장님에게는 어디에도 닿지 않는 입구.""" 같이 꺼진다. 스위치만 보면 없는 환경에서 **눌러도 되는 입구** 생긴."""
monkeypatch.setattr(runtime.provider, "active", lambda: SimpleNamespace(is_configured=lambda: True)) monkeypatch.setattr(runtime.provider, "active", lambda: SimpleNamespace(is_configured=lambda: True))
monkeypatch.delenv("AGENT_CHAT_ENABLED", raising=False) monkeypatch.delenv("AGENT_CHAT_ENABLED", raising=False)
assert runtime.is_configured() is False assert runtime.is_configured() is True # 기본은 켜짐(2026-09-22)
monkeypatch.setenv("AGENT_CHAT_ENABLED", "1")
assert runtime.is_configured() is True
monkeypatch.setenv("AGENT_CHAT_ENABLED", "0") 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): async def test_닫아_두면_대화_요청은_거절된다(client, auth_headers, monkeypatch, db_engine):
monkeypatch.delenv("AGENT_CHAT_ENABLED", raising=False) monkeypatch.setenv("AGENT_CHAT_ENABLED", "0")
h, pid = await seed(client, auth_headers) h, pid = await seed(client, auth_headers)
res = await client.post(f"/v1/agent/chat/{pid}", headers=h, json={"message": "안녕"}) res = await client.post(f"/v1/agent/chat/{pid}", headers=h, json={"message": "안녕"})
assert res.status_code == 409 assert res.status_code == 409