Override session name generator for ollama provider (#3710)

This commit is contained in:
Angela Ning
2025-08-02 13:36:21 -04:00
committed by GitHub
parent 9cd07b88c9
commit d93063b686
2 changed files with 92 additions and 25 deletions
+27 -25
View File
@@ -340,30 +340,22 @@ pub trait Provider: Send + Sync {
}
}
/// Generate a session name/description based on the conversation history
/// This method can be overridden by providers to implement custom session naming strategies.
/// The default implementation creates a prompt asking for a concise description in 4 words or less.
async fn generate_session_name(&self, messages: &[Message]) -> Result<String, ProviderError> {
// Create a prompt for a concise description
let mut description_prompt = "Based on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description".to_string();
// Get context from the first 3 user messages
let context: Vec<String> = messages
/// Returns the first 3 user messages as strings for session naming
fn get_initial_user_messages(&self, messages: &[Message]) -> Vec<String> {
messages
.iter()
.filter(|m| m.role == rmcp::model::Role::User)
.take(3)
.map(|m| m.as_concat_text())
.collect();
.collect()
}
if !context.is_empty() {
description_prompt = format!(
"Here are the first few user messages:\n{}\n\n{}",
context.join("\n"),
description_prompt
);
}
let message = Message::user().with_text(&description_prompt);
/// Generate a session name/description based on the conversation history
/// Creates a prompt asking for a concise description in 4 words or less.
async fn generate_session_name(&self, messages: &[Message]) -> Result<String, ProviderError> {
let context = self.get_initial_user_messages(messages);
let prompt = self.create_session_name_prompt(&context);
let message = Message::user().with_text(&prompt);
let result = self
.complete(
"Reply with only a description in four words or less",
@@ -373,13 +365,23 @@ pub trait Provider: Send + Sync {
.await?;
let description = result.0.as_concat_text();
let sanitized_description = if description.chars().count() > 100 {
safe_truncate(&description, 100)
} else {
description
};
Ok(sanitized_description)
Ok(safe_truncate(&description, 100))
}
// Generate a prompt for a session name based on the conversation history
fn create_session_name_prompt(&self, context: &[String]) -> String {
// Create a prompt for a concise description
let mut prompt = "Based on the conversation so far, provide a concise description of this session in 4 words or less. This will be used for finding the session later in a UI with limited space - reply *ONLY* with the description".to_string();
if !context.is_empty() {
prompt = format!(
"Here are the first few user messages:\n{}\n\n{}",
context.join("\n"),
prompt
);
}
prompt
}
}
+65
View File
@@ -5,8 +5,10 @@ use crate::impl_provider_default;
use crate::message::Message;
use crate::model::ModelConfig;
use crate::providers::formats::openai::{create_request, get_usage, response_to_message};
use crate::utils::safe_truncate;
use anyhow::Result;
use async_trait::async_trait;
use regex::Regex;
use reqwest::Client;
use rmcp::model::Tool;
use serde_json::Value;
@@ -150,4 +152,67 @@ impl Provider for OllamaProvider {
super::utils::emit_debug_trace(&self.model, &payload, &response, &usage);
Ok((message, ProviderUsage::new(model, usage)))
}
/// Generate a session name based on the conversation history
/// This override filters out reasoning tokens that some Ollama models produce
async fn generate_session_name(&self, messages: &[Message]) -> Result<String, ProviderError> {
let context = self.get_initial_user_messages(messages);
let message = Message::user().with_text(self.create_session_name_prompt(&context));
let result = self
.complete(
"You are a title generator. Output only the requested title of 4 words or less, with no additional text, reasoning, or explanations.",
&[message],
&[],
)
.await?;
let mut description = result.0.as_concat_text();
description = Self::filter_reasoning_tokens(&description);
Ok(safe_truncate(&description, 100))
}
}
impl OllamaProvider {
/// Filter out reasoning tokens and thinking patterns from model responses
fn filter_reasoning_tokens(text: &str) -> String {
let mut filtered = text.to_string();
// Remove common reasoning patterns
let reasoning_patterns = [
r"<think>.*?</think>",
r"<thinking>.*?</thinking>",
r"Let me think.*?\n",
r"I need to.*?\n",
r"First, I.*?\n",
r"Okay, .*?\n",
r"So, .*?\n",
r"Well, .*?\n",
r"Hmm, .*?\n",
r"Actually, .*?\n",
r"Based on.*?I think",
r"Looking at.*?I would say",
];
for pattern in reasoning_patterns {
if let Ok(re) = Regex::new(pattern) {
filtered = re.replace_all(&filtered, "").to_string();
}
}
// Remove any remaining thinking markers
filtered = filtered
.replace("<think>", "")
.replace("</think>", "")
.replace("<thinking>", "")
.replace("</thinking>", "");
// Clean up extra whitespace
filtered = filtered
.lines()
.map(|line| line.trim())
.filter(|line| !line.is_empty())
.collect::<Vec<_>>()
.join(" ");
filtered
}
}