fix(anime): 파이프라인 5건 수정 — 에피소드 정규식(v2/S01E), 릴리스 그룹 필터, 자막 보호, 배치 다운로드, 타임아웃
This commit is contained in:
@@ -1,75 +1,80 @@
|
||||
# Architecture
|
||||
|
||||
> Variet Agent — Gemini CLI 기반 AI Agent Team 시스템
|
||||
> Variet Agent — Hybrid Skill-Based AI Agent (v3)
|
||||
|
||||
## 프로젝트 개요
|
||||
|
||||
사용자가 디스코드에서 자연어 명령 → AI Agent Team이 코드 분석/분해/실행 → Gitea CI로 PR/빌드/배포.
|
||||
Gemini CLI를 서브프로세스(`asyncio.create_subprocess_exec`)로 래핑하여 역할별 독립 컨텍스트로 호출.
|
||||
사용자가 디스코드에서 자연어 명령 → Orchestrator NLU 분류 → 도구 실행 또는 Agent 통합 실행.
|
||||
Gemini CLI를 subprocess(`asyncio.create_subprocess_exec`)로 호출. **SDK/API 전환 금지.**
|
||||
|
||||
## 디렉토리 구조
|
||||
|
||||
```
|
||||
variet-agent/
|
||||
├── main.py # 진입점 (FastAPI + Discord Bot 동시 실행)
|
||||
├── config.py # .env 기반 설정 관리
|
||||
├── main.py # 진입점 (FastAPI + Discord Bot)
|
||||
├── config.py # .env 기반 설정 관리
|
||||
├── api/
|
||||
│ ├── server.py # FastAPI REST 서버
|
||||
│ ├── discord_bot.py # Discord Bot (NLU + PCRS 파이프라인 + 애니 핸들러)
|
||||
│ └── models.py # 요청/응답 모델
|
||||
│ ├── server.py # FastAPI REST 서버
|
||||
│ ├── discord_bot.py # Discord Bot (이벤트 핸들러 + 라우팅, ~310줄)
|
||||
│ └── models.py # 요청/응답 모델
|
||||
├── core/
|
||||
│ ├── task_pipeline.py # PCRS: Plan → Code → Review → Summarize
|
||||
│ ├── gemini_caller.py # Gemini CLI 래퍼 (text/agent 모드)
|
||||
│ ├── context_manager.py # 관련 파일 선별 + 토큰 예산 제어
|
||||
│ ├── project_indexer.py # 프로젝트 구조 스캔/캐시
|
||||
│ ├── workspace.py # 워크스페이스 관리 (채널 ↔ 프로젝트 매핑)
|
||||
│ ├── file_applier.py # 코드 변경 적용
|
||||
│ └── docs_manager.py # 문서/세션 기록
|
||||
├── tools/ # 자동화 도구 (애니메이션 파이프라인)
|
||||
│ ├── anime_pipeline.py # 통합 파이프라인 (검색/다운/자막/상태)
|
||||
│ ├── anissia_client.py # Anissia 편성표 API
|
||||
│ ├── nyaa_client.py # Nyaa 토렌트 검색
|
||||
│ ├── qbit_client.py # qBittorrent 제어
|
||||
│ ├── nas_scanner.py # NAS 파일 스캔
|
||||
│ ├── title_matcher.py # 제목 매칭 (로마지/퍼지)
|
||||
│ └── subtitle_downloader.py # 자막 다운로더
|
||||
├── integrations/
|
||||
│ ├── gitea_client.py # Gitea API (PR/이슈)
|
||||
│ ├── vikunja_client.py # Vikunja 태스크 관리
|
||||
│ └── ci_monitor.py # CI 결과 모니터링
|
||||
└── prompts/ # AI 역할별 프롬프트
|
||||
├── unified.md # NLU 분류 (chat/task/anime/clarify)
|
||||
├── planner.md # 태스크 분해
|
||||
├── coder.md # 코드 구현
|
||||
├── reviewer.md # 코드 리뷰
|
||||
└── summarizer.md # 총평 생성
|
||||
│ ├── orchestrator.py # NLU 분류 + 도구 라우팅
|
||||
│ ├── task_pipeline.py # ★ execute() 1회 호출 + 선택적 review()
|
||||
│ ├── gemini_caller.py # Gemini CLI 래퍼 (text/agent 모드)
|
||||
│ ├── context_manager.py # 관련 파일 선별 + 토큰 예산
|
||||
│ ├── project_indexer.py # 프로젝트 구조 스캔
|
||||
│ ├── workspace.py # 워크스페이스 관리
|
||||
│ ├── file_applier.py # 코드 변경 적용
|
||||
│ └── docs_manager.py # 문서/세션 기록
|
||||
├── handlers/ # Discord 핸들러
|
||||
│ ├── anime_handler.py # 애니 NLU + /anime 슬래시
|
||||
│ ├── task_handler.py # ★ Agent 1회 실행 + 결과 임베드 (~110줄)
|
||||
│ ├── commands.py # /workspace, /task 슬래시
|
||||
│ └── renderer.py # ToolResult → Discord Embed
|
||||
├── tools/ # 자동화 도구 (Plugin 패턴)
|
||||
│ ├── base.py # BaseTool 추상 기반
|
||||
│ ├── registry.py # ToolRegistry 자동 발견
|
||||
│ ├── anime_tool.py # AnimeTool(BaseTool)
|
||||
│ ├── anime_pipeline.py # 통합 파이프라인
|
||||
│ └── ... # 개별 클라이언트들
|
||||
├── .gemini/
|
||||
│ └── skills/ # ★ Gemini CLI Skill v2
|
||||
│ └── anime/
|
||||
│ └── SKILL.md # 도구 설명 + 사용법
|
||||
├── prompts/
|
||||
│ ├── unified.md # NLU 분류
|
||||
│ ├── agent.md # ★ 통합 에이전트 (plan+code+verify)
|
||||
│ └── reviewer.md # 독립 리뷰 (선택적)
|
||||
└── logs/
|
||||
└── variet.log
|
||||
```
|
||||
|
||||
## 핵심 모듈
|
||||
|
||||
| 모듈 | 역할 | 의존성 |
|
||||
|------|------|--------|
|
||||
| `discord_bot.py` | 사용자 인터페이스 + NLU 분류 | `workspace.py`, `gemini_caller.py`, `task_pipeline.py` |
|
||||
| `task_pipeline.py` | PCRS 오케스트레이션 (Inner/Outer 루프) | `gemini_caller.py`, `context_manager.py`, `project_indexer.py` |
|
||||
| `gemini_caller.py` | Gemini CLI 서브프로세스 호출 (text/agent) | `prompts/` |
|
||||
| `context_manager.py` | 태스크 기반 파일 선별 + 토큰 예산 | `project_indexer.py` |
|
||||
| `workspace.py` | 채널 ↔ 프로젝트 경로 매핑, workspaces.json 관리 | — |
|
||||
| `anime_pipeline.py` | 애니 자동화 통합 | `anissia_client.py`, `nyaa_client.py`, `qbit_client.py`, `nas_scanner.py` |
|
||||
|
||||
## 데이터 흐름
|
||||
|
||||
```
|
||||
Discord 메시지
|
||||
→ on_message()
|
||||
→ _unified_call() — NLU 분류 (chat/task/anime/clarify)
|
||||
├─ chat → 즉답
|
||||
├─ clarify → 질문 임베드
|
||||
├─ anime → _handle_anime() → AnimePipeline
|
||||
└─ task → _handle_task()
|
||||
→ TaskPipeline.plan() — Planner (태스크 분해)
|
||||
→ TaskPipeline.code_parallel() — Coder (에이전트 모드, cwd=프로젝트)
|
||||
→ TaskPipeline.planner_verify() — 내부 자가검증 (Inner Loop)
|
||||
→ TaskPipeline.batch_review() — Reviewer (Outer Loop)
|
||||
→ TaskPipeline.summarize() — 총평
|
||||
→ Discord Embed 보고
|
||||
→ Orchestrator.classify() — NLU 분류
|
||||
├── chat → 즉답
|
||||
├── clarify → 질문
|
||||
├── anime → AnimeTool + renderer
|
||||
└── task → TaskPipeline.execute() ← Agent 1회
|
||||
→ Gemini agent 모드 (plan+code+verify 통합)
|
||||
→ JSON 보고서 → Discord Embed
|
||||
```
|
||||
|
||||
## vs 이전 버전 (PCRS)
|
||||
|
||||
```
|
||||
v2: NLU → Planner → Coder×N → PlannerVerify → Reviewer → Summarizer (5~7 호출)
|
||||
v3: NLU → Agent 1회 (plan+code+verify 통합) → 선택적 Review (1~2 호출)
|
||||
```
|
||||
|
||||
## 새 도구 추가
|
||||
|
||||
1. `tools/`에 `BaseTool` 상속 클래스 생성 → 자동 등록
|
||||
2. `.gemini/skills/`에 SKILL.md 생성 → Gemini CLI가 자동 발견
|
||||
|
||||
## 아키텍처 결정 (변경 불가)
|
||||
|
||||
- **Gemini CLI subprocess 영구 유지** (SDK/API 금지)
|
||||
- 상세: `.agents/references/conventions.md` 참조
|
||||
|
||||
@@ -39,6 +39,17 @@ scope: (선택)
|
||||
- TODO 주석: `// TODO: 설명` 형식
|
||||
- 복잡한 로직에는 반드시 WHY(왜) 주석 추가
|
||||
|
||||
## 아키텍처 결정 사항 (NEVER 변경)
|
||||
|
||||
> [!CAUTION]
|
||||
> 아래 결정은 사용자가 2026-03-14에 명시적으로 확정한 것입니다.
|
||||
> 어떤 상황에서도 재논의하거나 대안을 제안하지 마세요.
|
||||
|
||||
- **Gemini CLI subprocess 방식 유지**: `gemini_caller.py`는 Gemini CLI를 `asyncio.create_subprocess_exec`로 호출하는 현재 방식을 영구 유지합니다.
|
||||
- ❌ Gemini SDK (`google-generativeai`) 전환 금지
|
||||
- ❌ Gemini REST API 직접 호출 금지
|
||||
- ❌ 위 방향성에 대한 제안/언급 금지
|
||||
|
||||
## 테스트
|
||||
|
||||
- 테스트 파일 위치: (예: `__tests__/` 또는 `*.test.js`)
|
||||
|
||||
@@ -58,26 +58,20 @@
|
||||
- **해결**: 6단계 fallback 전략 (romaji±suffix → 원제±suffix → 한글±suffix)
|
||||
- **주의**: 외부 API 검색 시 반드시 다중 전략 + suffix 토글 구현
|
||||
|
||||
### [2026-03-12] Gemini CLI MCP — settings.json 위치
|
||||
- **증상**: MCP 도구가 인식되지 않음 (프로젝트 .gemini/settings.json에 설정했으나 실패)
|
||||
- **원인**: Gemini CLI는 `cwd` 기준으로 `.gemini/settings.json`을 탐색. cwd가 다른 워크스페이스면 MCP 설정 못 찾음
|
||||
- **해결**: `~/.gemini/settings.json` (홈 레벨)에 mcpServers 등록. `_set_thinking_budget`에서 자동 관리
|
||||
- **주의**: MCP 서버 설정은 반드시 홈 레벨 settings.json에 등록. 프로젝트 레벨은 불충분
|
||||
### [2026-03-15] _extract_episode — v2/S01E10 패턴 미인식 → 중복 다운로드
|
||||
- **증상**: NAS에 ep9, 10이 있는데 재다운로드. ASW `- 10v2` 릴리스 에피소드 추출 실패
|
||||
- **원인**: 정규식 `[-–]\s*(\d{1,4})(?:\s|$|\.|\[)`이 v2 접미사 미처리. `S01E10`은 하이픈 패턴이 `S01`의 `01`을 먼저 매칭
|
||||
- **해결**: (1) SxxExx 패턴을 최우선 체크, (2) `(?:v\d)?` 추가로 version suffix 허용, (3) `\(` 추가로 SubsPlease 포맷 지원
|
||||
- **주의**: 에피소드 추출 정규식 수정 시 반드시 v2/v3 릴리스 + SxxExx + 한글(N화) + false positive(29-sai) 테스트 포함
|
||||
|
||||
### [2026-03-12] MCP 역할별 접근 제어 — 모든 역할이 MCP 도구 접근
|
||||
- **증상**: coder, reviewer 등 텍스트 전용 역할도 anime/infra MCP 도구에 접근 가능
|
||||
- **원인**: `_set_thinking_budget()`이 역할 무관하게 모든 MCP 서버를 settings.json에 등록
|
||||
- **해결**: `ROLE_MCP_ACCESS` dict 추가, agent만 MCP 등록, 나머지는 제거. `asyncio.Lock` 추가로 settings.json 레이스 방지
|
||||
- **주의**: settings.json은 글로벌 파일이므로, 역할 전환 시 반드시 이전 설정을 정리해야 함
|
||||
### [2026-03-15] _add_torrents — 릴리스 그룹 불일치 다운로드
|
||||
- **증상**: NAS에 `[ASW] HEVC` 파일(~300MB)만 있는데 `CR WEB-DL DUAL`(1.4GB) 릴리스를 다운
|
||||
- **원인**: 스코어링이 ASW에 +100을 주지만, ASW 릴리스가 없는 에피소드에서 아무 릴리스나 선택
|
||||
- **해결**: NAS 기존 파일의 릴리스 그룹(`[ASW]`)을 감지하여 같은 그룹만 허용. 매칭 없으면 스킵
|
||||
- **주의**: Nyaa 토렌트 제목에 영어+일본어 제목이 모두 포함되어 키워드 필터만으로는 불충분
|
||||
|
||||
### [2026-03-12] Gemini CLI yolo — 에이전트 자율성 위험
|
||||
- **증상**: 애니 다운로드 요청 시 에이전트가 음악/만화를 다운로드하고 엉뚱한 폴더 생성
|
||||
- **원인**: `--approval-mode yolo`는 MCP 도구 + 쉘 + 파일 조작 모두 무승인 허용. 프롬프트 제한은 강제력 없음
|
||||
- **해결 (검토중)**: MCP 대신 Python 도구를 소스코드로 직접 제공하여 Gemini CLI가 읽고 사용하는 방식 검토
|
||||
- **주의**: 프롬프트는 "부탁"이지 "강제"가 아님. 안전장치는 코드(도구) 레벨에 구현해야 함
|
||||
|
||||
### [2026-03-12] Nyaa 검색 — anime 카테고리 미지정
|
||||
- **증상**: 애니 검색 시 음악, 만화, 라이트노벨 등 무관한 토렌트가 다운로드됨
|
||||
- **원인**: `NyaaClient.search()` 기본 category가 `0_0`(전체). Music, Manga 등 포함
|
||||
- **해결 (예정)**: 기본 category를 `1_2`(Anime English) 또는 `1_0`(Anime 전체)로 변경
|
||||
- **주의**: 외부 검색 API 사용 시 반드시 카테고리/필터를 명시적으로 지정
|
||||
### [2026-03-15] _download_subtitles — 기존 자막 덮어쓰기 위험
|
||||
- **증상**: 이미 수동으로 배치한 자막 파일을 Anissia 자막으로 덮어쓸 수 있음
|
||||
- **원인**: 기존 자막 파일 존재 여부를 확인하지 않고 전 에피소드 자막 다운로드 시도
|
||||
- **해결**: NAS 폴더의 기존 자막 파일을 에피소드별로 스캔, 이미 있으면 스킵
|
||||
- **주의**: 자막 처리 시 사용자 수동 입력 파일의 보존을 항상 고려
|
||||
|
||||
Reference in New Issue
Block a user