Files
life-helper/docs/design/312-tool-prefix-corpus/fn-corpus_logger.md
joungmin 94a9cd474b [Architect] #312 design spec — tool call prefix corpus & 조건부 push
설계서 3 + 절차서 1.
- README.md: 기능 설계서 (15 케이스 corpus, 임계 5/15, 경로 A/B)
- fn-corpus_logger.md: optional debug logger (kDebugMode + dart-define 가드)
- fn-userTurn_partial_push.md: chat_providers.dart 의 break 분기 수정안 (경로 A/B)
- corpus-procedure.md: 빌드/캡처/15 프롬프트/임계 판정 절차

R1-R5 모두 해소 (Architect 채택안).
ADR-0006 슬롯 = 경로 B 채택 시 작성 (Developer 단계).

Refs #312
2026-06-15 14:17:47 +09:00

5.8 KiB

함수 설계서: CorpusLogger (#312)

부모 설계서: ./README.md · 상태: Draft 작성: [AI] Architect · 구현: app/lib/ai/diagnostics/corpus_logger.dart (신규) · 테스트: app/test/ai/diagnostics/corpus_logger_test.dart (신규)

1. 시그니처

abstract class CorpusLogger {
  void onTextChunk(int turn, String text);
  void onFunctionCall(
    int turn,
    String accumulatedPrefix,
    String toolName,
    Map<String, dynamic> args,
  );
}

class DebugCorpusLogger implements CorpusLogger {
  /// kDebugMode + --dart-define=ENABLE_CORPUS_LOG=1 일 때만 non-null.
  /// production 빌드에서는 항상 null.
  static CorpusLogger? maybeCreate();

  @override
  void onTextChunk(int turn, String text);

  @override
  void onFunctionCall(...);
}

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

ChatSessionController 의 event 루프에서 발생한 텍스트 청크와 function call 의 raw payload 를 디버그 빌드에서 stdout 으로 dump 한다 — corpus 수집 부담을 줄이기 위한 한시적 진단 도구.

3. 입력

onTextChunk

파라미터 타입 제약/검증 설명
turn int ≥0 userTurn 내 multi-turn 루프의 turn index (0=첫 LLM 응답).
text String non-null 도착한 텍스트 청크 (raw, 누적 X).

onFunctionCall

파라미터 타입 제약/검증 설명
turn int ≥0 동일.
accumulatedPrefix String non-null tool call 도착 시점까지의 누적 텍스트 (chunks 의 concat).
toolName String non-null, non-empty Gemma 가 호출한 도구 이름.
args Map<String, dynamic> non-null tool 인자. JSON serializable 가정.

maybeCreate

  • 입력 없음.

4. 출력

  • onTextChunk / onFunctionCall: 반환 없음. 부수효과 = stdout 한 줄 emit (디버그). I/O 실패 시 swallow.
  • maybeCreate: 반환 CorpusLogger?kDebugMode 가 true 이고 const bool.fromEnvironment('ENABLE_CORPUS_LOG') 가 true 일 때 DebugCorpusLogger() 인스턴스, 그 외 null.

5. 동작 / 알고리즘

DebugCorpusLogger.onTextChunk

1. _emit({
     'kind': 'text_chunk',
     'turn': turn,
     'text': text,
   });

DebugCorpusLogger.onFunctionCall

1. _emit({
     'kind': 'function_call',
     'turn': turn,
     'accumulated_prefix': accumulatedPrefix,
     'tool_name': toolName,
     'args': args,
   });

_emit(Map<String, dynamic> payload)

1. try:
2.   final line = '[CorpusLogger] ' + jsonEncode(payload);
3.   developer.log(line, name: 'CorpusLogger');
4. catch (_):
5.   // swallow — diagnostic 이 user flow 를 깨지 않게.

DebugCorpusLogger.maybeCreate

1. if (!kDebugMode) return null;
2. const enabled = bool.fromEnvironment('ENABLE_CORPUS_LOG', defaultValue: false);
3. if (!enabled) return null;
4. return DebugCorpusLogger._();

6. 에러 & 실패 모드

조건 처리 반환/예외
jsonEncode 가 args 의 non-serializable 키로 throw _emit 의 try/catch swallow void (silent)
developer.log I/O 실패 swallow void
maybeCreate 가 production 호출 kDebugMode=false 분기에서 null null (정상)
ctor 직접 호출 시도 private ctor _() 로 차단 컴파일 에러

7. 엣지케이스

  • 매우 빈번한 호출: 토큰 단위 stream 이라 onTextChunk 가 초당 수십 회. developer.log 가 stdout flush 부담 — 단, debug only + 한시적이라 수용.
  • args 에 BigInt / DateTime: jsonEncode 가 throw → swallow. corpus 결과 누락 시 코드 보강 (toString fallback) 가능하지만 본 설계는 swallow 만.
  • multi-turn 루프: 같은 userTurn 내에서 turn 0, 1, 2 ... 각각의 prefix 가 모두 캡처되어야 비교 가능 — caller (ChatSessionController) 가 정확한 turn idx 를 넘긴다.
  • logger null 이지만 inject 됨: callsite 가 logger?.onTextChunk(...) 패턴이므로 null-safe.

8. 복잡도 / 성능

  • 시간: 각 호출 O(payload 크기). jsonEncode 가 prefix 길이에 선형.
  • 공간: emit 마다 임시 string. 영구 보관 없음 (stdout sink).
  • 호출 빈도: 토큰당 1회 (text_chunk), tool call 당 1회 (function_call). 한 userTurn 에 수십-수백 호출 가능 — debug only 라 수용.

9. 의존성

  • dart:convertjsonEncode.
  • dart:developerlog.
  • flutter/foundation.dartkDebugMode.
  • 환경 변수: ENABLE_CORPUS_LOG (dart-define).

10. 테스트 케이스

  • maybeCreate: production 시뮬 (kDebugMode false fake) → null 반환.
  • maybeCreate: debug + ENABLE_CORPUS_LOG=false → null.
  • maybeCreate: debug + ENABLE_CORPUS_LOG=true → non-null DebugCorpusLogger.
  • onFunctionCall happy: 인자 정상 → stdout 에 [CorpusLogger] 라벨 + JSON 한 줄 emit (capture for verification).
  • onFunctionCall non-serializable args: {'date': DateTime.now()} → throw 안 함 (swallow), test 가 timeout 없이 종료.
  • onTextChunk 빈 텍스트 → swallow 없이 정상 emit (filter 안 함, 무엇이 들어왔는지 그대로 기록하는 게 corpus 의 정직성).

ENABLE_CORPUS_LOG 의 dart-define 기반 테스트는 --dart-define=ENABLE_CORPUS_LOG=true 로 별도 flutter test invocation 또는 mockable wrapper 로 분리. Architect 권고 = wrapper (bool _readEnableFlag() 를 visibleForTesting 으로 expose) 로 테스트 간소화.

11. 추적성

  • 인수조건: AC1 (corpus 수집 인프라).
  • 관련 ADR: 없음 (한시적 진단 도구).
  • 본 이슈 종료 후 제거 여부 검토 — follow-up 이슈로 발행 권장 ("CorpusLogger 정리 — corpus 결과 반영 후 logger 제거 또는 영구화 결정").