@@ -10,7 +10,7 @@ use redis::aio::ConnectionManager;
1010use regex:: Regex ;
1111use serde:: { Deserialize , Serialize } ;
1212use teloxide:: prelude:: * ;
13- use teloxide:: types:: ReplyParameters ;
13+ use teloxide:: types:: { MessageId , ReplyParameters , ThreadId } ;
1414use teloxide:: RequestError ;
1515
1616const FEDOR_CHAT_GPT_SYSTEM_CONTEXT : & str = "Предоставь грубый ответ. \
@@ -76,55 +76,37 @@ pub async fn handle_chat_gpt_question(
7676 return Ok ( ( ) ) ;
7777 } ;
7878 info ! ( "gpt invocation: chat_id: {chat_id}, message: {message}" ) ;
79- let bot_profiles = & * BOT_PROFILES ;
80- let bot_configuration = bot_profiles
81- . iter ( )
82- . find ( |& x| x. is_correct_config ( message) )
83- . unwrap_or ( & bot_profiles[ 0 ] ) ;
84- let bot_context_key = & format ! ( "{:#?}:chat:{:#?}" , bot_configuration. profile, chat_id. 0 ) ;
79+
80+ let bot_configuration = bot_configuration_for_message ( message) ;
81+ let bot_context_key = format ! ( "{:#?}:chat:{:#?}" , bot_configuration. profile, chat_id. 0 ) ;
8582 let user_message = ChatMessage {
8683 role : User ,
8784 content : message. to_string ( ) ,
8885 } ;
89- let summary_request_regex = & * CHAT_SUMMARY_REQUEST_REGEX ;
9086 let mut redis_cm = gpt_parameters. redis_connection_manager . clone ( ) ;
91- let context = match summary_request_regex. is_match ( message) {
92- true => {
93- fetch_chat_summary_context (
94- & mut redis_cm,
95- chat_id. 0 ,
96- & user_message,
97- bot_configuration. gpt_system_context ,
98- )
99- . await
100- }
101- false => {
102- fetch_bot_context (
103- & mut redis_cm,
104- bot_context_key,
105- & user_message,
106- bot_configuration. gpt_system_context ,
107- )
108- . await
109- }
110- } ;
87+ let context = build_question_context (
88+ & mut redis_cm,
89+ chat_id,
90+ & bot_context_key,
91+ & user_message,
92+ bot_configuration,
93+ )
94+ . await ;
11195
11296 let gpt_response_message = gpt_service:: chat_gpt_call ( gpt_parameters, chat_id, context) . await ;
113- let bot_reply_msg_response = if let Some ( thread_id) = msg. thread_id {
114- bot. send_message ( chat_id, & gpt_response_message. content )
115- . reply_parameters ( ReplyParameters :: new ( msg. id ) )
116- . message_thread_id ( thread_id)
117- . await
118- } else {
119- bot. send_message ( chat_id, & gpt_response_message. content )
120- . reply_parameters ( ReplyParameters :: new ( msg. id ) )
121- . await
122- } ;
97+ let bot_reply_msg_response = send_gpt_reply (
98+ & bot,
99+ chat_id,
100+ msg. id ,
101+ msg. thread_id ,
102+ & gpt_response_message. content ,
103+ )
104+ . await ;
123105
124106 update_bot_context_and_identifiers (
125107 & mut redis_cm,
126108 bot_configuration. profile ,
127- bot_context_key,
109+ & bot_context_key,
128110 & user_message,
129111 & gpt_response_message,
130112 bot_reply_msg_response,
@@ -133,6 +115,72 @@ pub async fn handle_chat_gpt_question(
133115 Ok ( ( ) )
134116}
135117
118+ /// Pick the bot profile whose mention regex matches the message, falling back
119+ /// to the first profile when none match.
120+ fn bot_configuration_for_message ( message : & str ) -> & ' static BotConfiguration < ' static > {
121+ let bot_profiles = & * BOT_PROFILES ;
122+ bot_profiles
123+ . iter ( )
124+ . find ( |& config| config. is_correct_config ( message) )
125+ . unwrap_or ( & bot_profiles[ 0 ] )
126+ }
127+
128+ /// Look up the bot profile a previous bot message was sent under, falling back
129+ /// to the first profile when it is unknown.
130+ fn bot_configuration_for_profile ( profile : BotProfile ) -> & ' static BotConfiguration < ' static > {
131+ let bot_profiles = & * BOT_PROFILES ;
132+ bot_profiles
133+ . iter ( )
134+ . find ( |& config| config. profile == profile)
135+ . unwrap_or ( & bot_profiles[ 0 ] )
136+ }
137+
138+ /// Build the GPT context for a fresh question: a chat-history summary when the
139+ /// message asks "what's going on", otherwise the profile's rolling context.
140+ async fn build_question_context (
141+ redis_cm : & mut ConnectionManager ,
142+ chat_id : ChatId ,
143+ bot_context_key : & String ,
144+ user_message : & ChatMessage ,
145+ bot_configuration : & BotConfiguration < ' _ > ,
146+ ) -> Vec < ChatMessage > {
147+ if CHAT_SUMMARY_REQUEST_REGEX . is_match ( & user_message. content ) {
148+ fetch_chat_summary_context (
149+ redis_cm,
150+ chat_id. 0 ,
151+ user_message,
152+ bot_configuration. gpt_system_context ,
153+ )
154+ . await
155+ } else {
156+ fetch_bot_context (
157+ redis_cm,
158+ bot_context_key,
159+ user_message,
160+ bot_configuration. gpt_system_context ,
161+ )
162+ . await
163+ }
164+ }
165+
166+ /// Send a bot reply, routing it into the originating message thread when there
167+ /// is one.
168+ async fn send_gpt_reply (
169+ bot : & Bot ,
170+ chat_id : ChatId ,
171+ reply_to : MessageId ,
172+ thread_id : Option < ThreadId > ,
173+ content : & str ,
174+ ) -> Result < Message , RequestError > {
175+ let request = bot
176+ . send_message ( chat_id, content. to_owned ( ) )
177+ . reply_parameters ( ReplyParameters :: new ( reply_to) ) ;
178+ match thread_id {
179+ Some ( thread_id) => request. message_thread_id ( thread_id) . await ,
180+ None => request. await ,
181+ }
182+ }
183+
136184async fn update_bot_context_and_identifiers (
137185 redis_connection_manager : & mut ConnectionManager ,
138186 bot_profile : BotProfile ,
@@ -182,51 +230,48 @@ pub async fn handle_reply(
182230 info ! ( "chat_key: {chat_key:?}" ) ;
183231 let reply_msg_id = reply_msg. id . 0 ;
184232 let mut redis_cm = gpt_parameters. redis_connection_manager . clone ( ) ;
185- if let Ok ( reply_msg_bot_profile) =
233+ let Ok ( reply_msg_bot_profile) =
186234 chat_repository:: get_bot_msg_profile ( & mut redis_cm, chat_key, reply_msg_id) . await
187- {
188- info ! (
189- "handle msg of bot msg reply_msg_id:'{reply_msg_id:#?}' under bot profile:'{reply_msg_bot_profile:#?}'"
190- ) ;
191- info ! (
192- "truing to reply chat_id:{chat_id:#?}, msg_id: {:?}, thread_id: {:#?}" ,
193- msg. id, msg. thread_id
194- ) ;
195- let bot_profiles = & * BOT_PROFILES ;
196- let bot_configuration = bot_profiles
197- . iter ( )
198- . find ( |& x| x. profile == reply_msg_bot_profile)
199- . unwrap_or ( & bot_profiles[ 0 ] ) ;
200- let bot_context_key = & format ! ( "{:#?}:chat:{:#?}" , bot_configuration. profile, chat_id. 0 ) ;
201- let user_message = ChatMessage {
202- role : User ,
203- content : message. to_string ( ) ,
204- } ;
205- let context = fetch_bot_context (
206- & mut redis_cm,
207- bot_context_key,
208- & user_message,
209- bot_configuration. gpt_system_context ,
210- )
211- . await ;
235+ else {
236+ return Ok ( ( ) ) ;
237+ } ;
238+ info ! (
239+ "handle msg of bot msg reply_msg_id:'{reply_msg_id:#?}' under bot profile:'{reply_msg_bot_profile:#?}'"
240+ ) ;
241+ info ! (
242+ "truing to reply chat_id:{chat_id:#?}, msg_id: {:?}, thread_id: {:#?}" ,
243+ msg. id, msg. thread_id
244+ ) ;
212245
213- let gpt_response_message =
214- gpt_service:: chat_gpt_call ( gpt_parameters, chat_id, context) . await ;
215- let bot_reply_msg_response = bot
216- . send_message ( chat_id, & gpt_response_message. content )
217- . reply_parameters ( ReplyParameters :: new ( msg. id ) )
218- . await ;
246+ let bot_configuration = bot_configuration_for_profile ( reply_msg_bot_profile) ;
247+ let bot_context_key = format ! ( "{:#?}:chat:{:#?}" , bot_configuration. profile, chat_id. 0 ) ;
248+ let user_message = ChatMessage {
249+ role : User ,
250+ content : message. to_string ( ) ,
251+ } ;
252+ let context = fetch_bot_context (
253+ & mut redis_cm,
254+ & bot_context_key,
255+ & user_message,
256+ bot_configuration. gpt_system_context ,
257+ )
258+ . await ;
219259
220- update_bot_context_and_identifiers (
221- & mut redis_cm,
222- bot_configuration. profile ,
223- bot_context_key,
224- & user_message,
225- & gpt_response_message,
226- bot_reply_msg_response,
227- )
260+ let gpt_response_message = gpt_service:: chat_gpt_call ( gpt_parameters, chat_id, context) . await ;
261+ let bot_reply_msg_response = bot
262+ . send_message ( chat_id, & gpt_response_message. content )
263+ . reply_parameters ( ReplyParameters :: new ( msg. id ) )
228264 . await ;
229- }
265+
266+ update_bot_context_and_identifiers (
267+ & mut redis_cm,
268+ bot_configuration. profile ,
269+ & bot_context_key,
270+ & user_message,
271+ & gpt_response_message,
272+ bot_reply_msg_response,
273+ )
274+ . await ;
230275 Ok ( ( ) )
231276}
232277
@@ -300,6 +345,7 @@ pub enum BotProfile {
300345
301346#[ cfg( test) ]
302347mod tests {
348+ use super :: { bot_configuration_for_message, bot_configuration_for_profile, BotProfile } ;
303349 use crate :: chat_gpt_handler:: SUMMARY_REQUEST_REGEX ;
304350 use regex:: Regex ;
305351
@@ -311,4 +357,36 @@ mod tests {
311357 assert ! ( summary_regex. is_match( "Фёдор, что тут происходит?" ) ) ;
312358 assert ! ( !summary_regex. is_match( "Fedor, kak dela?" ) ) ;
313359 }
360+
361+ #[ test]
362+ fn bot_configuration_for_message_selects_by_mention ( ) {
363+ assert_eq ! (
364+ bot_configuration_for_message( "привет fedor" ) . profile,
365+ BotProfile :: Fedor
366+ ) ;
367+ assert_eq ! (
368+ bot_configuration_for_message( "а вот и феликс" ) . profile,
369+ BotProfile :: Felix
370+ ) ;
371+ assert_eq ! (
372+ bot_configuration_for_message( "ferris the crab" ) . profile,
373+ BotProfile :: Ferris
374+ ) ;
375+ }
376+
377+ #[ test]
378+ fn bot_configuration_for_message_defaults_to_first_profile ( ) {
379+ // No mention keyword -> first configured profile (Fedor).
380+ assert_eq ! (
381+ bot_configuration_for_message( "nothing relevant here" ) . profile,
382+ BotProfile :: Fedor
383+ ) ;
384+ }
385+
386+ #[ test]
387+ fn bot_configuration_for_profile_round_trips ( ) {
388+ for profile in [ BotProfile :: Fedor , BotProfile :: Felix , BotProfile :: Ferris ] {
389+ assert_eq ! ( bot_configuration_for_profile( profile) . profile, profile) ;
390+ }
391+ }
314392}
0 commit comments