Skip to content

Commit 0d2d741

Browse files
authored
Merge pull request #101293 from HeidiSteen/heidist-python
Azure Cog Search: Custom Skill (Python)
2 parents 1930d7c + b834a3b commit 0d2d741

File tree

2 files changed

+272
-0
lines changed

2 files changed

+272
-0
lines changed

articles/search/TOC.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@
249249
items:
250250
- name: Integrate custom skills
251251
href: cognitive-search-custom-skill-interface.md
252+
- name: Example - Azure Functions (Python)
253+
href: cognitive-search-custom-skill-python.md
252254
- name: Example - Azure Functions
253255
href: cognitive-search-create-custom-skill-example.md
254256
- name: Example - Containers
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
title: 'Custom skill example (Python)'
3+
titleSuffix: Azure Cognitive Search
4+
description: Creates a custom skill using Azure Functions and Python, using in an AI-enriched indexing pipeline in Azure Cognitive Search. This skill
5+
6+
manager: nitinme
7+
author: luiscabrer
8+
ms.author: luisca
9+
ms.service: cognitive-search
10+
ms.topic: conceptual
11+
ms.date: 01/15/2020
12+
---
13+
14+
# Example: Create a custom skill using Python
15+
16+
In this Azure Cognitive Search skillset example, you will learn how to create a web API custom skill using Python and Visual Studio Code. The example uses an [Azure Function](https://azure.microsoft.com/services/functions/) that implements the [custom skill interface](cognitive-search-custom-skill-interface.md).
17+
18+
The custom skill is simple by design (it concatenates two strings) so that you can focus on the tools and technologies used for custom skill development in Python. Once you succeed with a simple skill, you can branch out with more complex scenarios.
19+
20+
## Prerequisites
21+
22+
+ Review the [custom skill interface](cognitive-search-custom-skill-interface.md) for an introduction into the input/output interface that a custom skill should implement.
23+
24+
+ Set up your environment. We followed [this tutorial end-to-end](https://docs.microsoft.com/azure/python/tutorial-vs-code-serverless-python-01) to set up serverless Azure Function using Visual Studio Code and Python extensions. The tutorial leads you through installation of the following tools and components:
25+
26+
+ [Python 3.75](https://www.python.org/downloads/release/python-375/)
27+
+ [Visual Studio Code](https://code.visualstudio.com/)
28+
+ [Python extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
29+
+ [Azure Functions Core Tools](https://docs.microsoft.com/azure/azure-functions/functions-run-local#v2)
30+
+ [Azure Functions extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
31+
32+
## Create an Azure Function
33+
34+
This example uses an Azure Function to demonstrate the concept of hosting a web API, but other approaches are possible. As long as you meet the [interface requirements for a cognitive skill](cognitive-search-custom-skill-interface.md), the approach you take is immaterial. Azure Functions, however, make it easy to create a custom skill.
35+
36+
### Create a function app
37+
38+
The Azure Functions project template in Visual Studio Code creates a project that can be published to a function app in Azure. A function app lets you group functions as a logical unit for management, deployment, and sharing of resources.
39+
40+
1. In Visual Studio Code, press F1 to open the command palette. In the command palette, search for and select `Azure Functions: Create new project...`.
41+
42+
1. Choose a directory location for your project workspace and choose **Select**.
43+
44+
> [!NOTE]
45+
> These steps were designed to be completed outside of a workspace. For this reason, do not select a project folder that is part of a workspace.
46+
47+
1. Select a language for your function app project. For this tutorial, select **Python**.
48+
1. Select the Python version, (version 3.7.5 is supported by Azure Functions)
49+
1. Select a template for your project's first function. Select **HTTP trigger** to create an HTTP triggered function in the new function app.
50+
1. Provide a function name. In this case, let's use **Concatenator**
51+
1. Select **Function** as the Authorization level. This means that we will provide a [function key](../azure-functions/functions-bindings-http-webhook.md#authorization-keys) to call the function's HTTP endpoint.
52+
1. Select how you would like to open your project. For this step, select **Add to workspace** to create the function app in the current workspace.
53+
54+
Visual Studio Code creates the function app project in a new workspace. This project contains the [host.json](../azure-functions/functions-host-json.md) and [local.settings.json](../azure-functions/functions-run-local.md#local-settings-file) configuration files, plus any language-specific project files.
55+
56+
A new HTTP triggered function is also created in the **Concatenator** folder of the function app project. Inside it there will be a file called "\__init__.py", with this content:
57+
58+
```py
59+
import logging
60+
61+
import azure.functions as func
62+
63+
64+
def main(req: func.HttpRequest) -> func.HttpResponse:
65+
logging.info('Python HTTP trigger function processed a request.')
66+
67+
name = req.params.get('name')
68+
if not name:
69+
try:
70+
req_body = req.get_json()
71+
except ValueError:
72+
pass
73+
else:
74+
name = req_body.get('name')
75+
76+
if name:
77+
return func.HttpResponse(f"Hello {name}!")
78+
else:
79+
return func.HttpResponse(
80+
"Please pass a name on the query string or in the request body",
81+
status_code=400
82+
)
83+
84+
```
85+
86+
Now let's modify that code to follow the [custom skill interface](cognitive-search-custom-skill-interface.md)). Modify the code with the following content:
87+
88+
```py
89+
import logging
90+
import azure.functions as func
91+
import json
92+
93+
def main(req: func.HttpRequest) -> func.HttpResponse:
94+
logging.info('Python HTTP trigger function processed a request.')
95+
96+
try:
97+
body = json.dumps(req.get_json())
98+
except ValueError:
99+
return func.HttpResponse(
100+
"Invalid body",
101+
status_code=400
102+
)
103+
104+
if body:
105+
result = compose_response(body)
106+
return func.HttpResponse(result, mimetype="application/json")
107+
else:
108+
return func.HttpResponse(
109+
"Invalid body",
110+
status_code=400
111+
)
112+
113+
114+
def compose_response(json_data):
115+
values = json.loads(json_data)['values']
116+
117+
# Prepare the Output before the loop
118+
results = {}
119+
results["values"] = []
120+
121+
for value in values:
122+
output_record = transform_value(value)
123+
if output_record != None:
124+
results["values"].append(output_record)
125+
return json.dumps(results, ensure_ascii=False)
126+
127+
## Perform an operation on a record
128+
def transform_value(value):
129+
try:
130+
recordId = value['recordId']
131+
except AssertionError as error:
132+
return None
133+
134+
# Validate the inputs
135+
try:
136+
assert ('data' in value), "'data' field is required."
137+
data = value['data']
138+
assert ('text1' in data), "'text1' field is required in 'data' object."
139+
assert ('text2' in data), "'text2' field is required in 'data' object."
140+
except AssertionError as error:
141+
return (
142+
{
143+
"recordId": recordId,
144+
"errors": [ { "message": "Error:" + error.args[0] } ]
145+
})
146+
147+
try:
148+
concatenated_string = value['data']['text1'] + " " + value['data']['text2']
149+
# Here you could do something more interesting with the inputs
150+
151+
except:
152+
return (
153+
{
154+
"recordId": recordId,
155+
"errors": [ { "message": "Could not complete operation for record." } ]
156+
})
157+
158+
return ({
159+
"recordId": recordId,
160+
"data": {
161+
"text": concatenated_string
162+
}
163+
})
164+
```
165+
166+
The **transform_value** method performs an operation on a single record. You may modify the method to meet your specific needs. Remember to do any necessary input validation and to return any errors and warnings produced if the operation could not be completed for the record.
167+
168+
### Debug your code locally
169+
170+
Visual Studio Code makes it easy to debug the code. Press 'F5' or go to the **Debug** menu and select **Start Debugging**.
171+
172+
You can set any breakpoints on the code by hitting 'F9' on the line of interest.
173+
174+
Once you started debugging, your function will run locally. You can use a tool like Postman or Fiddler to issue the request to localhost. Note the location of your local endpoint on the Terminal window.
175+
176+
## Publish your function
177+
178+
When you're satisfied with the function behavior, you can publish it.
179+
180+
1. In Visual Studio Code, press F1 to open the command palette. In the command palette, search for and select **Deploy to Function App...**.
181+
182+
1. Select the Azure Subscription where you would like to deploy your application.
183+
184+
1. Select **+ Create New Function App in Azure**
185+
186+
1. Enter a globally unique name for your function app.
187+
188+
1. Select Python version (Python 3.7.x works for this function).
189+
190+
1. Select a location for the new resource (for example, West US 2).
191+
192+
At this point, the necessary resources will be created in your Azure subscription to host the new Azure Function on Azure. Wait for the deployment to complete. The output window will show you the status of the deployment process.
193+
194+
1. In the [Azure portal](https://portal.azure.com), navigate to **All Resources** and look for the function you published by its name. If you named it **Concatenator**, select the resource.
195+
196+
1. Click the **</> Get Function URL** button. This will allow you to copy the URL to call the function.
197+
198+
## Test the function in Azure
199+
200+
Now that you have the default host key, test your function as follows:
201+
202+
```http
203+
POST [Function URL you copied above]
204+
```
205+
206+
### Request Body
207+
```json
208+
{
209+
"values": [
210+
{
211+
"recordId": "e1",
212+
"data":
213+
{
214+
"text1": "Hello",
215+
"text2": "World"
216+
}
217+
},
218+
{
219+
"recordId": "e2",
220+
"data": "This is an invalid input"
221+
}
222+
]
223+
}
224+
```
225+
226+
This example should produce the same result you saw previously when running the function in the local environment.
227+
228+
## Connect to your pipeline
229+
230+
Now that you have a new custom skill, you can add it to your skillset. The example below shows you how to call the skill to
231+
concatenate the Title and the Author of the document into a single field which we call merged_title_author. Replace `[your-function-url-here]` with the URL of your new Azure Function.
232+
233+
```json
234+
{
235+
"skills": [
236+
"[... your existing skills remain here]",
237+
{
238+
"@odata.type": "#Microsoft.Skills.Custom.WebApiSkill",
239+
"description": "Our new search custom skill",
240+
"uri": "https://[your-function-url-here]",
241+
"context": "/document/merged_content/organizations/*",
242+
"inputs": [
243+
{
244+
"name": "text1",
245+
"source": "/document/metadata_title"
246+
},
247+
{
248+
"name": "text2",
249+
"source": "/document/metadata_author"
250+
},
251+
],
252+
"outputs": [
253+
{
254+
"name": "text",
255+
"targetName": "merged_title_author"
256+
}
257+
]
258+
}
259+
]
260+
}
261+
```
262+
263+
## Next steps
264+
Congratulations! You've created your first custom skill. Now you can follow the same pattern to add your own custom functionality. Click the following links to learn more.
265+
266+
+ [Power Skills: a repository of custom skills](https://github.com/Azure-Samples/azure-search-power-skills)
267+
+ [Add a custom skill to an AI enrichment pipeline](cognitive-search-custom-skill-interface.md)
268+
+ [How to define a skillset](cognitive-search-defining-skillset.md)
269+
+ [Create Skillset (REST)](https://docs.microsoft.com/rest/api/searchservice/create-skillset)
270+
+ [How to map enriched fields](cognitive-search-output-field-mapping.md)

0 commit comments

Comments
 (0)