docs: 01_conversation + 02_skills 워크플로우 문서 현행화 — n8n 제거, SKILL.md+executor 기준 재작성
- coldmail_ir_notification_sync: LangGraph+APScheduler 기반으로 전면 재작성 - slack_action_extractor_request: v2 IntentClassifier→executor 흐름 기준 재작성 - slack_thread_summary_request: 동일 - message_flow_v2: _format_as_robeing() 해석 단계 보완 - skill_calendar/email/news_request: SKILL.md→executor 직접 호출 기준 재작성 - skill_slack_send_message_bridge: slack_sdk 직접 호출 + skill-slack HTTP 이중 경로 기술 - slack_direct_api_send: grounding 개선 사항 반영 Refs: DOCS#8 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9c9de9ebae
commit
9cb0145a4e
@ -1,26 +1,94 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, coldmail, langgraph, scheduler, ir-deck]
|
||||
last_updated: 2026-04-06
|
||||
---
|
||||
|
||||
# coldmail_ir_notification_sync 워크플로우
|
||||
|
||||
## 목적
|
||||
콜드메일 수신 시 IR 자료 포함 여부를 판별하고, Slack에 요약 알림 + IR 분석 버튼을 게시한다.
|
||||
|
||||
콜드메일을 자동 감지하여 IR 자료 포함 여부를 판별하고, Slack에 요약 알림을 게시한다. 신뢰도에 따라 자동 등록 또는 사용자 확인을 거친다.
|
||||
|
||||
## 현행 아키텍처
|
||||
|
||||
- **스케줄러**: APScheduler DB 기반 cron (`coldmail_briefing` 잡)
|
||||
- **워크플로우 엔진**: LangGraph StateGraph + AsyncSqliteSaver 체크포인트
|
||||
- **Slack 전송**: `skill-slack` 서비스 HTTP 호출 (`SKILL_SLACK_URL` 환경변수)
|
||||
|
||||
n8n은 사용하지 않는다.
|
||||
|
||||
## 흐름
|
||||
|
||||
```
|
||||
Webhook In → Format Coldmail Data → Post Basic Notification → Has IR Deck? → (true) Add IR Analysis Button
|
||||
→ (false) 종료
|
||||
APScheduler cron (평일 09:05)
|
||||
→ coldmail_briefing.py._run_coldmail_briefing()
|
||||
→ briefing_window 계산 (월요일 72h, 화~금 24h)
|
||||
→ LangGraph 워크플로우 실행:
|
||||
fetch → filter → process → wait_confirmation (interrupt) → confirm → send → END
|
||||
```
|
||||
|
||||
## 주요 노드
|
||||
| 노드 | 설명 |
|
||||
|---|---|
|
||||
| Webhook In | `POST /notifications/coldmail-ir` 수신 |
|
||||
| Format Coldmail Data | 회사명, 발신자, 제목, 요약, IR 문서 유무 추출 |
|
||||
| Post Basic Notification | Slack 채널에 콜드메일 요약 게시 |
|
||||
| Has IR Deck? | `has_ir_deck && selected_document_id` 조건 분기 |
|
||||
| Add IR Analysis Button | 스레드에 IR 지표 추출/밸류에이션 버튼 추가 |
|
||||
## 단계별 상세
|
||||
|
||||
### 1. 스케줄러 트리거
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 코드 | `app/scheduler/jobs/coldmail_briefing.py` |
|
||||
| 설정 | `settings.COLDMAIL_BRIEFING_ENABLED`, `settings.COLDMAIL_BRIEFING_SCHEDULE` |
|
||||
| 스케줄 | cron 표현식 → APScheduler 형식으로 변환 |
|
||||
| 사용자 | `settings.NAVERWORKS_COMPANY_EMAIL` → UUID 조회 |
|
||||
|
||||
### 2. LangGraph 워크플로우
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 코드 | `app/services/workflows/coldmail_workflow.py` |
|
||||
| 상태 | `ColdmailState` TypedDict (emails, coldmail_candidates, processed_results, waiting_confirmation 등) |
|
||||
| 체크포인트 | `AsyncSqliteSaver` (`settings.LANGGRAPH_STATE_DIR` / `settings.LANGGRAPH_SQLITE_FILE`) |
|
||||
| thread_id | `coldmail:{user_id}:{date}` |
|
||||
|
||||
### 3. 노드 <20><>명
|
||||
|
||||
| 노드 | 함수 | 역할 |
|
||||
|------|------|------|
|
||||
| fetch | `fetch_node` | `coldmail_email_fetcher.fetch_emails()` 호출. 사전 주입 이메일이 있으면 그대로 사용 |
|
||||
| filter | `filter_node` | `coldmail_hybrid_filter.hybrid_coldmail_filter()` — 하이브리드 필터(첨부파일+본문) 적용 |
|
||||
| process | `process_node` | 신뢰도 95% 이상 자동 처리, 미만은 `waiting_confirmation`에 추가 |
|
||||
| wait_confirmation | `wait_confirmation_node` | `interrupt()` 호출하여 워크플로우 일시정지. 사용자 확인/거부 대기 |
|
||||
| confirm | `confirm_node` | 확인된 이메일을 리스트에 등록하고 로딩 메시지 업데이트 |
|
||||
| send | `send_node` | Slack 채널에 처리 결과 요약 전송 |
|
||||
|
||||
### 4. 조건부 라우팅
|
||||
|
||||
| 분기점 | 조건 | 다음 노드 |
|
||||
|--------|------|-----------|
|
||||
| filter 후 | coldmail_candidates 있음 | process |
|
||||
| filter 후 | coldmail_candidates 없음 | END |
|
||||
| wait_confirmation 후 | confirmed_email_id 있음 | confirm |
|
||||
| wait_confirmation 후 | waiting_confirmation 남음 | wait_confirmation (루프) |
|
||||
| wait_confirmation 후 | 둘 다 없음 | send |
|
||||
| confirm 후 | waiting_confirmation 남음 | wait_confirmation |
|
||||
| confirm 후 | 없음 | send |
|
||||
|
||||
## 엔드포인트
|
||||
- 인바운드: `POST /notifications/coldmail-ir` (n8n webhook)
|
||||
- 아웃바운드: Slack `chat.postMessage` (기본 알림 + IR 버튼)
|
||||
|
||||
- **인바운드**: 없음 (스케줄러가 내부 직접 호출)
|
||||
- **아웃바운드**: `POST {SKILL_SLACK_URL}/api/v1/send` — Slack 요약 전송
|
||||
- **아웃바운드**: `POST {SKILL_SLACK_URL}/api/v1/update` — 로딩 메시지 업데이트
|
||||
|
||||
## 환경변수
|
||||
|
||||
| 변수 | 용도 |
|
||||
|------|------|
|
||||
| `COLDMAIL_CHANNEL_ID` | Slack 알림 채널 |
|
||||
| `SKILL_SLACK_URL` | skill-slack 서비스 URL |
|
||||
| `SKILL_SLACK_API_KEY` / `SERVICE_API_KEY` | skill-slack 인증 |
|
||||
| `NAVERWORKS_COMPANY_EMAIL` | 이메일 조회 대상 사용자 |
|
||||
| `LANGGRAPH_STATE_DIR` | 체크포인트 DB 디렉터리 |
|
||||
| `LANGGRAPH_SQLITE_FILE` | 체크포인트 DB 파일명 |
|
||||
|
||||
## 관련 문서
|
||||
- [slack_basic_dialogue](./slack_basic_dialogue.md)
|
||||
|
||||
- [message_flow_v2.md](./message_flow_v2.md) — 대화 처리 흐름 (v2)
|
||||
- [skill-slack SKILL.md](/home/admin/robeing/DOCS/skills/skill-slack/SKILL.md) — Slack 스킬 계약
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, message-flow, rb8001, intent, skill, executor]
|
||||
last_updated: 2026-04-03
|
||||
last_updated: 2026-04-06
|
||||
upper_principle:
|
||||
- /home/admin/0_VALUE/20_Governance/coding-principles.md
|
||||
- /home/admin/0_VALUE/20_Governance/work-system-principle.md
|
||||
@ -64,11 +64,22 @@ upper_principle:
|
||||
| internal_python | 내부 handler 함수 직접 호출 |
|
||||
| 미등록 스킬 | `{"success": false, "error": "미등록"}` 반환 |
|
||||
|
||||
### 5. 응답
|
||||
### 5. 해석 (_format_as_robeing)
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 결과 조립 | executor 결과를 사용자 메시지로 변환 |
|
||||
| 코드 | `app/services/message_service_v2.py:_format_as_robeing()` |
|
||||
| 입력 | 사용자 원문, IntentResult, executor 실행 결과(JSON) |
|
||||
| 방식 | DB 프롬프트(`response_formatter_system`) + LLM 호출 (model: `settings.DEFAULT_LLM_MODEL`, temp: 0.3, max_tokens: 200) |
|
||||
| 출력 | 자연어 문자열. 스킬 결과를 로빙의 말투로 변환 |
|
||||
| 실패 시 | 원본 JSON 문자열 그대로 반환 |
|
||||
| 근거 | coding-principles "응답 형식은 접속 대상이 결정한다" — 사람이 대상이므로 문자열 |
|
||||
|
||||
### 6. 응답
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 대화 저장 | `_save_conversation()` — DB에 user/assistant 메시지 저장 |
|
||||
| 채널 전달 | Slack reply / 웹 응답 |
|
||||
|
||||
## 스킬 참여 예시
|
||||
|
||||
@ -1,27 +1,51 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, slack, action-extractor, skill-slack, intent]
|
||||
last_updated: 2026-04-06
|
||||
---
|
||||
|
||||
# slack_action_extractor_request 워크플로우
|
||||
|
||||
## 목적
|
||||
Slack 메시지에서 "할 일/액션/todo/action" 의도를 감지하면, skill-slack의 액션 추출 API를 호출하여 결과를 스레드에 회신한다.
|
||||
|
||||
Slack 메시지에서 "할 일/액션/todo" 의도를 감지하면, skill-slack의 액션 추출 API를 호출하여 결과를 반환한다.
|
||||
|
||||
## 현행 아키텍처
|
||||
|
||||
v2 메시지 흐름(IntentClassifier → executor)을 통해 처리된다. n8n Trigger, 별도 webhook은 사용하지 않는다.
|
||||
|
||||
## 흐름
|
||||
|
||||
```
|
||||
Slack Trigger → Detect Action Intent → Action Request? → (true) Call skill-slack Extract Actions → Build Action Reply → Reply to Slack
|
||||
→ (false) 종료
|
||||
사용자 발화 ("할 일 뽑아줘", "액션 아이템 정리해줘")
|
||||
→ message_service_v2.route_message()
|
||||
→ IntentClassifier.classify()
|
||||
— SKILL.md 본문에서 skill-slack 트리거 매칭
|
||||
— IntentResult(skill="skill-slack", action="action_extractor", slots=...)
|
||||
→ executor.execute()
|
||||
— registry에서 SKILL_SLACK_URL 환경변수 해소
|
||||
— POST {SKILL_SLACK_URL}/api/v1/actions (httpx)
|
||||
→ _format_as_robeing() — 결과를 자연어로 변환
|
||||
→ 채널 응답
|
||||
```
|
||||
|
||||
## 주요 노드
|
||||
| 노드 | 설명 |
|
||||
|---|---|
|
||||
| Slack Trigger | 채널 메시지 이벤트 수신 |
|
||||
| Detect Action Intent | 정규식으로 액션 키워드 감지, channel/user 유효성 확인 |
|
||||
| Call skill-slack Extract Actions | `POST :8502/api/v1/extract-actions` 호출 (24h, auto_assign, priority_detection) |
|
||||
| Build Action Reply | 추출된 액션 아이템을 번호 목록 텍스트로 포맷 |
|
||||
| Reply to Slack | 원래 스레드에 결과 회신 |
|
||||
## 주요 코드 경로
|
||||
|
||||
| 단계 | 코드 |
|
||||
|------|------|
|
||||
| 진입 | `app/services/message_service_v2.py:route_message()` |
|
||||
| 의도분류 | `app/services/brain/intent_classifier.py:IntentClassifier.classify()` |
|
||||
| 스킬 실행 | `app/services/skills/executor.py:execute()` |
|
||||
| URL 해소 | `app/services/skills/registry.py:SkillMeta.resolve_url()` |
|
||||
| 스킬 정의 | `DOCS/skills/skill-slack/SKILL.md` (트리거: `action_extractor`) |
|
||||
|
||||
## 엔드포인트
|
||||
- 아웃바운드: `POST http://192.168.219.52:8502/api/v1/extract-actions`
|
||||
- 아웃바운드: Slack `chat.postMessage` (스레드 회신)
|
||||
|
||||
- **아웃바운드**: `POST {SKILL_SLACK_URL}/api/v1/actions` — skill-slack 서비스
|
||||
- `SKILL_SLACK_URL` 환경변수로 해소. IP 하드코딩 없음
|
||||
|
||||
## 관련 문서
|
||||
- [slack_thread_summary_request](./slack_thread_summary_request.md)
|
||||
- [slack_basic_dialogue](./slack_basic_dialogue.md)
|
||||
|
||||
- [message_flow_v2.md](./message_flow_v2.md) — v2 메시지 흐름
|
||||
- [slack_thread_summary_request.md](./slack_thread_summary_request.md) — 스레드 요약 (같은 skill-slack)
|
||||
- [skill-slack SKILL.md](/home/admin/robeing/DOCS/skills/skill-slack/SKILL.md) — 스킬 계약
|
||||
|
||||
@ -1,27 +1,51 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, slack, thread-summary, skill-slack, intent]
|
||||
last_updated: 2026-04-06
|
||||
---
|
||||
|
||||
# slack_thread_summary_request 워크플로우
|
||||
|
||||
## 목적
|
||||
Slack 메시지에서 "요약/정리/digest/summary" 의도를 감지하면, skill-slack의 요약 API를 호출하여 결과를 스레드에 회신한다.
|
||||
|
||||
Slack 메시지에서 "요약/정리/digest/summary" 의도를 감지하면, skill-slack의 요약 API를 호출하여 결과를 반환한다.
|
||||
|
||||
## 현행 아키텍처
|
||||
|
||||
v2 메시지 흐름(IntentClassifier → executor)을 통해 처리된다. n8n Trigger, 별도 webhook은 사용하지 않는다.
|
||||
|
||||
## 흐름
|
||||
|
||||
```
|
||||
Slack Trigger → Detect Summary Intent → Summary Request? → (true) Call skill-slack Summarize → Build Summary Reply → Reply to Slack
|
||||
→ (false) 종료
|
||||
사용자 발화 ("스레드 요약해줘", "대화 정리해줘")
|
||||
→ message_service_v2.route_message()
|
||||
→ IntentClassifier.classify()
|
||||
— SKILL.md 본문에서 skill-slack 트리거 매칭
|
||||
— IntentResult(skill="skill-slack", action="slack_thread", slots=...)
|
||||
→ executor.execute()
|
||||
— registry에서 SKILL_SLACK_URL 환경변수 해소
|
||||
— POST {SKILL_SLACK_URL}/api/v1/summarize (httpx)
|
||||
→ _format_as_robeing() — 결과를 자연어로 변환
|
||||
→ 채널 응답
|
||||
```
|
||||
|
||||
## 주요 노드
|
||||
| 노드 | 설명 |
|
||||
|---|---|
|
||||
| Slack Trigger | 채널 메시지 이벤트 수신 |
|
||||
| Detect Summary Intent | 정규식으로 요약 키워드 감지, channel/user 유효성 확인 |
|
||||
| Call skill-slack Summarize | `POST :8502/api/v1/summarize` 호출 (24h, include_files=true) |
|
||||
| Build Summary Reply | 요약 텍스트 추출 |
|
||||
| Reply to Slack | 원래 스레드에 결과 회신 |
|
||||
## 주요 코드 경로
|
||||
|
||||
| 단계 | 코드 |
|
||||
|------|------|
|
||||
| 진입 | `app/services/message_service_v2.py:route_message()` |
|
||||
| 의도분류 | `app/services/brain/intent_classifier.py:IntentClassifier.classify()` |
|
||||
| 스킬 실행 | `app/services/skills/executor.py:execute()` |
|
||||
| URL 해소 | `app/services/skills/registry.py:SkillMeta.resolve_url()` |
|
||||
| 스킬 정의 | `DOCS/skills/skill-slack/SKILL.md` (트리거: `slack_thread`) |
|
||||
|
||||
## 엔드포인트
|
||||
- 아웃바운드: `POST http://192.168.219.52:8502/api/v1/summarize`
|
||||
- 아웃바운드: Slack `chat.postMessage` (스레드 회신)
|
||||
|
||||
- **아웃바운드**: `POST {SKILL_SLACK_URL}/api/v1/summarize` — skill-slack 서비스
|
||||
- `SKILL_SLACK_URL` 환경변수로 해소. IP 하드코딩 없음
|
||||
|
||||
## 관련 문서
|
||||
- [slack_action_extractor_request](./slack_action_extractor_request.md)
|
||||
- [slack_basic_dialogue](./slack_basic_dialogue.md)
|
||||
|
||||
- [message_flow_v2.md](./message_flow_v2.md) — v2 메시지 흐름
|
||||
- [slack_action_extractor_request.md](./slack_action_extractor_request.md) — 액션 추출 (같은 skill-slack)
|
||||
- [skill-slack SKILL.md](/home/admin/robeing/DOCS/skills/skill-slack/SKILL.md) — 스킬 계약
|
||||
|
||||
@ -1,28 +1,64 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, skill-calendar, calendar, executor, intent]
|
||||
last_updated: 2026-04-06
|
||||
---
|
||||
|
||||
# skill_calendar_request 워크플로우
|
||||
|
||||
## 목적
|
||||
캘린더 스킬의 webhook 브릿지. 일정 생성(create) 또는 조회(query) 요청을 skill-calendar API로 전달하고 결과를 반환한다.
|
||||
|
||||
캘린더 스킬 요청 처리. 일정 생성(create), 조회(list/get), 삭제(delete)를 SKILL.md 기반 executor를 통해 skill-calendar 서비스로 전달한다.
|
||||
|
||||
## 현행 아키텍처
|
||||
|
||||
- **의도분류**: IntentClassifier가 SKILL.md 본문으로 `skill-calendar` 매칭
|
||||
- **실행**: executor가 `SKILL_CALENDAR_URL` 환경변수에서 URL 해소 후 HTTP 호출
|
||||
- **메서드 결정**: SKILL.md frontmatter `endpoints`가 action별 HTTP 메서드(GET/POST/DELETE)와 경로를 정의
|
||||
|
||||
n8n webhook 브릿지는 사용하지 않는다.
|
||||
|
||||
## 흐름
|
||||
|
||||
```
|
||||
Webhook In → Normalize Payload → Action Type → (create) Create Event → Return Result
|
||||
→ (else) Query Events → Return Result
|
||||
사용자 발화 ("내일 2시 미팅 잡아줘")
|
||||
-> IntentClassifier.classify()
|
||||
-- IntentResult(skill="skill-calendar", action="create_event", endpoint="/api/events", slots={...})
|
||||
-> executor.execute()
|
||||
-- registry.resolve_url() -> SKILL_CALENDAR_URL 환경변수
|
||||
-- registry.resolve_method("create_event") -> POST (SKILL.md frontmatter)
|
||||
-- POST {SKILL_CALENDAR_URL}/api/events (httpx, X-User-Id 헤더)
|
||||
-> _format_as_robeing() -> 응답
|
||||
```
|
||||
|
||||
## 주요 노드
|
||||
| 노드 | 설명 |
|
||||
|---|---|
|
||||
| Webhook In | `POST /skills/calendar/request` 수신 |
|
||||
| Normalize Payload | action(create/query), user_id, event_data, query 추출 |
|
||||
| Action Type | action === "create" 분기 |
|
||||
| Create Event | `POST :8512/api/events` (X-User-Id 헤더) |
|
||||
| Query Events | `GET :8512/api/events?query=...` (X-User-Id 헤더) |
|
||||
| Return Result | webhook 응답 반환 |
|
||||
## SKILL.md 엔드포인트 정의
|
||||
|
||||
## 엔드포인트
|
||||
- 인바운드: `POST /skills/calendar/request` (n8n webhook)
|
||||
- 아웃바운드: `http://192.168.219.52:8512/api/events` (GET/POST)
|
||||
| action | 메서드 | 경로 |
|
||||
|--------|--------|------|
|
||||
| `create_event` | POST | `/api/events` |
|
||||
| `list_events` | GET | `/api/events` |
|
||||
| `get_events` | GET | `/api/events` |
|
||||
| `delete_event` | DELETE | `/api/events/{event_id}` |
|
||||
|
||||
경로 파라미터(`{event_id}`)는 executor가 params에서 자동 보간한다.
|
||||
|
||||
## 주요 코드 경로
|
||||
|
||||
| 단계 | 코드 |
|
||||
|------|------|
|
||||
| 스킬 정의 | `DOCS/skills/skill-calendar/SKILL.md` |
|
||||
| 의도분류 | `app/services/brain/intent_classifier.py` |
|
||||
| 실행 | `app/services/skills/executor.py:execute()` |
|
||||
| URL/메서드 해소 | `app/services/skills/registry.py:SkillMeta` |
|
||||
|
||||
## 환경변수
|
||||
|
||||
| 변수 | 용도 |
|
||||
|------|------|
|
||||
| `SKILL_CALENDAR_URL` | skill-calendar 서비스 베이스 URL |
|
||||
|
||||
## 관련 문서
|
||||
- [skill_email_send_request](./skill_email_send_request.md)
|
||||
- [skill_news_briefing_request](./skill_news_briefing_request.md)
|
||||
|
||||
- [skill-calendar SKILL.md](/home/admin/robeing/DOCS/skills/skill-calendar/SKILL.md) -- 스킬 계약
|
||||
- [skill_email_send_request.md](./skill_email_send_request.md)
|
||||
- [message_flow_v2.md](../01_conversation/message_flow_v2.md) -- v2 메시지 흐름
|
||||
|
||||
@ -1,24 +1,69 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, skill-email, email, executor, intent]
|
||||
last_updated: 2026-04-06
|
||||
---
|
||||
|
||||
# skill_email_send_request 워크플로우
|
||||
|
||||
## 목적
|
||||
이메일 발송 스킬의 webhook 브릿지. 요청 payload를 그대로 skill-email API에 전달하고 결과를 반환한다.
|
||||
|
||||
## 흐름
|
||||
이메일 스킬 요청 처리. 이메일 발송(send), 조회(read), 요약(process)을 SKILL.md 기반 executor 또는 `email_integration` 모듈을 통해 skill-email 서비스로 전달한다.
|
||||
|
||||
## 현행 아키텍처
|
||||
|
||||
- **v2 경로**: IntentClassifier -> executor -> `SKILL_EMAIL_URL` 환경변수에서 URL 해소 후 HTTP 호출
|
||||
- **Slack 경로**: `slack/message_service.py`에서 "네이버웍스 메일" 키워드 감지 시 `email_integration.send_naverworks_to_slack()` 직접 호출
|
||||
- **email_integration**: `app/services/skills/email_integration.py` -- Gmail/NaverWorks 멀티 프로바이더 지원
|
||||
|
||||
n8n webhook 브릿지는 사용하지 않는다.
|
||||
|
||||
## 흐름 (v2 메시지 경로)
|
||||
|
||||
```
|
||||
Webhook In → Call skill-email → Return Response
|
||||
사용자 발화 ("메일 확인해줘")
|
||||
-> IntentClassifier.classify()
|
||||
-- IntentResult(skill="skill-email", action="email_read", endpoint="/messages", slots={...})
|
||||
-> executor.execute()
|
||||
-- registry.resolve_url() -> SKILL_EMAIL_URL 환경변수
|
||||
-- registry.resolve_method("email_read") -> GET (SKILL.md frontmatter)
|
||||
-- GET {SKILL_EMAIL_URL}/messages (httpx, X-User-Id 헤더)
|
||||
-> _format_as_robeing() -> 응답
|
||||
```
|
||||
|
||||
## 주요 노드
|
||||
| 노드 | 설명 |
|
||||
|---|---|
|
||||
| Webhook In | `POST /skills/email/send` 수신 |
|
||||
| Call skill-email | `POST :8501/api/v1/send` (body 그대로 전달) |
|
||||
| Return Response | webhook 응답 반환 |
|
||||
## SKILL.md 엔드포인트 정의
|
||||
|
||||
## 엔드포인트
|
||||
- 인바운드: `POST /skills/email/send` (n8n webhook)
|
||||
- 아웃바운드: `POST http://192.168.219.52:8501/api/v1/send`
|
||||
| action | 메서드 | 경로 |
|
||||
|--------|--------|------|
|
||||
| `send` / `email_send` | POST | `/send` |
|
||||
| `read` / `email_read` | GET | `/messages` |
|
||||
| `process` / `email_summary` | POST | `/process` |
|
||||
|
||||
## email_integration 모듈
|
||||
|
||||
`app/services/skills/email_integration.py`의 `EmailIntegration` 클래스가 다음을 담당한다:
|
||||
|
||||
| 기능 | 메서드 |
|
||||
|------|--------|
|
||||
| Gmail 장착 확인 | `check_gmail_equipped()` -- DB 직접 조회 |
|
||||
| 프로바이더 결정 | `get_email_provider()` -- 키워드 또는 DB 조회 |
|
||||
| 이메일 요청 처리 | `process_email_request()` -- skill-email HTTP 호출 |
|
||||
| NaverWorks->Slack | `send_naverworks_to_slack()` -- 메일 조회 후 Slack 블록 전송 |
|
||||
| UUID 매핑 | `get_uuid_from_slack()` -- AUTH_SERVER_URL 매핑 API |
|
||||
|
||||
## 환경변수
|
||||
|
||||
| 변수 | 용도 |
|
||||
|------|------|
|
||||
| `SKILL_EMAIL_URL` | skill-email 서비스 베이스 URL |
|
||||
| `SKILL_SLACK_URL` | Slack 전송 시 사용 |
|
||||
| `AUTH_SERVER_URL` | Slack ID -> UUID 매핑 API |
|
||||
| `INTERNAL_API_KEY` | skill-email 인증 |
|
||||
| `SERVICE_API_KEY` | skill-slack 인증 |
|
||||
| `POSTGRES_CONNECTION_STRING` | Gmail 토큰/프로바이더 DB 조회 |
|
||||
|
||||
## 관련 문서
|
||||
- [skill_calendar_request](./skill_calendar_request.md)
|
||||
- [skill_slack_send_message_bridge](./skill_slack_send_message_bridge.md)
|
||||
|
||||
- [skill-email SKILL.md](/home/admin/robeing/DOCS/skills/skill-email/SKILL.md) -- 스킬 계약
|
||||
- [skill_calendar_request.md](./skill_calendar_request.md)
|
||||
- [message_flow_v2.md](../01_conversation/message_flow_v2.md) -- v2 메시지 흐름
|
||||
|
||||
@ -1,24 +1,61 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, skill-news, news, executor, intent]
|
||||
last_updated: 2026-04-06
|
||||
---
|
||||
|
||||
# skill_news_briefing_request 워크플로우
|
||||
|
||||
## 목적
|
||||
뉴스 브리핑 스킬의 webhook 브릿지. 요청 payload를 그대로 skill-news API에 전달하고 결과를 반환한다.
|
||||
|
||||
뉴스 스킬 요청 처리. 뉴스 검색(search), 요약(summarize), 최신 뉴스 조회(latest)를 SKILL.md 기반 executor를 통해 skill-news 서비스로 전달한다.
|
||||
|
||||
## 현행 아키텍처
|
||||
|
||||
- **의도분류**: IntentClassifier가 SKILL.md 본문으로 `skill-news` 매칭
|
||||
- **실행**: executor가 `SKILL_NEWS_URL` 환경변수에서 URL 해소 후 HTTP 호출
|
||||
|
||||
n8n webhook 브릿지는 사용하지 않는다.
|
||||
|
||||
## 흐름
|
||||
|
||||
```
|
||||
Webhook In → Call skill-news → Return Response
|
||||
사용자 발화 ("요즘 뉴스 있어?")
|
||||
-> IntentClassifier.classify()
|
||||
-- IntentResult(skill="skill-news", action="news_fetch", slots={...})
|
||||
-> executor.execute()
|
||||
-- registry.resolve_url() -> SKILL_NEWS_URL 환경변수
|
||||
-- POST {SKILL_NEWS_URL}/api/news/search (httpx)
|
||||
-> _format_as_robeing() -> 응답
|
||||
```
|
||||
|
||||
## 주요 노드
|
||||
| 노드 | 설명 |
|
||||
|---|---|
|
||||
| Webhook In | `POST /skills/news/briefing` 수신 |
|
||||
| Call skill-news | `POST :8505/api/v1/briefing` (body 그대로 전달) |
|
||||
| Return Response | webhook 응답 반환 |
|
||||
## SKILL.md 엔드포인트
|
||||
|
||||
## 엔드포인트
|
||||
- 인바운드: `POST /skills/news/briefing` (n8n webhook)
|
||||
- 아웃바운드: `POST http://192.168.219.52:8505/api/v1/briefing`
|
||||
| action | 메서드 | 경로 | 설명 |
|
||||
|--------|--------|------|------|
|
||||
| `news_fetch` | POST | `/api/news/search` | 키워드 기반 뉴스 검색 |
|
||||
| `news_summary` | POST | `/api/news/summarize` | 기사 요약 |
|
||||
| (조회) | GET | `/api/news/latest` | 최신 뉴스 목록 |
|
||||
|
||||
## 주요 코드 경로
|
||||
|
||||
| 단계 | 코드 |
|
||||
|------|------|
|
||||
| 스킬 정의 | `DOCS/skills/skill-news/SKILL.md` |
|
||||
| 의도분류 | `app/services/brain/intent_classifier.py` |
|
||||
| 실행 | `app/services/skills/executor.py:execute()` |
|
||||
| 뉴스 게시 | `app/services/skills/news_posting_skill.py` -- Slack 채널 뉴스 게시 |
|
||||
| 스타트업 뉴스 | `app/services/skills/startup_news_skill.py` |
|
||||
| 헤드라인 워크플로우 | `app/services/workflows/headlines_workflow.py` -- 일일 헤드라인 |
|
||||
|
||||
## 환경변수
|
||||
|
||||
| 변수 | 용도 |
|
||||
|------|------|
|
||||
| `SKILL_NEWS_URL` | skill-news 서비스 베이스 URL |
|
||||
|
||||
## 관련 문서
|
||||
- [skill_calendar_request](./skill_calendar_request.md)
|
||||
- [scheduled_daily_briefing](../04_scheduler/scheduled_daily_briefing.md)
|
||||
|
||||
- [skill-news SKILL.md](/home/admin/robeing/DOCS/skills/skill-news/SKILL.md) -- 스킬 계약
|
||||
- [skill_calendar_request.md](./skill_calendar_request.md)
|
||||
- [message_flow_v2.md](../01_conversation/message_flow_v2.md) -- v2 메시지 흐름
|
||||
|
||||
@ -1,25 +1,65 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, skill-slack, slack, send-message, slack-sdk]
|
||||
last_updated: 2026-04-06
|
||||
---
|
||||
|
||||
# skill_slack_send_message_bridge 워크플로우
|
||||
|
||||
## 목적
|
||||
Slack 메시지 발송 브릿지. 내부 서비스가 Slack 메시지를 보내야 할 때 이 webhook을 통해 n8n Slack 노드로 발송한다.
|
||||
|
||||
## 흐름
|
||||
내부 서비스가 Slack 메시지를 보내야 할 때의 경로. 현행에서는 `slack_sdk.WebClient`로 직접 Slack API를 호출하거나, skill-slack 서비스를 HTTP로 호출한다.
|
||||
|
||||
## 현행 아키텍처
|
||||
|
||||
n8n Slack 노드 경유는 사용하지 않는다. 두 가지 경로가 존재한다:
|
||||
|
||||
### 경로 1: slack_sdk.WebClient 직접 호출 (Slack <20><><EFBFBD>벤트 응답)
|
||||
|
||||
`app/services/slack/message_service.py`에서 `WebClient(token=bot_token)`으로 Slack API를 직접 호출한다.
|
||||
|
||||
```
|
||||
Webhook In → Normalize Payload → Post to Slack → Return Response
|
||||
slack_endpoint.py (events 수신)
|
||||
-> slack_handler.py (이벤트 처리)
|
||||
-> slack/message_service.py:process_slack_message()
|
||||
-- WebClient(token=bot_token)
|
||||
-- local_client.chat_postMessage(channel, text, thread_ts, blocks)
|
||||
```
|
||||
|
||||
## 주요 노드
|
||||
| 노드 | 설명 |
|
||||
|---|---|
|
||||
| Webhook In | `POST /skills/slack/send` 수신 |
|
||||
| Normalize Payload | channel, text, thread_ts, blocks 추출 |
|
||||
| Post to Slack | Slack `chat.postMessage` (스레드 지원) |
|
||||
| Return Response | `{success, channel, thread_ts}` 반환 |
|
||||
봇 토큰은 Slack 이벤트 컨텍스트에서 전달받는다.
|
||||
|
||||
## 엔드포인트
|
||||
- 인바운드: `POST /skills/slack/send` (n8n webhook)
|
||||
- 아웃바운드: Slack `chat.postMessage`
|
||||
### 경로 2: skill-slack HTTP 호출 (스케줄러/워크플로우)
|
||||
|
||||
스케줄러 잡이나 LangGraph 워크플로우에서 Slack 메시지를 보낼 때<><EB958C><EFBFBD> skill-slack 서비스를 HTTP로 호출한다.
|
||||
|
||||
```
|
||||
coldmail_workflow.py / headlines_workflow.py
|
||||
-> POST {SKILL_SLACK_URL}/api/v1/send
|
||||
headers: X-API-Key
|
||||
body: { channel, text, blocks, thread_ts }
|
||||
```
|
||||
|
||||
## 주요 코드 경로
|
||||
|
||||
| 용도 | 코드 | Slack 호출 <20><><EFBFBD>식 |
|
||||
|------|------|----------------|
|
||||
| Slack 이벤트 응답 | `app/services/slack/message_service.py` | `slack_sdk.WebClient` 직접 |
|
||||
| 콜드메일 알림 | `app/services/workflows/coldmail_workflow.py:send_node()` | skill-slack HTTP |
|
||||
| 헤드라인 게시 | `app/services/workflows/headlines_workflow.py` | skill-slack HTTP |
|
||||
| 뉴스 게시 | `app/services/skills/news_posting_skill.py` | `slack_sdk.WebClient` 직접 |
|
||||
| DM 발송 | `app/services/skills/dm_skill.py` | `slack_sdk.WebClient` 직접 |
|
||||
| 명확화 UI | `app/services/slack/clarify_service.py` | `slack_sdk.WebClient` 직접 |
|
||||
|
||||
## 환경변수
|
||||
|
||||
| 변수 | 용도 |
|
||||
|------|------|
|
||||
| `SKILL_SLACK_URL` | skill-slack 서비스 베이스 URL (HTTP 경로) |
|
||||
| `SKILL_SLACK_API_KEY` / `SERVICE_API_KEY` | skill-slack 인증 키 |
|
||||
| `SLACK_BOT_TOKEN` | 기본 워크스페이스 봇 토큰 (직접 호출 시) |
|
||||
|
||||
## 관련 문서
|
||||
- [skill_email_send_request](./skill_email_send_request.md)
|
||||
- [slack_basic_dialogue](../01_conversation/slack_basic_dialogue.md)
|
||||
|
||||
- [slack_direct_api_send.md](./slack_direct_api_send.md) -- DB 봇토큰 직접 전송 경로
|
||||
- [skill-slack SKILL.md](/home/admin/robeing/DOCS/skills/skill-slack/SKILL.md) -- 스킬 계약
|
||||
- [message_flow_v2.md](../01_conversation/message_flow_v2.md) -- v2 메시지 흐름
|
||||
|
||||
@ -1,24 +1,30 @@
|
||||
---
|
||||
type: workflow
|
||||
tags: [workflow, slack, direct-api, bot-token, grounding]
|
||||
last_updated: 2026-04-06
|
||||
---
|
||||
|
||||
# Slack Direct API 메시지 전송 워크플로우
|
||||
|
||||
## 목적
|
||||
|
||||
에이전트(23 클로드 등)가 Slack API를 직접 호출하여 특정 워크스페이스 채널에 메시지를 보내는 경로.
|
||||
n8n 브릿지(`skill_slack_send_message_bridge`)와 별개로, DB에 저장된 봇 토큰을 사용하여 직접 전송한다.
|
||||
에이전<EFBFBD><EFBFBD>(23 클로드 등)가 Slack API를 직접 호출하여 특정 워크스페이스 채널에 메시지를 보내는 경로.
|
||||
DB에 저장된 봇 토큰을 사용하여 직접 전송한다.
|
||||
|
||||
## 실측 경로 (260323 검증)
|
||||
|
||||
```
|
||||
1. DB에서 봇 토큰 조회
|
||||
└─ slack_workspace 테이블: slack_team_id → bot_token
|
||||
-- slack_workspace 테이블: slack_team_id -> bot_token
|
||||
|
||||
2. Slack API 호출
|
||||
└─ POST https://slack.com/api/chat.postMessage
|
||||
-- POST https://slack.com/api/chat.postMessage
|
||||
Headers: Authorization: Bearer {bot_token}
|
||||
Body: { channel, text }
|
||||
|
||||
3. 응답 확인
|
||||
└─ ok: true → 전송 성공
|
||||
└─ ok: false → error 필드 확인
|
||||
-- ok: true -> 전송 성공
|
||||
-- ok: false -> error 필드 확인
|
||||
```
|
||||
|
||||
## 워크스페이스별 봇 토큰 조회
|
||||
@ -33,23 +39,11 @@ FROM slack_workspace;
|
||||
| 디지털비징_로빙 | T0925SXPS4D | 내부 테스트 |
|
||||
| 컴퍼니엑스 | T09C98KB933 | 실서비스 (Company X) |
|
||||
|
||||
## 채널 ID 확인
|
||||
|
||||
```bash
|
||||
# 봇이 접근 가능한 DM 목록
|
||||
curl -s -H "Authorization: Bearer {bot_token}" \
|
||||
"https://slack.com/api/conversations.list?types=im,mpim&limit=50"
|
||||
|
||||
# 특정 채널 최근 메시지
|
||||
curl -s -H "Authorization: Bearer {bot_token}" \
|
||||
"https://slack.com/api/conversations.history?channel={channel_id}&limit=5"
|
||||
```
|
||||
|
||||
## 메시지 전송
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer {bot_token}" \
|
||||
-H "Authorization: Bearer $BOT_TOKEN" \
|
||||
-H "Content-Type: application/json; charset=utf-8" \
|
||||
-d '{
|
||||
"channel": "{channel_id}",
|
||||
@ -58,29 +52,32 @@ curl -s -X POST \
|
||||
"https://slack.com/api/chat.postMessage"
|
||||
```
|
||||
|
||||
봇 토큰은 DB(`slack_workspace` 테이블)에서 조회한다. 환경변수 또는 코드에 하드코딩하지 않는다.
|
||||
|
||||
## 주의사항
|
||||
|
||||
1. **봇 토큰으로 보내면 발신자가 Robeing** — 봇이 자기 자신에게 보내는 꼴이라 로빙이 응답하지 않음
|
||||
1. **봇 토큰으로 <EFBFBD><EFBFBD>내면 발신자가 Robeing** -- 봇이 자기 자신에게 보내는 꼴이라 로빙이 응답하지 않음
|
||||
2. **사용자에게 메시지를 보내려면** 사용자의 user_id로 `conversations.open` 후 DM 채널 생성 필요
|
||||
3. **워크스페이스가 다르면 토큰도 다름** — 반드시 대상 워크스페이스의 bot_token 사용
|
||||
4. **channel_not_found 에러** — 봇이 해당 채널에 접근 권한이 없거나, 다른 워크스페이스의 채널 ID를 사용한 경우
|
||||
3. **워크스페이스가 다르면 토큰도 다름** -- 반드시 대상 워크스페이스의 bot_token 사용
|
||||
4. **channel_not_found 에러** -- 봇이 해당 채널에 접근 권한이 없거나, 다른 워크스페이스의 채널 ID를 사용한 경우
|
||||
|
||||
## 260323 실측 결과
|
||||
|
||||
| 시도 | 워크스페이스 | 채널 | 결과 |
|
||||
|------|-------------|------|------|
|
||||
| 1차 | 디지털비징_로빙 (T0925SXPS4D) | D09DWLAV88Y | channel_not_found (워크스페이스 불일치) |
|
||||
| 2차 | 디지털비징_로빙 | D0935RJ8LLQ | 전송 성공 (CTO DM) — 단, 로빙 응답 없음 (봇→봇) |
|
||||
| 3차 | 컴퍼니엑스 (T09C98KB933) | D09DWLAV88Y | 전송 성공 — 로빙 응답함 (grounding 질문만 답, 나머지 무시) |
|
||||
| 2차 | 디지털비징_로빙 | D0935RJ8LLQ | 전송 성공 (CTO DM) -- 단, 로빙 응답 없음 (봇->봇) |
|
||||
| 3차 | 컴퍼니엑스 (T09C98KB933) | D09DWLAV88Y | 전송 성공 -- 로빙 응답함 (grounding 질문만 답, 나머지 무시) |
|
||||
|
||||
## 로빙 응답 분석 (260323)
|
||||
## 로빙 응답 분석 (260323) 및 이후 개선
|
||||
|
||||
질문 6개 중 "컴퍼니엑스가 뭐하는 회사야?"만 답변. 나머지 5개(시간/날짜/이름/산수/대화이력) 전부 무시.
|
||||
- 원인: "컴퍼니엑스" 마커에 grounding이 트리거되면 RAG 결과만 반환하고 나머지 질문 드랍
|
||||
- 참고: `[24]_260323_로빙교육_못하는것3가지.md`, `[24]_260323_로빙교육_못하는것_2차진단.md`
|
||||
질문 6개 중 "컴퍼니엑스가 뭐하<EBAD90><ED9598> 회사야?"만 답변. 나머지 5개(시간/날짜/이름/산수/대화이력) 전부 무시.
|
||||
|
||||
- **원인**: "컴퍼니엑스" 마커에 grounding이 트리거되면 RAG 결과만 반환하고 나머지 질문 드랍
|
||||
- **현행 개선**: `companyx_grounding_service.py`에서 `should_handle_companyx_grounding()` 함수가 LLM이 명확한 의도를 이미 확정한 경우 grounding이 덮어쓰지 않도록 개선됨. `_looks_like_companyx_grounding_question()` 함수가 grounding 대상 여부를 사전 판별한다.
|
||||
- **team_id 기반 <20><><EFBFBD>기**: Slack team_id로 워크스페이스를 식별하고, DB team UUID로 매핑한 후 grounding/RAG 검색에 활용
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [skill_slack_send_message_bridge](./skill_slack_send_message_bridge.md) — n8n 브릿지 경로
|
||||
- [slack_basic_dialogue](../01_conversation/slack_basic_dialogue.md) — 슬랙 대화 워크플로우
|
||||
- [companyx_grounding_pipeline](../03_rag/companyx_grounding_pipeline.md) — grounding 라우팅
|
||||
- [skill_slack_send_message_bridge.md](./skill_slack_send_message_bridge.md) -- skill-slack HTTP/직접 <20><><EFBFBD>송 경로
|
||||
- [companyx_grounding_pipeline.md](../03_rag/companyx_grounding_pipeline.md) -- grounding 라우팅
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user