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 {
36 format_type: JSONType::JsonSchema,
37 name: "PromptCreationResponse".to_string(),
38 schema: Schema::strict_object(
39 IndexMap::from([
40 (
41 "prompt".to_string(),
42 SchemaPropertyType::Item(JsonItem {
43 type_field: JSONType::String,
44 description: None,
45 }),
46 ),
47 (
48 "first_message".to_string(),
49 SchemaPropertyType::Item(JsonItem {
50 type_field: JSONType::String,
51 description: None,
52 }),
53 ),
54 (
55 "suggested_messages".to_string(),
56 SchemaPropertyType::ArrayProperty(ArrayProperty {
57 type_field: JSONType::Array,
58 items: ArrayItem::JsonItem(JsonItem {
59 type_field: JSONType::String,
60 description: None,
61 }),
62 description: None,
63 }),
64 ),
65 ]),
66 None,
67 ),
68 strict: true,
69 }
70}
71
72fn prompt_if_course(course_name: Option<String>, course_desc: Option<String>) -> String {
73 let Some(c_n) = course_name else {
74 return "".to_string();
75 };
76 let mut course_info = format!("\n\nThe chatbot appears on a course called {}.", c_n);
77 if let Some(d) = course_desc {
78 course_info += &format!("The course has the following description: {d}");
79 }
80 course_info += "\n\n Constraints:\n\n- Don't assume information about the course, refer to the description if it's provided\n";
81
82 course_info
83}
84
85const SYSTEM_PROMPT_1: &str = r#"
86You 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.
87
88Constraints:
89- Create exactly 5 suggested example user messages.
90- Create brief, concise and clear messages. Use as few words and sentences as possible.
91- Maintain a supportive, respectful, and clear tone in the messages.
92- Create an informative and professional prompt.
93- Do not assume specifics about the chatbot's intended purpose. Refer to the provided description of the chatbot.
94
95The chatbot that this prompt will be used on has the following description, including its specified purpose and task:
96
97"#;
98
99pub async fn generate_prompt(
101 app_config: &ApplicationConfiguration,
102 task_lm: TaskLMSpec,
103 course_name: Option<String>,
104 course_desc: Option<String>,
105 chatbot_purpose: &str,
106) -> ChatbotResult<PromptCreationResponse> {
107 let prompt =
108 SYSTEM_PROMPT_1.to_string() + chatbot_purpose + &prompt_if_course(course_name, course_desc);
109 debug!("{}", &prompt);
110 let input = vec![APIInputMessage {
111 message_type: InputItem::Message {
112 role: MessageRole::System,
113 content: MessageContent::Text(prompt),
114 },
115 }];
116 let (params, max_output_tokens) = if model_is_thinking(task_lm.model_type) {
117 (
118 LLMRequestParams::GPTThinking(ThinkingParams { reasoning: None }),
119 Some(7000),
120 )
121 } else {
122 (
123 LLMRequestParams::GPTNonThinking(NonThinkingParams {
124 temperature: None,
125 top_p: None,
126 frequency_penalty: None,
127 presence_penalty: None,
128 }),
129 Some(4000),
130 )
131 };
132
133 let res: PromptCreationResponse = request_structured_json(
134 input,
135 task_lm.model.to_owned(),
136 params,
137 max_output_tokens,
138 response_format(),
139 app_config,
140 || {
141 chatbot_err!(
142 FailedAzureResponse,
143 "Invalidly structured response from Azure"
144 )
145 },
146 )
147 .await?;
148
149 Ok(res)
150}