mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
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
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user