1use headless_lms_base::config::ApplicationConfiguration;
2use headless_lms_base::prelude_base_and_re_exports::BackendError;
3use headless_lms_models::{
4 application_task_default_language_models::TaskLMSpec,
5 chatbot_conversation_message_messages::MessageRole,
6};
7use headless_lms_utils::json_schema_types::{
8 ArrayItem, ArrayProperty, JSONType, JsonItem, Schema, SchemaPropertyType,
9};
10use indexmap::IndexMap;
11use tracing::debug;
12use utoipa::ToSchema;
13
14use crate::{
15 azure_chatbot::azure::protocol::{
16 InputItem, LLMRequestParams, LLMRequestResponseFormatParam, NonThinkingParams,
17 ThinkingParams,
18 },
19 llm_utils::{APIInputMessage, MessageContent, model_is_thinking, request_structured_json},
20 prelude::{ChatbotError, ChatbotErrorType, ChatbotResult, chatbot_err},
21};
22
23#[derive(serde::Serialize, serde::Deserialize, ToSchema, Debug)]
24pub struct PromptCreationResponse {
25 pub prompt: String,
26 pub first_message: String,
27 pub suggested_messages: Vec<String>,
28}
29
30pub const RESPONSE_FORMAT_NAME: &str = "PromptCreationResponse";
31
32fn response_format() -> LLMRequestResponseFormatParam {
35 LLMRequestResponseFormatParam::JsonSchema {
36 name: RESPONSE_FORMAT_NAME.to_string(),
37 schema: Schema::strict_object(
38 IndexMap::from([
39 (
40 "prompt".to_string(),
41 SchemaPropertyType::Item(JsonItem {
42 type_field: JSONType::String,
43 description: None,
44 }),
45 ),
46 (
47 "first_message".to_string(),
48 SchemaPropertyType::Item(JsonItem {
49 type_field: JSONType::String,
50 description: None,
51 }),
52 ),
53 (
54 "suggested_messages".to_string(),
55 SchemaPropertyType::ArrayProperty(ArrayProperty {
56 type_field: JSONType::Array,
57 items: ArrayItem::JsonItem(JsonItem {
58 type_field: JSONType::String,
59 description: None,
60 }),
61 description: None,
62 }),
63 ),
64 ]),
65 None,
66 ),
67 strict: true,
68 }
69}
70
71fn prompt_if_course(course_name: Option<String>, course_desc: Option<String>) -> String {
72 let Some(c_n) = course_name else {
73 return "".to_string();
74 };
75 let mut course_info = format!("\n\nThe chatbot appears on a course called {}.", c_n);
76 if let Some(d) = course_desc {
77 course_info += &format!("The course has the following description: {d}");
78 }
79 course_info += "\n\n Constraints:\n\n- Don't assume information about the course, refer to the description if it's provided\n";
80
81 course_info
82}
83
84const SYSTEM_PROMPT_1: &str = r#"
85You are an expert prompt engineer. Generate a high-quality system prompt, a first message, and suggested messages for an LLM-based chatbot. The system prompt should be clear and informative. The first message is a message this chatbot sends to the user at the start of a conversation and should be designed to engage the user and help them understand how the chatbot can be useful. The first message should be short and concise. Avoid overwhelming the user with information. The suggested messages are example messages that the user could send after reading the first message sent by the chatbot. They should help orient the user towards learning and suggest how the user can use and benefit from the chatbot.
86
87Constraints:
88- Create exactly 5 suggested example user messages.
89- Create brief, concise and clear messages. Use as few words and sentences as possible.
90- Maintain a supportive, respectful, and clear tone in the messages.
91- Create an informative and professional prompt.
92- Do not assume specifics about the chatbot's intended purpose. Refer to the provided description of the chatbot.
93
94The chatbot that this prompt will be used on has the following description, including its specified purpose and task:
95
96"#;
97
98pub async fn generate_prompt(
100 app_config: &ApplicationConfiguration,
101 task_lm: TaskLMSpec,
102 course_name: Option<String>,
103 course_desc: Option<String>,
104 chatbot_purpose: &str,
105) -> ChatbotResult<PromptCreationResponse> {
106 let prompt =
107 SYSTEM_PROMPT_1.to_string() + chatbot_purpose + &prompt_if_course(course_name, course_desc);
108 debug!("{}", &prompt);
109 let input = vec![APIInputMessage {
110 message_type: InputItem::Message {
111 role: MessageRole::System,
112 content: MessageContent::Text(prompt),
113 },
114 }];
115 let (params, max_output_tokens) = if model_is_thinking(task_lm.model_type) {
116 (
117 LLMRequestParams::GPTThinking(ThinkingParams { reasoning: None }),
118 Some(7000),
119 )
120 } else {
121 (
122 LLMRequestParams::GPTNonThinking(NonThinkingParams {
123 temperature: None,
124 top_p: None,
125 frequency_penalty: None,
126 presence_penalty: None,
127 }),
128 Some(4000),
129 )
130 };
131
132 let res: PromptCreationResponse = request_structured_json(
133 input,
134 task_lm.model.to_owned(),
135 params,
136 max_output_tokens,
137 response_format(),
138 app_config,
139 || {
140 chatbot_err!(
141 FailedAzureResponse,
142 "Invalidly structured response from Azure"
143 )
144 },
145 )
146 .await?;
147
148 Ok(res)
149}