workflow: coldmail IR, calendar, companyx grounding, admin 파이프라인 추가

Made-with: Cursor
This commit is contained in:
happybell80 2026-03-19 00:05:26 +09:00
parent d16124b499
commit c0a3478dd2
6 changed files with 579 additions and 10 deletions

View File

@ -0,0 +1,105 @@
{
"name": "robeing-slack-coldmail-ir-notification",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "notifications/coldmail-ir",
"responseMode": "responseNode",
"options": {}
},
"id": "cm-ir-001",
"name": "Webhook In",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [200, 240]
},
{
"parameters": {
"jsCode": "const body = $json.body || {};\nconst has_ir = body.has_ir_deck && body.selected_document_id;\nreturn {\n company_name: body.company_name || '알 수 없는 회사',\n sender: body.sender || '',\n subject: body.subject || '',\n summary: body.summary || '요약 정보가 없습니다.',\n has_ir,\n document_id: body.selected_document_id || '',\n document_name: body.selected_filename || '',\n slack_channel: body.slack_channel || 'C07...',\n robeing_id: body.robeing_id || 'rb8001'\n};"
},
"id": "cm-ir-002",
"name": "Format Coldmail Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [440, 240]
},
{
"parameters": {
"resource": "message",
"operation": "post",
"channel": "={{ $json.slack_channel }}",
"text": "=📩 *새 콜드메일 감지: {{ $json.company_name }}*\\n보낸이: {{ $json.sender }}\\n제목: {{ $json.subject }}\\n\\n*요약*\\n{{ $json.summary }}",
"otherOptions": {
"includeLinkToApp": false
}
},
"id": "cm-ir-003",
"name": "Post Basic Notification",
"type": "n8n-nodes-base.slack",
"typeVersion": 2,
"position": [700, 240]
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.has_ir }}",
"value2": true
}
]
}
},
"id": "cm-ir-004",
"name": "Has IR Deck?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [960, 240]
},
{
"parameters": {
"resource": "message",
"operation": "post",
"channel": "={{ $json.slack_channel }}",
"text": "=📄 *IR 자료 감지됨: {{ $json.document_name }}*",
"otherOptions": {
"threadTs": "={{ $json.ts }}",
"attachments": [
{
"fallback": "IR 분석 버튼",
"callback_id": "coldmail_analyze_ir",
"actions": [
{
"name": "analyze",
"text": "📊 IR 지표 추출 및 밸류에이션",
"type": "button",
"value": "={{ $json.document_id }}",
"style": "primary"
}
]
}
]
}
},
"id": "cm-ir-005",
"name": "Add IR Analysis Button",
"type": "n8n-nodes-base.slack",
"typeVersion": 2,
"position": [1240, 160]
}
],
"connections": {
"Webhook In": { "main": [[{ "node": "Format Coldmail Data", "type": "main", "index": 0 }]] },
"Format Coldmail Data": { "main": [[{ "node": "Post Basic Notification", "type": "main", "index": 0 }]] },
"Post Basic Notification": { "main": [[{ "node": "Has IR Deck?", "type": "main", "index": 0 }]] },
"Has IR Deck?": {
"main": [
[{ "node": "Add IR Analysis Button", "type": "main", "index": 0 }],
[]
]
}
},
"pinData": {},
"meta": { "templateCredsSetupCompleted": true, "instanceId": "robeing-conversation" }
}

View File

@ -0,0 +1,125 @@
{
"name": "robeing-skill-calendar-request",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "skills/calendar/request",
"responseMode": "responseNode",
"options": {}
},
"id": "cal-req-001",
"name": "Webhook In",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [200, 240]
},
{
"parameters": {
"jsCode": "const body = $json.body || {};\nconst action = body.action || 'query'; // 'create', 'query', 'delete'\nreturn {\n action,\n user_id: body.user_id,\n robeing_id: body.robeing_id || 'rb8001',\n query: body.query || '',\n event_data: body.event_data || {},\n event_id: body.event_id || ''\n};"
},
"id": "cal-req-002",
"name": "Normalize Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [440, 240]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.action }}",
"operation": "equals",
"value2": "create"
}
]
}
},
"id": "cal-req-003",
"name": "Action Type",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [680, 240]
},
{
"parameters": {
"method": "POST",
"url": "=http://192.168.219.52:8512/api/events",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-User-Id",
"value": "={{ $json.user_id }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.event_data }}",
"options": {}
},
"id": "cal-req-004",
"name": "Create Event",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [940, 160]
},
{
"parameters": {
"method": "GET",
"url": "=http://192.168.219.52:8512/api/events",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-User-Id",
"value": "={{ $json.user_id }}"
}
]
},
"qs": {
"parameters": [
{
"name": "query",
"value": "={{ $json.query }}"
}
]
},
"options": {}
},
"id": "cal-req-005",
"name": "Query Events",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [940, 320]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json.body || $json }}",
"options": {}
},
"id": "cal-req-006",
"name": "Return Result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [1220, 240]
}
],
"connections": {
"Webhook In": { "main": [[{ "node": "Normalize Payload", "type": "main", "index": 0 }]] },
"Normalize Payload": { "main": [[{ "node": "Action Type", "type": "main", "index": 0 }]] },
"Action Type": {
"main": [
[{ "node": "Create Event", "type": "main", "index": 0 }],
[{ "node": "Query Events", "type": "main", "index": 0 }]
]
},
"Create Event": { "main": [[{ "node": "Return Result", "type": "main", "index": 0 }]] },
"Query Events": { "main": [[{ "node": "Return Result", "type": "main", "index": 0 }]] }
},
"pinData": {},
"meta": { "templateCredsSetupCompleted": true, "instanceId": "robeing-skills" }
}

