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 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}