Files
life-helper/docs/design/260-gemma-tool-calling/fn-tool_dispatcher.md
joungmin eca097aa2c [02-Architect] #260 design spec + ADR-0005
- design/260-gemma-tool-calling/README.md — overall (12 AC + 7 OQ + 모듈 구조)
- fn-tool_dispatcher.md — multi-tool router (validate → confirm gate → handler → envelope)
- fn-add_habit_handler.md — destructive 대표 (R3/R7/R8 enforce)
- fn-confirm_gate.md — 모달 AlertDialog 흐름 (OQ-3 = 모달 확정)
- fn-chat_session_controller.md — multi-turn loop 상태 머신 (MAX_TURNS=4)
- ADR-0005 — in-app tool runtime + R 규칙 = 핸들러 책임 + schema SoT=Dart + 모달

OQ-1/2/4 = README §11 결정. OQ-3 = 모달 (사용자 결정).
신규 OQ-5/6/7 = Developer 가 구현 중 검증.

Refs #260
2026-06-15 10:15:44 +09:00

5.0 KiB

함수 설계서: ToolDispatcher.dispatch (#260)

부모 설계서: ./README.md · 상태: Draft 작성: [AI] Architect · 구현: lib/ai/tools/tool_dispatcher.dart:dispatch · 테스트: test/ai/tools/tool_dispatcher_test.dart

1. 시그니처

Future<ToolResult> dispatch({
  required String toolName,
  required Map<String, dynamic> rawArgs,
  required BuildContext? confirmContext, // null 이면 destructive tool 자동 cancel
  required ToolDeps deps,
});

ToolDeps = { HabitDao habitDao, TrackerDao trackerDao, CatalogRepository catalog, String userId }.

2. 책임 (단일 책임, 1줄)

toolName 으로 핸들러를 찾아, args 검증 → Confirm gate → 핸들러 호출 → 결과를 envelope 으로 감싸 반환한다.

3. 입력

파라미터 타입 제약/검증 설명
toolName String non-empty, registry 에 등록된 이름 LLM 의 FunctionCallResponse.name
rawArgs Map<String, dynamic> 어떤 타입이든 — 검증은 내부에서 LLM 의 FunctionCallResponse.args
confirmContext BuildContext? 살아있는 widget context destructive 가 아니면 무관. null + destructive = 자동 cancel
deps ToolDeps non-null 핸들러가 호출할 Repository 묶음

4. 출력

  • 반환: Future<ToolResult>ToolOk / ToolErr / ToolCancelled. 절대 throw 하지 않음.
  • 부수효과:
    • Confirm gate 호출 시 모달 표시 (UI side effect)
    • 핸들러 내부에서 DB write 가능 (destructive 인 경우만)

5. 동작 / 알고리즘

1. tool = ToolRegistry.byName(toolName)
   if tool == null:
     return ToolErr(code: 'unknown_tool', reason: '알 수 없는 도구: $toolName')

2. validatedArgs = ToolArgsValidator.validate(tool.parametersSchema, rawArgs)
   if validatedArgs is ValidationError:
     return ToolErr(code: 'validation', reason: '인자 오류: ${err.message}')

3. if tool.isDestructive:
     if confirmContext == null:
       return ToolCancelled()
     ok = await ConfirmGate.show(confirmContext, tool, validatedArgs)
     if !ok:
       return ToolCancelled()

4. try:
     payload = await tool.handler(validatedArgs, deps)
     // handler 가 이미 ToolResult 를 반환하는 형태이므로 passthrough
     return payload
   catch (e, st):
     log('tool_error', tool=$toolName, err=$e)
     return ToolErr(code: 'handler_error', reason: '도구 실행 실패: ${e.runtimeType}')

6. 에러 & 실패 모드

조건 처리 반환
toolName 미등록 log warn ToolErr('unknown_tool', ...)
rawArgs schema 위배 log info ToolErr('validation', ...)
destructive + confirmContext null 조용히 ToolCancelled()
사용자 모달 거부 조용히 ToolCancelled()
핸들러 예외 log error + stacktrace ToolErr('handler_error', ...) — 사용자 메시지엔 타입만
핸들러가 R 규칙 위배 detect 핸들러 자체에서 반환 passthrough ToolErr('r3_quota', ...)

불변식: dispatch 는 throw 하지 않는다. 모든 실패 경로는 ToolResult 로 환원.

7. 엣지케이스

  • 빈 args: {} 가 들어와도 schema 가 required 필드 검증으로 잡음.
  • redundant args (스키마에 없는 키): 무시 — 모델이 환각해도 통과시키되 로깅.
  • 모달 race: confirmContext 가 dispatch 호출 후 dispose 되는 경우 → ConfirmGate 내부에서 context.mounted 체크 후 false 반환.
  • dispatch 중 사용자가 chat 화면 dismiss: 핸들러는 계속 실행됨 (취소 안 함). 결과는 폐기되지만 DB write 는 commit 된 채 남음. ChatSessionController 가 lifecycle 책임짐 (Architect 결정: side effect 보존 = 사용자가 의도적으로 chat 닫았다고 가정).

8. 복잡도 / 성능

  • O(1) registry lookup (Map<String, ToolDefinition>).
  • args validate ≤ 50자 keyword 등 small payload — O(n) JSON schema 매칭.
  • 호출 빈도: 사용자 대화 turn 당 0~N (보통 0 또는 1). 폴링 루프 아님.
  • 메모리: stateless — instance 변수 없음.

9. 테스트 케이스 (필수)

케이스 입력 기대
unknown tool dispatch('foo', {}, ...) ToolErr('unknown_tool', ...)
validation fail dispatch('add_habit', {'protocol_id': 123}, ...) ToolErr('validation', ...) (123 is int not string)
destructive + null context dispatch('add_habit', validArgs, null, ...) ToolCancelled()
destructive + user accept mock ConfirmGate → true handler 결과 그대로
destructive + user reject mock ConfirmGate → false ToolCancelled()
handler throw mock handler throws ToolErr('handler_error', ...)
read-only normal dispatch('search_catalog', validArgs, null, ...) ToolOk(data:...)

10. 의존

  • ToolRegistry (정적 lookup)
  • ToolArgsValidator (JSON schema validator — 간단 자체 구현 권장)
  • ConfirmGate.show (UI)
  • ToolDeps 내부의 Repository 들 (각 핸들러가 사용)