Skip to main content

headless_lms_server/controllers/
mock_azure.rs

1use headless_lms_chatbot::{
2    azure_chatbot::InputItem, cms_ai_suggestion::USER_PROMPT_PREFIX,
3    llm_utils::AzureCompletionRequest, message_suggestion::USER_PROMPT,
4};
5use regex::Regex;
6
7use crate::prelude::*;
8
9fn get_response(base_url: String) -> Result<String, ControllerError> {
10    let url1 = format!("{base_url}/api/v0/mock-document-storage/test/documents/document1");
11    let url2 = format!("{base_url}/api/v0/mock-document-storage/test/documents/document2");
12    let url3 = format!("{base_url}/api/v0/mock-document-storage/test/documents/document3");
13
14    let re = Regex::new(r"!URLS!")
15        .map_err(|e| controller_err!(InternalServerError, "Error in mock azure endpoint: ", e))?;
16    let res = re
17        .replace_all(
18            RESPONSE,
19            &format!("\\\"{url1}\\\",\\\"{url2}\\\",\\\"{url3}\\\""),
20        )
21        .to_string();
22
23    Ok(res)
24}
25
26const RESPONSE: &str = r#"
27event: response.created
28data: {"type": "response.created","response": {"id": "resp_0","object": "response","created_at": 1774260901,"status": "in_progress","background": false,"completed_at": null,"content_filters": null,"error": null,"frequency_penalty": 0.0,"incomplete_details": null,"instructions": null,"max_output_tokens": null,"max_tool_calls": null,"model": "mock-gpt","output": [],"parallel_tool_calls": true,"presence_penalty": 0.0,"previous_response_id": null,"prompt_cache_key": null,"prompt_cache_retention": null,"reasoning": {"effort": "medium","summary": null},"safety_identifier": null,"service_tier": "auto","store": true,"temperature": 1.0,"text": {"format": {"type": "text"},"verbosity": "medium"},"tool_choice": null,"tools": [{"type": "azure_ai_search","azure_ai_search": {"indexes": [{"project_connection_id": "connection-id","index_name": "mock-index","query_type": "semantic","top_k": 5}]}}],"top_logprobs": 0,"top_logprobs": 0,"top_p": 0.85,"truncation": "disabled","usage": null,"user": null,"metadata": {}},"sequence_number": 0}
29
30event: response.in_progress
31data: {"type": "response.in_progress","response": {"id": "resp_0","object": "response","created_at": 1774260901,"status": "in_progress","background": false,"completed_at": null,"content_filters": null,"error": null,"frequency_penalty": 0.0,"incomplete_details": null,"instructions": null,"max_output_tokens": null,"max_tool_calls": null,"model": "mock-gpt","output": [],"parallel_tool_calls": true,"presence_penalty": 0.0,"previous_response_id": null,"prompt_cache_key": null,"prompt_cache_retention": null,"reasoning": {"effort": "medium","summary": null},"safety_identifier": null,"service_tier": "auto","store": true,"temperature": 1.0,"text": {"format": {"type": "text"},"verbosity": "medium"},"tool_choice": null,"tools": [{"type": "azure_ai_search","azure_ai_search": {"indexes": [{"project_connection_id": "connection-id","index_name": "mock-index","query_type": "semantic","top_k": 5}]}}],"top_logprobs": 0,"top_logprobs": 0,"top_p": 0.85,"truncation": "disabled","usage": null,"user": null,"metadata": {}},"sequence_number": 1}
32
33event: response.output_item.added
34data: {"type": "response.output_item.added","item": {"type": "reasoning","id": "rs_0","response_id": "resp_0","summary": []},"output_index": 0,"sequence_number": 2}
35
36event: response.output_item.done
37data: {"type": "response.output_item.done","item": {"type": "reasoning","id": "rs_0","response_id": "resp_0","summary": []},"output_index": 0,"sequence_number": 3}
38
39event: response.output_item.added
40data: {"type": "response.output_item.added","item": {"type": "azure_ai_search_call","id": "fc_0","response_id": "resp_0","call_id": "call_0","arguments": "","status": "in_progress"},"output_index": 1,"sequence_number": 4}
41
42event: response.output_item.done
43data: {"type": "response.output_item.done","item": {"type": "azure_ai_search_call","id": "fc_0","response_id": "resp_0","call_id": "call_0","arguments": "{\"query\":\"tell me more\"}","status": "completed"},"output_index": 1,"sequence_number": 5}
44
45event: response.output_item.added
46data: {"type": "response.output_item.added","item": {"type": "azure_ai_search_call_output","id": "fco_0","response_id": "resp_0","call_id": "call_0","output": "[]","status": "in_progress"},"output_index": 2,"sequence_number": 6}
47
48event: response.output_item.done
49data: {"type": "response.output_item.done","item": {"type": "azure_ai_search_call_output","id": "fco_0","response_id": "resp_0","call_id": "call_0","output": "{\"documents\": [{\"id\": \"doc1\", \"content\": \"This chunk is a snippet from page {} of the course {}. Mock test page content This is test content blah\", \"filepath\": \"document1\", \"title\": \"Cited course page\", \"url\": \"\",\"score\": 0.016666668, \"knowledgeSourceIndex\": 0},{\"id\": \"doc2\",\"content\": \"Mock test page content 2 This is another test page.\",\"filepath\": \"document2\",\"title\": \"Cited course page 2\",\"url\": \"\",\"score\": 0.016666668,\"knowledgeSourceIndex\": 0},{\"id\": \"doc3\",\"content\": \"More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long.\",\"filepath\": \"document1\",\"title\": \"Cited course page\",\"url\": \"\",\"score\": 0.016666668,\"knowledgeSourceIndex\": 0}],\"get_urls\": [!URLS!]}","status": "completed"},"output_index": 2,"sequence_number": 7}
50
51event: response.output_item.added
52data: {"type": "response.output_item.added","item": {"type": "message","id": "msg_0","response_id": "resp_0","role": "assistant","content": [],"status": "in_progress"},"output_index": 3,"sequence_number": 8}
53
54event: response.content_part.added
55data: {"type": "response.content_part.added","content_index": 0,"item_id": "msg_0","output_index": 3,"part": {"type": "output_text","annotations": [],"logprobs": [],"text": ""},"sequence_number": 9}
56
57event: response.output_text.delta
58data: {"type": "response.output_text.delta","content_index": 0,"delta": "Hello","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 10}
59
60event: response.output_text.delta
61data: {"type": "response.output_text.delta","content_index": 0,"delta": "!","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 11}
62
63event: response.output_text.delta
64data: {"type": "response.output_text.delta","content_index": 0,"delta": " How","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 12}
65
66event: response.output_text.delta
67data: {"type": "response.output_text.delta","content_index": 0,"delta": " can","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 13}
68
69event: response.output_text.delta
70data: {"type": "response.output_text.delta","content_index": 0,"delta": " I","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 14}
71
72event: response.output_text.delta
73data: {"type": "response.output_text.delta","content_index": 0,"delta": " assist","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 15}
74
75event: response.output_text.delta
76data: {"type": "response.output_text.delta","content_index": 0,"delta": " 【0:3†source】","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 16}
77
78event: response.output_text.delta
79data: {"type": "response.output_text.delta","content_index": 0,"delta": " you","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 17}
80
81event: response.output_text.delta
82data: {"type": "response.output_text.delta","content_index": 0,"delta": " 【0:2†source】","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 18}
83
84event: response.output_text.delta
85data: {"type": "response.output_text.delta","content_index": 0,"delta": " today","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 19}
86
87event: response.output_text.delta
88data: {"type": "response.output_text.delta","content_index": 0,"delta": "?","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 20}
89
90event: response.output_text.delta
91data: {"type": "response.output_text.delta","content_index": 0,"delta": "【0:3†source】","item_id": "msg_0","logprobs": [],"obfuscation": "","output_index": 3,"sequence_number": 21}
92
93event: response.output_text.done
94data: {"type": "response.output_text.done","content_index": 0,"item_id": "msg_0","logprobs": [],"output_index": 3,"sequence_number": 22,"text": "Hello! How can I assist 【0:3†source】 you 【0:2†source】 today? 【0:3†source】"}
95
96event: response.content_part.done
97data: {"type": "response.content_part.done","content_index": 0,"item_id": "msg_0","output_index": 3,"part": {"type": "output_text","annotations": [],"logprobs": [],"text": "Hello! How can I assist 【0:3†source】 you 【0:2†source】 today? 【0:3†source】"},"sequence_number": 23}
98
99event: response.output_item.done
100data: {"type": "response.output_item.done","item": {"type": "message","id": "msg_0","response_id": "resp_0","role": "assistant","content": [{"type": "output_text","text": "Hello! How can I assist 【0:3†source】 you 【0:2†source】 today? 【0:3†source】","annotations": [],"logprobs": []}],"status": "completed"},"output_index": 3,"sequence_number": 24}
101
102event: response.completed
103data: {"type": "response.completed","response": {"id": "resp_0","object": "response","created_at": 1774422684,"status": "completed","background": false,"completed_at": 1774422685,"content_filters": [{"blocked": false,"source_type": "prompt","content_filter_raw": [],"content_filter_results": {"jailbreak": {"filtered": false,"detected": false},"self_harm": {"filtered": false,"severity": "safe"},"hate": {"filtered": false,"severity": "safe"},"violence": {"filtered": false,"severity": "safe"},"sexual": {"filtered": false,"severity": "safe"}},"content_filter_offsets": {"start_offset": 918,"end_offset": 930,"check_offset": 0}}],"error": null,"frequency_penalty": 0.0,"incomplete_details": null,"instructions": null,"max_output_tokens": null,"max_tool_calls": null,"model": "gpt-5.4-nano","output": [{"type": "reasoning","id": "rs_0","response_id": "resp_0","summary": []},{"type": "azure_ai_search_call","id": "fc_0","response_id": "resp_0","call_id": "call_0","arguments": "{\"query\":\"tell me more\"}","status": "completed"},{"type": "azure_ai_search_call_output","id": "fco_0","response_id": "resp_0","call_id": "call_0","output": "{\"documents\":[{\"id\": \"doc1\",\"content\": \"This chunk is a snippet from page {} of the course {}. ,|||,Mock test page content\n This is test content blah\",\"filepath\": \"document1\",\"title\": \"Cited course page\",\"url\": \"\",\"score\": 0.016666668,\"knowledgeSourceIndex\": 0},{\"id\": \"doc2\",\"content\": \"Mock test page content 2\n This is another test page.\",\"filepath\": \"document2\",\"title\": \"Cited course page 2\",\"url\": \"\",\"score\": 0.016666668,\"knowledgeSourceIndex\": 0},{\"id\": \"doc3\",\"content\": \"More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long. More content on the same mock course page. Another snippet. Long.\",\"filepath\": \"document1\",\"title\": \"Cited course page\",\"url\": \"\",\"score\": 0.016666668,\"knowledgeSourceIndex\": 0},],\"get_urls\":[!URLS!]}","status": "completed"},{"type": "message","id": "msg_0","response_id": "resp_0","phase": "final_answer","role": "assistant","content": [{"type": "output_text","text": "Hello! How can I assist 【0:3†source】 you 【0:2†source】 today? 【0:3†source】","annotations": [],"logprobs": []}],"status": "completed"}],"parallel_tool_calls": true,"presence_penalty": 0.0,"previous_response_id": null,"prompt_cache_key": null,"prompt_cache_retention": null,"reasoning": {"effort": "high","summary": null},"safety_identifier": null,"service_tier": "auto","store": true,"temperature": 1.0,"text": {"format": {"type": "text"},"verbosity": "medium"},"tool_choice": "required","tools": [{"type": "azure_ai_search","azure_ai_search": {"indexes": [{"project_connection_id": "connection-id","index_name": "mock-index","query_type": "semantic","top_k": 5}]}}],"top_logprobs": 0,"top_logprobs": 0,"top_p": 0.85,"truncation": "disabled","usage": {"input_tokens": 38,"input_tokens_details": {"cached_tokens": 0},"output_tokens": 79,"output_tokens_details": {"reasoning_tokens": 64},"total_tokens": 117},"user": null,"metadata": {}},"sequence_number": 25}
104"#;
105
106const SUGGESTION: &str = r#"{"metadata": {},"top_logprobs": 0,"temperature": 1,"top_p": 0.98,"service_tier": "default","model": "mock-gpt","reasoning": {"effort": "medium","summary": "detailed"},"background": false,"text": {"format": {"type": "text"},"verbosity": "medium"},"tools": [],"tool_choice": "auto","truncation": "disabled","id": "resp_0","object": "response","status": "completed","created_at": 1776144780,"completed_at": 1776144781,"error": null,"incomplete_details": null,"output": [{"type": "message","id": "msg_0","response_id": "resp_0","phase": "final_answer","role": "assistant","content": [{"type": "output_text","text": "{\"suggestions\":[\"Can you pls help me?\",\"Nice weather we're having.\",\"Hello?\"]}","annotations": [],"logprobs": []}],"status": "completed"}],"instructions": null,"usage": {"input_tokens": 30,"input_tokens_details": {"cached_tokens": 0},"output_tokens": 15,"output_tokens_details": {"reasoning_tokens": 0},"total_tokens": 45},"parallel_tool_calls": true,"agent_reference": null}
107"#;
108
109const CMS_SUGGESTION: &str = r#"{"metadata": {},"top_logprobs": 0,"temperature": 1,"top_p": 0.98,"service_tier": "default","model": "mock-gpt","reasoning": {"effort": "medium","summary": "detailed"},"background": false,"text": {"format": {"type": "text"},"verbosity": "medium"},"tools": [],"tool_choice": "auto","truncation": "disabled","id": "resp_0","object": "response","status": "completed","created_at": 1776144780,"completed_at": 1776144781,"error": null,"incomplete_details": null,"output": [{"type": "message","id": "msg_0","response_id": "resp_0","phase": "final_answer","role": "assistant","content": [{"type": "output_text","text": "{\"suggestions\":[\"Mock suggestion 1: The paragraph has been improved.\",\"Mock suggestion 2: Here is an alternative version of the paragraph.\",\"Mock suggestion 3: A third distinct rewrite of the paragraph.\"]}","annotations": [],"logprobs": []}],"status": "completed"}],"instructions": null,"usage": {"input_tokens": 30,"input_tokens_details": {"cached_tokens": 0},"output_tokens": 15,"output_tokens_details": {"reasoning_tokens": 0},"total_tokens": 45},"parallel_tool_calls": true,"agent_reference": null}"#;
110
111// GET /api/v0/mock_azure/test/v1/responses
112// POST /api/v0/mock_azure/test/v1/responses
113async fn mock_azure_chat_responses(
114    app_conf: web::Data<ApplicationConfiguration>,
115    payload: web::Json<AzureCompletionRequest>,
116) -> ControllerResult<String> {
117    assert!(app_conf.test_chatbot && app_conf.test_mode);
118    let message_suggestion_user_prompt = USER_PROMPT;
119
120    let message_kind = &payload
121        .base
122        .input
123        .last()
124        .ok_or(ControllerError::new(
125            ControllerErrorType::BadRequest,
126            "No messages in request, there should be at least one.",
127            None,
128        ))?
129        .message_type;
130    let message = match message_kind {
131        InputItem::Message {
132            role: _role,
133            content,
134        } => Ok(content.to_owned()),
135        _ => Err(ControllerError::new(
136            ControllerErrorType::BadRequest,
137            "The request had a tool call or tool response. This shouldn't happen when using message suggestion LLM.",
138            None,
139        )),
140    }?.get_content_text();
141
142    let suggest_prompt_match = message
143        .matches(message_suggestion_user_prompt)
144        .collect::<Vec<&str>>();
145    let cms_suggest_match = message.contains(USER_PROMPT_PREFIX);
146    let res = if !suggest_prompt_match.is_empty() {
147        SUGGESTION.to_string()
148    } else if cms_suggest_match {
149        CMS_SUGGESTION.to_string()
150    } else {
151        get_response(app_conf.base_url.clone())?
152    };
153    let token = skip_authorize();
154    token.authorized_ok(res)
155}
156
157pub fn _add_routes(cfg: &mut ServiceConfig) {
158    cfg.route(
159        "/test/v1/responses",
160        web::get().to(mock_azure_chat_responses),
161    )
162    .route(
163        "/test/v1/responses",
164        web::post().to(mock_azure_chat_responses),
165    );
166}