Суть урока
Корпоративный клиент живёт в Microsoft. Teams вместо Slack, SharePoint вместо Google Drive, Outlook вместо Gmail, Power Automate вместо Zapier. Если ты хочешь продавать AI в корпорации — учишь язык Microsoft. Это не сложнее обычного, просто другой диалект.
Ключевые концепции
- Microsoft Graph API — единая точка входа ко всем данным Microsoft 365
- Вебхуки Teams (приложение Workflows) — Claude отправляет уведомления в Teams без бота
- Office интеграция — Word, Excel, PowerPoint через Python-библиотеки
- Power Platform — Power Automate + Claude без кода
- Azure и Microsoft Foundry — инфраструктура для корпоративного Claude
- Microsoft 365 Agents SDK (раньше Bot Framework) — полноценный бот в Teams
Теория
Архитектура Microsoft 365
Microsoft 365 экосистема
├── Коммуникации: Teams, Outlook
├── Документы: Word, Excel, PowerPoint, OneNote
├── Хранилище: SharePoint, OneDrive
├── Автоматизация: Power Automate, Power Apps
├── Данные: Power BI, Dataverse
└── Инфраструктура: Azure, Active DirectoryMicrosoft Graph API — это один API который даёт доступ ко всему. Один токен, одна точка входа.
https://graph.microsoft.com/v1.0/
├── /me → данные текущего пользователя
├── /users/{id}/messages → Outlook письма
├── /teams/{id}/channels → Teams каналы
├── /sites/{id}/drives → SharePoint файлы
└── /me/calendar/events → Calendar событияMicrosoft Teams + Claude
Способ 1: Incoming Webhook (самый простой)
Webhook — это ссылка. Отправляешь POST запрос на эту ссылку — сообщение появляется в Teams канале. Никакого бота, никакой регистрации приложения.
⚠️ Старые вебхуки через Office 365 Connectors Microsoft выводит из эксплуатации, и создание новых скоро будет блокироваться. Вебхук теперь делают через приложение Workflows в Teams: оно построено на Power Automate.
Получить webhook:
- Teams → Канал →
...→ Workflows - Выбрать шаблон «Send webhook alerts to a channel», настроить параметры и нажать Save
- Скопировать ссылку вебхука (названия пунктов меню Teams меняются: актуальные шаги в документации Microsoft Learn)
Ограничения: сообщение не больше 28 КБ, а если запросов больше четырёх в секунду, Teams притормаживает соединение. Workflow привязан к своему владельцу: если он уйдёт из организации, вебхук останется без владельца, поэтому добавь соавторов.
Базовая отправка:
import os
import requests
def notify_teams(webhook_url: str, message: str):
"""Простое текстовое сообщение в Teams."""
payload = {"text": message}
response = requests.post(webhook_url, json=payload)
return response.status_code in (200, 202)
# Пример
notify_teams(
webhook_url=os.environ["TEAMS_WEBHOOK_URL"],
message="✅ Отчёт за неделю готов. Claude проанализировал 1,247 транзакций."
)Адаптивные карточки (богатый формат):
Вебхук Workflows принимает Adaptive Card. Старый формат MessageCard с кнопками он не отображает, поэтому кнопки делают через Action.OpenUrl. Проверь, как карточка выглядит в твоём канале.
import os
import json
import requests
import anthropic
from datetime import datetime
def notify_teams_rich(webhook_url: str, title: str, facts: dict, actions: list = None):
"""
Адаптивная карточка с таблицей фактов и кнопками.
facts = {"Ключ": "Значение", ...}
actions = [{"title": "Открыть", "url": "https://..."}]
"""
card = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.2",
"body": [
{"type": "TextBlock", "text": title, "weight": "Bolder", "size": "Medium", "wrap": True},
{"type": "TextBlock", "text": f"Сгенерировано: {datetime.now().strftime('%d.%m.%Y %H:%M')}",
"isSubtle": True, "wrap": True},
{"type": "FactSet", "facts": [{"title": k, "value": v} for k, v in facts.items()]}
]
}
if actions:
card["actions"] = [
{"type": "Action.OpenUrl", "title": a["title"], "url": a["url"]} for a in actions
]
payload = {
"type": "message",
"attachments": [{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": card
}]
}
requests.post(webhook_url, json=payload)
# Claude анализирует данные → отправляет в Teams
def weekly_sales_report():
data = get_weekly_sales()
client = anthropic.Anthropic()
analysis = "".join(b.text for b in client.messages.create(
model="claude-sonnet-5-5",
max_tokens=300,
messages=[{
"role": "user",
"content": f"Кратко проанализируй: продажи {data['total']}, новые клиенты {data['new_clients']}, лучший продукт: {data['top_product']}"
}]
).content if b.type == "text")
notify_teams_rich(
webhook_url=os.environ["TEAMS_WEBHOOK_URL"],
title="📊 Еженедельный отчёт продаж",
facts={
"Выручка": f"${data['total']:,}",
"Новые клиенты": str(data['new_clients']),
"Топ продукт": data['top_product'],
"AI-анализ": analysis[:200]
},
actions=[{"title": "Полный отчёт", "url": "https://dashboard.company.com"}]
)Способ 2: AI-бот в Teams (полноценный)
Настоящий бот который отвечает на сообщения в Teams. Старый Bot Framework SDK Microsoft заменяет на Microsoft 365 Agents SDK: для него есть руководство по миграции, а новые боты разумно начинать сразу на нём (есть ещё Teams SDK). Ниже вариант на Agents SDK для Python.
# bot.py — бот для Teams на Microsoft 365 Agents SDK (нужен Python 3.10+)
# pip install microsoft-agents-hosting-core microsoft-agents-hosting-aiohttp \
# microsoft-agents-hosting-teams microsoft-agents-authentication-msal anthropic
#
# AGENT_APP (AgentApplication) создаётся по документации Agents SDK:
# CloudAdapter, MsalAuth.from_environment(), хранилище состояния.
from microsoft_agents.hosting.core import AgentApplication, TurnState, TurnContext
import anthropic
claude = anthropic.AsyncAnthropic()
conversations = {} # История по conversation_id (в продакшне — база или Redis)
@AGENT_APP.activity("message")
async def on_message(context: TurnContext, _state: TurnState):
user_message = context.activity.text
conversation_id = context.activity.conversation.id
# Загрузить историю разговора
history = conversations.get(conversation_id, [])
history.append({"role": "user", "content": user_message})
# Claude отвечает
response = await claude.messages.create(
model="claude-sonnet-5-5",
max_tokens=1000,
system="Ты корпоративный AI-ассистент компании. Отвечай кратко и по делу.",
messages=history
)
reply = "".join(b.text for b in response.content if b.type == "text")
history.append({"role": "assistant", "content": reply})
# Сохранить историю (только последние 20 сообщений)
conversations[conversation_id] = history[-20:]
await context.send_activity(reply)Регистрация бота в Azure:
- portal.azure.com → Bot Services → Create
- Выбрать "Azure Bot"
- Получить App ID и App Password
- Настроить Teams Channel
- Задеплоить через Azure App Service
Названия пунктов портала Azure меняются, актуальные шаги смотри в документации Microsoft 365 Agents SDK.
Microsoft Office + Claude
Word: генерация документов
import json
from datetime import datetime
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
import anthropic
def generate_word_report(topic: str, data: dict, output_path: str):
"""Claude пишет контент → python-docx создаёт документ."""
client = anthropic.Anthropic()
# Claude генерирует структурированный контент
content_response = "".join(b.text for b in client.messages.create(
model="claude-sonnet-5-5",
max_tokens=3000,
messages=[{
"role": "user",
"content": f"""
Напиши деловой отчёт на тему: {topic}
Данные для включения:
{json.dumps(data, ensure_ascii=False, indent=2)}
Формат (строго):
ЗАГОЛОВОК: [название]
РЕЗЮМЕ: [3-4 предложения]
РАЗДЕЛ1: [название]
[текст раздела]
РАЗДЕЛ2: [название]
[текст раздела]
ВЫВОДЫ: [2-3 пункта]
"""
}]
).content if b.type == "text")
# Парсим и создаём Word документ
doc = Document()
# Стили
style = doc.styles['Normal']
style.font.name = 'Arial'
style.font.size = Pt(11)
lines = content_response.split('\n')
for line in lines:
if line.startswith('ЗАГОЛОВОК:'):
title = doc.add_heading(line.replace('ЗАГОЛОВОК:', '').strip(), 0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
elif line.startswith('РЕЗЮМЕ:'):
p = doc.add_paragraph()
run = p.add_run(line.replace('РЕЗЮМЕ:', '').strip())
run.italic = True
p.paragraph_format.space_after = Pt(12)
elif line.startswith('РАЗДЕЛ'):
section_title = line.split(':', 1)[1].strip() if ':' in line else line
doc.add_heading(section_title, level=1)
elif line.startswith('ВЫВОДЫ:'):
doc.add_heading('Выводы', level=1)
elif line.strip() and not any(line.startswith(k) for k in ['ЗАГОЛОВОК', 'РЕЗЮМЕ', 'РАЗДЕЛ', 'ВЫВОДЫ']):
doc.add_paragraph(line.strip())
# Добавить дату и подпись
doc.add_page_break()
doc.add_paragraph(f"Сгенерировано: {datetime.now().strftime('%d.%m.%Y')}")
doc.add_paragraph("Подготовлено с использованием Claude AI")
doc.save(output_path)
return output_path
# Использование
generate_word_report(
topic="Квартальный отчёт по продажам",
data={"выручка": "100 000", "рост": "8%", "топ_регионы": ["Север", "Юг"]}, # условные данные
output_path="sales_report.docx"
)Excel: анализ данных через Claude
import json
import anthropic
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.chart import BarChart, Reference
def analyze_excel_with_claude(excel_path: str) -> str:
"""Claude анализирует данные из Excel и возвращает insights."""
wb = openpyxl.load_workbook(excel_path)
ws = wb.active
# Извлечь данные как текст
data_rows = []
headers = [cell.value for cell in ws[1]]
for row in ws.iter_rows(min_row=2, values_only=True):
if any(cell is not None for cell in row):
data_rows.append(dict(zip(headers, row)))
# Первые 50 строк для анализа
sample = data_rows[:50]
client = anthropic.Anthropic()
analysis = "".join(b.text for b in client.messages.create(
model="claude-sonnet-5-5",
max_tokens=1500,
messages=[{
"role": "user",
"content": f"""
Проанализируй данные из Excel:
Колонки: {headers}
Строк всего: {len(data_rows)}
Первые записи: {json.dumps(sample[:10], ensure_ascii=False, default=str)}
Дай:
1. Краткое описание что в таблице
2. Ключевые наблюдения (3-5 пунктов)
3. Аномалии если есть
4. Рекомендации
"""
}]
).content if b.type == "text")
return analysis
def generate_excel_report(data: list, output_path: str, title: str = "AI Report"):
"""Создать красивый Excel отчёт с данными и графиком."""
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Данные"
# Заголовок
ws.merge_cells('A1:E1')
title_cell = ws['A1']
title_cell.value = title
title_cell.font = Font(bold=True, size=14, color="FFFFFF")
title_cell.fill = PatternFill(start_color="0076D7", fill_type="solid")
title_cell.alignment = Alignment(horizontal='center')
# Заголовки колонок
if data:
headers = list(data[0].keys())
for col, header in enumerate(headers, 1):
cell = ws.cell(row=2, column=col, value=header)
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="E0E7FF", fill_type="solid")
# Данные
for row_idx, row_data in enumerate(data, 3):
for col_idx, value in enumerate(row_data.values(), 1):
ws.cell(row=row_idx, column=col_idx, value=value)
wb.save(output_path)
return output_pathPowerPoint: автоматические презентации
from datetime import datetime
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
import anthropic
def generate_pptx_from_claude(topic: str, key_points: list, output_path: str):
"""Claude создаёт контент → python-pptx строит презентацию."""
client = anthropic.Anthropic()
# Генерируем слайды
slides_content = "".join(b.text for b in client.messages.create(
model="claude-sonnet-5-5",
max_tokens=2000,
messages=[{
"role": "user",
"content": f"""
Создай структуру презентации из 5 слайдов на тему: {topic}
Ключевые тезисы: {', '.join(key_points)}
Формат для каждого слайда:
СЛАЙД N:
ЗАГОЛОВОК: [название]
ТЕЗИСЫ:
- [тезис 1]
- [тезис 2]
- [тезис 3]
ЗАМЕТКИ: [для спикера]
---
"""
}]
).content if b.type == "text")
prs = Presentation()
prs.slide_width = Inches(13.33)
prs.slide_height = Inches(7.5)
# Титульный слайд
slide = prs.slides.add_slide(prs.slide_layouts[0])
slide.shapes.title.text = topic
slide.placeholders[1].text = f"Подготовлено: {datetime.now().strftime('%d.%m.%Y')}"
# Парсим и добавляем слайды
current_slide_data = {}
for line in slides_content.split('\n'):
if line.startswith('СЛАЙД'):
if current_slide_data.get('title'):
_add_content_slide(prs, current_slide_data)
current_slide_data = {'title': '', 'bullets': [], 'notes': ''}
elif line.startswith('ЗАГОЛОВОК:'):
current_slide_data['title'] = line.replace('ЗАГОЛОВОК:', '').strip()
elif line.startswith('- '):
current_slide_data['bullets'].append(line[2:].strip())
elif line.startswith('ЗАМЕТКИ:'):
current_slide_data['notes'] = line.replace('ЗАМЕТКИ:', '').strip()
if current_slide_data.get('title'):
_add_content_slide(prs, current_slide_data)
prs.save(output_path)
return output_path
def _add_content_slide(prs, data):
slide = prs.slides.add_slide(prs.slide_layouts[1])
slide.shapes.title.text = data['title']
tf = slide.placeholders[1].text_frame
tf.text = ''
for bullet in data['bullets']:
p = tf.add_paragraph()
p.text = bullet
p.level = 0Power Platform + Claude
Power Automate: HTTP connector
В Power Automate создай Flow:
Trigger: Manual / Schedule / Email received
↓
Action: HTTP
Method: POST
URL: https://api.anthropic.com/v1/messages
Headers:
x-api-key: [твой ключ]
anthropic-version: 2023-06-01
content-type: application/json
Body: {
"model": "claude-haiku-4-5",
"max_tokens": 500,
"messages": [{"role": "user", "content": "@{triggerBody()?['text']}"}]
}
↓
Action: Send email / Post to Teams / Update SharePointЭто простейшая no-code интеграция — Claude без единой строки кода. Действие HTTP относится к premium-коннекторам Power Automate, поэтому нужна подходящая лицензия. Ключ API не вставляй в поток открытым текстом: храни его в защищённом хранилище секретов (например, Azure Key Vault).
Power Apps: кастомный коннектор
- Power Apps → Custom Connectors → New
- Добавить Anthropic API как коннектор
- Настроить actions: sendMessage, etc.
- Использовать в любом Power App как компонент
Microsoft Graph API: единая точка данных
from msal import ConfidentialClientApplication
import requests
class MSGraphClient:
def __init__(self):
app = ConfidentialClientApplication(
client_id=os.environ["AZURE_CLIENT_ID"],
client_credential=os.environ["AZURE_CLIENT_SECRET"],
authority=f"https://login.microsoftonline.com/{os.environ['AZURE_TENANT_ID']}"
)
token_result = app.acquire_token_for_client(
scopes=["https://graph.microsoft.com/.default"]
)
self.headers = {
"Authorization": f"Bearer {token_result['access_token']}",
"Content-Type": "application/json"
}
self.base = "https://graph.microsoft.com/v1.0"
def get_recent_emails(self, user_id: str, count: int = 10) -> list:
url = f"{self.base}/users/{user_id}/messages"
params = {"$top": count, "$orderby": "receivedDateTime desc"}
r = requests.get(url, headers=self.headers, params=params)
return r.json().get("value", [])
def send_teams_message(self, team_id: str, channel_id: str, message: str):
# Отправка в канал через Graph работает только с делегированным токеном (от имени пользователя).
# С токеном приложения, как в этом классе, Graph вернёт 403: для уведомлений используй вебхук Workflows.
url = f"{self.base}/teams/{team_id}/channels/{channel_id}/messages"
payload = {"body": {"content": message}}
requests.post(url, headers=self.headers, json=payload)
def get_sharepoint_files(self, site_id: str, folder: str = "root") -> list:
url = f"{self.base}/sites/{site_id}/drive/root:/{folder}:/children"
r = requests.get(url, headers=self.headers)
return r.json().get("value", [])
# Claude обрабатывает корпоративные данные
def process_emails_with_claude(user_id: str):
graph = MSGraphClient()
emails = graph.get_recent_emails(user_id, count=20)
email_texts = [
f"От: {e['from']['emailAddress']['address']}\nТема: {e['subject']}\n"
f"Дата: {e['receivedDateTime'][:10]}"
for e in emails
]
client = anthropic.Anthropic()
summary = "".join(b.text for b in client.messages.create(
model="claude-haiku-4-5",
max_tokens=500,
messages=[{
"role": "user",
"content": f"Сделай краткое резюме этих писем, выдели срочные:\n" + "\n---\n".join(email_texts)
}]
).content if b.type == "text")
return summaryAzure + Claude: корпоративная инфраструктура
Два подхода:
1. Claude API напрямую из Azure инфраструктуры:
Azure App Service → Claude API (api.anthropic.com)
Azure Functions → Claude API
Azure Container Apps → Claude APIТвой код работает в Azure, вызывает Anthropic напрямую. Просто, надёжно.
2. Claude в Microsoft Foundry (раньше Azure AI Foundry):
Если клиент говорит "нам нельзя данные выпускать за Azure" — Claude доступен в Microsoft Foundry. Есть два варианта размещения: на инфраструктуре Azure («Hosted on Azure») и на инфраструктуре Anthropic («Hosted on Anthropic infrastructure»); не каждая модель есть в обоих. Нужны платная подписка Azure в поддерживаемой стране и подписка на модель через Azure Marketplace. Где именно обрабатываются данные и какие регионы доступны, проверяй в документации Microsoft Learn и в договоре: само слово «Azure» ещё не закрывает требования compliance.
# Claude через Microsoft Foundry: тот же Anthropic SDK, другой клиент и адрес
import os
from anthropic import AnthropicFoundry
client = AnthropicFoundry(
api_key=os.environ["AZURE_AI_KEY"],
base_url=os.environ["AZURE_AI_ENDPOINT"], # https://<имя-ресурса>.services.ai.azure.com/anthropic
)
response = client.messages.create(
model=os.environ["AZURE_DEPLOYMENT_NAME"], # имя развёртывания, выбранное при деплое
max_tokens=500,
messages=[{"role": "user", "content": "Привет"}]
)
print("".join(b.text for b in response.content if b.type == "text"))Вместо ключа лучше использовать Microsoft Entra ID (DefaultAzureCredential): пример смотри в документации Microsoft Learn («Deploy and use Claude models in Microsoft Foundry»).
Практика
Шаг 1: Teams Webhook (15 мин)
- Создай тестовый Teams канал
- Создай вебхук через приложение Workflows и скопируй ссылку
- Запусти базовый пример из урока
- Убедись что сообщение появилось
Шаг 2: Word через python-docx (20 мин)
pip install python-docx anthropicСгенерируй Word документ на любую тему используя код из урока. Открой файл, проверь форматирование.
Шаг 3: Excel анализ (15 мин)
pip install openpyxlВозьми любой Excel файл с данными. Запусти analyze_excel_with_claude(). Посмотри что Claude скажет о данных.
Шаг 4: Power Automate (опционально, нужен M365)
Если есть доступ к Microsoft 365:
- Создай Flow в Power Automate
- Триггер: вручную
- HTTP action → Claude API (premium-коннектор)
- Отправить результат в Teams
Шаг 5: Задание для практики
Выбери один реальный сценарий:
Вариант А: Еженедельный отчёт в Teams. Скрипт собирает данные из любого источника, Claude анализирует, результат — карточка в Teams канале.
Вариант Б: Word-отчёт по шаблону. Claude генерирует контент под конкретную задачу, python-docx оформляет в корпоративный документ.
Вариант В: Email-дайджест. Graph API читает почту, Claude делает резюме, результат в Outlook или Teams.
Инструменты и ресурсы
- python-docx:
pip install python-docx - openpyxl:
pip install openpyxl - python-pptx:
pip install python-pptx - MSAL (Microsoft auth):
pip install msal - Microsoft Graph Docs: learn.microsoft.com/graph
- Teams Webhooks Docs: learn.microsoft.com/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook
- Microsoft Foundry (раньше Azure AI Foundry): ai.azure.com
- Microsoft 365 Agents SDK: learn.microsoft.com/microsoft-365/agents-sdk
- Power Automate: make.powerautomate.com
- Adaptive Cards Designer: adaptivecards.io/designer
Ключевые выводы
Корпоративный рынок живёт в Microsoft. Умение интегрировать Claude с Teams, Office и Power Platform — полезный навык для работы с корпоративными клиентами. Начни с самого простого: вебхук Workflows для Teams — это несколько десятков строк кода. Дальше — Graph API открывает данные организации (в рамках выданных прав). Microsoft Foundry помогает ответить на вопрос о размещении данных для enterprise-клиентов, но требования compliance проверяй в документации и договоре.
Связанные уроки
Отметка хранится только в этом браузере и никуда не отправляется. Мой прогресс