Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions crates/goose-server/src/routes/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,21 @@ async fn create_recipe(
State(state): State<Arc<AppState>>,
Json(request): Json<CreateRecipeRequest>,
) -> Result<Json<CreateRecipeResponse>, (StatusCode, Json<CreateRecipeResponse>)> {
tracing::info!(
"Recipe creation request received with {} messages",
request.messages.len()
);

let error_response = CreateRecipeResponse {
recipe: None,
error: Some("Missing agent".to_string()),
};
let agent = state
.get_agent()
.await
.map_err(|_| (StatusCode::PRECONDITION_FAILED, Json(error_response)))?;
let agent = state.get_agent().await.map_err(|e| {
tracing::error!("Failed to get agent for recipe creation: {}", e);
(StatusCode::PRECONDITION_FAILED, Json(error_response))
})?;

tracing::debug!("Agent retrieved successfully, creating recipe from conversation");

// Create base recipe from agent state and messages
let recipe_result = agent
Expand All @@ -99,6 +106,8 @@ async fn create_recipe(

match recipe_result {
Ok(mut recipe) => {
tracing::info!("Recipe created successfully with title: '{}'", recipe.title);

// Update with user-provided metadata
recipe.title = request.title;
recipe.description = request.description;
Expand All @@ -114,16 +123,23 @@ async fn create_recipe(
});
}

tracing::debug!("Recipe metadata updated, returning success response");

Ok(Json(CreateRecipeResponse {
recipe: Some(recipe),
error: None,
}))
}
Err(e) => {
// Log the detailed error for debugging
tracing::error!("Recipe creation failed: {}", e);
tracing::error!("Error details: {:?}", e);

// Return 400 Bad Request with error message
let error_message = format!("Recipe creation failed: {}", e);
let error_response = CreateRecipeResponse {
recipe: None,
error: Some(e.to_string()),
error: Some(error_message),
};
Err((StatusCode::BAD_REQUEST, Json(error_response)))
}
Expand Down
64 changes: 59 additions & 5 deletions crates/goose/src/agents/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,12 +1364,19 @@ impl Agent {
}

pub async fn create_recipe(&self, mut messages: Conversation) -> Result<Recipe> {
tracing::info!("Starting recipe creation with {} messages", messages.len());

let extensions_info = self.extension_manager.get_extensions_info().await;
tracing::debug!("Retrieved {} extensions info", extensions_info.len());

// Get model name from provider
let provider = self.provider().await?;
let provider = self.provider().await.map_err(|e| {
tracing::error!("Failed to get provider for recipe creation: {}", e);
e
})?;
let model_config = provider.get_model_config();
let model_name = &model_config.model_name;
tracing::debug!("Using model: {}", model_name);

let prompt_manager = self.prompt_manager.lock().await;
let system_prompt = prompt_manager.build_system_prompt(
Expand All @@ -1381,22 +1388,51 @@ impl Agent {
Some(model_name),
false,
);
tracing::debug!(
"Built system prompt with {} characters",
system_prompt.len()
);

let recipe_prompt = prompt_manager.get_recipe_prompt().await;
let tools = self.extension_manager.get_prefixed_tools(None).await?;
let tools = self
.extension_manager
.get_prefixed_tools(None)
.await
.map_err(|e| {
tracing::error!("Failed to get tools for recipe creation: {}", e);
e
})?;
tracing::debug!("Retrieved {} tools for recipe creation", tools.len());

messages.push(Message::user().with_text(recipe_prompt));
tracing::debug!(
"Added recipe prompt to messages, total messages: {}",
messages.len()
);

tracing::info!("Calling provider to generate recipe content");
let (result, _usage) = self
.provider
.lock()
.await
.as_ref()
.unwrap()
.ok_or_else(|| {
let error = anyhow!("Provider not available during recipe creation");
tracing::error!("{}", error);
error
})?
.complete(&system_prompt, messages.messages(), &tools)
.await?;
.await
.map_err(|e| {
tracing::error!("Provider completion failed during recipe creation: {}", e);
e
})?;

let content = result.as_concat_text();
tracing::debug!(
"Provider returned content with {} characters",
content.len()
);

// the response may be contained in ```json ```, strip that before parsing json
let re = Regex::new(r"(?s)```[^\n]*\n(.*?)\n```").unwrap();
Expand All @@ -1406,10 +1442,17 @@ impl Agent {
.unwrap_or(&content)
.trim()
.to_string();
tracing::debug!(
"Cleaned content for parsing: {}",
&clean_content[..std::cmp::min(200, clean_content.len())]
);

// try to parse json response from the LLM
tracing::debug!("Attempting to parse recipe content as JSON");
let (instructions, activities) =
if let Ok(json_content) = serde_json::from_str::<Value>(&clean_content) {
tracing::debug!("Successfully parsed JSON content");

let instructions = json_content
.get("instructions")
.ok_or_else(|| anyhow!("Missing 'instructions' in json response"))?
Expand All @@ -1432,6 +1475,7 @@ impl Agent {

(instructions, activities)
} else {
tracing::warn!("Failed to parse JSON, falling back to string parsing");
// If we can't get valid JSON, try string parsing
// Use split_once to get the content after "Instructions:".
let after_instructions = content
Expand Down Expand Up @@ -1491,6 +1535,12 @@ impl Agent {
temperature: Some(model_config.temperature.unwrap_or(0.0)),
};

tracing::debug!(
"Building recipe with {} activities and {} extensions",
activities.len(),
extension_configs.len()
);

let recipe = Recipe::builder()
.title("Custom recipe from chat")
.description("a custom recipe instance from this chat session")
Expand All @@ -1500,8 +1550,12 @@ impl Agent {
.settings(settings)
.author(author)
.build()
.expect("valid recipe");
.map_err(|e| {
tracing::error!("Failed to build recipe: {}", e);
anyhow!("Recipe build failed: {}", e)
})?;

tracing::info!("Recipe creation completed successfully");
Ok(recipe)
}
}
Expand Down
Loading