View File

@ -0,0 +1,98 @@
{
"name": "robeing-rag-companyx-grounding-pipeline",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "rag/companyx/grounding",
"responseMode": "responseNode",
"options": {}
},
"id": "cx-rag-001",
"name": "Webhook In",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [200, 240]
},
{
"parameters": {
"jsCode": "const body = $json.body || {};\nreturn {\n query: body.query || '',\n user_uuid: body.user_id || '',\n team_id: '79441171-3951-4870-beb8-916d07fe8be5', // Company X Team ID\n limit: body.limit || 5\n};"
},
"id": "cx-rag-002",
"name": "Set Context (Company X)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [440, 240]
},
{
"parameters": {
"method": "POST",
"url": "=http://192.168.219.52:8508/api/search",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"query\": \"{{ $json.query }}\",\n \"team_id\": \"{{ $json.team_id }}\",\n \"limit\": {{ $json.limit }}\n}",
"options": {}
},
"id": "cx-rag-003",
"name": "Search Internal Docs",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [680, 240]
},
{
"parameters": {
"jsCode": "const results = $json.body || [];\nconst context = results.map(r => `[출처: ${r.filename}]\\n${r.text}`).join('\\n---\\n');\nconst original = $('Set Context (Company X)').item.json;\nreturn {\n message: original.query,\n grounding_context: context,\n user_id: original.user_uuid,\n robeing_id: 'rb8001'\n};"
},
"id": "cx-rag-004",
"name": "Prepare LLM Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [940, 240]
},
{
"parameters": {
"method": "POST",
"url": "=http://192.168.219.52:8001/api/chat",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-User-Id",
"value": "={{ $json.user_id }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"message\": \"{{ $json.message }}\",\n \"context\": \"아래의 컴퍼니엑스 내부 문서 내용을 바탕으로 답변하세요:\\n{{ $json.grounding_context }}\",\n \"source\": \"n8n-grounding\"\n}",
"options": {}
},
"id": "cx-rag-005",
"name": "Call rb8001 with Context",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1200, 240]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json.body }}",
"options": {}
},
"id": "cx-rag-006",
"name": "Return Grounded Answer",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [1460, 240]
}
],
"connections": {
"Webhook In": { "main": [[{ "node": "Set Context (Company X)", "type": "main", "index": 0 }]] },
"Set Context (Company X)": { "main": [[{ "node": "Search Internal Docs", "type": "main", "index": 0 }]] },
"Search Internal Docs": { "main": [[{ "node": "Prepare LLM Prompt", "type": "main", "index": 0 }]] },
"Prepare LLM Prompt": { "main": [[{ "node": "Call rb8001 with Context", "type": "main", "index": 0 }]] },
"Call rb8001 with Context": { "main": [[{ "node": "Return Grounded Answer", "type": "main", "index": 0 }]] }
},
"pinData": {},
"meta": { "templateCredsSetupCompleted": true, "instanceId": "robeing-rag" }
}

View File

