[fix] solution/backend: 새 도구의 인자가 모델에 닿지 않던 것 — 응답 스키마 칸 부족

실배포 확인에서 잡았다(킹서버, 2026-09-28). 도구 선택은 6/6 정확했는데 인자가
엉뚱하게 왔다:
  move_section       기대 {name,to}  → 실제 {key,value}
  hide_photo         기대 {name}     → 실제 {key}
  set_primary_photo  기대 {name}     → 실제 {key}

응답 스키마의 args 가 {key,value,keyword} 로 고정돼 있어 모델이 name·to·enabled 를
넣을 자리가 없었다. 이대로면 새 도구 다섯이 전부 "못 찾았어요" 로 끝난다.

- prompts/agent: args 에 name·to·enabled 추가(strict 라 required 도 같이)
- ★ test_도구가_선언한_인자는_응답_스키마에_있다 추가 — 다른 테스트는 _choose 를
  monkeypatch 해서 이 층을 건너뛴다. 그래서 단위 테스트가 전부 초록인데도 실제로는
  안 됐다. 소스로 대조해 같은 일이 다시 나지 않게 한다

test_agent_runtime 33 passed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hbyang 2026-09-28 15:33:55 +09:00
parent af6e3799eb
commit 47bde7388e
2 changed files with 23 additions and 1 deletions

View File

@ -20,14 +20,22 @@ RESPONSE_SCHEMA = {
"tool": {"type": "string"}, "tool": {"type": "string"},
# ★ strict 모드는 모든 프로퍼티를 required 로 만든다(llm/openai._to_strict_schema). # ★ strict 모드는 모든 프로퍼티를 required 로 만든다(llm/openai._to_strict_schema).
# 그래서 안 쓰는 인자는 빈 문자열로 온다 — 도구는 "" 를 '없음' 으로 읽는다. # 그래서 안 쓰는 인자는 빈 문자열로 온다 — 도구는 "" 를 '없음' 으로 읽는다.
# ★ 여기 없는 이름은 모델이 채울 자리가 없다 — 도구가 아무리 선언해도 빈손으로 온다.
# 실측(2026-09-28, 킹서버): move_section 이 {name,to} 를 받는데 스키마에 그 칸이 없어
# {key,value} 로 왔고, 도구는 "어느 부분인지 못 찾았어요" 로 끝났다. 도구 선택은
# 6/6 정확했는데도 그랬다 — 단위 테스트는 _choose 를 대신해서 이 층을 건너뛴다.
# 그래서 `test_도구가_선언한_인자는_응답_스키마에_있다` 가 소스로 대조한다.
"args": { "args": {
"type": "object", "type": "object",
"properties": { "properties": {
"key": {"type": "string"}, "key": {"type": "string"},
"value": {"type": "string"}, "value": {"type": "string"},
"keyword": {"type": "string"}, "keyword": {"type": "string"},
"name": {"type": "string"},
"to": {"type": "string"},
"enabled": {"type": "string"},
}, },
"required": ["key", "value", "keyword"], "required": ["key", "value", "keyword", "name", "to", "enabled"],
}, },
# 도구를 못 고른 경우에만 쓴다(되묻기·안내). # 도구를 못 고른 경우에만 쓴다(되묻기·안내).
"message": {"type": "string"}, "message": {"type": "string"},

View File

@ -463,3 +463,17 @@ async def test_업로드_도구는_만들지_않았다():
assert tools.REGISTRY["list_photos"].grade == ToolGrade.READ assert tools.REGISTRY["list_photos"].grade == ToolGrade.READ
assert tools.REGISTRY["hide_photo"].grade == ToolGrade.REVERSIBLE assert tools.REGISTRY["hide_photo"].grade == ToolGrade.REVERSIBLE
assert tools.REGISTRY["set_primary_photo"].grade == ToolGrade.REVERSIBLE assert tools.REGISTRY["set_primary_photo"].grade == ToolGrade.REVERSIBLE
def test_도구가_선언한_인자는_응답_스키마에_있다():
"""★ 스키마에 없는 이름은 모델이 채울 자리가 없다 — 도구가 선언해도 빈손으로 온다.
실측(2026-09-28, 킹서버): move_section 이 {name,to} 를 받는데 스키마에 그 칸이 없어
{key,value} 로 왔고 도구는 "못 찾았어요" 로 끝났다. **도구 선택은 6/6 정확했는데도** 그랬다 —
다른 테스트는 `_choose` 를 monkeypatch 해서 이 층을 통째로 건너뛰므로 아무도 못 잡는다."""
from services.prompts import agent as prompt
allowed = set(prompt.RESPONSE_SCHEMA["properties"]["args"]["properties"])
for name, tool in tools.REGISTRY.items():
missing = set(tool.args) - allowed
assert not missing, f"{name} 이 선언한 인자가 응답 스키마에 없다: {missing}"