[fix] solution/backend: 카톡 응답이 5초 벽에 계속 걸리던 것 — 프롬프트 축소
실사용에서 모든 발화가 4.5초 상한에 걸려 "확인하는 데 시간이 조금 걸리네요" 만
반복됐다(킹서버 로그: 4.60s · 4.51s · 4.51s).
- 이미 연결된 사람이 코드를 또 보내면 LLM 을 부르지 않는다. 실제로 그랬고,
6자리가 그냥 발화로 넘어가 유료 호출 + 대기만 쌓였다
- 사이트 상태를 프롬프트에서 뺀다. 그 한 줄 때문에 매 턴 사이트 조회 + 슬러그
계산이 돌았고, 정작 필요할 때는 get_site_status 도구를 부르면 된다
- fact 는 key:value 만, 상한 30개. label 은 항목 목록에 이미 있어 두 번 보내면
프롬프트만 커지고 모델이 얻는 것이 없다
- 항목 목록도 JSON 대신 `key: 이름` 줄로
★ 근본 해결은 콜백이다(f500210). 오픈빌더에서 '콜백 사용' 이 꺼져 있으면
callbackUrl 이 안 와서 조용히 동기 경로로만 돈다 — 지금 로그가 그 상태다.
test_kakao_webhook·test_agent_runtime 43 passed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f500210cd8
commit
bbcdf78c62
@ -224,6 +224,11 @@ async def handle(utterance: str, channel_user_key: str) -> dict:
|
||||
if user is None:
|
||||
return _say("계정을 찾지 못했어요. 관리자 화면에서 다시 연결해 주세요.")
|
||||
|
||||
# ★ 이미 연결된 사람이 코드를 또 보내는 일이 실제로 있었다(2026-09-22). 그대로 두면
|
||||
# 6자리가 그냥 발화로 모델에 넘어가 유료 호출 + 대기만 쌓인다 — 여기서 끊는다.
|
||||
if CODE_PATTERN.fullmatch(utterance.upper()):
|
||||
return _say("이미 연결되어 있어요. 바로 말씀하시면 됩니다.\n예) 체크인 시간 3시로 바꿔줘")
|
||||
|
||||
# ── 확인 이어받기 ────────────────────────────────────────────────────
|
||||
pending = None
|
||||
if row.pending_tool and row.pending_expires_at and row.pending_expires_at > _now():
|
||||
|
||||
@ -71,14 +71,18 @@ async def _context_facts(user: UserInfo, place_id: str, place) -> list[dict]:
|
||||
res = await FactService(FactCRUD(), PlaceCRUD()).list_facts(user, place_id, publishable_only=True)
|
||||
schema = get_schema(PlaceCategory(place.category))
|
||||
out = []
|
||||
for f in (res.facts or [])[:60]:
|
||||
for f in (res.facts or []):
|
||||
spec = schema.get(f.key)
|
||||
if spec and spec.scope == "place" and (f.value or "").strip():
|
||||
out.append({"key": f.key, "label": spec.label, "value": f.value})
|
||||
return out
|
||||
# ★ label 은 싣지 않는다 — 아래 '항목 목록' 에 이미 key↔label 이 있다.
|
||||
# 같은 표를 두 번 보내면 프롬프트만 커지고 모델이 얻는 것은 없다.
|
||||
out.append({f.key: f.value})
|
||||
# ★ 상한을 둔다. 실측(2026-09-22): 필드 43 + fact 수십 개가 실린 프롬프트가 5초 벽을
|
||||
# 넘겼다. 무한정 싣지 않는다 — 대화 한 턴에 필요한 맥락은 그렇게 많지 않다.
|
||||
return out[:30]
|
||||
|
||||
|
||||
async def _choose(place, fields, facts, site_line, message) -> dict:
|
||||
async def _choose(place, fields, facts, message) -> dict:
|
||||
"""LLM 한 번. 고른 도구 이름과 인자만 받는다."""
|
||||
active = provider.active()
|
||||
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as client:
|
||||
@ -90,7 +94,6 @@ async def _choose(place, fields, facts, site_line, message) -> dict:
|
||||
tools=registry.describe(),
|
||||
fields=fields,
|
||||
facts=facts,
|
||||
site={"요약": site_line},
|
||||
message=message,
|
||||
),
|
||||
response_schema=prompt.RESPONSE_SCHEMA,
|
||||
@ -126,10 +129,11 @@ async def chat(user: UserInfo, place_id: str, message: str, confirm: dict | None
|
||||
|
||||
fields = registry.fields_of(place)
|
||||
facts = await _context_facts(user, place_id, place)
|
||||
site_line = await registry.REGISTRY["get_site_status"].run(ctx, {})
|
||||
# ★ 사이트 상태는 프롬프트에 싣지 않는다. 그 한 줄 때문에 매 턴 사이트 조회 + 슬러그 계산이
|
||||
# 돌았고, 정작 모델이 필요할 때는 `get_site_status` 도구를 부르면 된다.
|
||||
|
||||
try:
|
||||
choice = await _choose(place, fields, facts, site_line, message)
|
||||
choice = await _choose(place, fields, facts, message)
|
||||
except LlmError as ex:
|
||||
LOG.w(f"[agent] 도구 선택 실패: {type(ex).__name__}")
|
||||
raise AgentError("AGENT_CALL_FAILED") from ex
|
||||
|
||||
@ -36,7 +36,7 @@ RESPONSE_SCHEMA = {
|
||||
}
|
||||
|
||||
|
||||
def build_prompt(*, place_name: str, tools: list[dict], fields: list[dict], facts: list[dict], site: dict, message: str) -> str:
|
||||
def build_prompt(*, place_name: str, tools: list[dict], fields: list[dict], facts: list[dict], message: str) -> str:
|
||||
"""사장님 발화 → 도구 하나.
|
||||
|
||||
★ 모호하면 실행하지 말고 되물으라고 명시한다. 티오더가 "유사한 메뉴가 2개 이상이면
|
||||
@ -55,14 +55,11 @@ def build_prompt(*, place_name: str, tools: list[dict], fields: list[dict], fact
|
||||
쓸 수 있는 도구:
|
||||
{json.dumps(tools, ensure_ascii=False, indent=1)}
|
||||
|
||||
가게 정보에 쓸 수 있는 항목(set_fact 의 key 는 반드시 이 중 하나다):
|
||||
{json.dumps(fields, ensure_ascii=False)}
|
||||
가게 정보에 쓸 수 있는 항목 — `key: 이름` (set_fact 의 key 는 반드시 이 중 하나다):
|
||||
{chr(10).join(f"{f['key']}: {f['label']}" for f in fields)}
|
||||
|
||||
지금 저장된 값:
|
||||
{json.dumps(facts, ensure_ascii=False)}
|
||||
|
||||
사이트 상태:
|
||||
{json.dumps(site, ensure_ascii=False)}
|
||||
|
||||
사장님 요청:
|
||||
{message}'''
|
||||
|
||||
Loading…
Reference in New Issue
Block a user