[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
This commit is contained in:
134
docs/design/312-tool-prefix-corpus/fn-corpus_logger.md
Normal file
134
docs/design/312-tool-prefix-corpus/fn-corpus_logger.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# 함수 설계서: `CorpusLogger` (#312)
|
||||
|
||||
> **부모 설계서**: ./README.md · **상태**: Draft
|
||||
> **작성**: [AI] Architect · **구현**: `app/lib/ai/diagnostics/corpus_logger.dart` (신규) · **테스트**: `app/test/ai/diagnostics/corpus_logger_test.dart` (신규)
|
||||
|
||||
## 1. 시그니처
|
||||
```dart
|
||||
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 test` invocation 또는 mockable wrapper 로 분리. Architect 권고 = wrapper (`bool _readEnableFlag()` 를 visibleForTesting 으로 expose) 로 테스트 간소화.
|
||||
|
||||
## 11. 추적성
|
||||
- 인수조건: AC1 (corpus 수집 인프라).
|
||||
- 관련 ADR: 없음 (한시적 진단 도구).
|
||||
- 본 이슈 종료 후 제거 여부 검토 — follow-up 이슈로 발행 권장 ("CorpusLogger 정리 — corpus 결과 반영 후 logger 제거 또는 영구화 결정").
|
||||
Reference in New Issue
Block a user