From 63990ac6328fe6093ab62ef6ccddd660a8515c67 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Mon, 11 May 2026 12:04:21 +0900 Subject: [PATCH] feat(memos): add AI event-kind triage fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-2B (Memo Inbox Triage) backend 1/2. plan: beszel-tingly-sloth.md 라운드 13. 사용자 비전 = 메모는 inbox, AI 는 triage assistant. AI worker 는 events row 직접 생성 X. Migrations 250–253 (실측 N=250): - 250 CREATE TYPE event_kind_hint AS ENUM (note|task|calendar_event|activity_log|reference) - 251 ALTER TABLE documents ADD ai_event_kind event_kind_hint - 252 ALTER TABLE documents ADD ai_event_confidence NUMERIC(3,2) + CHECK 0–1 - 253 CREATE INDEX idx_documents_ai_event_kind partial WHERE ai_event_kind IS NOT NULL ORM: - Document.ai_event_kind / ai_event_confidence 컬럼 추가 (Enum SQLAlchemy 동기) - source_channel enum 에 'voice' 추가 (PR-2C 와 호환) Worker: - classify_worker Phase 3 (Gemma 4B triage) 확장 · TriageOutput 에 event_kind_hint + event_kind_confidence 필드 추가 · 4B 응답에 hint 가 있을 때만 Document 에 저장 (enum 외 값은 무시) - prompt p3a_short_summary.txt 확장 — note/task/calendar_event/activity_log/reference 분류 기준 + confidence + default='note' 명시 원칙: AI worker 는 hint 만 제공. events 생성은 다음 commit 의 promote endpoint 에서만. --- app/models/document.py | 10 ++++++++++ app/prompts/policy/p3a_short_summary.txt | 13 ++++++++++++- app/workers/classify_worker.py | 17 +++++++++++++++++ migrations/250_event_kind_hint_enum.sql | 11 +++++++++++ migrations/251_documents_ai_event_kind.sql | 6 ++++++ .../252_documents_ai_event_confidence.sql | 5 +++++ migrations/253_documents_ai_event_kind_idx.sql | 7 +++++++ 7 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 migrations/250_event_kind_hint_enum.sql create mode 100644 migrations/251_documents_ai_event_kind.sql create mode 100644 migrations/252_documents_ai_event_confidence.sql create mode 100644 migrations/253_documents_ai_event_kind_idx.sql diff --git a/app/models/document.py b/app/models/document.py index 7de81ee..a7cd6df 100644 --- a/app/models/document.py +++ b/app/models/document.py @@ -47,6 +47,15 @@ class Document(Base): importance: Mapped[str | None] = mapped_column(String(20), default="medium") ai_confidence: Mapped[float | None] = mapped_column() + # Memo Intake Upgrade PR-2B — Gemma 4B triage 가 추론한 메모 의도 분류 hint + # ('note' | 'task' | 'calendar_event' | 'activity_log' | 'reference') + # AI 자동 events 생성 X — 사용자 1-click promote 시점에만 events row 생성 (안전 boundary). + ai_event_kind: Mapped[str | None] = mapped_column( + Enum("note", "task", "calendar_event", "activity_log", "reference", + name="event_kind_hint") + ) + ai_event_confidence: Mapped[float | None] = mapped_column() + # 3계층: 벡터 임베딩 embedding = mapped_column(Vector(1024), nullable=True) embed_model_version: Mapped[str | None] = mapped_column(String(50)) @@ -95,6 +104,7 @@ class Document(Base): source_channel: Mapped[str | None] = mapped_column( Enum("law_monitor", "devonagent", "email", "web_clip", "tksafety", "inbox_route", "manual", "drive_sync", "news", "memo", + "voice", name="source_channel") ) data_origin: Mapped[str | None] = mapped_column( diff --git a/app/prompts/policy/p3a_short_summary.txt b/app/prompts/policy/p3a_short_summary.txt index 076030f..4b401f0 100644 --- a/app/prompts/policy/p3a_short_summary.txt +++ b/app/prompts/policy/p3a_short_summary.txt @@ -31,9 +31,20 @@ subject_description: {subject_description} "recommend_deep_summary": bool, "recommend_entity_pass": bool, "escalate_to_26b": bool, - "risk_flags": ["..."] + "risk_flags": ["..."], + "event_kind_hint": "note|task|calendar_event|activity_log|reference|null", + "event_kind_confidence": 0.0~1.0 }} +event_kind_hint 분류 (사용자 메모 inbox triage 용 — AI 가 events row 직접 생성하지 않고 사용자 1-click promote 의 추천만 제공): +- "task": 사용자가 미래에 해야 할 일 (예: "내일 견적 요청", "세무사 전화하기"). due 시각 있어도 task 가능. +- "calendar_event": 시간/날짜가 고정된 일정 (예: "5/15 14:00 회의", "내일 2시 세무사 전화"). 본문에 명시적 시간 단서. +- "activity_log": 이미 한 행동 기록 (예: "방금 PR 머지 완료", "오늘 GPU 서버 점검함"). 과거형 또는 "방금/오늘/지금" 표지. +- "reference": 나중에 참조할 자료/링크/요약 (예: 웹 클립, 외부 자료, "이거 나중에 봐야 함"). +- "note": 위 4개 어디에도 명확하지 않은 일반 메모/생각 (default). +- event_kind_confidence: 0.0–1.0. 명확하지 않으면 낮게 (< 0.5). 사용자가 결정. +- 본문이 짧거나 의도 불명이면 "note" + confidence 낮게. + recommend_deep_summary=true 조건: - 본문 > 40,000 chars - 다수 당사자 또는 시계열 전개가 있는 법령/절차/보고서 diff --git a/app/workers/classify_worker.py b/app/workers/classify_worker.py index 3e46f3c..fe5dbf4 100644 --- a/app/workers/classify_worker.py +++ b/app/workers/classify_worker.py @@ -87,6 +87,11 @@ class TriageOutput(BaseModel): escalate_to_26b: bool = False risk_flags: list[str] = Field(default_factory=list) + # Memo Intake Upgrade PR-2B — 메모 의도 분류 hint (선택 응답) + # 4B 가 출력하지 않으면 None 유지. AI 자동 events 생성 X (사용자 promote 시점만). + event_kind_hint: str | None = None # 'note' | 'task' | 'calendar_event' | 'activity_log' | 'reference' + event_kind_confidence: float | None = None # 0.0–1.0 + # ───────────────────────── legacy classify (primary) ────────────────── @@ -656,6 +661,18 @@ async def _apply_triage_result( if not parse_error: doc.ai_tldr = (triage_out.tldr or "").strip() or None doc.ai_bullets = triage_out.bullets or [] + # Memo Intake Upgrade PR-2B — event kind hint (4B 가 출력했을 때만) + # 허용 enum 외 값이면 무시 (DB enum 제약). AI worker 는 events row 직접 생성 X. + valid_kinds = {"note", "task", "calendar_event", "activity_log", "reference"} + hint = (triage_out.event_kind_hint or "").strip().lower() or None + if hint in valid_kinds: + doc.ai_event_kind = hint + try: + conf = triage_out.event_kind_confidence + if conf is not None and 0.0 <= float(conf) <= 1.0: + doc.ai_event_confidence = float(conf) + except (TypeError, ValueError): + pass doc.ai_analysis_tier = "triage" # R2 — backlog guard (hard 제외 soft escalate 만 억제) diff --git a/migrations/250_event_kind_hint_enum.sql b/migrations/250_event_kind_hint_enum.sql new file mode 100644 index 0000000..d0f51c2 --- /dev/null +++ b/migrations/250_event_kind_hint_enum.sql @@ -0,0 +1,11 @@ +-- Memo Intake Upgrade PR-2B (1/4) — event_kind_hint enum +-- 메모 본문에서 AI(Gemma 4B triage) 가 추론한 메모 의도 분류. +-- events.kind (event_kind) 와 별 type 으로 분리 — 의도 hint 만, 실제 events row 생성은 사용자 promote 시점. + +CREATE TYPE event_kind_hint AS ENUM ( + 'note', + 'task', + 'calendar_event', + 'activity_log', + 'reference' +); diff --git a/migrations/251_documents_ai_event_kind.sql b/migrations/251_documents_ai_event_kind.sql new file mode 100644 index 0000000..349fe9f --- /dev/null +++ b/migrations/251_documents_ai_event_kind.sql @@ -0,0 +1,6 @@ +-- Memo Intake Upgrade PR-2B (2/4) — documents.ai_event_kind 컬럼 추가 +-- AI 추천값. 사용자 1-click 승급 또는 dismiss 기준이 됨. +-- nullable: classify worker 미통과 (extracted_text NULL 등) 행은 비어 있음. + +ALTER TABLE documents + ADD COLUMN IF NOT EXISTS ai_event_kind event_kind_hint; diff --git a/migrations/252_documents_ai_event_confidence.sql b/migrations/252_documents_ai_event_confidence.sql new file mode 100644 index 0000000..72d1d3a --- /dev/null +++ b/migrations/252_documents_ai_event_confidence.sql @@ -0,0 +1,5 @@ +-- Memo Intake Upgrade PR-2B (3/4) — documents.ai_event_confidence 컬럼 추가 +-- 4B triage 의 ai_event_kind 신뢰도 (0.00–1.00). full-auto promote 결정 임계값에 활용. + +ALTER TABLE documents + ADD COLUMN IF NOT EXISTS ai_event_confidence NUMERIC(3, 2) CHECK (ai_event_confidence IS NULL OR (ai_event_confidence >= 0 AND ai_event_confidence <= 1)); diff --git a/migrations/253_documents_ai_event_kind_idx.sql b/migrations/253_documents_ai_event_kind_idx.sql new file mode 100644 index 0000000..45a658c --- /dev/null +++ b/migrations/253_documents_ai_event_kind_idx.sql @@ -0,0 +1,7 @@ +-- Memo Intake Upgrade PR-2B (4/4) — partial index on ai_event_kind +-- 메모 list 의 분류 결과 필터 + 사용자 inbox triage 흐름 핵심 인덱스. +-- ai_event_kind IS NULL (분류 대기 / 미통과) 행은 size 절감 목적 partial 로 제외. + +CREATE INDEX IF NOT EXISTS idx_documents_ai_event_kind + ON documents (ai_event_kind, created_at DESC) + WHERE ai_event_kind IS NOT NULL;