@ -0,0 +1,104 @@
{
"name": "robeing-admin-diary-reflection-pipeline",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 2 * * *"
}
]
}
},
"id": "diary-001",
"name": "Daily at 2AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [200, 240]
},
{
"parameters": {
"httpMethod": "POST",
"path": "admin/diary/trigger",
"options": {}
},
"id": "diary-002",
"name": "Manual Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [200, 440]
},
{
"parameters": {
"jsCode": "const now = new Date();\nconst yesterday = new Date(now);\nyesterday.setDate(yesterday.getDate() - 1);\nconst dateStr = yesterday.toISOString().split('T')[0];\nreturn {\n date: dateStr,\n robeing_id: 'rb8001'\n};"
},
"id": "diary-003",
"name": "Set Yesterday Date",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [460, 340]
},
{
"parameters": {
"method": "POST",
"url": "=http://192.168.219.52:8001/api/diary/generate",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"date\": \"{{ $json.date }}\",\n \"robeing_id\": \"{{ $json.robeing_id }}\"\n}",
"options": {
"timeout": 300000
}
},
"id": "diary-004",
"name": "Generate Diary (rb8001)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [700, 340]
},
{
"parameters": {
"method": "GET",
"url": "=http://192.168.219.52:8001/api/diary/{{ $json.date }}",
"qs": {
"parameters": [
{
"name": "robeing_id",
"value": "rb8001"
}
]
},
"options": {}
},
"id": "diary-005",
"name": "Get Diary Content",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [940, 340]
},
{
"parameters": {
"resource": "message",
"operation": "post",
"channel": "C07...",
"text": "=📜 *로빙 일기 ({{ $json.date }})*\\n\\n{{ $json.summary }}\\n\\n지배적 감정: {{ $json.dominant_emotion }}\\n\\n[대시보드에서 자세히 보기](http://192.168.219.45:3000/diary)",
"otherOptions": {}
},
"id": "diary-006",
"name": "Post to Slack",
"type": "n8n-nodes-base.slack",
"typeVersion": 2,
"position": [1180, 340]
}
],
"connections": {
"Daily at 2AM": { "main": [[{ "node": "Set Yesterday Date", "type": "main", "index": 0 }]] },
"Manual Trigger": { "main": [[{ "node": "Set Yesterday Date", "type": "main", "index": 0 }]] },
"Set Yesterday Date": { "main": [[{ "node": "Generate Diary (rb8001)", "type": "main", "index": 0 }]] },
"Generate Diary (rb8001)": { "main": [[{ "node": "Get Diary Content", "type": "main", "index": 0 }]] },
"Get Diary Content": { "main": [[{ "node": "Post to Slack", "type": "main", "index": 0 }]] }
},
"pinData": {},
"meta": { "templateCredsSetupCompleted": true, "instanceId": "robeing-admin" }
}

View File

@ -0,0 +1,107 @@
{
"name": "robeing-admin-service-health-check",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"interval": 10
}
]
}
},
"id": "health-001",
"name": "Every 10 mins",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [200, 240]
},
{
"parameters": {
"jsCode": "return [\n { name: 'robeing-gateway', url: 'http://192.168.219.45:8100/health' },\n { name: 'auth-server', url: 'http://192.168.219.45:9000/health' },\n { name: 'rb8001', url: 'http://192.168.219.52:8001/health' },\n { name: 'skill-email', url: 'http://192.168.219.52:8501/healthz' },\n { name: 'skill-news', url: 'http://192.168.219.52:8505/healthz' },\n { name: 'skill-rag-file', url: 'http://192.168.219.52:8508/healthz' },\n { name: 'skill-calendar', url: 'http://192.168.219.52:8512/health' }\n];"
},
"id": "health-002",
"name": "Service List",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [440, 240]
},
{
"parameters": {
"method": "GET",
"url": "={{ $json.url }}",
"options": {
"response": {
"response": {
"fullResponse": true
}
},
"timeout": 5000
}
},
"id": "health-003",
"name": "Check Health",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [680, 240],
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "const service = $item.input.json.name;\nconst response = $json.body || {};\nconst statusCode = $json.statusCode;\nconst isUp = statusCode === 200 || statusCode === 201;\nreturn {\n service,\n is_up: isUp,\n status: isUp ? 'UP' : 'DOWN',\n code: statusCode || 'TIMEOUT/ERROR'\n};"
},
"id": "health-004",
"name": "Assess Status",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [940, 240]
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.is_up }}",
"value2": false
}
]
}
},
"id": "health-005",
"name": "Is Down?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [1180, 240]
},
{
"parameters": {
"resource": "message",
"operation": "post",
"channel": "C07...",
"text": "=🚨 *서비스 장애 감지*\\n\\n서비스: *{{ $json.service }}*\\n상태: DOWN (Code: {{ $json.code }})\\n\\n확인이 필요합니다.",
"otherOptions": {}
},
"id": "health-006",
"name": "Alert Slack",
"type": "n8n-nodes-base.slack",
"typeVersion": 2,
"position": [1440, 180]
}
],
"connections": {
"Every 10 mins": { "main": [[{ "node": "Service List", "type": "main", "index": 0 }]] },
"Service List": { "main": [[{ "node": "Check Health", "type": "main", "index": 0 }]] },
"Check Health": { "main": [[{ "node": "Assess Status", "type": "main", "index": 0 }]] },
"Assess Status": { "main": [[{ "node": "Is Down?", "type": "main", "index": 0 }]] },
"Is Down?": {
"main": [
[{ "node": "Alert Slack", "type": "main", "index": 0 }],
[]
]
}
},
"pinData": {},
"meta": { "templateCredsSetupCompleted": true, "instanceId": "robeing-admin" }
}

