feat: add POST /insights/generate/agentic handler and route
Register the agentic insight endpoint that validates tool-calling capability, runs the agentic loop, and returns the stored PhotoInsightResponse. Returns 400 for unsupported models, 500 for other errors. Max iterations configurable via AGENTIC_MAX_ITERATIONS env var (default 10). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -211,6 +211,108 @@ pub async fn get_all_insights_handler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// POST /insights/generate/agentic - Generate insight using agentic tool-calling loop
|
||||||
|
#[post("/insights/generate/agentic")]
|
||||||
|
pub async fn generate_agentic_insight_handler(
|
||||||
|
http_request: HttpRequest,
|
||||||
|
_claims: Claims,
|
||||||
|
request: web::Json<GeneratePhotoInsightRequest>,
|
||||||
|
insight_generator: web::Data<InsightGenerator>,
|
||||||
|
insight_dao: web::Data<std::sync::Mutex<Box<dyn InsightDao>>>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let parent_context = extract_context_from_request(&http_request);
|
||||||
|
let tracer = global_tracer();
|
||||||
|
let mut span = tracer.start_with_context("http.insights.generate_agentic", &parent_context);
|
||||||
|
|
||||||
|
let normalized_path = normalize_path(&request.file_path);
|
||||||
|
|
||||||
|
span.set_attribute(KeyValue::new("file_path", normalized_path.clone()));
|
||||||
|
if let Some(ref model) = request.model {
|
||||||
|
span.set_attribute(KeyValue::new("model", model.clone()));
|
||||||
|
}
|
||||||
|
if let Some(ref prompt) = request.system_prompt {
|
||||||
|
span.set_attribute(KeyValue::new("has_custom_prompt", true));
|
||||||
|
span.set_attribute(KeyValue::new("prompt_length", prompt.len() as i64));
|
||||||
|
}
|
||||||
|
if let Some(ctx) = request.num_ctx {
|
||||||
|
span.set_attribute(KeyValue::new("num_ctx", ctx as i64));
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_iterations: usize = std::env::var("AGENTIC_MAX_ITERATIONS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(10);
|
||||||
|
|
||||||
|
span.set_attribute(KeyValue::new("max_iterations", max_iterations as i64));
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Agentic insight generation triggered for photo: {} with model: {:?}, max_iterations: {}",
|
||||||
|
normalized_path,
|
||||||
|
request.model,
|
||||||
|
max_iterations
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = insight_generator
|
||||||
|
.generate_agentic_insight_for_photo(
|
||||||
|
&normalized_path,
|
||||||
|
request.model.clone(),
|
||||||
|
request.system_prompt.clone(),
|
||||||
|
request.num_ctx,
|
||||||
|
max_iterations,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
span.set_status(Status::Ok);
|
||||||
|
// Fetch the stored insight to return it
|
||||||
|
let otel_context = opentelemetry::Context::new();
|
||||||
|
let mut dao = insight_dao.lock().expect("Unable to lock InsightDao");
|
||||||
|
match dao.get_insight(&otel_context, &normalized_path) {
|
||||||
|
Ok(Some(insight)) => {
|
||||||
|
let response = PhotoInsightResponse {
|
||||||
|
id: insight.id,
|
||||||
|
file_path: insight.file_path,
|
||||||
|
title: insight.title,
|
||||||
|
summary: insight.summary,
|
||||||
|
generated_at: insight.generated_at,
|
||||||
|
model_version: insight.model_version,
|
||||||
|
};
|
||||||
|
HttpResponse::Ok().json(response)
|
||||||
|
}
|
||||||
|
Ok(None) => HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"success": true,
|
||||||
|
"message": "Agentic insight generated successfully"
|
||||||
|
})),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Insight stored but failed to retrieve: {:?}", e);
|
||||||
|
HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
"success": true,
|
||||||
|
"message": "Agentic insight generated successfully"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let error_msg = format!("{:?}", e);
|
||||||
|
log::error!("Failed to generate agentic insight: {}", error_msg);
|
||||||
|
span.set_status(Status::error(error_msg.clone()));
|
||||||
|
|
||||||
|
if error_msg.contains("tool calling not supported")
|
||||||
|
|| error_msg.contains("model not available")
|
||||||
|
{
|
||||||
|
HttpResponse::BadRequest().json(serde_json::json!({
|
||||||
|
"error": format!("Failed to generate agentic insight: {}", error_msg)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
HttpResponse::InternalServerError().json(serde_json::json!({
|
||||||
|
"error": format!("Failed to generate agentic insight: {}", error_msg)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// GET /insights/models - List available models from both servers with capabilities
|
/// GET /insights/models - List available models from both servers with capabilities
|
||||||
#[get("/insights/models")]
|
#[get("/insights/models")]
|
||||||
pub async fn get_available_models_handler(
|
pub async fn get_available_models_handler(
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ pub mod sms_client;
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use daily_summary_job::{generate_daily_summaries, strip_summary_boilerplate};
|
pub use daily_summary_job::{generate_daily_summaries, strip_summary_boilerplate};
|
||||||
pub use handlers::{
|
pub use handlers::{
|
||||||
delete_insight_handler, generate_insight_handler, get_all_insights_handler,
|
delete_insight_handler, generate_agentic_insight_handler, generate_insight_handler,
|
||||||
get_available_models_handler, get_insight_handler,
|
get_all_insights_handler, get_available_models_handler, get_insight_handler,
|
||||||
};
|
};
|
||||||
pub use insight_generator::InsightGenerator;
|
pub use insight_generator::InsightGenerator;
|
||||||
pub use ollama::{ModelCapabilities, OllamaClient};
|
pub use ollama::{ModelCapabilities, OllamaClient};
|
||||||
|
|||||||
@@ -1189,6 +1189,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
.service(get_file_metadata)
|
.service(get_file_metadata)
|
||||||
.service(memories::list_memories)
|
.service(memories::list_memories)
|
||||||
.service(ai::generate_insight_handler)
|
.service(ai::generate_insight_handler)
|
||||||
|
.service(ai::generate_agentic_insight_handler)
|
||||||
.service(ai::get_insight_handler)
|
.service(ai::get_insight_handler)
|
||||||
.service(ai::delete_insight_handler)
|
.service(ai::delete_insight_handler)
|
||||||
.service(ai::get_all_insights_handler)
|
.service(ai::get_all_insights_handler)
|
||||||
|
|||||||
Reference in New Issue
Block a user