Чотирирівнева архітектура для агента виконавчих брифінгів у Azure
Як агент з детерміністичним підходом до обробки даних розподіляє завдання між Fabric, процедурами зберігання даних SQL, FastAPI у Azure Functions та Copilot Studio для створення керівницьких документів.
Кожен, хто готує керівника до зустрічі з клієнтом, знає, наскільки розрізненою є сировина для цього. Дані CRM знаходяться в одній системі, показники доходу — у іншій, інформація про використання продуктів — у третій, а стратегічний звіт доводиться складати вручну, поєднуючи дані з усіх цих джерел. Система для підготовки брифінгів саме на цю складну роботу й спрямована: користувач просто запитує брифінг простою мовою, а платформа повертає документ, готовий до використання керівником, створений на основі фрагментованих операційних, фінансових та даних про впровадження ШІ.
Те, що робить цю архітектуру гідною вивчення, — це те, наскільки мало в ній місця для випадковостей. Замість того, щоб просити модель написати звіт на основі запиту, тут використовується оркестрація, обмежена контрактами, замість ручного об’єднання даних. Дані об’єднуються в межах чіткого керування, збираються за допомогою детерміністичних SQL-запитів, передаються через безстандартні сервіси та надаються через контрольований діалоговий процес. Архітектура встановлює чіткі межі виконання між чотирма шарами — фундаментом, даними, сервісами та користувацьким досвідом, що дозволяє відтворювати результати, перевіряти їх та надавати зі швидкістю корпоративних систем у межах суворих вимог безпеки.
Цей дизайн є результатом внутрішніх зусиль з розвитку ШІ в екосистемі Microsoft, а твердження щодо продуктивності та результатів, наведені нижче, належать самим розробникам та не були перевірені незалежно. Він найбільш корисний для архітекторів корпоративних систем, інженерів ШІ та керівників платформ, яким потрібні детерміністичні системи ШІ, що об’єднують консолідацію даних, агрегацію за допомогою SQL, оркестрацію без стану та робочі процеси у форматі діалогу.
Чотирирівневий дизайн
Агент працює за моделлю оркестрації з чотирма рівнями, кожен з яких виконує конкретні функції та охоплює кілька сервісів Azure та меж безпеки:
- Основа: Microsoft Fabric Lakehouse, який консолідує дані під контролем керування.
- Дані: Fabric SQL (хранилище або кінцева точка SQL), що містить процедури зберігання для операційних запитів та агрегації.
Разом вони утворюють однонаправлений канал передачі інформації. Дані піддаються обробці та керуванню на базовому рівні, детерміновано агрегуються в SQL, об’єднуються за допомогою логіки сфери в рівні сервісу та нарешті представляються через взаємодію з природною мовою. Кожен рівень має чіткий контракт із сусідніми рівнями, тож елемент ШІ допомагає у прийнятті рішень, а не ускладнює його.
Основний рівень: керована консолідація даних
Microsoft Fabric є уніфікованою аналітичною платформою та єдиним джерелом інформації про деталі облікових записів, фінансову ситуацію, показники використання та історичні тенденції. Основне правило просте: зберігати дані один раз та використовувати їх багаторазово, щоб нічого не дублювалося між системами.
Пайплайн оновлюється щодня, отримуючи та об’єднуючи корпоративні дані з Dynamics 365, фінансових систем та зовнішніх API. Цей графік забезпечує передбачувані терміни обробки та гарантує, що кожна джерельна система завершить свою щоденну роботу перед початком об’єднання даних. Lakehouse зберігає дані у їхньому первинному форматі разом із інформацією про їхнє походження та стандартами управління, а опрацьовані набори даних потім переміщуються до Fabric SQL — швидкого операційного рівня запитів, структура якого оптимізована для підготовки звітів для керівництва.
Рівень даних: операційний двигун, створений для доступу через API
Цей шар перетворює ретельно підготовлені базові дані на детерміновані контракти запитів з низькою затримкою для створення коротких звітів. Він встановлює суворі межі агрегації та гарантії продуктивності, яких очікують керівники від інструменту з швидкою відповіддю.
Архітектура. Fabric SQL Warehouse — це реляційний інтерфейс, який обгортає кожну операцію з даними, пов’язану з інформацією для брифінгів, у процедури зберігання даних. Рівень послуг ніколи не здійснює прямих запитів до таблиць. Складні з’єднання між таблицями, значення кожної агрегації та бізнес-логіка виконуються всередині бази даних, а не у коді додатку, що забезпечує прогнозовані часи відповіді при великих обсягах даних. SQL налаштований для операційних запитів, а не для аналітичного дослідження; конструкція спрямована на детерміністичні з’єднання, які забезпечують послідовні результати, на використання планів запитів, які можна повторно використовувати для стабільної продуктивності, на параметризовану обробку для безпеки та на агрегацію, яка легше підтримувати.
Філософія схеми: запит на першому місці. Схема ґрунтується на шаблонах запитів, а не на стандартних правилах нормалізації. Ентитет Account є основною структурою кластеризації, а допоміжні таблиці організовані так, щоб об’єднання даних відбувалися ефективно за допомогою бізнес-ідентифікатора — TPID.
--Accounts Table - Primary Business Entity
CREATE TABLE [aieo].[Accounts] (
[Id] INT IDENTITY(1,1) PRIMARY KEY,
[TPID] BIGINT NOT NULL,
[AccountName] NVARCHAR(255) NOT NULL,
[AccountNumber] NVARCHAR(50),
[SearchTokens] NVARCHAR(500),
[Segment] NVARCHAR(100),
[Industry] NVARCHAR(100),
[ACR_FY25] DECIMAL(15,2),
[RevenueRank] INT,
[CreatedDate] DATETIME2 DEFAULT GETUTCDATE(),
[LastSync] DATETIME2,
[IsActive] BIT DEFAULT 1
);
CREATE CLUSTERED INDEX IX_Accounts_TPID ON [aieo].[Accounts]([TPID]);
CREATE NONCLUSTERED INDEX IX_Accounts_Name ON [aieo].[Accounts](
Таблиця Accounts є центральною entитетом, у якої ключем для групування є TPID. Це дозволяє здійснювати приблизно логарифмічне пошукове звернення для найпоширеніших запитів, пов’язаних із акаунтами, а також обробляє як прямі запити за TPID, так і пошук за назвою.
Допоміжні таблиці. Деякі з допоміжних таблиць демонструють, як обробляються фінансові показники, дані про використання ШІ та інформація, отримана з інших систем.
-- Revenue Table - Historical and Forecast Data
CREATE TABLE [aieo].[Revenue] (
[TPID] BIGINT,
[FiscalYear] NVARCHAR(10),
[RevenueType] NVARCHAR(20),
[Amount] DECIMAL(15,2),
[LastUpdated] DATETIME2
);
CREATE INDEX IX_Revenue_TPID_FY ON [aieo].[Revenue]([TPID], [FiscalYear]);
Таблиця доходів зберігає фінансові дані за кілька років та використовує композитні індекси, які підходять для обчислень часових рядів та трендів.
-- AI Usage Table - Product Adoption Metrics
CREATE TABLE [aieo].[AIUsage] (
[TPID] BIGINT,
[ProductCategory] NVARCHAR(50),
[UsageLevel] NVARCHAR(20),
[SeatCount] INT,
[AdoptionRate] DECIMAL(5,2),
[LastUpdated] DATETIME2
);
CREATE INDEX IX_AIUsage_TPID_Product ON [aieo].[AIUsage]([TPID], [ProductCategory]);
Таблиця використання ШІ фіксує прийняття продукту за категоріями, що дозволяє аналізувати моделі взаємодії та освоєння технологій, які мають значення для планування рахунків.
Стратегія збережених процедур: бізнес-логіка знаходиться в базі даних. Три основні процедури здійснюють усю обробку інформації. SearchAccounts знаходить рахунки та ранжує їх за доходом, GetAccountDetails об’єднує дані з багатьох джерел у повний контекст для керівників, а GetBriefingTemplate контролює авторизацію шаблонів за ролями, щоб доступ мали лише відповідні особи. Шар послуг викликає лише ці процедури, і створення динамічного SQL тут не дозволяється.
--Account Search Procedure - Revenue-Prioritized Discovery
CREATE OR ALTER PROCEDURE [aieo].[SearchAccounts]
@searchTerm NVARCHAR(255),
@top INT = 5
AS
BEGIN
SET NOCOUNT ON;
DECLARE @likeTerm NVARCHAR(257) = '%' + REPLACE(REPLACE(@searchTerm, '[', '[[]'), '%', '[%]') + '%'
SELECT TOP (@top)
a.TPID,
a.AccountName,
a.AccountNumber,
ISNULL(acr.ACR_YTD, 0) as ACR_YTD
FROM [aieo].[Accounts] a
LEFT JOIN [aieo].[ACR] acr ON a.TPID = acr.TPID
WHERE
a.AccountName LIKE @likeTerm
OR CAST(a.TPID AS NVARCHAR) = @searchTerm
OR a.AccountNumber LIKE @likeTerm
ORDER BY
ISNULL(acr.ACR_YTD, 0) DESC,
a.AccountName ASC
FOR JSON PATH;
END;
--Account Details Retrieval Procedure - Comprehensive Intelligence Aggregation
CREATE PROCEDURE [aieo].[GetAccountDetails]
(@TPID int)
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT TOP 1
JSON_QUERY(ap.[AccountProfile]) AS [Account],
JSON_QUERY(ai.[AIUsage]) AS [AI],
JSON_QUERY(acr.[Data]) AS [ACR],
deal.[Deals.AgreementValue],
deal.[Deals.DealType],
deal.[Deals.Term],
deal.[Deals.Remaining],
deal.[Deals.EndDate],
ecif.[ECIF.Committed],
aco.[ACO.IncrementalRevenue],
aco.[ACO.Discount],
aco.[ACO.ACO],
JSON_QUERY(bot.[Data]) AS [BoT],
JSON_QUERY(inv.[Data]) AS [Investments],
JSON_QUERY(rev.[Data]) AS [Revenue]
FROM [dbo].[vw_Customer] P
OUTER APPLY (
SELECT TOP 1
a.[TPID],
a.[CRMAccountName] AS [AccountName],
a.[Segment],
a.[Industry],
a.[EOU],
a.[OU],
LOWER(TRIM(c.[Value])) AS [ATU_Manager.Alias],
aau.[Mail] AS [ATU_Manager.Email],
[dbo].[RemoveJobTitle](aau.[DisplayName]) AS [ATU_Manager.DisplayName],
NULLIF(TRIM(aau.[BusinessPhone]), '') AS [ATU_Manager.PhoneNumber],
JSON_QUERY([am].[Data]) AS [AM],
JSON_QUERY([atum].[Data]) AS [ATUM]
FROM [dbo].[vw_Customer] A
OUTER APPLY STRING_SPLIT(a.[AM], ',', 1) C
LEFT JOIN [cm].[vw_AAD_User] AAU ON LOWER(TRIM(C.[Value])) = LOWER(AAU.[UserPrincipalName])
WHERE A.TPID = P.TPID
) ap
WHERE P.TPID = @TPID;
END;
--Template Management Procedure - Role-Based Access Control
CREATE OR ALTER PROCEDURE [aieo].[GetBriefingTemplate]
(
@UserAlias NVARCHAR(200),
@BriefingType NVARCHAR(200)
)
AS
BEGIN
SET @UserAlias = IIF(CHARINDEX('@', @UserAlias) > 0,
LEFT(@UserAlias, CHARINDEX('@', @UserAlias) - 1),
@UserAlias)
SELECT DISTINCT
[BriefingTemplateId],[TemplateDescription],[BriefingType],
[UserAlias],FullName,[Filename],[FileType],[PreviewFilename]
FROM [hr].[DimPerson] p
INNER JOIN [aieo].[ExecutiveOfficeMember] eom ON eom.[PersonnelNumber] = p.[PersonnelNumber]
INNER JOIN [aieo].[ExecutiveOffice] o ON o.[ExecutiveOfficeId] = eom.[ExecutiveOfficeId]
INNER JOIN [aieo].[BriefingTemplate] bt ON bt.[ExecutiveOfficeId] = o.[ExecutiveOfficeId]
UNION
SELECT DISTINCT
[BriefingTemplateId],[TemplateDescription],[BriefingType],
p.EmailName As UserAlias,p.FullName,[Filename],[FileType],[PreviewFilename]
FROM [aieo].[BriefingTemplate] bt
CROSS JOIN [hr].[DimPerson] p
WHERE bt.[UserAlias] = 'ALL' AND p.EmailName = @UserAlias
AND bt.BriefingType = @BriefingType;
END;
Продуктивність та безпека. Для підвищення ефективності транзакції використовують SET NOCOUNT ON, а для операцій з читанням — SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED, щоб вони не блокували одна одну. Обробка JSON здійснюється за допомогою вбудованих функцій SQL Server JSON_QUERY та FOR JSON PATH, які ефективно обробляють дані та серіалізують їх безпосередньо з бази даних. Дозволяється лише параметризоване виконання, причому сервісна ідентичність додатку має право на виконання лише збережених процедур, без прямого доступу до таблиць.
GRANT EXECUTE ON SCHEMA::[aieo] TO [service_identity];
- No direct table access permitted
Шар послуг: оркестрація без стану та межі виконання
Шар послуг здійснює суто оркестрацію даних. Він отримує дані, форматує шаблони та не застосовує власних бізнес-правил. Кожна послуга має одну функцію та є слабко пов’язаною, що дозволяє чітко розділити шари даних та візуалізації.
API та перевірка даних. Додаток FastAPI надає чітко визначені REST-кінцеві точки з суворо типованими контрактами, визначеними як моделі Pydantic:
POST /api/briefing/generate— це основна кінцева точка для створення інформації, з повною перевіркою вхідних даних.GET /api/health— повідомляє про статус системи.GET /api/templates— відображає список шаблонів, доступних для користувача, який звертається.
from pydantic import BaseModel
from typing import Optional
class BriefingRequest(BaseModel):
account_name_or_tpid: str
meeting_datetime: Optional[str] = None
meeting_objective: Optional[str] = None
ms_attendees: Optional[str] = None
template_type: Optional[str] = None
Модель забезпечує сувору перевірку та безпеку типів, автоматично створює документацію OpenAPI та коригує формат кожного запиту. Жоден неструктурований вхідний даний не може пройти через цей контроль.
Модель виконання. Сервіс виконує детерміновану п’ятикрокову послідовність:
- Перевірка запиту. Моделі Pydantic переконуються, що типи та дані є прийнятними.
- Визначення облікового запису. Формат вхідних даних визначає, чи потрібно безпосередньо знаходити TPID, чи викликати
SearchAccountsдля пошуку за назвою. - Одночасне отримання даних.
GetAccountDetailsтаGetBriefingTemplateвиконуються одночасно за допомогоюasyncio.gather(). - Складання контексту. Результати процедур безпосередньо переносяться у об’єкти, готові до використання в шаблоні, без проміжної обробки.
Асинхронна обробка та пулінг з’єднань через абстракцію DatabaseService означають, що ідентичні вхідні дані дають ідентичні результати.
Керівні принципи. Слой послуг керується п’ятьма правилами. У ньому немає бізнес-логіки, оскільки обробка даних відбувається лише всередині процедур зберігання, що дозволяє уникнути правил, які ускладнюють тестування та підтримку. Він займається суто організацією роботи з даними, координуючи їх отримання та відображення без будь-яких перетворень чи обчислень. Мапування контексту є прямим, тож результати процедур потрапляють у структури шаблонів без затримок, що зменшує ризик відхилень у значеннях між даними та їх відображенням. Обробка є детерміністичною, тож для одних і тих самих вхідних даних завжди отримується однаковий результат, незалежно від часу виконання чи стану інфраструктури. Крім того, функції чітко розділені: логіка обробки даних знаходиться у SQL-процедурах, організація роботи — у сервісах Python FastAPI, а відображення — у шаблонах Jinja2.
Слой досвіду: Copilot Studio як двигун розмови
У верхній частині фреймворк AdaptiveDialog у Microsoft Copilot Studio перетворює запит мовою природи на детермінований робочий процес. Він займається вирішенням проблем із обліковими записами, авторизацією шаблонів, інтеграцією зовнішніх даних, генерацією документів та їх доставкою через кілька каналів. Процес виконується послідовно — від першого запиту через вирішення проблем із обліковими записами, авторизацію шаблонів, збір зовнішніх даних та їх надсилання до SharePoint, з точками прийняття рішень та кроками перевірки на кожному етапі розмови.
Основна діалогова частина. Ця частина автоматично збирає ідентифікатор облікового запису, визначає фрази-тригери, які запускають процес, та встановлює основну точку початку розмови:
kind: AdaptiveDialog
inputs:
- kind: AutomaticTaskInput
propertyName: Account_Name_or_TPID
name: Account Name or TPID
description: The account/customer TPID (unique identifier) or name
shouldPromptUser: true
modelDescription: Create briefing document to prepare for meeting with customer
beginDialog:
kind: OnRecognizedIntent
intent:
displayName: Topic for creating briefing document to prepare for meeting with customer
triggerQueries:
- Generate a briefing document for account Accenture
- I need a briefing for Amazon
- Create briefing for Apple
- I have a meeting with JPMorgan Chase at 4PM
Вирішення проблем з обліковими записами. Перевірка IsNumeric() класифікує вхідні дані як прямий TPID або назву облікового запису, яку потрібно знайти. Уникнення пошуку, коли це можливо, зменшує кількість викликів API та прискорює отримання відповіді:
- kind: SetVariable
id: setVariable_IsNumeric
variable: Topic.IsNumeric
value: =IsNumeric(Topic.UserInput)
- kind: ConditionGroup
id: conditionGroup_InputType
conditions:
- id: condition_IsTPID
condition: =Topic.IsNumeric
actions:
- kind: SetVariable
variable: Topic.Account_TPID
value: =Topic.UserInput
Виклик для пошуку облікового запису. Для імен процес викликає точку входу пошуку шару послуг через REST. Він URL-кодує спеціальні символи, автентифікується за допомогою ключа підписки та визначає схему відповіді, щоб дані автоматично перевірялися:
- kind: HttpRequestAction
id: httpRequest_SearchAccounts
displayName: Search Accounts by Name
url: =Global.ApiBaseUrl & "/api/mssales/search/" & EncodeUrl(Topic.UserInput)
headers:
Ocp-Apim-Subscription-Key: =Global.ApiKey
response: Topic.SearchResults
responseSchema:
kind: Record
properties:
accounts:
type:
kind: Table
properties:
account_name: String
tpid: String
total_count: Number
Карта дисанібігуації. Якщо пошук знаходить кілька облікових записів, адаптивна карта відображає динамічний набір варіантів, створений за допомогою ForAll() з результатів, причому кожен елемент показує свій TPID. Таким чином, нечіткий вхідний даний не призводить до зупинки розмови:
- kind: AdaptiveCardPrompt
id: adaptiveCard_SelectAccount
card: "=Concatenate('{
\"$schema\":\"https://adaptivecards.io/schemas/adaptive-card.json\",
\"type\":\"AdaptiveCard\",\"version\":\"1.5\",
\"body\":[
{\"type\":\"TextBlock\",\"text\":\"Multiple accounts found\",\"weight\":\"Bolder\"},
{\"type\":\"Input.ChoiceSet\",\"id\":\"selectedAccount\",\"isRequired\":true,
\"choices\":',
JSON(ForAll(Topic.SearchResults.accounts,
{title: Concatenate(account_name, \" (TPID: \", tpid, \")\"), value: tpid})),
'}],
\"actions\":[{\"type\":\"Action.Submit\",\"title\":\"Continue\"}]
}')"
Авторизація шаблонів. За допомогою GetBriefingTemplate процес перевіряє, чи має користувач право використовувати керівні шаблони, виходячи з його організаційної ролі та членства у керівному офісі. Ім’я принципала підтверджує особу, тип інформації фільтрує результати, а керування шаблонами за замовчуванням є безпечним:
- kind: HttpRequestAction
id: httpRequest_GetUserTemplates
url: =Global.ApiBaseUrl & "/api/briefings/templates?user_alias=" &
EncodeUrl(System.User.PrincipalName) & "&briefing_type=Customer"
headers:
Ocp-Apim-Subscription-Key: =Global.ApiKey
response: Topic.UserTemplates
Карта деталей зустрічі. Наступна карта використовує принцип поступового розкриття інформації: обов’язкові поля відображаються спочатку, а необов’язкові — ховаються за перемикачем. Це зменшує когнітивне навантаження, при цьому все необхідне для інформування все одно збирається:
- kind: AdaptiveCardPrompt
id: adaptiveCard_MeetingDetails
card: "=Concatenate('{
\"$schema\":\"https://adaptivecards.io/schemas/adaptive-card.json\",
\"type\":\"AdaptiveCard\",\"version\":\"1.5\",
\"body\":[
{\"type\":\"TextBlock\",\"text\":\"Create Executive Briefing Document\",
\"weight\":\"Bolder\",\"size\":\"Large\",\"color\":\"Accent\"},
{\"type\":\"Input.Date\",\"id\":\"meetingDate\",\"isRequired\":true,
\"label\":\"Meeting Date\"},
{\"type\":\"Input.Time\",\"id\":\"meetingTime\",\"value\":\"09:00\"},
{\"type\":\"Input.Text\",\"id\":\"msAttendees\",\"isVisible\":false,
\"label\":\"Microsoft Attendees\"}
],
\"actions\":[
{\"type\":\"Action.ToggleVisibility\",\"title\":\"Additional Options ▼\",
\"targetElements\":[\"msAttendees\"]},
{\"type\":\"Action.Submit\",\"title\":\"Generate Briefing\"}
]
}')"
Одночасне збирання паралельних даних та створення документів. Шар досвіду одночасно викликає API Account Details та Azure AI Foundry, причому для кожного встановлений таймаут у 60 секунд. Після отримання відповідей від обох сервісів формується один структурований JSON-пакет з усіх джерел, який потім передається API для створення документів; процеси верифікації та об’єднання даних залишаються інтегрованими, щоб забезпечити детерміноване створення інформаційного звіту.
Azure AI Foundry: організаційний інтелект
Інтеграція з Foundry розширює можливості агента за межі зовнішніх ринкових даних, включаючи контекст зсередини організації, за допомогою компонента „M365 Researcher“. Вона використовує делегований доступ до Microsoft Graph для пошуку електронних листів у Outlook та розмов у Teams, при цьому діє в межах корпоративної безпеки та принципів нульової довіри.
Доступ до Microsoft Graph через делегування без зберігання секретів. Дослідник використовує механізм делегованої автентифікації, який повністю усуває необхідність зберігання клієнтських секретів. Для цього використовуються конектори, якими керує Azure Logic Apps – це первинні додатки Microsoft із заздалегідь погодженими обмеженнями доступу. Це усуває необхідність керування реєстраціями додатків, оскільки Azure відповідає за життєвий цикл токенів та їх автоматичне оновлення.
Крім того, короткострокові інстанції Logic Apps забезпечують доступ до даних користувача через HTTP-тригери, захищені механізмом SAS. Кожен пошук ініціює створення нової інстанції Logic App, яка одночасно отримує дані з Microsoft Graph та негайно її видаляє; таким чином SAS-URL не може бути використаний знову, а жодні постійні токени не залишаються відкритими.
// Ephemeral Logic App Security Pattern
const createSearchLogicApp = async (userAlias, searchQuery) => {
const logicAppName = `Researcher-${userAlias}`;
// Create Logic App with SAS trigger
const logicApp = await armClient.logicApps.createOrUpdate({
resourceGroupName: 'Azure_OpenAI',
workflowName: logicAppName,
definition: buildResearchWorkflow(userAlias)
});
// Execute search via SAS URL
const searchResults = await triggerLogicApp(logicApp.triggerUrl, searchQuery);
// Immediate cleanup for security
await armClient.logicApps.delete(logicAppName);
return searchResults;
};
Паралельне отримання даних та налаштування Graph. Дані електронної пошти та Teams збираються одночасно, причому існують обмеження на паралельність, щоб сеанси багатьох користувачів не спричиняли обмежень у швидкості обробки. Для пошти коннектор Office 365 здійснює масове отримання останніх 250 повідомлень від кожного користувача, а фільтрація відбувається на клієнті, щоб обійти обмеження Graph щодо пошуку вмісту повідомлень. Для Teams використовується двофазний підхід: спочатку через сторінковані запити виявляються чати, а потім отримуються повідомлення з окремих чат-тредів із налаштовуваним лімітом паралельності.
# Logic App Parallel Execute Pattern
HTTP_Trigger_SAS_Secured:
Email_Branch:
- Get_Emails: Office365_Connector.Mail (top=250)
Teams_Branch_Parallel:
- Get_Teams_Chats_Page1: Teams_Connector.Chats (top=50)
- Get_Teams_Chats_Page2: Teams_Connector.Chats (skip=50, top=50)
- For_Each_Chat:
concurrency: 10
actions:
- Get_Chat_Messages: Teams_Connector.Messages (top=50)
- Append_Chat_Data: Variable_Accumulation
Response_Assembly:
- Combine: Email_Results + Teams_Results + Metadata
- Return: JSON_Structured_Response
Типовий пошук здійснює близько 101 виклику Graph API: один для пошуку електронної пошти, два для перелічення чатів у Teams та в середньому 98 для отримання повідомлень у чатах. Це охоплює велику кількість даних, залишаючись в межах лімітів одночасних операцій Graph та, згідно з проектуванням, забезпечуючи час відповіді менше 60 секунд.
Підключення та токени на користувача. Кожному користувачеві потрібно два керовані підключення для зберігання делегованих токенів OAuth протягом усіх сеансів пошуку: office365-{alias} для роботи з електронною поштою із діапазоном прав Mail.ReadWrite та teams-{alias} для чату із діапазоном прав Chat.Read. Інфраструктура керованих конекторів Azure автоматично оновлює токени та забезпечує дотримання вимог. Процес налаштування відбувається за принципом отримання згоди: користувач увійде один раз через вбудовану систему OAuth від Microsoft, а делеговані права залишатимуться дійсними до моменту їх скасування. Функційний додаток керує життєвим циклом підключень за допомогою API Azure Resource Manager через керовану ідентичність, тому жодні клієнтські облікові дані не зберігаються на рівні додатку.
// Per-User Connection Provisioning
const provisionUserConnections = async (userAlias) => {
const connections = [
{
name: `office365-${userAlias}`,
api: '/providers/Microsoft.PowerApps/apis/shared_office365',
scopes: ['Mail.ReadWrite']
},
{
name: `teams-${userAlias}`,
api: '/providers/Microsoft.PowerApps/apis/shared_teams',
scopes: ['Chat.Read']
}
];
for (const conn of connections) {
await armClient.connections.createOrUpdate({
resourceGroupName: 'Azure_OpenAI',
connectionName: conn.name,
properties: {
displayName: conn.name,
api: { id: conn.api },
parameterValues: {}
}
});
}
return generateConsentUrls(connections);
};
Синтез за допомогою моделі GPT. Сирі повідомлення перетворюються на готову до використання організаційну інформацію за допомогою моделі з сервісу Azure OpenAI Service (яку описують як GPT-4.1, хоча у зразку коду вказано прев’ю-версію GPT-4). Цей процес перетворює відфільтровану електронну пошту та контент Teams на структуровані висновки, які охоплюють аналіз зацікавлених сторін, моделі комунікації та дії, важливі для зустрічі. Запити орієнтовані на потреби керівництва, такі як візуалізація взаємовідносин, статус проекту та стратегічні теми, а модель повертає структурований JSON-формат, який інтегрується у наявні шаблони Jinja2 із послідовним форматуванням у всіх документах.
# Organizational Intelligence Synthesis
async def synthesize_organizational_context(filtered_data, account_context):
synthesis_prompt = """
Analyze the following organizational communications for executive briefing preparation:
Account Context: {account_name}
Email Communications: {email_count} relevant messages
Teams Discussions: {chat_count} relevant conversations
Generate executive intelligence focusing on:
Financial performance, earnings trends, and capital allocation signals.
Corporate strategy, market positioning, and competitive landscape.
AI strategy, digital transformation initiatives, and cloud ecosystem alignment.
Recent material developments (≤ 90 days) with actionable insights for executive engagement.
Format as structured JSON with executive_summary, stakeholder_analysis, recent_activities, and recommended_actions.
"""
response = await azure_openai_client.chat.completions.create(
model="gpt-4-1106-preview",
messages=[{
"role": "system",
"content": "You are an executive intelligence analyst."
}, {
"role": "user",
"content": synthesis_prompt.format(**filtered_data, **account_context)
}],
temperature=0.1,
max_tokens=2000
)
return parse_structured_intelligence(response.choices[0].message.content)
Інтеграція в діалог.** Дослідник приєднується до робочого процесу Copilot Studio одночасно з отриманням даних зі стандартного облікового запису та зовнішньою інформацією про ринок, тому додатковий організаційний контекст, ймовірно, не сповільнить загальний процес створення звіту.
# Enhanced Copilot Studio Parallel Execution
- kind: ParallelExecution
id: parallelExecution_ComprehensiveIntelligence
branches:
- account_data:
kind: HttpRequestAction
url: =Global.ApiBaseUrl & "/api/accounts/" & Topic.Account_TPID
- organizational_intelligence:
kind: HttpRequestAction
url: =Global.ApiBaseUrl & "/api/search?q=" & EncodeUrl(Topic.Account_Name) & "&alias=" & System.User.PrincipalName
requestTimeoutInMilliseconds: 60000
continueOnError: true
- market_intelligence:
kind: HttpRequestAction
url: =Global.FoundryBaseUrl & "/bingnews/api/AgentFunction"
- kind: SetVariable
id: setVariable_CombinedIntelligence
variable: Topic.BriefingContext
value: ={
account_details: Topic.AccountData,
organizational_context: Topic.OrganizationalIntelligence,
market_insights: Topic.MarketIntelligence
}
Управління та дотримання правил. Завдяки делегованим дозволам користувачі можуть шукати лише власну електронну пошту та повідомлення в Teams, що запобігає доступу з боку інших користувачів та забезпечує суворий контроль над даними. Кожен виклик Graph API фіксується з контекстом користувача, параметрами пошуку та метаданими результатів. Термін зберігання даних можна налаштувати, а з’єднання, які не використовуються протягом встановленого періоду, зазвичай 80 днів, автоматично видаляються. Обробка даних відбувається всередині тенанта Azure, а механізми контролю резидентності даних підтримують політики управління організації.
// Enterprise Governance and Cleanup
const implementGovernanceControls = async () => {
// Automated stale connection cleanup
const staleThreshold = 80; // days
const allConnections = await listManagedConnections();
const staleConnections = allConnections.filter(connection => {
const lastUsed = parseISO(connection.properties.lastConnection);
const daysSinceUse = differenceInDays(new Date(), lastUsed);
return daysSinceUse > staleThreshold;
});
// Compliance audit logging
for (const connection of staleConnections) {
await auditLogger.log({
action: 'CONNECTION_CLEANUP',
userAlias: connection.userAlias,
reason: 'AUTOMATED_GOVERNANCE',
retentionPolicy: `${staleThreshold}_DAYS_INACTIVE`,
timestamp: new Date().toISOString()
});
await deleteUserResources(connection.userAlias);
}
};
Продуктивність та стійкість до збоїв. Система кешує статус підключення користувача та перевіряє його стан перед виконанням ресурсомістких операцій Graph. Якщо доступ до Graph обмежений або тимчасово недоступний, система плавно переходить у режим зниженої продуктивності, використовуючи експоненційне затримування для тимчасових несправностей та жорсткі ліміти часу, щоб генерація звітів не затримувалась. Якщо пошук у організації зазнає невдачі або досягає ліміту часу, звіт все одно надсилається разом із даними про обліковий запис та зовнішньою ринковою інформацією, тож керівники отримують послідовний досвід незалежно від того, чи доступний Microsoft 365 чи ні.
// Fault-Tolerant Execution Pattern
const executeOrganizationalIntelligence = async (searchQuery, userAlias) => {
const executionTimeout = 60000; // 60 second maximum
const fallbackResponse = { summary: "Organizational context unavailable", status: "fallback" };
try {
// Health check before expensive operations
const connectionsHealthy = await verifyConnectionHealth(userAlias);
if (!connectionsHealthy) {
return fallbackResponse;
}
// Execute with timeout boundary
const intelligencePromise = gatherOrganizationalIntelligence(searchQuery, userAlias);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('TIMEOUT')), executionTimeout)
);
return await Promise.race([intelligencePromise, timeoutPromise]);
} catch (error) {
// Graceful degradation logging
await logger.warn(`M365 Researcher fallback: ${error.message}`, {
userAlias,
searchQuery,
fallbackMode: true
});
return fallbackResponse;
}
};
Після того, як дослідник призначений, агент переходить від збору інформації з зовнішнього ринку до створення комплексної картини, яка включає дані про внутрішніх зацікавлених сторін, що допомагає приймати кращі стратегічні рішення без погіршення стандартів безпеки, управління чи продуктивності.
Виклик для генерації документа. На цьому етапі створюється остаточний звіт шляхом об’єднання всіх зібраних під час розмови даних у один пакет для сервісного шару. Він містить контекст користувача, деталі зустрічі та необов’язкові дані від зовнішніх ШІ, причому час виконання всього процесу створення документа обмежений 60 секундами:
- kind: HttpRequestAction
id: p4tceX
method: Post
url: =Global.ApiBaseUrl & "/api/briefings/" & Topic.templateId & "/accounts/" & Topic.Account_TPID
body:
kind: JsonRequestContent
content: "={
user_alias: Topic.SenderEmail,
MeetingDateTime: Topic.MeetingDateTime,
msAttendees: Topic.msAttendees,
FoundryResponse: Topic.FoundryResponse
}"
requestTimeoutInMilliseconds: 60000
response: Topic.BriefingDocument
Доставка до SharePoint. Усі створені документи потрапляють до центрального сховища через конектор SharePoint Power Platform із керованою автентифікацією, тож кожен документ зберігається та залишається доступним незалежно від способу другорядної доставки, що задовольняє вимоги до управління. Цільовий сайт та папка у наведеному нижче прикладі є замінниками.
- kind: InvokeConnectorAction
id: invokeConnectorAction_YKkMK4
input:
binding:
dataset: https://microsoft.sharepoint.com/teams/MCAPSAIIncubationHub
folderPath: /Shared Documents/General/AI Prototypes & Solutions/AI Executive Office Use Cases/Published Cust Template/
name: =Topic.CustomFilename
file: =Global.File
Назви файлів створюються так, щоб вони були зрозумілими, поєднуючи ім’я облікового запису, ідентифікатор керівника та дату зустрічі. Функція Substitute() робить їх безпечними для файлової системи та забезпечує послідовність найменувань у робочих процесах керівників:
- kind: SetVariable
id: setVariable_CustomFilename
variable: Topic.CustomFilename
value: =Concatenate(
Substitute(Topic.Account_Name, " ", "_"), "_",
Topic.ExecutiveName, "_Brief_",
Text(Topic.meetingDate, "yyyy-MM-dd"), ".docx"
)
Обробка помилок та стан системи. При неможливості отримати доступ до шаблону відображається інформація з контексту у вигляді попередження на картці типу Adaptive Card, а не загальна помилка. Система аналізує конкретні коди помилок від API-сервісу, повідомляє користувачеві про відсутню авторизацію та приховує конфіденційні деталі системи:
- kind: ConditionGroup
id: conditionGroup_CheckBriefingError
conditions:
- condition: =!IsBlank(Topic.BriefingDocument.error) && Topic.BriefingDocument.error.error_code = "TEMPLATE_ACCESS_DENIED"
actions:
- kind: AdaptiveCardPrompt
card: ={
"$schema":"https://adaptivecards.io/schemas/adaptive-card.json",
"type":"AdaptiveCard","version":"1.5",
"body":[{
"type":"Container","style":"warning",
"items":[{
"type":"TextBlock","text":"Template Access Required",
"weight":"Bolder","color":"Attention"
},{
"type":"TextBlock","wrap":true,
"text": Topic.BriefingDocument.error.user_message
}]
}]
}
Перевірка даних відбувається на клієнтській стороні, тож недійсні дані ніколи не потрапляють до сервісів на сервері, що покращує користувацький досвід та уникає зайвих викликів API. Наприклад, дата зустрічі у минулому відхиляється негайно з порадами щодо подальших дій:
- kind: ConditionGroup
id: conditionGroup_ValidateInputs
conditions:
- condition: =Topic.meetingDate < Today()
actions:
- kind: SendActivity
activity: The meeting date cannot be in the past. Please start over and enter a future date.
- kind: EndConversation
Крок очищення, пов’язаний із питаннями безпеки, видаляє всі змінні, які зберігають дані прорахунків, відповіді API та дані, введені користувачем, після завершення розмови. Це задовольняє вимоги до конфіденційності та запобігає накопиченню даних у сервісах тривалих розмов, які обробляють конфіденційну ділову інформацію:
- kind: SetVariable
id: setVariable_ClearUserInput
variable: Topic.UserInput
value: =Blank()
- kind: SetVariable
id: setVariable_ClearAccountDetails
variable: Topic.AccountDetails
value: =Blank()
- kind: SetVariable
id: setVariable_ClearFoundryResponse
variable: Topic.FoundryResponse
value: =Blank()
- kind: SetVariable
id: setVariable_ClearBriefingDocument
variable: Topic.BriefingDocument
value: =Blank()
У сукупності шар досвіду забезпечує оркестрацію діалогу за допомогою детерміністичних робочих процесів, розширену обробку помилок, стійкі до збоїв виклики зовнішніх сервісів, авторизацію на основі ролей та багатоканальну доставку через фреймворк AdaptiveDialog.
Безпека та деталі реалізації
Федерована ідентичність та автентифікація
Система використовує облікові дані федерованої ідентичності Azure, щоб не зберігати жодних секретів, забезпечуючи однакову процедуру входу у різних середовищах. Селектор облікових даних визначає місце виконання коду та обирає відповідний механізм:
# Environment-aware authentication pattern
def get_credential():
if is_azure_environment():
return ManagedIdentityCredential(client_id=os.getenv('AZURE_CLIENT_ID'))
else:
return AzureCliCredential()
У Azure використовується керована ідентичність, при цьому облікові дані автоматично надаються та оновлюються, а для локальної розробки використовуються облікові дані Azure CLI. Токени зберігаються у безпечному від потокових конфліктів способі, а термін їх дії автоматично контролюється.
Структура сервісу та керування залежностями
Слой сервісу ґрунтується на введенні залежностей, що дозволяє одному й тому ж коду працювати як з імітаціями під час тестування, так і з повністю налаштованими екземплярами в продакшені. Такі міжфункціональні аспекти, як обробка помилок та керування конфігурацією, є спільними для всіх компонентів сервісу, причому кожен з них (SharePoint, база даних, Power BI) приймає необов’язкові залежності через свій конструктор, залишаючись при цьому слабко пов’язаним із зовнішніми API та ресурсами Azure. Це сприяє ретельному тестуванню за допомогою імітаційних реалізацій, а також надійній роботі в продакшені завдяки належній абстракції.
Оркестрація є асинхронною для максимального використання можливостей паралельного збирання даних. Під час створення інформаційного зведення кілька джерел обробляються одночасно за допомогою механізму async/await та ретельного керування помилками, щоб система грайно реагувала на збої в одному з джерел.
Підключення до бази даних та продуктивність
Рівень бази даних використовує пулінг підключень через механізм керування двигуном SQLAlchemy, з використанням патерну singleton для ефективного використання ресурсів. Запити об’єднані у запрограмовані процедури, які містять бізнес-логіку безпосередньо в базі даних, що зменшує обсяг мережевого трафіку та спрощує код додатку. Великі набори результатів обробляються шляхом розумної переробки JSON, що дозволяє обійти обмеження pyodbc при роботі з великими відповідями, тож обробка залишається надійною незалежно від розміру результату, а використання пам’яті залишається оптимальним.
Інтеграція зовнішніх сервісів
Інтеграція з SharePoint та Microsoft Graph затримує зберігання властивостей у кеші, щоб зменшити кількість викликів API та водночас підтримувати актуальність даних. ID сайтів та папок зберігаються у кеші з розумним механізмом скасування даних та динамічним вибором бібліотек, що забезпечує надійність завантажень. Інтеграція з Dynamics 365 аналізує відповіді Search v2.0 та перетворює первинні результати на структури, зрозумілі для бізнесу, з логікою повторних спроб для подолання обмежень швидкості та ретельним обробленням помилок.
Створення документів
Процес генерації поєднує обробку шаблонів Jinja2 із формуванням контексту, яке перетворює необроблені дані на інсайти, готові до використання керівництвом. Контекст шаблону включає необроблені дані, обчислені показники, інструменти форматування та аналітичну інформацію, отриману в результаті аналізу різних систем. Механізм керування шаблонами перевіряє синтаксис та обробляє помилки, що забезпечує надійність генерації навіть під час їх редагування, а кілька форматів виводу підтримуються завдяки налаштуванням типів MIME для гнучкої передачі даних.
Помилки та винятки
У додатку є структурована ієрархія винятків, яка надає детальну інформацію про помилки, дотримуючись при цьому принципів безпеки. Кастомні класи винятків містять контекст, який допомагає у виправленні проблем, не розкриваючи внутрішньої структури системи:
Structured exception hierarchy
class AIExecutiveOfficeError(Exception):
def __init__(self, message: str, error_code: str = None, details: dict = None):
self.message = message
self.error_code = error_code or self.__class__.__name__
self.details = details or {}
self.timestamp = datetime.utcnow()
Глобальний обробник винятків перетворює технічні помилки на структуровані відповіді API, які допомагають користувачеві, не розкриваючи занадто багато деталей.
Оцінка та перевірка якості
Система оцінюється за допомогою структурованої системи тестування, яка перевіряє технічні характеристики та якість інформування у кількох корпоративних облікових записах. Кількісні та якісні методи поєднуються у процесі оцінки з використанням кількох нотатників, який тестує кожен архітектурний компонент пакетно.
З кількісної точки зору основна увага приділяється точності даних. Показники у готових шаблонах інформаційних звітів порівнюються з вихідними даними з базового рівня, і система обчислює показники точності та середню абсолютну відсоткову похибку (MAPE). Вони показують, наскільки точно сервісний рівень передає дані протягом усього процесу обробки, від результатів запрограмованих процедур до кінцевого документа. Пакетні виконання охоплюють багато облікових записів та формують об’єднаний звіт про продуктивність, що надає статистичних доказів того, що дизайн, заснований на чистій оркестрації, зберігає точність даних, не вводячи помилок у обчисленнях чи семантичних відхилень між базовим та користувацьким рівнями.
Якісний аспект вимірює одну характеристику — здатність компанії до ефективного спілкування, тобто наскільки добре у звіті відображається справжнє розуміння цільової організації та наскільки він адекватно про неї говорить. Суддя, який використовує ШІ, оцінює кожен звіт за п’ятьома стандартними критеріями: релевантність (чи підходить контент для прийняття рішень керівництвом), послідовність (логічна структура та послідовність викладу), точність (фактична правильність та відповідність джерельним даним), чіткість (мова та формат подання, придатні для керівників) та повнота (охоплення стратегічних аспектів). Цей підхід забезпечує послідовну та масштабовану оцінку з об’єктивними балами для різних типів клієнтів та галузей, проте, як і у будь-якій оцінці на основі ШІ, він успадковує власні упередження моделі судді.
Наступні кроки: перехід до Microsoft Agent Framework
Команда вважає чотирирівневу архітектуру гарною основою для міграції до Microsoft Agent Framework (MAF), що дозволить посилити оркестрацію ШІ, зберігаючи при цьому принципи детермінізму. MAF переважно буде впроваджуватися на рівні сервісів, замінюючи логіку оркестрації FastAPI агентами MAF, які дотримуються тих самих шаблонів чистої оркестрації даних. Рівень користувацького інтерфейсу зміниться повністю: Copilot Studio поступиться місцем нативній інтеграції з Microsoft 365, яка розміщує бізнес-процеси всередині Outlook, Teams та SharePoint, використовуючи здатність MAF до розуміння мови для кращого розпізнавання намірів та багатокрокових діалогів, при цьому все ще використовуючи ті самі збережені процедури для аналітики бізнесу.
Вбудовані інструменти для спостережливості, керування повідомленнями та управління життєвим циклом агентів у MAF дозволять позбутися необхідності в створенні власної інфраструктури та забезпечать моніторинг та розгортання на рівні корпорацій. Шлях міграції зберігає існуючі інвестиції у процедури зберігання даних, шаблони Jinja2 та інтеграцію з SharePoint, водночас додаючи можливості для складніших сценаріїв, таких як підготовка інформації для кількох облікових записів, аналіз конкурентів та генерація шаблонів динамічно на основі контексту зустрічі та учасників. Завдяки інтеграції з Microsoft 365 генерація інформаційних звітів стане частиною існуючих робочих процесів, а не окремим чат-інтерфейсом, при цьому рівні даних та послуг залишатимуться детермінованими.
Що показує дизайн
Цей агент перетворює підготовку інформаційних звітів з процесу ручної координації на автоматизоване створення інтелектуальних даних за допомогою чотирьох рівнів, які відповідають за об’єднання даних, їх операційну агрегацію, організацію бізнес-процесів та взаємодію мовою природи. За словами розробників у інтегрованій екосистемі Microsoft цей детермінований підхід забезпечує швидке створення звітів (стверджується, що це відбувається «за кілька секунд», хоча власні таймаути процесу становлять 60 секунд, тож це, ймовірно, стосується кроків обробки в базі даних та сервісах), а також забезпечує рівень безпеки, керування та аудиту, які системам, що базуються виключно на запитах користувача, важко гарантувати.
Загальний урок полягає у тому, що успішні корпоративні додатки на основі ШІ ставлять вирішення бізнес-проблем на перше місце перед технічною складністю, і що вони створюють цінність завдяки ефективній інтеграції даних та автоматизації процесів, а не завдяки передовим технологіям машинного навчання. Розділення функцій на різні рівні, введення залежностей та чіткі межі виконання роблять цю архітектуру придатною для повторного використання у автоматизації обслуговування клієнтів, робочих процесах фінансового аналізу та системах оперативної інтелігенції в інших організаціях.