Override session name generator for ollama provider (#3710)
This commit is contained in:
@@ -340,30 +340,22 @@ pub trait Provider: Send + Sync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a session name/description based on the conversation history
|
/// Returns the first 3 user messages as strings for session naming
|
||||||
/// This method can be overridden by providers to implement custom session naming strategies.
|
fn get_initial_user_messages(&self, messages: &[Message]) -> Vec<String> {
|
||||||
/// The default implementation creates a prompt asking for a concise description in 4 words or less.
|
messages
|
||||||
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
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|m| m.role == rmcp::model::Role::User)
|
.filter(|m| m.role == rmcp::model::Role::User)
|
||||||
.take(3)
|
.take(3)
|
||||||
.map(|m| m.as_concat_text())
|
.map(|m| m.as_concat_text())
|
||||||
.collect();
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
if !context.is_empty() {
|
/// Generate a session name/description based on the conversation history
|
||||||
description_prompt = format!(
|
/// Creates a prompt asking for a concise description in 4 words or less.
|
||||||
"Here are the first few user messages:\n{}\n\n{}",
|
async fn generate_session_name(&self, messages: &[Message]) -> Result<String, ProviderError> {
|
||||||
context.join("\n"),
|
let context = self.get_initial_user_messages(messages);
|
||||||
description_prompt
|
let prompt = self.create_session_name_prompt(&context);
|
||||||
);
|
let message = Message::user().with_text(&prompt);
|
||||||
}
|
|
||||||
|
|
||||||
let message = Message::user().with_text(&description_prompt);
|
|
||||||
let result = self
|
let result = self
|
||||||
.complete(
|
.complete(
|
||||||
"Reply with only a description in four words or less",
|
"Reply with only a description in four words or less",
|
||||||
@@ -373,13 +365,23 @@ pub trait Provider: Send + Sync {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let description = result.0.as_concat_text();
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ use crate::impl_provider_default;
|
|||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
use crate::model::ModelConfig;
|
use crate::model::ModelConfig;
|
||||||
use crate::providers::formats::openai::{create_request, get_usage, response_to_message};
|
use crate::providers::formats::openai::{create_request, get_usage, response_to_message};
|
||||||
|
use crate::utils::safe_truncate;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use regex::Regex;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use rmcp::model::Tool;
|
use rmcp::model::Tool;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -150,4 +152,67 @@ impl Provider for OllamaProvider {
|
|||||||
super::utils::emit_debug_trace(&self.model, &payload, &response, &usage);
|
super::utils::emit_debug_trace(&self.model, &payload, &response, &usage);
|
||||||
Ok((message, ProviderUsage::new(model, 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user