Skip to main content

headless_lms_chatbot/
azure_skillset.rs

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