- 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
5.0 KiB
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 들 (각 핸들러가 사용)