Slack 멘션·DM 을 AWS Lambda 에서 처리하고, OpenAI · AWS Bedrock · xAI(Grok) LLM 으로 네이티브 function calling 기반 툴 오케스트레이션을 수행하는 봇입니다.
모든 사용자 메시지는 다음 네 단계를 순서대로 통과합니다:
질문 ── 의도·계획 ── 툴 사용 (반복) ── 응답
(user) (LLM) (tools) (LLM)
의도 파악과 계획은 한 번의 LLM 호출로 통합되어 있습니다 (OpenAI / Claude / Nova 의 native function calling). 같은 응답에 "무슨 요청인지 파악한 결과" 와 "다음에 부를 tool_calls" 가 함께 담겨 옵니다. 별도의 intent 분류 hop 을 추가하지 않습니다.
- 의도·계획은 LLM 이 한다. 키워드 매칭(예:
"그려"→ 이미지)으로 우회하지 않는다. LLM 이 메시지를 읽고tool_calls로 의도를 표현한다. - 단계 단축 금지. 이미지 요청처럼 명확해 보여도
LLM 판단 → generate_image tool → LLM 응답 합성전 과정을 거친다. 응답 합성 단계를 건너뛰면 caption·후속 대응·에러 처리가 사라진다. - Agent 루프는
src/agent.py안에 있고,app.py는 Slack 관련 부분(placeholder, streaming, 히스토리) 만 담당한다. - 속도 문제는 파이프라인 단축이 아닌 스트리밍·비동기·모델 선택으로 해결한다.
- 이벤트:
app_mention, DM(message.im) - Provider: OpenAI · AWS Bedrock(Anthropic Claude 3/3.5/4.x · Amazon Nova) · xAI(Grok) 선택 가능
- Tools (네이티브 function calling)
read_attached_images— 첨부 이미지 Vision 요약fetch_thread_history— 스레드 히스토리 조회search_web— Tavily (TAVILY_API_KEY 설정 시) 또는 DuckDuckGogenerate_image— 이미지 생성 후 Slack 업로드get_current_time— 서버 기본 TZ(또는timezone인자) 로 현재 시각/요일 반환read_attached_document— 첨부 PDF/텍스트 파일 추출 (페이지·바이트·문자 상한 적용)
- Production 기반
- DynamoDB 조건부 put 으로 Slack 재시도 중복 제거
- 채널 allowlist · 유저당 동시 요청 throttle
- DynamoDB 기반 스레드 대화 메모리 (TTL 1h)
- 긴 응답 계층적 분할 전송 (코드블록 → 문단 → 문장 → hard slice),
chat.update가msg_too_long에 걸리지 않도록MAX_LEN_SLACK기반 rolling 스트리밍 + 최종 답변 자동 split - 스트리밍
chat_postMessage+ 반복chat_updatefallback (네이티브chat.startStream/appendStream/stopStream은 AI 워크스페이스에서 추가 "searching" 상태 UI 를 띄워 두 개의 응답처럼 보이는 이슈 때문에 기본 비활성화,enable_native=True로만 사용). 회신 데이터가 없을 때 (thinking / tool_use / tool_result 단계) 는assistant_threads_setStatus로 타이핑 인디케이터만 표시하고, 첫 content delta 가 도착한 시점에 비로소stream_msg.start()를 지연 호출해 placeholder 메시지를 posting — 상태 UI 와 placeholder 가 동시에 뜨지 않아 두 개의 응답으로 보이던 이슈 해소. - 구조화 JSON 로깅 + request_id, agent 루프 관찰값 기록
- 에러 메시지 sanitize (토큰·경로 redaction)
| 변수 | 필수 | 기본값 | 설명 |
|---|---|---|---|
SLACK_BOT_TOKEN |
✅ | — | xoxb-… |
SLACK_SIGNING_SECRET |
✅ | — | Slack Signing Secret |
OPENAI_API_KEY |
OpenAI 사용 시 | — | OpenAI API 키 |
XAI_API_KEY |
xAI 사용 시 | — | xAI (Grok) API 키 — https://console.x.ai |
TAVILY_API_KEY |
— | 설정 시 Tavily 웹 검색 활성화 | |
LLM_PROVIDER |
openai |
openai / bedrock / xai |
|
LLM_MODEL |
gpt-4o-mini |
텍스트 모델 | |
IMAGE_PROVIDER |
openai |
openai / bedrock / xai |
|
IMAGE_MODEL |
gpt-image-1 |
이미지 모델 | |
AGENT_MAX_STEPS |
3 |
tool 루프 최대 iteration | |
RESPONSE_LANGUAGE |
ko |
ko / en |
|
DYNAMODB_TABLE_NAME |
lambda-slack-bot-dev |
dedup / 대화 저장 테이블 | |
AWS_REGION |
us-east-1 |
AWS 리전 | |
ALLOWED_CHANNEL_IDS |
(empty) | 콤마 구분. 비어있으면 모든 채널 허용 | |
ALLOWED_CHANNEL_MESSAGE |
— | 비허용 채널 응답 메시지 | |
MAX_LEN_SLACK |
3000 |
메시지 분할 기준 (≥500). Slack chat.update 의 한계 회피용 안전 margin. .env.example / serverless.yml 기본값. 변수 자체가 미설정이면 config.py fallback 2000. |
|
MAX_OUTPUT_TOKENS |
4096 |
LLM hop 당 출력 토큰 상한 (≥256) | |
MAX_THROTTLE_COUNT |
100 |
유저별 동시 요청 상한 | |
MAX_HISTORY_CHARS |
4000 |
저장되는 대화 직렬화 최대 길이 | |
DEFAULT_TIMEZONE |
Asia/Seoul |
get_current_time 기본 TZ (IANA). 잘못된 이름이면 기본값으로 폴백 + 경고 |
|
MAX_DOC_CHARS |
20000 |
read_attached_document 추출 텍스트 최대 문자수 (≥1000) |
|
MAX_DOC_PAGES |
50 |
read_attached_document PDF 최대 페이지수 (≥1) |
|
MAX_DOC_BYTES |
26214400 |
read_attached_document 다운로드 최대 바이트 (기본 25MB, ≥65536) |
|
BOT_CURSOR |
:robot_face: |
플레이스홀더·스트림 인디케이터 이모지 | |
SYSTEM_MESSAGE |
— | 시스템 프롬프트 오버라이드 | |
LOG_LEVEL |
INFO |
로그 레벨 |
| 용도 | OpenAI | Bedrock | xAI (Grok) |
|---|---|---|---|
| 텍스트 + tool calling | gpt-4o-mini, gpt-4o, gpt-5-*, o1/o3/o4 |
us.anthropic.claude-opus-4-6-v1, us.anthropic.claude-sonnet-4-5-..., amazon.nova-pro-v1:0 |
grok-4-1-fast-reasoning, grok-4.20-0309-reasoning, grok-4.20-multi-agent-0309 |
| 이미지 생성 | gpt-image-1, dall-e-3 |
amazon.nova-canvas-v1:0, amazon.titan-image-generator-v2:0 |
grok-imagine-image, grok-imagine-image-pro |
- Claude 는 Messages API (
tools=[{name, description, input_schema}]), Nova 는 Converse API (toolConfig) 로 자동 분기됩니다. - xAI 는 OpenAI wire 호환이라 OpenAI Python SDK 에
base_url="https://api.x.ai/v1"만 swap 해서 호출합니다. 별도XAIProvider클래스로 분리되어 있습니다. - Bedrock 최신 모델은
us./eu./apac./global.inference-profile prefix 가 붙은 ID 로만 호출됩니다.BedrockProvider가 자동 인식합니다.
pip install -r requirements.txt
pip install -r requirements-dev.txt
cp .env.example .env.local # 값 채우기
# CLI 실행 (스트리밍이 기본값)
python localtest.py "오늘 서울 날씨"
python localtest.py --no-stream "React 훅 설명해줘" # 전체 답변을 한 번에 출력
python localtest.py --quiet-steps "…" # 중간 step 로그 숨김
python localtest.py # 대화형 (stdin, Ctrl+D)
# 테스트
python -m pytest --cov=src --cov-report=term-missing.env.local 은 src/config.py 가 python-dotenv 로 자동 로드합니다. SLACK_BOT_TOKEN 이 placeholder 이면 localtest.py 가 Slack 호출을 stub 으로 대체합니다.
role/lambda-slack-bot 을 AWS 계정에 생성하고 GitHub OIDC trust + 배포용 policy 를 연결합니다. 템플릿과 상세 절차는 .github/aws-role/ 에 있습니다:
cd .github/aws-role
export NAME="lambda-slack-bot"
aws iam create-role --role-name "${NAME}" --assume-role-policy-document file://trust-policy.json
aws iam create-policy --policy-name "${NAME}" --policy-document file://role-policy.json
export ACCOUNT_ID=$(aws sts get-caller-identity | jq -r .Account)
aws iam attach-role-policy --role-name "${NAME}" --policy-arn "arn:aws:iam::${ACCOUNT_ID}:policy/${NAME}"trust-policy.json 은 nalbam/lambda-slack-bot repo 의 OIDC 토큰을, role-policy.json 은 CloudFormation / Lambda / IAM / S3 / DynamoDB / API Gateway / CloudWatch Logs 권한을 (lambda-slack-bot-* 스코프) 포함합니다.
- Secrets:
AWS_ACCOUNT_ID,SLACK_BOT_TOKEN,SLACK_SIGNING_SECRET,OPENAI_API_KEY,XAI_API_KEY(xAI 사용 시),TAVILY_API_KEY(선택) - Variables:
LLM_PROVIDER,LLM_MODEL,IMAGE_PROVIDER,IMAGE_MODEL,RESPONSE_LANGUAGE,ALLOWED_CHANNEL_IDS,ALLOWED_CHANNEL_MESSAGE,SYSTEM_MESSAGE,BOT_CURSOR,MAX_LEN_SLACK,MAX_OUTPUT_TOKENS,MAX_THROTTLE_COUNT,MAX_HISTORY_CHARS,AGENT_MAX_STEPS,LOG_LEVEL,DEFAULT_TIMEZONE,MAX_DOC_CHARS,MAX_DOC_PAGES,MAX_DOC_BYTES
main 브랜치에 push 하면 .github/workflows/push-main.yml 이 pytest → Serverless deploy 순으로 수행합니다. 수동 실행은 workflow_dispatch.
# 로컬 배포 (선택)
npm i -g serverless@3 && npm i serverless-python-requirements
# Secrets + Variables 를 현재 셸에 export 한 뒤
serverless deploy --stage dev --region us-east-1DynamoDB 테이블 (해시키 id, GSI user-index, TTL expire_at) 은 CloudFormation 이 생성합니다.
┌────────────────┐ POST /slack/events
│ Slack workspace│──────────────────┐
└────────────────┘ ▼
┌───────────────────────────────────┐
│ API Gateway → Lambda (app.py) │
│ ├─ X-Slack-Retry-Num early return │
│ └─ SlackRequestHandler (Bolt) │
└────────┬───────────────────┬──────┘
│ │
┌──────────▼─────────┐ ┌──────▼─────────┐
│ app_mention handler│ │ message handler│
└──────────┬─────────┘ └──────┬─────────┘
└──────┬────────────┘
▼
┌───────────────────────────────────────────┐
│ _process() │
│ 1. DedupStore.reserve (conditional put) │
│ 2. channel_allowed / throttle │
│ 3. set_thread_status + placeholder say │
│ 4. ConversationStore.get → history │
│ 5. SlackMentionAgent.run ──┐ │
│ 6. send_long_message │ │
│ 7. ConversationStore.put │ │
└─────────────────────────────┼─────────────┘
│
┌───────────────────────▼───────────────┐
│ Agent loop (native function calling) │
│ LLM.chat(messages, tools=registry) │
│ ↓ tool_calls? │
│ ToolExecutor.execute (per-call t/o) │
│ ↓ role=tool result │
│ (loop up to AGENT_MAX_STEPS) │
│ streaming chat_update on final step │
└────────────┬──────────────────────────┘
│
┌───────────────┼────────────────┐
▼ ▼ ▼
┌───────────┐ ┌────────────┐ ┌──────────────┐
│ OpenAI │ │ Bedrock │ │ Slack Web API│
│ Chat API │ │ Messages / │ │ (tools) │
│ Vision │ │ Converse │ └──────────────┘
└───────────┘ └────────────┘
▲
│
┌──────┴─────┐
│ DynamoDB │
│ (dedup+ctx)│
└────────────┘
app.lambda_handler
-
@<bot>멘션에 정상 응답 - DM 대화에 응답
- 긴 응답이 여러 청크로 쪼개져 스레드에 전송됨
- 이미지 생성 요청 (
"고양이 그려줘") 이 업로드됨 - 이미지 첨부 요약 (
read_attached_images) 동작 - PDF/텍스트 첨부 요약 (
read_attached_document) 이 페이지·문자 상한 안에서 동작 (암호화 PDF 는 오류 반환) -
ALLOWED_CHANNEL_IDS외 채널에서 차단 메시지 표시 - Slack retry 중복 호출이 dedup 로 무시됨 (CloudWatch 로그에
dedup.skip확인) - 툴 실행 중 타이핑 인디케이터 (
assistant_threads_setStatus) 만 뜨고 placeholder 메시지는 아직 올라오지 않음 (회신 데이터가 없을 때) - 회신 생성이 시작되면 단일 메시지가 올라와 스트리밍으로 업데이트 됨 (상태 UI 와 placeholder 가 동시에 보이지 않음)
- 같은 스레드 재멘션 시 이전 대화 맥락 참조
- Bedrock Knowledge Base (RAG) 통합
reaction_added이벤트 훅 및 도메인 특화 로직- CloudWatch Alarms / X-Ray tracing