View File

@ -1,16 +1,46 @@
# Robeing n8n Workflows
로빙의 반복 실행 절차를 n8n import용 JSON으로 분류해 둔 폴더입니다.
로빙의 분산 마이크로서비스 및 비즈니스 로직을 n8n으로 자동화하기 위한 템플릿 저장소입니다.
## 폴더
## 폴더 구조
- `01_conversation/`: Slack 대화 진입, 스레드 요약, 액션 추출
- `02_skills/`: `skill-*` 서비스 호출 브리지
- `03_rag/`: 업로드, 임베딩, 검색, 인덱싱
- `04_scheduler/`: APScheduler 성격의 정기 실행 대체/보조 흐름
- `01_conversation/`: Slack 진입점, 대화 정규화, 콜드메일 IR 알림 및 액션 추출
- `02_skills/`: `skill-*` 서비스 브리지 (Email, News, Calendar, Slack)
- `03_rag/`: 파일 업로드, 인덱싱, 임베딩, CompanyX 전용 Grounding 파이프라인
- `04_scheduler/`: 정기 브리핑, 상태 알림, 재시도 로직
- `05_admin/`: 서비스 헬스 체크, 일기(Reflection) 자동 생성 및 동기화
## 사용 원칙
## 시스템 구성 및 엔드포인트
- 이 JSON은 n8n import 템플릿입니다.
- 실제 운영 전 credential, URL, channel, API key, DB 연결은 환경에 맞게 교체해야 합니다.
- 로빙의 판단 루프 자체를 대체하지 않고, 반복 실행 절차를 분리하는 용도로 사용합니다.
| 서버 | 역할 | IP | 주요 포트 |
|------|------|----|-----------|
| 51123 | 메인/인증/DB | 192.168.219.45 | Gateway(8100), Auth(9000), DB(5432) |
| 51124 | 로빙/스킬 | 192.168.219.52 | rb8001(8001), Email(8501), News(8505), RAG(8508), Calendar(8512) |
## 주요 워크플로우 가이드
### 1. 로빙 일기 시스템 (`05_admin/diary_reflection_pipeline.json`)
- 매일 오전 2시, `rb8001`의 하루 활동과 감정을 집계하여 일기를 생성합니다.
- 생성된 요약본을 Slack 관리자 채널로 전송합니다.
### 2. 콜드메일 IR 분석 (`01_conversation/coldmail_ir_notification_sync.json`)
- 네이버웍스 등을 통해 수신된 콜드메일을 감지하고, IR Deck(PDF) 존재 여부를 판별합니다.
- IR 자료가 있을 경우 Slack에 분석 버튼을 노출하여 원클릭으로 지표 추출을 실행합니다.
### 3. 서비스 헬스 모니터 (`05_admin/service_health_check.json`)
- 10분 간격으로 모든 마이크로서비스의 `/health` 또는 `/healthz`를 체크합니다.
- 장애 발생 시 Slack 알림을 통해 즉각적인 대응을 지원합니다.
### 4. 캘린더 스킬 브리지 (`02_skills/skill_calendar_request.json`)
- 구글 캘린더 연동을 위한 브리지 워크플로우입니다.
- 일정 조회, 등록, 삭제 요청을 `skill-calendar` 서비스로 전달합니다.
## 사용 시 주의사항
- **Credential 설정**: 각 노드의 Slack, HTTP Request 노드에서 실제 환경의 Credentials(Token, API Key)를 설정해야 합니다.
- **IP 주소**: 현재 템플릿은 내부 IP(`192.168.219.*`)를 기본값으로 사용합니다. 환경이 다를 경우 일괄 변경이 필요합니다.
- **채널 ID**: Slack 노드에 설정된 `channelId`는 예시 값이므로, 실제 알림을 받을 채널 ID로 수정하십시오.
---
**최근 업데이트**: 2026-03-18
**상태**: 16개 워크플로우 반영 완료 (기존 12개 + 신규 4개)