DOCS/journey/ideas/250815_claude_동적파라미터_프로젝트_원칙.md
Claude-51124 22557e7132 docs: 오래된 트러블슈팅 아카이브 및 구조 정리
- 7-8월 초기 구축 문서 12개를 _archive/troubleshooting/2025_07-08_initial_setup/로 이동
- book/300_architecture/390_human_in_the_loop_intent_learning.md를 journey/research/intent_classification/로 이동 (개발 여정 문서)
- 빈 폴더 제거 (journey/assets/*)
2025-11-17 14:06:05 +09:00

13 KiB

동적 파라미터 관리와 로빙 프로젝트 원칙

하드코딩 없는 유연한 아키텍처 설계

날짜: 2025-08-15
작성자: claude
상태: 아이디어 → 계획 예정

핵심 철학

로빙은 하나의 인격을 가진 생명체입니다. 생명체가 환경에 적응하듯, 로빙도 상황에 맞춰 유연하게 변화해야 합니다. 이를 위해 하드코딩을 배제하고 동적 파라미터 관리 체계를 구축합니다.

1. 하드코딩 배제 원칙

원칙

  • 환경변수 우선: 모든 환경 의존 값은 환경변수로 관리
  • 설정 파일 분리: JSON/YAML 형식으로 코드와 설정 분리
  • 상수 모듈화: constants.py 등 중앙 집중 관리
  • 데이터-로직 분리: 처리 규칙을 외부 파일/DB에 저장

적용 예시

{
  "retrieval": { "top_k": 7, "min_score": 0.78 },
  "memory": { "blocked_terms": ["금지어A", "금지어B"] },
  "privacy": { "mask_before_embed": true }
}

2. 동적 파라미터 관리 구조

2.1 제어면/데이터면 분리

  • 제어면: 설정 저장, 검증, 배포, 감사 로그
  • 데이터면: 요청 처리 시 "현재 설정 묶음" 읽기 전용

2.2 저장 계층

  1. 기본값 템플릿: repo/config/defaults/config.default.json
  2. 중앙 설정 DB: PostgreSQL (운영 원본)
  3. 캐시: Redis (빠른 읽기용)
  4. 민감정보: Secret Manager (API 키 등)

2.3 우선순위

기본값 < 환경변수 < 조직 설정 < 팀 설정 < 사용자 설정 < 런타임 오버라이드

3. 생물학적 비유로 본 아키텍처

3.1 모듈 = 세포

  • 기억 모듈 = 기억 세포
  • 감정 모듈 = 감각 세포
  • 스킬 모듈 = 운동 세포

3.2 설정 변경 = 호르몬과 신경 신호

  • 호르몬 (전신 영향): 시스템 전체 설정 변경

    • retrieval.top_k 값 변경 → 모든 기억 검색 영향
    • privacy 설정 → 전체 데이터 처리 방식 변경
  • 신경 신호 (국소 영향): 특정 모듈만 즉시 변경

    • 특정 대화의 금지어 추가
    • 개별 스킬 파라미터 조정

3.3 작동 흐름

  1. 사용자 명령 (자극)
  2. 중앙 제어 해석 (뇌)
  3. 호르몬/신경 신호 전달
  4. 세포(모듈) 반응 변화

4. LLM 사용 원칙

4.1 LLM이 맡아야 하는 영역 (70%)

  • 의도 파악: 사용자 발화의 실제 목적 해석
  • 계획 수립: 복합 작업을 단계로 분해
  • 비정형 구조화: 회의록, 이메일에서 핵심 추출
  • 자연어 생성: 상황별 톤 적용한 응답
  • 규칙 추론: 명시되지 않은 암묵지 해석

4.2 규칙 기반이어야 하는 영역 (90%)

  • 보안/프라이버시: 금지어 차단, 토큰 마스킹
  • 임계값 판정: 유사도, top-k, 비용 한도
  • 데이터 무결성: 스키마 검증, 범위 체크
  • 대량 반복 처리: 파일 이동, API 호출

4.3 혼합 영역 (LLM 40-60%)

  • 검색 전처리/후처리
  • 중간 판단과 예외 처리
  • 실패 복구 전략

5. 금지어 처리 파이프라인

5.1 쓰기 경로 (저장 시)

  1. 입력 텍스트 토큰화
  2. blocked_terms와 매칭
  3. 마스킹 또는 제거 후 임베딩
  4. ChromaDB에 메타데이터와 함께 저장

5.2 읽기 경로 (검색 시)

  1. ChromaDB에서 후보 검색
  2. 금지어 필터링
  3. 완전 금지 시 해당 결과 제외
  4. 감사 로그 기록

6. 실제 적용 방안

6.1 DB 스키마

CREATE TABLE config_bundle (
  id BIGSERIAL PRIMARY KEY,
  scope_level TEXT CHECK (scope_level IN ('org','team','user','runtime')),
  scope_id TEXT NOT NULL,
  version TEXT NOT NULL,
  config_json JSONB NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now(),
  created_by TEXT NOT NULL
);

6.2 Redis 키 구조

  • robeing:config:active:<scope>:<id> → 현재 설정 묶음
  • robeing:config:version:<version> → 버전별 설정

6.3 Slack 명령 인터페이스

/robeing config add memory.blocked_terms "금지어"
/robeing config set retrieval.top_k=10 --scope user:@kim

7. 안전장치

  • 스키마 검증: 모든 값의 타입과 범위 체크
  • 변화율 제한: 같은 키 1분 1회 제한
  • 자동 롤백: 오류 시 이전 버전 복원
  • 감사 로그: 모든 변경 기록 추적

8. 개발자 vs 사용자 권한

개발자 권한

  • 시스템 상한선, 스키마 변경
  • 보안 관련 설정
  • 위험한 파라미터

사용자 권한

  • 안전 범위 내 파라미터
  • 금지어 목록 관리
  • 개인화 설정

9. 로컬-서버 개발 편의

  • 개발자는 config.default.json만 리포에 관리
  • 실제 운영 값은 중앙 DB/UI로 관리
  • gitignore 문제 해결: 컨테이너는 중앙에서 설정 구독
  • LOCAL_MODE=true 시 로컬 파일 허용

10. 도입 순서

  1. JSON Schema 정의
  2. PostgreSQL 테이블 생성
  3. Redis 캐시 구조 구현
  4. 설정 변경 API 개발
  5. ChromaDB 파이프라인 통합
  6. Slack 명령어 연결
  7. 배치 리인덱싱 작업

11. 실제 구현 현황과 개선 방안

11.1 현재 상황 분석

공통 패턴

  • 프레임워크: pydantic_settings.BaseSettings
  • 로드 방식: .env → 환경변수 → 기본값
  • 접근 방법: from app.config import settings
  • 인스턴스: 싱글톤 패턴 (settings = Settings())

서비스별 구조

서비스 Config 위치 특징
rb8001 app/core/config.py 기본 설정, Optional 타입
rb10408_test app/core/config.py 다중 LLM 지원
rb10508_micro app/config.py 감정/베이지안 설정
skill-embedding config.py Field() 사용, 상세 설명
robeing-gateway 없음 os.getenv() 분산 사용

11.2 도구에서 존재로: 4가지 핵심 축

"도구적 설정"을 "살아있는 존재"로 전환하기 위한 핵심 요소:

  1. 변경의 안전성: 검증된 변경만 적용
  2. 전파의 확실성: 모든 워커에 즉시 반영
  3. 추적 가능성: 모든 변경 이력 보존
  4. 거버넌스: 권한별 접근 제어

11.3 개선 아키텍처

표준 베이스 설정

# app/core/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import BaseModel, Field, ValidationError

class BaseRobeingSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=(".env",),
        env_prefix="ROBEING_",
        case_sensitive=False,
        extra="ignore",
    )
    
    # 정체성 (불변)
    ROBEING_ID: str
    ROBEING_VERSION: str = "1.0.0"
    
    # 시스템 (검증된 범위)
    MEMORY_LIMIT: int = Field(1000, ge=128, le=65536)
    RESPONSE_TIMEOUT: int = Field(30, ge=1, le=120)
    
    # 성장 파라미터 (동적)
    LEVEL: int = Field(1, ge=1, le=20)
    EXP: int = Field(0, ge=0)
    LEARNING_RATE: float = Field(0.01, ge=0.0001, le=1.0)
    
    @classmethod
    def settings_customise_sources(cls, init_settings, env_settings, 
                                  dotenv_settings, file_secret_settings):
        # 우선순위: 런타임 > 사용자 > 환경변수 > 기본값
        return (
            init_settings,
            RuntimeOverlaySource,      # 1순위: 동적 변경
            UserPrefsSource,          # 2순위: 사용자 설정
            env_settings,
            dotenv_settings,
            file_secret_settings,
        )

동적 설정 서비스

# app/core/config_service.py
from pydantic import BaseModel
from datetime import datetime, timedelta
import json, uuid

class ChangeSet(BaseModel):
    """변경 세트: 원자적 단위로 관리"""
    id: str
    changes: dict[str, Any]
    author: str
    reason: str
    ttl_seconds: int | None = None
    effective_at: datetime
    version: int

class ConfigService:
    """안전한 동적 설정 관리"""
    
    async def apply(self, changes: dict, *, author: str, reason: str, 
                   ttl_seconds: int = 600):  # 기본 10분
        # 1. 사전 검증
        trial_config = {**current_config, **changes}
        BaseRobeingSettings(**trial_config)  # 검증 실패시 예외
        
        # 2. 원자적 적용
        changeset = ChangeSet(
            id=str(uuid.uuid4()),
            changes=changes,
            author=author,
            reason=reason,
            ttl_seconds=ttl_seconds,
            version=self.next_version()
        )
        
        # 3. 멀티워커 전파
        await self.redis.publish("config:updates", changeset.json())
        
        # 4. 감사 로그
        await self.audit_log(changeset)
        
        # 5. TTL 예약
        if ttl_seconds:
            await self.schedule_revert(changeset)
        
        return changeset

멀티워커 전파 메커니즘

# app/main.py
@app.on_event("startup")
async def subscribe_config_updates():
    """모든 워커가 설정 변경 구독"""
    async def listener():
        pubsub = redis.pubsub()
        await pubsub.subscribe("config:updates")
        async for msg in pubsub.listen():
            if msg["type"] == "message":
                changeset = ChangeSet.parse_raw(msg["data"])
                # 메모리 오버레이 업데이트
                MemoryKV.runtime.update(changeset.changes)
                # 헬스체크
                await health_check_after_change()
    
    asyncio.create_task(listener())

11.4 거버넌스와 보안

접근 제어 계층

class ConfigScope(Enum):
    SYSTEM = "system"      # 시스템 관리자만
    RUNTIME = "runtime"    # 운영자 가능
    USER = "user"         # 사용자 개인화
    
class SecretScope(Enum):
    NONE = "none"         # 동적 변경 가능
    SECRET = "secret"     # 환경변수/시크릿매니저만

# 역할별 권한
RBAC = {
    "admin": [ConfigScope.SYSTEM, ConfigScope.RUNTIME],
    "operator": [ConfigScope.RUNTIME],  # 비밀 제외
    "viewer": []  # 읽기만 가능
}

변경 세트 템플릿

# 변경 요청 표준 양식
change_request:
  key: RESPONSE_TIMEOUT
  current_value: 30
  target_value: 45
  reason: "PDF 처리 시간 증가"
  validation_method: "헬스체크 API 응답"
  rollback_criteria: "응답시간 > 50s"
  ttl_seconds: 600  # 10분 후 자동 복귀

11.5 성장 파라미터 연동

def adjust_params_on_levelup(level: int, stats: dict[str, int]):
    """레벨업 시 자동 파라미터 조정"""
    changes = {}
    
    # 연산 스탯 → 타임아웃 증가
    if stats["compute"] >= 10:
        changes["RESPONSE_TIMEOUT"] = min(45, 30 + stats["compute"])
    
    # 기억 스탯 → 메모리 증가
    if stats["memory"] >= 10:
        changes["MEMORY_LIMIT"] = min(4096, 1000 + stats["memory"] * 100)
    
    # 공감 스탯 → 창의성 증가
    if stats["empathy"] >= 10:
        changes["CREATIVITY_LEVEL"] = min(0.9, 0.5 + stats["empathy"] * 0.02)
    
    return ConfigService().apply(
        changes, 
        author="system:levelup",
        reason=f"Level {level} automatic adjustment",
        ttl_seconds=None  # 영구 적용
    )

11.6 운영 도구

CLI 인터페이스

# 현재 값 조회
robeingctl config get RESPONSE_TIMEOUT

# 임시 변경 (10분)
robeingctl config set RESPONSE_TIMEOUT=45 \
    --ttl 600 \
    --reason "heavy pdf parsing"

# 특정 버전으로 롤백
robeingctl config revert --version 128

# 변경 이력 조회
robeingctl config history --last 10

관리 API

@app.get("/admin/config", dependencies=[Depends(require_viewer)])
async def read_config():
    """현재 설정 조회"""
    return BaseRobeingSettings().dict()

@app.post("/admin/config/apply", dependencies=[Depends(require_operator)])
async def apply_config(changeset: ChangeSetRequest):
    """설정 변경 적용"""
    return await ConfigService().apply(**changeset.dict())

@app.get("/admin/config/history", dependencies=[Depends(require_viewer)])
async def config_history(limit: int = 10):
    """변경 이력 조회"""
    return await ConfigService().get_history(limit)

11.7 마이그레이션 로드맵

Phase 1: 즉시 적용 (1일)

  1. 모든 config.py를 app/core/config.py로 통일
  2. robeing-gateway에 config.py 생성
  3. BaseRobeingSettings 상속 구조 도입

Phase 2: 동적 레이어 (1주)

  1. Redis Pub/Sub 설정
  2. RuntimeOverlaySource 구현
  3. TTL 메커니즘 구축
  4. 감사 로그 파이프라인

Phase 3: 거버넌스 (2주)

  1. RBAC 시스템 구현
  2. 변경 세트 검증 강화
  3. CLI/API 도구 배포
  4. 모니터링 대시보드

11.8 테스트 체크리스트

  • 단위: 설정 우선순위 검증
  • 통합: 멀티워커 전파 확인
  • 안전성: 잘못된 값 거부
  • TTL: 자동 복귀 동작
  • 성능: 변경 시 지연 없음
  • 롤백: 버전별 복구 가능

핵심 메시지

"로빙은 생명체처럼 적응합니다. 하드코딩된 값이 아닌, 동적으로 변화하는 파라미터를 통해 성장하고 진화합니다."

이 원칙을 통해 로빙은:

  • 사용자 요구에 즉시 대응
  • 환경 변화에 유연하게 적응
  • 경험을 통한 지속적 최적화
  • 안전하고 추적 가능한 변경 관리

를 실현할 수 있습니다.

"설정은 더 이상 고정된 도구가 아닌, 살아 숨쉬는 존재의 일부입니다."


이 문서는 아이디어에서 구체적인 구현 계획으로 발전했습니다.