diff --git a/docs/configuration/text-generation.md b/docs/configuration/text-generation.md index 16689cf..d85584b 100644 --- a/docs/configuration/text-generation.md +++ b/docs/configuration/text-generation.md @@ -77,11 +77,14 @@ Prompts may contain the following **placeholder variables** which will be replac |---------------------------|-------------|---------| | `{{ baibot_name }}` | Name of the bot as configured in the `user.name` field in the [Static configuration](./README.md#static-configuration) | `Baibot` | | `{{ baibot_model_id }}` | Text-Generation model ID as configured in the [🤖 agent](../agents.md)'s configuration | `gpt-4o` | -| `{{ baibot_now_utc }}` | Current date and time in UTC | `2024-09-20 (Friday), 14:26:42 UTC` | +| `{{ baibot_now_utc }}` | Current date and time in UTC (⚠️ usage may break prompt caching - see below) | `2024-09-20 (Friday), 14:26:42 UTC` | +| `{{ baibot_conversation_start_time_utc }}` | The date and time in UTC that the conversation started | `2024-09-20 (Friday), 14:26:42 UTC` | + +💡 `{{ baibot_now_utc }}` changes as time goes on, which prevents [prompt caching](https://platform.openai.com/docs/guides/prompt-caching) from working. It's better to use `{{ baibot_conversation_start_time_utc }}` in prompts, as its value doesn't change yet still orients the bot to the current date/time. Here's a prompt that combines some of the above variables: -> You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." +> You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." ### 🌡️ Temperature Override diff --git a/docs/sample-provider-configs/anthropic.yml b/docs/sample-provider-configs/anthropic.yml index 3f4a28b..9918f6b 100644 --- a/docs/sample-provider-configs/anthropic.yml +++ b/docs/sample-provider-configs/anthropic.yml @@ -2,7 +2,7 @@ base_url: https://api.anthropic.com/v1 api_key: YOUR_API_KEY_HERE text_generation: model_id: claude-3-5-sonnet-20240620 - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 8192 max_context_tokens: 204800 diff --git a/docs/sample-provider-configs/groq.yml b/docs/sample-provider-configs/groq.yml index 5684a02..6f55a3c 100644 --- a/docs/sample-provider-configs/groq.yml +++ b/docs/sample-provider-configs/groq.yml @@ -2,7 +2,7 @@ base_url: https://api.groq.com/openai/v1 api_key: YOUR_API_KEY_HERE text_generation: model_id: llama3-70b-8192 - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 4096 max_context_tokens: 131072 diff --git a/docs/sample-provider-configs/localai.yml b/docs/sample-provider-configs/localai.yml index 92dd85a..8be57b9 100644 --- a/docs/sample-provider-configs/localai.yml +++ b/docs/sample-provider-configs/localai.yml @@ -2,7 +2,7 @@ base_url: http://my-localai-self-hosted-service:8080/v1 api_key: YOUR_API_KEY_HERE text_generation: model_id: gpt-4 - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 4096 max_context_tokens: 128000 diff --git a/docs/sample-provider-configs/mistral.yml b/docs/sample-provider-configs/mistral.yml index 4bfd155..32324db 100644 --- a/docs/sample-provider-configs/mistral.yml +++ b/docs/sample-provider-configs/mistral.yml @@ -2,7 +2,7 @@ base_url: https://api.mistral.ai/v1 api_key: YOUR_API_KEY_HERE text_generation: model_id: mistral-large-latest - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 4096 max_context_tokens: 128000 diff --git a/docs/sample-provider-configs/ollama.yml b/docs/sample-provider-configs/ollama.yml index 2a7852e..7c6a1be 100644 --- a/docs/sample-provider-configs/ollama.yml +++ b/docs/sample-provider-configs/ollama.yml @@ -2,7 +2,7 @@ base_url: http://my-ollama-self-hosted-service:11434/v1 api_key: YOUR_API_KEY_HERE text_generation: model_id: gemma2:2b - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 4096 max_context_tokens: 128000 diff --git a/docs/sample-provider-configs/openai-compatible.yml b/docs/sample-provider-configs/openai-compatible.yml index 1ce12f1..c8be96e 100644 --- a/docs/sample-provider-configs/openai-compatible.yml +++ b/docs/sample-provider-configs/openai-compatible.yml @@ -2,7 +2,7 @@ base_url: '' api_key: YOUR_API_KEY_HERE text_generation: model_id: some-model - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 4096 max_context_tokens: 128000 diff --git a/docs/sample-provider-configs/openai.yml b/docs/sample-provider-configs/openai.yml index 2c5db6c..ffe9be3 100644 --- a/docs/sample-provider-configs/openai.yml +++ b/docs/sample-provider-configs/openai.yml @@ -2,7 +2,7 @@ base_url: https://api.openai.com/v1 api_key: YOUR_API_KEY_HERE text_generation: model_id: gpt-4o - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 16384 max_context_tokens: 128000 diff --git a/docs/sample-provider-configs/openrouter.yml b/docs/sample-provider-configs/openrouter.yml index eabf664..9fee522 100644 --- a/docs/sample-provider-configs/openrouter.yml +++ b/docs/sample-provider-configs/openrouter.yml @@ -2,7 +2,7 @@ base_url: https://openrouter.ai/api/v1 api_key: YOUR_API_KEY_HERE text_generation: model_id: mattshumer/reflection-70b:free - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 2048 max_context_tokens: 8192 diff --git a/docs/sample-provider-configs/together-ai.yml b/docs/sample-provider-configs/together-ai.yml index 836b937..28825a1 100644 --- a/docs/sample-provider-configs/together-ai.yml +++ b/docs/sample-provider-configs/together-ai.yml @@ -2,7 +2,7 @@ base_url: https://api.together.xyz/v1 api_key: YOUR_API_KEY_HERE text_generation: model_id: meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo - prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." temperature: 1.0 max_response_tokens: 2048 max_context_tokens: 8192 diff --git a/etc/app/config.yml.dist b/etc/app/config.yml.dist index b661937..e5ec30c 100644 --- a/etc/app/config.yml.dist +++ b/etc/app/config.yml.dist @@ -73,7 +73,7 @@ agents: # api_key: "" # text_generation: # model_id: gpt-4o - # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." # temperature: 1.0 # max_response_tokens: 16384 # max_context_tokens: 128000 @@ -97,7 +97,7 @@ agents: # api_key: null # text_generation: # model_id: gpt-4 - # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." # temperature: 1.0 # max_response_tokens: 16384 # max_context_tokens: 128000 @@ -122,7 +122,7 @@ agents: # api_key: null # text_generation: # model_id: "gemma2:2b" - # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + # prompt: "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." # temperature: 1.0 # max_response_tokens: 4096 # max_context_tokens: 128000 diff --git a/src/agent/mod.rs b/src/agent/mod.rs index 81644c4..64381d0 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -21,5 +21,5 @@ pub use provider::{AgentProvider, AgentProviderInfo, ControllerTrait}; pub use purpose::AgentPurpose; pub(super) fn default_prompt() -> &'static str { - "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time now is: {{ baibot_now_utc }}." + "You are a brief, but helpful bot called {{ baibot_name }} powered by the {{ baibot_model_id }} model. The date/time of this conversation's start is: {{ baibot_conversation_start_time_utc }}." } diff --git a/src/agent/provider/anthropic/controller.rs b/src/agent/provider/anthropic/controller.rs index 4d43980..93d0a55 100644 --- a/src/agent/provider/anthropic/controller.rs +++ b/src/agent/provider/anthropic/controller.rs @@ -72,6 +72,7 @@ impl ControllerTrait for Controller { let messages = vec![LLMMessage { author: LLMAuthor::User, message_text: "Hello!".to_string(), + timestamp: chrono::Utc::now(), }]; let conversation = LLMConversation { messages }; @@ -108,6 +109,7 @@ impl ControllerTrait for Controller { Some(LLMMessage { author: LLMAuthor::Prompt, message_text: prompt_text, + timestamp: chrono::Utc::now(), }) }; diff --git a/src/agent/provider/entity/text_generation/prompt_variables.rs b/src/agent/provider/entity/text_generation/prompt_variables.rs index 05989b1..d25b557 100644 --- a/src/agent/provider/entity/text_generation/prompt_variables.rs +++ b/src/agent/provider/entity/text_generation/prompt_variables.rs @@ -7,17 +7,33 @@ pub struct TextGenerationPromptVariables { impl Default for TextGenerationPromptVariables { fn default() -> Self { - Self::new("unnamed", "unknown-model", Utc::now()) + let now = Utc::now(); + Self::new("unnamed", "unknown-model", now, Some(now)) } } impl TextGenerationPromptVariables { - pub fn new(bot_name: &str, model_id: &str, utc_time: DateTime) -> Self { + pub fn new( + bot_name: &str, + model_id: &str, + now_time: DateTime, + conversation_start_time: Option>, + ) -> Self { let mut map = HashMap::new(); map.insert("baibot_name".to_string(), bot_name.to_string()); map.insert("baibot_model_id".to_string(), model_id.to_string()); - map.insert("baibot_now_utc".to_string(), format_utc_time(utc_time)); + map.insert("baibot_now_utc".to_string(), format_utc_time(now_time)); + + let baibot_conversation_start_time_utc = match conversation_start_time { + Some(conversation_start_time) => format_utc_time(conversation_start_time), + None => "unknown".to_string(), + }; + + map.insert( + "baibot_conversation_start_time_utc".to_string(), + baibot_conversation_start_time_utc, + ); Self { map } } @@ -52,7 +68,18 @@ mod tests { .with_nanosecond(250000000) .unwrap(); - let variables = TextGenerationPromptVariables::new("baibot", "gpt-4o", now_utc); + let conversation_start_time_utc = Utc + .with_ymd_and_hms(2024, 9, 19, 18, 34, 15) + .unwrap() + .with_nanosecond(250000000) + .unwrap(); + + let variables = TextGenerationPromptVariables::new( + "baibot", + "gpt-4o", + now_utc, + Some(conversation_start_time_utc), + ); assert_eq!( variables.map.get("baibot_name"), @@ -66,9 +93,13 @@ mod tests { variables.map.get("baibot_now_utc"), Some(&format_utc_time(now_utc)) ); + assert_eq!( + variables.map.get("baibot_conversation_start_time_utc"), + Some(&format_utc_time(conversation_start_time_utc)) + ); - let prompt = "Hello, I'm {{ baibot_name }} using {{ baibot_model_id }}. The date/time now is {{ baibot_now_utc }}."; - let expected = "Hello, I'm baibot using gpt-4o. The date/time now is 2024-09-20 (Friday), 18:34:15 UTC."; + let prompt = "Hello, I'm {{ baibot_name }} using {{ baibot_model_id }}. The date/time now is {{ baibot_now_utc }} and this conversation started at {{ baibot_conversation_start_time_utc }}."; + let expected = "Hello, I'm baibot using gpt-4o. The date/time now is 2024-09-20 (Friday), 18:34:15 UTC and this conversation started at 2024-09-19 (Thursday), 18:34:15 UTC."; assert_eq!(variables.format(prompt), expected); } diff --git a/src/agent/provider/openai/controller.rs b/src/agent/provider/openai/controller.rs index b1911bc..5331af8 100644 --- a/src/agent/provider/openai/controller.rs +++ b/src/agent/provider/openai/controller.rs @@ -63,6 +63,7 @@ impl ControllerTrait for Controller { let messages = vec![LLMMessage { author: LLMAuthor::User, message_text: "Hello!".to_string(), + timestamp: chrono::Utc::now(), }]; let conversation = LLMConversation { messages }; @@ -99,6 +100,7 @@ impl ControllerTrait for Controller { Some(LLMMessage { author: LLMAuthor::Prompt, message_text: prompt_text, + timestamp: chrono::Utc::now(), }) }; diff --git a/src/agent/provider/openai_compat/controller.rs b/src/agent/provider/openai_compat/controller.rs index d56c9ab..9cd7f8f 100644 --- a/src/agent/provider/openai_compat/controller.rs +++ b/src/agent/provider/openai_compat/controller.rs @@ -61,6 +61,7 @@ impl ControllerTrait for Controller { let messages = vec![LLMMessage { author: LLMAuthor::User, message_text: "Hello!".to_string(), + timestamp: chrono::Utc::now(), }]; let conversation = LLMConversation { messages }; @@ -97,6 +98,7 @@ impl ControllerTrait for Controller { Some(LLMMessage { author: LLMAuthor::Prompt, message_text: prompt_text, + timestamp: chrono::Utc::now(), }) }; diff --git a/src/controller/chat_completion/mod.rs b/src/controller/chat_completion/mod.rs index 6ab7f9e..963acb9 100644 --- a/src/controller/chat_completion/mod.rs +++ b/src/controller/chat_completion/mod.rs @@ -492,6 +492,7 @@ async fn handle_stage_text_generation( .text_generation_model_id() .unwrap_or("unknown-model".to_owned()), chrono::Utc::now(), + conversation.start_time(), ); let params = TextGenerationParams { diff --git a/src/controller/image/prompt.rs b/src/controller/image/prompt.rs index 70eff73..b78520c 100644 --- a/src/controller/image/prompt.rs +++ b/src/controller/image/prompt.rs @@ -46,6 +46,8 @@ mod tests { #[test] fn test_build_prompt() { + let timestamp = chrono::Utc::now(); + let test_cases = vec![ // Simple case TestCase { @@ -59,6 +61,7 @@ mod tests { messages: vec![Message { author: Author::User, message_text: "Must be blue".to_owned(), + timestamp, }], expected_prompt: "Generate a picture of a dog\nOther criteria:\n- Must be blue", }, @@ -68,14 +71,17 @@ mod tests { messages: vec![Message { author: Author::User, message_text: "Must be blue".to_owned(), + timestamp, }, Message { author: Author::Assistant, message_text: "Whatever".to_owned(), + timestamp, }, Message { author: Author::User, message_text: "Must be 3-legged.\nMust be flying.".to_owned(), + timestamp, }], expected_prompt: "Generate a picture of an elephant\nOther criteria:\n- Must be blue\n- Must be 3-legged.. Must be flying.", }, @@ -85,18 +91,22 @@ mod tests { messages: vec![Message { author: Author::User, message_text: "Must be blue".to_owned(), + timestamp, }, Message { author: Author::Assistant, message_text: "Whatever".to_owned(), + timestamp, }, Message { author: Author::User, message_text: "Again".to_owned(), + timestamp, }, Message { author: Author::User, message_text: "again".to_owned(), + timestamp, }], expected_prompt: "Generate a picture of a grizzly bear\nOther criteria:\n- Must be blue", }, diff --git a/src/conversation/llm/entity.rs b/src/conversation/llm/entity.rs index 671d406..76569b7 100644 --- a/src/conversation/llm/entity.rs +++ b/src/conversation/llm/entity.rs @@ -1,3 +1,5 @@ +use chrono::{DateTime, Utc}; + #[derive(Debug, Clone, PartialEq)] pub enum Author { Prompt, @@ -9,6 +11,7 @@ pub enum Author { pub struct Message { pub author: Author, pub message_text: String, + pub timestamp: DateTime, } #[derive(Debug)] @@ -52,39 +55,59 @@ impl Conversation { messages: new_messages, } } + + pub fn start_time(&self) -> Option> { + self.messages.first().map(|message| message.timestamp) + } } #[cfg(test)] mod tests { use super::*; + use chrono::{TimeZone, Utc}; #[test] fn combine_consecutive_messages() { + let timestamp_1 = Utc.with_ymd_and_hms(2024, 9, 20, 18, 34, 15).unwrap(); + + let timestamp_2 = Utc.with_ymd_and_hms(2024, 9, 21, 18, 34, 15).unwrap(); + + let timestamp_3 = Utc.with_ymd_and_hms(2024, 9, 22, 18, 34, 15).unwrap(); + let conversation = Conversation { messages: vec![ + // User's turn Message { author: Author::User, message_text: "Hello".to_string(), + timestamp: timestamp_1, }, Message { author: Author::User, message_text: "How are you?".to_string(), + timestamp: timestamp_2, }, Message { author: Author::User, message_text: "I'm OK, btw.".to_string(), + timestamp: timestamp_3, }, + // Assistant's turn Message { author: Author::Assistant, message_text: "Hi there!".to_string(), + timestamp: timestamp_2, }, Message { author: Author::Assistant, message_text: "I'm doing well, thank you.".to_string(), + timestamp: timestamp_3, }, + // User's turn Message { author: Author::User, message_text: "That's great!".to_string(), + timestamp: timestamp_3, }, ], }; @@ -97,12 +120,17 @@ mod tests { conversation.messages[0].message_text, "Hello\nHow are you?\nI'm OK, btw." ); + assert_eq!(conversation.messages[0].timestamp, timestamp_1); + assert_eq!(conversation.messages[1].author, Author::Assistant); assert_eq!( conversation.messages[1].message_text, "Hi there!\nI'm doing well, thank you." ); + assert_eq!(conversation.messages[1].timestamp, timestamp_2); + assert_eq!(conversation.messages[2].author, Author::User); assert_eq!(conversation.messages[2].message_text, "That's great!"); + assert_eq!(conversation.messages[2].timestamp, timestamp_3); } } diff --git a/src/conversation/llm/tests.rs b/src/conversation/llm/tests.rs index a50b9cd..79a8338 100644 --- a/src/conversation/llm/tests.rs +++ b/src/conversation/llm/tests.rs @@ -15,6 +15,7 @@ fn test_messages_by_the_bot_are_identified_correctly() { message_type: super::super::matrix::MatrixMessageType::Text, message_text: "Hello!".to_owned(), mentioned_users: vec![], + timestamp: chrono::Utc::now(), }; let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id).unwrap(); @@ -39,6 +40,7 @@ fn test_notice_messages_by_bot_with_speech_to_text_prefix_are_cleaned_up_and_con message_type: super::super::matrix::MatrixMessageType::Notice, message_text, mentioned_users: vec![], + timestamp: chrono::Utc::now(), }; let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id).unwrap(); @@ -62,6 +64,7 @@ fn test_notice_error_messages_by_bot_are_ignored() { message_type: super::super::matrix::MatrixMessageType::Notice, message_text, mentioned_users: vec![], + timestamp: chrono::Utc::now(), }; let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id); @@ -86,6 +89,7 @@ fn test_other_notice_messages_by_the_bot_are_ignored() { message_type: super::super::matrix::MatrixMessageType::Notice, message_text: message_text.to_owned(), mentioned_users: vec![], + timestamp: chrono::Utc::now(), }; let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id); diff --git a/src/conversation/llm/tokenization.rs b/src/conversation/llm/tokenization.rs index 93cbea0..a2c0847 100644 --- a/src/conversation/llm/tokenization.rs +++ b/src/conversation/llm/tokenization.rs @@ -86,6 +86,7 @@ pub mod test { let message = super::Message { author: super::Author::User, message_text: "Hello there!".to_owned(), + timestamp: chrono::Utc::now(), }; let tokens = super::calculate_token_size_for_message(&bpe, model, &message); @@ -104,6 +105,7 @@ pub mod test { let prompt = super::Message { author: super::Author::Prompt, message_text: "You are a bot!".to_owned(), + timestamp: chrono::Utc::now(), }; let prompt_length = 10; @@ -117,6 +119,7 @@ pub mod test { let first = super::Message { author: super::Author::User, message_text: "Hello there!".to_owned(), + timestamp: chrono::Utc::now(), }; let first_length = 8; @@ -130,6 +133,7 @@ pub mod test { let second = super::Message { author: super::Author::Assistant, message_text: "Hello!".to_owned(), + timestamp: chrono::Utc::now(), }; let second_length = 7; @@ -144,6 +148,7 @@ pub mod test { author: super::Author::User, message_text: "This is the 3rd message in this conversation. It shall be preserved." .to_owned(), + timestamp: chrono::Utc::now(), }; let third_length = 21; @@ -157,6 +162,7 @@ pub mod test { let forth = super::Message { author: super::Author::Assistant, message_text: "This is yet another message that shall be preserved.".to_owned(), + timestamp: chrono::Utc::now(), }; let forth_length = 15; @@ -201,6 +207,7 @@ pub mod test { let prompt = super::Message { author: super::Author::User, message_text: "あなたはボットです。".to_owned(), + timestamp: chrono::Utc::now(), }; let prompt_length = 14; @@ -214,6 +221,7 @@ pub mod test { let first = super::Message { author: super::Author::User, message_text: "こんにちは!".to_owned(), + timestamp: chrono::Utc::now(), }; let first_length = 7; @@ -227,6 +235,7 @@ pub mod test { let second = super::Message { author: super::Author::Assistant, message_text: "こんにちは。今日は元気ですか。".to_owned(), + timestamp: chrono::Utc::now(), }; let second_length = 15; @@ -240,6 +249,7 @@ pub mod test { let third = super::Message { author: super::Author::User, message_text: "これは第3のメッセージなので、保存されます。".to_owned(), + timestamp: chrono::Utc::now(), }; let third_length = 22; @@ -253,6 +263,7 @@ pub mod test { let forth = super::Message { author: super::Author::Assistant, message_text: "これはもう一つの保存されますメッセージです。".to_owned(), + timestamp: chrono::Utc::now(), }; let forth_length = 21; diff --git a/src/conversation/llm/utils.rs b/src/conversation/llm/utils.rs index be6f663..f8039db 100644 --- a/src/conversation/llm/utils.rs +++ b/src/conversation/llm/utils.rs @@ -17,19 +17,30 @@ pub fn convert_matrix_message_to_llm_message( fn convert_bot_message(matrix_message: &MatrixMessage) -> Option { match matrix_message.message_type { - MatrixMessageType::Text => convert_bot_text_message(&matrix_message.message_text), - MatrixMessageType::Notice => convert_bot_notice_message(&matrix_message.message_text), + MatrixMessageType::Text => { + convert_bot_text_message(&matrix_message.message_text, &matrix_message.timestamp) + } + MatrixMessageType::Notice => { + convert_bot_notice_message(&matrix_message.message_text, &matrix_message.timestamp) + } } } -fn convert_bot_text_message(text: &str) -> Option { +fn convert_bot_text_message( + text: &str, + timestamp: &chrono::DateTime, +) -> Option { Some(Message { author: Author::Assistant, message_text: text.to_owned(), + timestamp: timestamp.to_owned(), }) } -fn convert_bot_notice_message(text: &str) -> Option { +fn convert_bot_notice_message( + text: &str, + timestamp: &chrono::DateTime, +) -> Option { // Notice messages sent by the bot are usually transcriptions of previous messages sent by the user. // Such transcriptions are prefixed with an emoji and blockquoted. // If we find a notice that doesn't match this pattern, we skip it. @@ -42,6 +53,7 @@ fn convert_bot_notice_message(text: &str) -> Option { return Some(Message { author: Author::User, message_text: text.to_owned(), + timestamp: timestamp.to_owned(), }); } @@ -52,5 +64,6 @@ fn convert_user_message(matrix_message: &MatrixMessage) -> Option { Some(Message { author: Author::User, message_text: matrix_message.message_text.clone(), + timestamp: matrix_message.timestamp.to_owned(), }) } diff --git a/src/conversation/matrix/entity.rs b/src/conversation/matrix/entity.rs index 978ba77..be4b3f5 100644 --- a/src/conversation/matrix/entity.rs +++ b/src/conversation/matrix/entity.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use regex::Regex; use mxlink::matrix_sdk::ruma::OwnedUserId; @@ -8,6 +9,7 @@ pub struct MatrixMessage { pub message_type: MatrixMessageType, pub message_text: String, pub mentioned_users: Vec, + pub timestamp: DateTime, } #[derive(Clone)] diff --git a/src/conversation/matrix/utils/mod.rs b/src/conversation/matrix/utils/mod.rs index ec63da2..4c845a5 100644 --- a/src/conversation/matrix/utils/mod.rs +++ b/src/conversation/matrix/utils/mod.rs @@ -234,6 +234,13 @@ pub fn convert_matrix_native_event_to_matrix_message( text }; + let timestamp = chrono::DateTime::::from( + matrix_native_event + .origin_server_ts() + .to_system_time() + .unwrap_or_else(std::time::SystemTime::now), + ); + let mentioned_users = room_message .mentions .map(|m| m.user_ids.iter().map(|u| u.to_owned()).collect()) @@ -248,6 +255,7 @@ pub fn convert_matrix_native_event_to_matrix_message( }, message_text: text, mentioned_users, + timestamp, }) } diff --git a/src/conversation/matrix/utils/tests.rs b/src/conversation/matrix/utils/tests.rs index 3383d49..6ed1e27 100644 --- a/src/conversation/matrix/utils/tests.rs +++ b/src/conversation/matrix/utils/tests.rs @@ -1,3 +1,5 @@ +use chrono::{TimeZone, Utc}; + use mxlink::matrix_sdk::ruma::OwnedUserId; use crate::conversation::matrix::{ @@ -11,11 +13,14 @@ fn is_message_from_allowed_sender() { let allowed_user_id = OwnedUserId::try_from("@user.someone:example.com").unwrap(); let unallowed_user_id = OwnedUserId::try_from("@another:example.com").unwrap(); + let timestamp = Utc.with_ymd_and_hms(2024, 9, 20, 18, 34, 15).unwrap(); + let bot_message = MatrixMessage { sender_id: bot_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "Hello!".to_owned(), mentioned_users: vec![], + timestamp, }; let allowed_user_message = MatrixMessage { @@ -23,6 +28,7 @@ fn is_message_from_allowed_sender() { message_type: MatrixMessageType::Text, message_text: "Hello!".to_owned(), mentioned_users: vec![], + timestamp, }; let unallowed_user_message = MatrixMessage { @@ -30,6 +36,7 @@ fn is_message_from_allowed_sender() { message_type: MatrixMessageType::Text, message_text: "Hello!".to_owned(), mentioned_users: vec![], + timestamp, }; let parsed_regex = match mxidwc::parse_pattern("@user.*:example.com") { @@ -77,11 +84,14 @@ async fn process_matrix_messages() { let allowed_user_id = OwnedUserId::try_from("@user.someone:example.com").unwrap(); let unallowed_user_id = OwnedUserId::try_from("@another:example.com").unwrap(); + let timestamp = Utc.with_ymd_and_hms(2024, 9, 20, 18, 34, 15).unwrap(); + let allowed_user_message = MatrixMessage { sender_id: allowed_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "Hello from the user!".to_owned(), mentioned_users: vec![], + timestamp, }; let allowed_user_message_with_prefix = MatrixMessage { @@ -89,6 +99,7 @@ async fn process_matrix_messages() { message_type: MatrixMessageType::Text, message_text: "!bai Hello from the user!".to_owned(), mentioned_users: vec![], + timestamp, }; let allowed_user_message_with_prefix_no_space = MatrixMessage { @@ -96,6 +107,7 @@ async fn process_matrix_messages() { message_type: MatrixMessageType::Text, message_text: "!baiHello from the user!".to_owned(), mentioned_users: vec![], + timestamp, }; let allowed_user_message_with_prefix_full_width_space = MatrixMessage { @@ -103,6 +115,7 @@ async fn process_matrix_messages() { message_type: MatrixMessageType::Text, message_text: "!bai Hello from the user!".to_owned(), mentioned_users: vec![], + timestamp, }; let bot_message = MatrixMessage { @@ -110,6 +123,7 @@ async fn process_matrix_messages() { message_type: MatrixMessageType::Text, message_text: "Hello from the bot!".to_owned(), mentioned_users: vec![], + timestamp, }; let allowed_user_message_with_bot_mention = MatrixMessage { @@ -117,6 +131,7 @@ async fn process_matrix_messages() { message_type: MatrixMessageType::Text, message_text: "@baibot: Hello from the user!".to_owned(), mentioned_users: vec![bot_user_id.to_owned()], + timestamp, }; // The message text is the same as above - it mentions the bot, but the actually-mentioned user is another user. @@ -125,6 +140,7 @@ async fn process_matrix_messages() { message_type: MatrixMessageType::Text, message_text: allowed_user_message_with_bot_mention.message_text.clone(), mentioned_users: vec![allowed_user_id.to_owned()], + timestamp, }; let unallowed_user_message = MatrixMessage { @@ -132,6 +148,7 @@ async fn process_matrix_messages() { message_type: MatrixMessageType::Text, message_text: "Hello from an unallowed user!".to_owned(), mentioned_users: vec![], + timestamp, }; let parsed_regex = match mxidwc::parse_pattern("@user.*:example.com") {