설계서 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
5.8 KiB
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:convert—jsonEncode.dart:developer—log.flutter/foundation.dart—kDebugMode.- 환경 변수:
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 testinvocation 또는 mockable wrapper 로 분리. Architect 권고 = wrapper (bool _readEnableFlag()를 visibleForTesting 으로 expose) 로 테스트 간소화.
11. 추적성
- 인수조건: AC1 (corpus 수집 인프라).
- 관련 ADR: 없음 (한시적 진단 도구).
- 본 이슈 종료 후 제거 여부 검토 — follow-up 이슈로 발행 권장 ("CorpusLogger 정리 — corpus 결과 반영 후 logger 제거 또는 영구화 결정").