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 {
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
99/// Create a prompt and an initial message for a chatbot configuration
100pub 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}