Skip to content

Commit f706c51

Browse files
authored
Merge pull request #178814 from sarkar-rajarshi/rsarkar/azure-func-docs
Azure function rule docs
2 parents 6ea7dde + 723c18b commit f706c51

File tree

5 files changed

+509
-83
lines changed

5 files changed

+509
-83
lines changed
31.3 KB
Loading
28.4 KB
Loading
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
---
2+
title: Azure Function Rule concepts for Azure Communication Services
3+
titleSuffix: An Azure Communication Services concept document
4+
description: Learn about the Azure Communication Services Job Router Azure Function Rule concepts.
5+
author: rsarkar
6+
manager: bo.gao
7+
services: azure-communication-services
8+
9+
ms.author: rsarkar
10+
ms.date: 02/23/2022
11+
ms.topic: conceptual
12+
ms.service: azure-communication-services
13+
---
14+
15+
# Azure function rule concepts
16+
17+
[!INCLUDE [Private Preview Disclaimer](../../includes/private-preview-include-section.md)]
18+
19+
As part of customer extensibility model, Azure Communication Services Job Router supports Azure Function Rule Engine. It gives you the ability to bring your own Azure function. With Azure function Rule, you can incorporate custom and complex logic into the process of routing.
20+
21+
A couple of examples are given below to showcase the flexibility that Azure Function Rule provides.
22+
23+
## Scenario: Custom scoring rule in best worker distribution mode
24+
25+
We want to distribute offers among their workers associated with a queue. The workers will be given a score based on their labels and skill set. The worker with the highest score should get the first offer (_BestWorker Distribution Mode_).
26+
27+
:::image type="content" source="../media/router/best-worker-distribution-mode-problem-statement.png" alt-text="Diagram showing Best Worker Distribution Mode problem statement" lightbox="../media/router/best-worker-distribution-mode-problem-statement.png":::
28+
29+
### Situation
30+
31+
- A job has been created and classified.
32+
- Job has the following **labels** associated with it
33+
- ["CommunicationType"] = "Chat"
34+
- ["IssueType"] = "XboxSupport"
35+
- ["Language"] = "en"
36+
- ["HighPriority"] = true
37+
- ["SubIssueType"] = "ConsoleMalfunction"
38+
- ["ConsoleType"] = "XBOX_SERIES_X"
39+
- ["Model"] = "XBOX_SERIES_X_1TB"
40+
- Job has the following **WorkerSelectors** associated with it
41+
- ["English"] >= 7
42+
- ["ChatSupport"] = true
43+
- ["XboxSupport"] = true
44+
- Job currently is in a state of '**Queued**'; enqueued in *Xbox Hardware Support Queue* waiting to be matched to a worker.
45+
- Multiple workers become available simultaneously.
46+
- **Worker 1** has been created with the following **labels**
47+
- ["HighPrioritySupport"] = true
48+
- ["HardwareSupport"] = true
49+
- ["Support_XBOX_SERIES_X"] = true
50+
- ["English"] = 10
51+
- ["ChatSupport"] = true
52+
- ["XboxSupport"] = true
53+
- **Worker 2** has been created with the following **labels**
54+
- ["HighPrioritySupport"] = true
55+
- ["HardwareSupport"] = true
56+
- ["Support_XBOX_SERIES_X"] = true
57+
- ["Support_XBOX_SERIES_S"] = true
58+
- ["English"] = 8
59+
- ["ChatSupport"] = true
60+
- ["XboxSupport"] = true
61+
- **Worker 3** has been created with the following **labels**
62+
- ["HighPrioritySupport"] = false
63+
- ["HardwareSupport"] = true
64+
- ["Support_XBOX"] = true
65+
- ["English"] = 7
66+
- ["ChatSupport"] = true
67+
- ["XboxSupport"] = true
68+
69+
### Expectation
70+
71+
We would like the following behavior when scoring workers to select which worker gets the first offer.
72+
73+
:::image type="content" source="../media/router/best-worker-distribution-mode-scoring-rule.png" alt-text="Decision flow diagram for scoring worker" lightbox="../media/router/best-worker-distribution-mode-scoring-rule.png":::
74+
75+
The decision flow (as shown above) is as follows:
76+
77+
- If a job is **NOT HighPriority**:
78+
- Workers with label: **["Support_XBOX"] = true**; get a score of *100*
79+
- Otherwise, get a score of *1*
80+
81+
- If a job is **HighPriority**:
82+
- Workers with label: **["HighPrioritySupport"] = false**; get a score of *1*
83+
- Otherwise, if **["HighPrioritySupport"] = true**:
84+
- Does Worker specialize in console type -> Does worker have label: **["Support_<**jobLabels.ConsoleType**>"] = true**? If true, worker gets score of *200*
85+
- Otherwise, get a score of *100*
86+
87+
### Creating an Azure function
88+
89+
Before moving on any further in the process, let us first define an Azure function that scores worker.
90+
> [!NOTE]
91+
> The following Azure function is using Javascript. For more information, please refer to [Quickstart: Create a JavaScript function in Azure using Visual Studio Code](../../../azure-functions/create-first-function-vs-code-node.md)
92+
93+
Sample input for **Worker 1**
94+
95+
```json
96+
{
97+
"job": {
98+
"CommunicationType": "Chat",
99+
"IssueType": "XboxSupport",
100+
"Language": "en",
101+
"HighPriority": true,
102+
"SubIssueType": "ConsoleMalfunction",
103+
"ConsoleType": "XBOX_SERIES_X",
104+
"Model": "XBOX_SERIES_X_1TB"
105+
},
106+
"selectors": [
107+
{
108+
"key": "English",
109+
"operator": "GreaterThanEqual",
110+
"value": 7,
111+
"ttl": null
112+
},
113+
{
114+
"key": "ChatSupport",
115+
"operator": "Equal",
116+
"value": true,
117+
"ttl": null
118+
},
119+
{
120+
"key": "XboxSupport",
121+
"operator": "Equal",
122+
"value": true,
123+
"ttl": null
124+
}
125+
],
126+
"worker": {
127+
"Id": "e3a3f2f9-3582-4bfe-9c5a-aa57831a0f88",
128+
"HighPrioritySupport": true,
129+
"HardwareSupport": true,
130+
"Support_XBOX_SERIES_X": true,
131+
"English": 10,
132+
"ChatSupport": true,
133+
"XboxSupport": true
134+
}
135+
}
136+
```
137+
138+
Sample implementation:
139+
140+
```javascript
141+
module.exports = async function (context, req) {
142+
context.log('Best Worker Distribution Mode using Azure Function');
143+
144+
let score = 0;
145+
const jobLabels = req.body.job;
146+
const workerLabels = req.body.worker;
147+
148+
const isHighPriority = !!jobLabels["HighPriority"];
149+
context.log('Job is high priority? Status: ' + isHighPriority);
150+
151+
if(!isHighPriority) {
152+
const isGenericXboxSupportWorker = !!workerLabels["Support_XBOX"];
153+
context.log('Worker provides general xbox support? Status: ' + isGenericXboxSupportWorker);
154+
155+
score = isGenericXboxSupportWorker ? 100 : 1;
156+
157+
} else {
158+
const workerSupportsHighPriorityJob = !!workerLabels["HighPrioritySupport"];
159+
context.log('Worker provides high priority support? Status: ' + workerSupportsHighPriorityJob);
160+
161+
if(!workerSupportsHighPriorityJob) {
162+
score = 1;
163+
} else {
164+
const key = `Support_${jobLabels["ConsoleType"]}`;
165+
166+
const workerSpecializeInConsoleType = !!workerLabels[key];
167+
context.log(`Worker specializes in consoleType: ${jobLabels["ConsoleType"]} ? Status: ${workerSpecializeInConsoleType}`);
168+
169+
score = workerSpecializeInConsoleType ? 200 : 100;
170+
}
171+
}
172+
context.log('Final score of worker: ' + score);
173+
174+
context.res = {
175+
// status: 200, /* Defaults to 200 */
176+
body: score
177+
};
178+
}
179+
```
180+
181+
Output for **Worker 1**
182+
183+
```markdown
184+
200
185+
```
186+
187+
With the aforementioned implementation, for the given job we'll get the following scores for workers:
188+
189+
| Worker | Score |
190+
|--------|-------|
191+
| Worker 1 | 200 |
192+
| Worker 2 | 200 |
193+
| Worker 3 | 1 |
194+
195+
### Distribute offers based on best worker mode
196+
197+
Now that the Azure function app is ready, let us create an instance of **BestWorkerDistribution** mode using Router SDK.
198+
199+
```csharp
200+
// ----- initialize router client
201+
// Setup Distribution Policy
202+
var bestWorkerDistributionMode = new BestWorkerMode(
203+
scoringRule: new AzureFunctionRule(
204+
functionAppUrl: "<insert function url>");
205+
206+
var distributionPolicy = await client.SetDistributionPolicyAsync(
207+
id: "BestWorkerDistributionMode",
208+
mode: bestWorkerDistributionMode,
209+
name: "XBox hardware support distribution",
210+
offerTTL: TimeSpan.FromMinutes(5));
211+
212+
// Setup Queue
213+
var queue = await client.SetQueueAsync(
214+
id: "XBox_Hardware_Support_Q",
215+
distributionPolicyId: distributionPolicy.Value.Id,
216+
name: "XBox Hardware Support Queue");
217+
218+
// Setup Channel
219+
var channel = await client.SetChannelAsync("Xbox_Chat_Channel");
220+
221+
// Create workers
222+
223+
var worker1Labels = new LabelCollection()
224+
{
225+
["HighPrioritySupport"] = true,
226+
["HardwareSupport"] = true,
227+
["Support_XBOX_SERIES_X"] = true,
228+
["English"] = 10,
229+
["ChatSupport"] = true,
230+
["XboxSupport"] = true
231+
};
232+
var worker1 = await client.RegisterWorkerAsync(
233+
id: "Worker_1",
234+
totalCapacity: 100,
235+
queueIds: new[] {queue.Value.Id},
236+
labels: worker1Labels,
237+
channelConfigurations: new[] {new ChannelConfiguration(channel.Value.Id, 10)});
238+
239+
var worker2Labels = new LabelCollection()
240+
{
241+
["HighPrioritySupport"] = true,
242+
["HardwareSupport"] = true,
243+
["Support_XBOX_SERIES_X"] = true,
244+
["Support_XBOX_SERIES_S"] = true,
245+
["English"] = 8,
246+
["ChatSupport"] = true,
247+
["XboxSupport"] = true
248+
};
249+
var worker2 = await client.RegisterWorkerAsync(
250+
id: "Worker_2",
251+
totalCapacity: 100,
252+
queueIds: new[] { queue.Value.Id },
253+
labels: worker2Labels,
254+
channelConfigurations: new[] { new ChannelConfiguration(channel.Value.Id, 10) });
255+
256+
var worker3Labels = new LabelCollection()
257+
{
258+
["HighPrioritySupport"] = false,
259+
["HardwareSupport"] = true,
260+
["Support_XBOX"] = true,
261+
["English"] = 7,
262+
["ChatSupport"] = true,
263+
["XboxSupport"] = true
264+
};
265+
var worker3 = await client.RegisterWorkerAsync(
266+
id: "Worker_3",
267+
totalCapacity: 100,
268+
queueIds: new[] { queue.Value.Id },
269+
labels: worker3Labels,
270+
channelConfigurations: new[] { new ChannelConfiguration(channel.Value.Id, 10) });
271+
272+
// Create Job
273+
var jobLabels = new LabelCollection()
274+
{
275+
["CommunicationType"] = "Chat",
276+
["IssueType"] = "XboxSupport",
277+
["Language"] = "en",
278+
["HighPriority"] = true,
279+
["SubIssueType"] = "ConsoleMalfunction",
280+
["ConsoleType"] = "XBOX_SERIES_X",
281+
["Model"] = "XBOX_SERIES_X_1TB"
282+
};
283+
var workerSelectors = new List<LabelSelector>()
284+
{
285+
new LabelSelector("English", LabelOperator.GreaterThanEqual, 7),
286+
new LabelSelector("ChatSupport", LabelOperator.Equal, true),
287+
new LabelSelector("XboxSupport", LabelOperator.Equal, true)
288+
};
289+
var job = await client.CreateJobAsync(
290+
channelId: channel.Value.Id,
291+
queueId: queue.Value.Id,
292+
priority: 100,
293+
channelReference: "ChatChannel",
294+
labels: jobLabels,
295+
workerSelectors: workerSelectors);
296+
297+
var getJob = await client.GetJobAsync(job.Value.Id);
298+
Console.WriteLine(getJob.Value.Assignments.Select(assignment => assignment.Value.WorkerId).First());
299+
```
300+
301+
Output
302+
303+
```markdown
304+
Worker_1 // or Worker_2
305+
306+
Since both workers, Worker_1 and Worker_2, get the same score of 200,
307+
the worker who has been idle the longest will get the first offer.
308+
```
309+
310+
## Next steps
311+
312+
- [Router Rule concepts](router-rule-concepts.md)

articles/communication-services/concepts/router/router-rule-concepts.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,8 @@ await client.upsertClassificationPolicy({
9191
});
9292
```
9393

94-
::: zone-end
94+
::: zone-end
95+
96+
## Next steps
97+
98+
- [Azure Function Rule](azure-function-rule-engine.md)

0 commit comments

Comments
 (0)