Skip to main content

headless_lms_chatbot/
prompt_creation.rs

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
32/// The structured output format the description LLM is asked to answer in. Must stay in
33/// sync with [PromptCreationResponse].
34fn 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
98/// Create a prompt and an initial message for a chatbot configuration
99pub 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}