All checks were successful
Build and Deploy Teams Planner Bot / build-and-run (push) Successful in 14s
112 lines
4.7 KiB
Markdown
112 lines
4.7 KiB
Markdown
# Teams Planner Bot
|
||
|
||
MS Teams 봇. 사용자의 자연어 작업 보고를 LLM으로 분류해서 Microsoft Planner에 자동으로 task를 생성하거나 업데이트한다.
|
||
|
||
## 동작 개요
|
||
|
||
```
|
||
사용자 (Teams) → 봇 → OAuthPrompt 로그인 (최초 1회)
|
||
→ Graph로 본인 Plans/Buckets/최근 Tasks 조회
|
||
→ LLM(Claude 또는 Azure OpenAI)에게 발화 + 컨텍스트 전달
|
||
→ tool/function call로 {action, planId, ...} JSON 강제 추출
|
||
→ Planner Graph API로 create / patch
|
||
→ Adaptive Card로 결과 응답
|
||
```
|
||
|
||
## 구성
|
||
|
||
```
|
||
src/
|
||
index.ts # restify 서버, CloudAdapter
|
||
config/index.ts # 환경변수 로딩/검증
|
||
bot/PlannerBot.ts # 메시지 핸들러 + OAuth 다이얼로그 + 오케스트레이션
|
||
graph/
|
||
graphClientFactory.ts # 사용자 토큰을 받아 Graph Client 생성
|
||
plannerClient.ts # Plans/Buckets/Tasks 조회·생성·업데이트 (ETag 처리 포함)
|
||
llm/
|
||
types.ts # ClassifiedAction 등 공용 타입
|
||
prompt.ts # 시스템 프롬프트 + tool/function 스키마
|
||
coerce.ts # LLM JSON → ClassifiedAction 검증
|
||
claudeClassifier.ts # Anthropic Claude 구현 (tool_use 강제)
|
||
azureOpenAiClassifier.ts # Azure OpenAI 구현 (function calling 강제)
|
||
factory.ts # LLM_PROVIDER 기반 분기
|
||
cards/confirmationCard.ts # 결과 Adaptive Card
|
||
appPackage/manifest.json # Teams 앱 매니페스트 (개발자 포털에 업로드)
|
||
```
|
||
|
||
## 사전 준비 (Azure / Microsoft 365)
|
||
|
||
1. **Azure AD 앱 등록** (Graph 위임 권한)
|
||
- Azure Portal → App registrations → New
|
||
- API permissions → Microsoft Graph → Delegated:
|
||
- `Tasks.ReadWrite`
|
||
- `Group.Read.All`
|
||
- `User.Read`
|
||
- `offline_access`
|
||
- Grant admin consent
|
||
- Certificates & secrets → Client secret 발급
|
||
- 노트: `tenantId`, `clientId`, `clientSecret`
|
||
|
||
2. **Azure Bot 리소스**
|
||
- Azure Portal → Create resource → Azure Bot (Multi Tenant)
|
||
- Microsoft App ID/Password 발급 → 노트
|
||
- **Configuration → OAuth Connection Settings → Add**
|
||
- Name: `GraphConnection` (이 이름이 `.env`의 `OAUTH_CONNECTION_NAME`)
|
||
- Service Provider: `Azure Active Directory v2`
|
||
- Client id / secret: 1번에서 만든 값
|
||
- Tenant ID: 1번의 tenantId
|
||
- Token Exchange URL: 비워두기 (SSO 안 쓸 경우)
|
||
- Scopes: `Tasks.ReadWrite Group.Read.All User.Read offline_access`
|
||
- "Test Connection" 으로 토큰이 떨어지는지 확인
|
||
|
||
3. **Teams 앱 매니페스트**
|
||
- `appPackage/manifest.json` 의 `REPLACE_WITH_YOUR_BOT_APP_ID` 를 Azure Bot의 App ID로 치환
|
||
- `color.png` (192×192), `outline.png` (32×32) 아이콘을 `appPackage/`에 추가
|
||
- 폴더를 zip 으로 압축 → Teams 관리자 센터(또는 개발자 포털)에서 사이드로딩
|
||
|
||
## 로컬 실행
|
||
|
||
```bash
|
||
npm install
|
||
cp .env.example .env
|
||
# .env 채우기
|
||
npm run dev
|
||
```
|
||
|
||
다른 터미널에서 ngrok 등으로 외부 노출:
|
||
|
||
```bash
|
||
ngrok http 3978
|
||
```
|
||
|
||
ngrok URL을 Azure Bot의 **Messaging endpoint** 에 `https://<ngrok>/api/messages` 형태로 설정.
|
||
|
||
## LLM 교체
|
||
|
||
`.env` 의 `LLM_PROVIDER` 만 바꾸면 됨:
|
||
|
||
- `claude` → `ANTHROPIC_API_KEY`, `CLAUDE_MODEL` 사용
|
||
- `azure-openai` → `AZURE_OPENAI_*` 사용
|
||
|
||
두 구현 모두 동일한 `LlmClassifier` 인터페이스를 구현하므로 호출부 변경 없음.
|
||
|
||
## 알아두면 좋을 점 / 함정
|
||
|
||
- **Planner write는 ETag 필수.** `plannerClient.ts` 가 PATCH 직전 항상 GET으로 ETag를 받아 `If-Match`에 넣는다. 만약 동시 편집이 잦은 환경이면 412 에러를 retry 로 감싸야 한다.
|
||
- **`/me/planner/plans` 는 사용자가 속한 그룹의 Plan 만 반환한다.** 봇이 "보이지 않는다" 면 Teams 채널에 그 사용자가 멤버인지부터 확인.
|
||
- **OAuthPrompt 의 토큰은 Bot Framework 의 토큰 서비스(token.botframework.com)에 캐시된다.** `logout` 명령으로 강제 갱신 가능.
|
||
- **MemoryStorage** 는 개발용. 프로덕션은 CosmosDB/Blob 백엔드로 교체.
|
||
- **확장 아이디어**:
|
||
- 다중 후보일 때 카드의 `Action.Submit` 으로 사용자가 직접 plan 선택
|
||
- Plans/Buckets 캐시 (현재는 매 발화마다 다시 조회)
|
||
- 일/주간 요약 발송 (proactive messaging)
|
||
|
||
## 다음 단계
|
||
|
||
1. `npm install` → 의존성 받기
|
||
2. Azure AD 앱 등록 + Azure Bot 리소스 + OAuth Connection 설정
|
||
3. `.env` 채우기
|
||
4. `npm run dev` 로 띄우고 ngrok 으로 노출
|
||
5. Teams 매니페스트 사이드로딩 → 개인 채팅으로 봇과 대화
|
||
6. `"오늘 견적서 초안 작성 시작했어"` 같은 첫 발화로 테스트
|