Skip to main content

headless_lms_chatbot/
azure_skillset.rs

1use secrecy::ExposeSecret;
2use serde_json::json;
3
4use crate::prelude::*;
5
6const API_VERSION: &str = "2024-07-01";
7
8pub async fn does_skillset_exist(
9    skillset_name: &str,
10    app_config: &ApplicationConfiguration,
11) -> ChatbotResult<bool> {
12    // Retrieve Azure configurations from the application configuration
13    let azure_config = app_config.azure_configuration.as_ref().ok_or_else(|| {
14        chatbot_err!(
15            AzureRequestBuildError,
16            "Azure configuration is missing from the application configuration"
17        )
18    })?;
19
20    let search_config = azure_config.search_config.as_ref().ok_or_else(|| {
21        chatbot_err!(
22            AzureRequestBuildError,
23            "Azure search configuration is missing from the Azure configuration"
24        )
25    })?;
26
27    let mut url = search_config.search_endpoint.clone();
28    url.set_path(&format!("skillsets('{}')", skillset_name));
29    url.set_query(Some(&format!("api-version={}", API_VERSION)));
30
31    let response = REQWEST_CLIENT
32        .get(url)
33        .header("Content-Type", "application/json")
34        .header("api-key", search_config.search_api_key.expose_secret())
35        .send()
36        .await?;
37
38    if response.status().is_success() {
39        Ok(true)
40    } else if response.status() == 404 {
41        Ok(false)
42    } else {
43        let status = response.status();
44        let error_text = response.text().await?;
45        Err(chatbot_err!(
46            FailedAzureResponse,
47            format!(
48                "Error checking if skillset exists. Status: {}. Error: {}",
49                status, error_text
50            )
51        ))
52    }
53}
54
55pub async fn create_skillset(
56    skillset_name: &str,
57    target_index_name: &str,
58    app_config: &ApplicationConfiguration,
59) -> ChatbotResult<()> {
60    let azure_config = app_config.azure_configuration.as_ref().ok_or_else(|| {
61        chatbot_err!(
62            AzureRequestBuildError,
63            "Azure configuration is missing from the application configuration"
64        )
65    })?;
66
67    let search_config = azure_config.search_config.as_ref().ok_or_else(|| {
68        chatbot_err!(
69            AzureRequestBuildError,
70            "Azure search configuration is missing from the Azure configuration"
71        )
72    })?;
73
74    let mut url = search_config.search_endpoint.clone();
75    url.set_path(&format!("skillsets/{}", skillset_name));
76    url.set_query(Some(&format!("api-version={}", API_VERSION)));
77
78    let skillset_definition = json!({
79        "name": skillset_name,
80        "description": "Skillset to chunk documents and generate embeddings",
81        "skills": [
82            {
83                "@odata.type": "#Microsoft.Skills.Text.SplitSkill",
84                "name": "#1",
85                "description": "Split skill to chunk documents",
86                "context": "/document",
87                "defaultLanguageCode": "en",
88                "textSplitMode": "pages",
89                "maximumPageLength": 2000,
90                "pageOverlapLength": 500,
91                "maximumPagesToTake": 0,
92                "inputs": [
93                    {
94                        "name": "text",
95                        "source": "/document/content"
96                    },
97                    {
98                        "name": "languageCode",
99                        "source": "/document/language"
100                    }
101                ],
102                "outputs": [
103                    {
104                        "name": "textItems",
105                        "targetName": "pages"
106                    }
107                ]
108            },
109            {
110                "@odata.type": "#Microsoft.Skills.Text.AzureOpenAIEmbeddingSkill",
111                "name": "#2",
112                "description": null,
113                "context": "/document/pages/*",
114                "resourceUri": search_config.vectorizer_resource_uri.clone(),
115                "apiKey": search_config.vectorizer_api_key.expose_secret(),
116                "deploymentId": search_config.vectorizer_deployment_id.clone(),
117                "dimensions": 1536,
118                "modelName": search_config.vectorizer_model_name.clone(),
119                "inputs": [
120                    {
121                        "name": "text",
122                        "source": "/document/pages/*",
123                        "sourceContext": null,
124                        "inputs": []
125                    }
126                ],
127                "outputs": [
128                    {
129                        "name": "embedding",
130                        "targetName": "text_vector"
131                    }
132                ],
133                "authIdentity": null
134            }
135        ],
136        "cognitiveServices": null,
137        "knowledgeStore": null,
138        "indexProjections": {
139            "selectors": [
140                {
141                    "targetIndexName": target_index_name,
142                    "parentKeyFieldName": "parent_id",
143                    "sourceContext": "/document/pages/*",
144                    "mappings": [
145                        {
146                            "name": "text_vector",
147                            "source": "/document/pages/*/text_vector",
148                            "sourceContext": null,
149                            "inputs": []
150                        },
151                        {
152                            "name": "chunk",
153                            "source": "/document/pages/*",
154                            "sourceContext": null,
155                            "inputs": []
156                        },
157                        {
158                            "name": "title",
159                            "source": "/document/title",
160                            "sourceContext": null,
161                            "inputs": []
162                        },
163                        {
164                          "name": "url",
165                          "source": "/document/url",
166                          "sourceContext": null,
167                          "inputs": []
168                        },
169                        {
170                          "name": "course_id",
171                          "source": "/document/course_id",
172                          "sourceContext": null,
173                          "inputs": []
174                        },
175                        {
176                          "name": "language",
177                          "source": "/document/language",
178                          "sourceContext": null,
179                          "inputs": []
180                        },
181                        {
182                          "name": "filepath",
183                          "source": "/document/filepath",
184                          "sourceContext": null,
185                          "inputs": []
186                        },
187                        {
188                            "name": "chunk_context",
189                            "source": "/document/chunk_context",
190                            "sourceContext": null,
191                            "inputs": []
192                        },
193                    ]
194                }
195            ],
196            "parameters": {
197                "projectionMode": "skipIndexingParentDocuments"
198            }
199        },
200        "encryptionKey": null
201    });
202
203    let response = REQWEST_CLIENT
204        .put(url)
205        .header("Content-Type", "application/json")
206        .header("api-key", search_config.search_api_key.expose_secret())
207        .json(&skillset_definition)
208        .send()
209        .await?;
210
211    if response.status().is_success() {
212        Ok(())
213    } else {
214        let status = response.status();
215        let error_text = response.text().await?;
216        Err(chatbot_err!(
217            FailedAzureResponse,
218            format!(
219                "Error creating skillset. Status: {}. Error: {}",
220                status, error_text
221            )
222        ))
223    }
224}