diff --git a/api/libs/litellm/src/client.rs b/api/libs/litellm/src/client.rs index da930c8aa..fa317fa75 100644 --- a/api/libs/litellm/src/client.rs +++ b/api/libs/litellm/src/client.rs @@ -66,10 +66,15 @@ impl LiteLLMClient { .post(&url) .json(&request) .send() - .await? - .json::() .await?; + // Print the raw response text + let response_text = response.text().await?; + println!("DEBUG: Raw response payload: {}", response_text); + + // Parse the response text into the expected type + let response: ChatCompletionResponse = serde_json::from_str(&response_text)?; + // Print tool calls if present if let Some(Message::Assistant { tool_calls: Some(tool_calls), @@ -128,7 +133,7 @@ impl LiteLLMClient { match chunk_result { Ok(chunk) => { let chunk_str = String::from_utf8_lossy(&chunk); - println!("DEBUG: Received raw stream chunk: {}", chunk_str); + println!("DEBUG: Raw response payload: {}", chunk_str); buffer.push_str(&chunk_str); while let Some(pos) = buffer.find("\n\n") { diff --git a/api/src/routes/rest/routes/chats/post_chat.rs b/api/src/routes/rest/routes/chats/post_chat.rs index 4c2b65baf..57507f11a 100644 --- a/api/src/routes/rest/routes/chats/post_chat.rs +++ b/api/src/routes/rest/routes/chats/post_chat.rs @@ -13,6 +13,7 @@ use serde_json::Value; use uuid::Uuid; use crate::routes::rest::ApiResponse; +use crate::utils::agent::AgentThread; use crate::{ database_dep::{ enums::Verification, @@ -88,19 +89,27 @@ async fn process_chat(request: ChatCreateNewChat, user: User) -> Result messages.push(msg), + Err(e) => return Err(e.into()), + } + } // Create and store initial message let message = Message { id: message_id, request: request.prompt, - response: serde_json::to_value(&output.messages)?, + response: serde_json::to_value(&messages)?, thread_id: chat_id, created_by: user.id.clone(), created_at: Utc::now(), @@ -118,7 +127,7 @@ async fn process_chat(request: ChatCreateNewChat, user: User) -> Result Result = output - .messages + let transformed_messages: Vec = messages .iter() .filter_map(|msg| { transform_message(&chat_id, &message_id, msg.clone()) diff --git a/api/src/utils/agent/agent.rs b/api/src/utils/agent/agent.rs index 2fff07f06..4ca18e704 100644 --- a/api/src/utils/agent/agent.rs +++ b/api/src/utils/agent/agent.rs @@ -375,6 +375,7 @@ impl Agent { if let Some(tool) = self.tools.read().await.get(&tool_call.function.name) { let params: Value = serde_json::from_str(&tool_call.function.arguments)?; let result = tool.execute(params).await?; + println!("Tool Call result: {:?}", result); let result_str = serde_json::to_string(&result)?; let tool_message = Message::tool( None, @@ -456,6 +457,7 @@ impl PendingToolCall { /// A trait that provides convenient access to Agent functionality /// when the agent is stored behind an Arc +#[async_trait::async_trait] pub trait AgentExt { fn get_agent(&self) -> &Arc; diff --git a/api/src/utils/agent/agents/dashboard_agent.rs b/api/src/utils/agent/agents/dashboard_agent.rs index 459b9cefd..aa4a93eeb 100644 --- a/api/src/utils/agent/agents/dashboard_agent.rs +++ b/api/src/utils/agent/agents/dashboard_agent.rs @@ -2,17 +2,15 @@ use std::sync::Arc; use anyhow::Result; use std::collections::HashMap; -use tokio::sync::mpsc::Receiver; use uuid::Uuid; use crate::utils::{ agent::{Agent, AgentExt, AgentThread}, tools::{ - file_tools::{ + agents_as_tools::dashboard_agent_tool::DashboardAgentOutput, file_tools::{ CreateDashboardFilesTool, CreateMetricFilesTool, ModifyDashboardFilesTool, - ModifyMetricFilesTool, OpenFilesTool, SearchFilesTool, - }, - IntoValueTool, ToolExecutor, + ModifyMetricFilesTool, + }, IntoValueTool, ToolExecutor }, }; @@ -37,8 +35,6 @@ impl DashboardAgent { let modify_dashboard_tool = ModifyDashboardFilesTool::new(Arc::clone(&agent)); let create_metric_tool = CreateMetricFilesTool::new(Arc::clone(&agent)); let modify_metric_tool = ModifyMetricFilesTool::new(Arc::clone(&agent)); - let open_files_tool = OpenFilesTool::new(Arc::clone(&agent)); - let search_files_tool = SearchFilesTool::new(Arc::clone(&agent)); // Add tools directly to the Arc agent @@ -65,18 +61,6 @@ impl DashboardAgent { modify_metric_tool.into_value_tool(), ) .await; - agent - .add_tool( - open_files_tool.get_name(), - open_files_tool.into_value_tool(), - ) - .await; - agent - .add_tool( - search_files_tool.get_name(), - search_files_tool.into_value_tool(), - ) - .await; Ok(Self { agent }) } @@ -90,9 +74,6 @@ impl DashboardAgent { let modify_dashboard_tool = ModifyDashboardFilesTool::new(Arc::clone(&agent)); let create_metric_tool = CreateMetricFilesTool::new(Arc::clone(&agent)); let modify_metric_tool = ModifyMetricFilesTool::new(Arc::clone(&agent)); - let open_files_tool = OpenFilesTool::new(Arc::clone(&agent)); - let search_files_tool = SearchFilesTool::new(Arc::clone(&agent)); - // Add tools to the agent agent @@ -119,32 +100,80 @@ impl DashboardAgent { modify_metric_tool.into_value_tool(), ) .await; - agent - .add_tool( - open_files_tool.get_name(), - open_files_tool.into_value_tool(), - ) - .await; - agent - .add_tool( - search_files_tool.get_name(), - search_files_tool.into_value_tool(), - ) - .await; Ok(Self { agent }) } - pub async fn run( - &self, - thread: &mut AgentThread, - ) -> Result>> { - // Process using agent's streaming functionality + fn is_completion_signal(msg: &AgentMessage) -> bool { + matches!(msg, AgentMessage::Assistant { content: Some(content), tool_calls: None, .. } + if content == "AGENT_COMPLETE") + } + + pub async fn run(&self, thread: &mut AgentThread) -> Result { + println!("Running dashboard agent"); + println!("Setting developer message"); thread.set_developer_message(DASHBOARD_AGENT_PROMPT.to_string()); - println!("Dashboard agent thread: {:?}", thread); + println!("Starting stream_process_thread"); + let mut rx = self.stream_process_thread(thread).await?; + println!("Got receiver from stream_process_thread"); - self.stream_process_thread(thread).await + println!("Starting message processing loop"); + // Process messages internally until we determine we're done + while let Some(msg_result) = rx.recv().await { + println!("Received message from channel"); + match msg_result { + Ok(msg) => { + println!("Message content: {:?}", msg.get_content()); + println!("Message has tool calls: {:?}", msg.get_tool_calls()); + + println!("Forwarding message to stream sender"); + if let Err(e) = self.get_agent().get_stream_sender().await.send(Ok(msg.clone())).await { + println!("Error forwarding message: {:?}", e); + // Continue processing even if we fail to forward + continue; + } + + if let Some(content) = msg.get_content() { + println!("Message has content: {}", content); + if content == "AGENT_COMPLETE" { + println!("Found completion signal, breaking loop"); + break; + } + } + } + Err(e) => { + println!("Error receiving message: {:?}", e); + println!("Error details: {:?}", e.to_string()); + // Log error but continue processing instead of returning error + continue; + } + } + } + println!("Exited message processing loop"); + + println!("Creating completion signal"); + let completion_msg = AgentMessage::assistant( + None, + Some("AGENT_COMPLETE".to_string()), + None, + None, + None, + ); + + println!("Sending completion signal"); + self.get_agent() + .get_stream_sender() + .await + .send(Ok(completion_msg)) + .await?; + + println!("Sent completion signal, returning output"); + Ok(DashboardAgentOutput { + message: "Dashboard processing complete".to_string(), + duration: 0, + files: vec![], + }) } } diff --git a/api/src/utils/agent/agents/manager_agent.rs b/api/src/utils/agent/agents/manager_agent.rs index 39b6dd4ad..2a6f267dc 100644 --- a/api/src/utils/agent/agents/manager_agent.rs +++ b/api/src/utils/agent/agents/manager_agent.rs @@ -7,6 +7,7 @@ use tracing::{debug, info}; use uuid::Uuid; use crate::utils::tools::agents_as_tools::{DashboardAgentTool, MetricAgentTool}; +use crate::utils::tools::file_tools::{send_assets_to_user, SendAssetsToUserTool}; use crate::utils::{ agent::{Agent, AgentExt, AgentThread}, tools::{ @@ -109,6 +110,7 @@ impl ManagerAgent { let create_or_modify_metrics_tool = MetricAgentTool::new(Arc::clone(&agent)); let create_or_modify_dashboards_tool = DashboardAgentTool::new(Arc::clone(&agent)); let exploratory_agent_tool = ExploratoryAgentTool::new(Arc::clone(&agent)); + let send_assets_to_user = SendAssetsToUserTool::new(Arc::clone(&agent)); // Add tools to the agent agent @@ -141,62 +143,22 @@ impl ManagerAgent { exploratory_agent_tool.into_value_tool(), ) .await; + agent + .add_tool( + send_assets_to_user.get_name(), + send_assets_to_user.into_value_tool(), + ) + .await; Ok(Self { agent }) } - pub async fn process_request( + pub async fn run( &self, - input: ManagerAgentInput, - user_id: Uuid, - ) -> Result { - let start_time = std::time::Instant::now(); - let thread_id = input.thread_id.unwrap_or_else(Uuid::new_v4); - info!("Starting manager request processing for thread: {}", thread_id); + thread: &mut AgentThread, + ) -> Result>> { + thread.set_developer_message(MANAGER_AGENT_PROMPT.to_string()); - // Create thread with manager context - let thread = AgentThread::new( - Some(thread_id), - user_id, - vec![ - AgentMessage::developer(MANAGER_AGENT_PROMPT.to_string()), - AgentMessage::user(input.prompt), - ], - ); - - // Process using agent's streaming functionality - let mut rx = self.run(&thread).await?; - let mut messages = Vec::new(); - - while let Some(msg_result) = rx.recv().await { - if let Ok(msg) = msg_result { - messages.push(msg); - } - } - - let duration = start_time.elapsed().as_millis() as i64; - let message = format!( - "Completed request processing with {} messages", - messages.len() - ); - - info!( - duration_ms = duration, - messages_count = messages.len(), - thread_id = %thread_id, - "Completed manager request processing" - ); - - Ok(ManagerAgentOutput { - message, - duration, - thread_id, - messages, - }) - } - - pub async fn run(&self, thread: &AgentThread) -> Result>> { - // Process using agent's streaming functionality self.stream_process_thread(thread).await } } diff --git a/api/src/utils/agent/types.rs b/api/src/utils/agent/types.rs index ac2a7760c..97e090a17 100644 --- a/api/src/utils/agent/types.rs +++ b/api/src/utils/agent/types.rs @@ -34,4 +34,11 @@ impl AgentThread { self.messages.insert(0, Message::developer(message)); } } + + /// Remove the most recent assistant message from the thread + pub fn remove_last_assistant_message(&mut self) { + if let Some(pos) = self.messages.iter().rposition(|msg| matches!(msg, Message::Assistant { .. })) { + self.messages.remove(pos); + } + } } diff --git a/api/src/utils/tools/agents_as_tools/dashboard_agent_tool.rs b/api/src/utils/tools/agents_as_tools/dashboard_agent_tool.rs index 5c969b80a..95c4c07c0 100644 --- a/api/src/utils/tools/agents_as_tools/dashboard_agent_tool.rs +++ b/api/src/utils/tools/agents_as_tools/dashboard_agent_tool.rs @@ -14,6 +14,11 @@ pub struct DashboardAgentTool { agent: Arc, } +fn is_completion_signal(msg: &AgentMessage) -> bool { + matches!(msg, AgentMessage::Assistant { content: Some(content), tool_calls: None, .. } + if content == "AGENT_COMPLETE") +} + #[derive(Debug, Serialize, Deserialize)] pub struct DashboardAgentParams { pub ticket_description: String, @@ -42,35 +47,34 @@ impl ToolExecutor for DashboardAgentTool { async fn execute(&self, params: Self::Params) -> Result { // Create and initialize the agent + println!("DashboardAgentTool: Creating dashboard agent"); let dashboard_agent = DashboardAgent::from_existing(&self.agent).await?; + println!("DashboardAgentTool: Dashboard agent created"); + println!("DashboardAgentTool: Getting current thread"); let mut current_thread = self .agent .get_current_thread() .await .ok_or_else(|| anyhow::anyhow!("No current thread"))?; + println!("DashboardAgentTool: Current thread retrieved"); - // Add the ticket description to the thread - current_thread - .messages - .push(AgentMessage::user(params.ticket_description)); + println!("DashboardAgentTool: Removing last assistant message"); + current_thread.remove_last_assistant_message(); + println!("DashboardAgentTool: Last assistant message removed"); - // Run the dashboard agent and get the receiver - let mut rx = dashboard_agent.run(&mut current_thread).await?; + println!("DashboardAgentTool: Starting dashboard agent run"); + // Run the dashboard agent and get the output + let output = dashboard_agent.run(&mut current_thread).await?; + println!("DashboardAgentTool: Dashboard agent run completed"); - // Process all messages from the receiver - let mut messages = Vec::new(); - while let Some(msg_result) = rx.recv().await { - match msg_result { - Ok(msg) => messages.push(msg), - Err(e) => return Err(e.into()), - } - } - - // Return the messages as part of the output + println!("DashboardAgentTool: Preparing success response"); + // Return success with the output Ok(serde_json::json!({ - "messages": messages, - "status": "completed" + "status": "success", + "message": output.message, + "duration": output.duration, + "files": output.files })) } diff --git a/api/src/utils/tools/agents_as_tools/exploratory_agent_tool.rs b/api/src/utils/tools/agents_as_tools/exploratory_agent_tool.rs index d8b356831..9030c74ab 100644 --- a/api/src/utils/tools/agents_as_tools/exploratory_agent_tool.rs +++ b/api/src/utils/tools/agents_as_tools/exploratory_agent_tool.rs @@ -43,27 +43,15 @@ impl ToolExecutor for ExploratoryAgentTool { .await .ok_or_else(|| anyhow::anyhow!("No current thread"))?; - // Add the ticket description to the thread - current_thread - .messages - .push(AgentMessage::user(params.ticket_description)); + current_thread.remove_last_assistant_message(); // Run the exploratory agent and get the receiver - let mut rx = exploratory_agent.run(&mut current_thread).await?; + let _rx = exploratory_agent.run(&mut current_thread).await?; - // Process all messages from the receiver - let mut messages = Vec::new(); - while let Some(msg_result) = rx.recv().await { - match msg_result { - Ok(msg) => messages.push(msg), - Err(e) => return Err(e.into()), - } - } - - // Return the messages as part of the output + // Return immediately with status Ok(serde_json::json!({ - "messages": messages, - "status": "completed" + "status": "running", + "message": "Exploratory agent started successfully" })) } diff --git a/api/src/utils/tools/agents_as_tools/metric_agent_tool.rs b/api/src/utils/tools/agents_as_tools/metric_agent_tool.rs index e4fc23dc5..402933aaf 100644 --- a/api/src/utils/tools/agents_as_tools/metric_agent_tool.rs +++ b/api/src/utils/tools/agents_as_tools/metric_agent_tool.rs @@ -51,26 +51,15 @@ impl ToolExecutor for MetricAgentTool { ticket_description: params.ticket_description, }; - // current_thread - // .messages - // .push(AgentMessage::user(agent_input.ticket_description)); + current_thread.remove_last_assistant_message(); // Run the metric agent and get the receiver - let mut rx = metric_agent.run(&mut current_thread).await?; + let _rx = metric_agent.run(&mut current_thread).await?; - // Process all messages from the receiver - let mut messages = Vec::new(); - while let Some(msg_result) = rx.recv().await { - match msg_result { - Ok(msg) => messages.push(msg), - Err(e) => return Err(e.into()), - } - } - - // Return the messages as part of the output + // Return immediately with status Ok(serde_json::json!({ - "messages": messages, - "status": "completed" + "status": "running", + "message": "Metric agent started successfully" })) } diff --git a/api/src/utils/tools/file_tools/file_types/dashboard_yml.rs b/api/src/utils/tools/file_tools/file_types/dashboard_yml.rs index 18cd25d27..a1ba35f71 100644 --- a/api/src/utils/tools/file_tools/file_types/dashboard_yml.rs +++ b/api/src/utils/tools/file_tools/file_types/dashboard_yml.rs @@ -7,15 +7,16 @@ use uuid::Uuid; pub struct DashboardYml { pub id: Option, pub updated_at: Option>, + #[serde(alias = "title")] pub name: String, pub rows: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Row { - pub items: Vec, // max number of items in a row is 4, min is 1 + pub items: Vec, // max number of items in a row is 4, min is 1 #[serde(skip_serializing_if = "Option::is_none")] - pub row_height: Option, // max is 550, min is 320 + pub row_height: Option, // max is 550, min is 320 #[serde(skip_serializing_if = "Option::is_none")] pub column_sizes: Option>, // max sum of elements is 12 min is 3 } diff --git a/api/src/utils/tools/file_tools/mod.rs b/api/src/utils/tools/file_tools/mod.rs index e1c999a15..5539b5684 100644 --- a/api/src/utils/tools/file_tools/mod.rs +++ b/api/src/utils/tools/file_tools/mod.rs @@ -9,14 +9,14 @@ pub mod modify_metric_files; pub mod open_files; pub mod search_data_catalog; pub mod search_files; -pub mod send_files_to_user; +pub mod send_assets_to_user; pub use create_files::CreateFilesTool; pub use modify_files::ModifyFilesTool; pub use open_files::OpenFilesTool; pub use search_data_catalog::SearchDataCatalogTool; pub use search_files::SearchFilesTool; -pub use send_files_to_user::SendFilesToUserTool; +pub use send_assets_to_user::SendAssetsToUserTool; pub use create_dashboard_files::CreateDashboardFilesTool; pub use create_metric_files::CreateMetricFilesTool; pub use modify_dashboard_files::ModifyDashboardFilesTool; diff --git a/api/src/utils/tools/file_tools/modify_metric_files.rs b/api/src/utils/tools/file_tools/modify_metric_files.rs index 254d817da..9a46144b2 100644 --- a/api/src/utils/tools/file_tools/modify_metric_files.rs +++ b/api/src/utils/tools/file_tools/modify_metric_files.rs @@ -392,67 +392,67 @@ impl ToolExecutor for ModifyMetricFilesTool { fn get_schema(&self) -> Value { serde_json::json!({ - "name": "bulk_modify_metrics", - "description": "Modifies existing metric configuration files by applying specified modifications to their content", - "strict": true, - "parameters": { - "type": "object", - "required": [ - "files" - ], - "properties": { - "files": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "file_name", - "modifications" - ], - "properties": { - "id": { - "type": "string", - "format": "uuid", - "description": "UUID of the file to modify" - }, - "file_name": { - "type": "string", - "description": "Name of the file to modify" - }, - "modifications": { - "type": "array", - "items": { - "type": "object", - "required": [ - "new_content", - "line_numbers" - ], - "properties": { - "new_content": { - "type": "string", - "description": "The new content to be inserted at the specified line numbers. METRIC CONFIGURATION SCHEMA (DOCUMENTATION + SPEC) # This YAML file shows a JSON Schema-like specification for defining a 'metric.'# # REQUIRED at the top level:# 1) title: string# 2) dataset_ids: array of strings# 2) sql: multi-line string (YAML pipe recommended)# 3) chart_config: must match exactly one of the possible chart sub-schemas # (bar/line, scatter, pie, combo, metric, table).# 4) data_metadata: array of columns. Each with { name, data_type }.# # 'columnLabelFormats' is a required field under chartConfig (in the base).## If a field is null or empty, simply omit it from your YAML rather than # including it with 'null.' That way, you keep the configuration clean.# ------------------------------------------------------------------------------type: objecttitle: 'Metric Configuration Schema'description: 'Specifies structure for a metric file, including SQL + one chart type.'properties: # ---------------------- # 1. TITLE (REQUIRED) # ---------------------- title: type: string description: > A human-readable title for this metric (e.g. 'Total Sales'). Always required. # ---------------------- # 2. DATASET IDS (REQUIRED) # ---------------------- dataset_ids: type: array description: > An array of dataset IDs that the metric belongs to. # ---------------------- # 3. SQL (REQUIRED, multi-line recommended) # ---------------------- sql: type: string description: > A SQL query string used to compute or retrieve the metric's data. It should be well-formatted, typically using YAML's pipe syntax (|). Example: sql: | SELECT date, SUM(sales_amount) AS total_sales FROM sales GROUP BY date ORDER BY date DESC Always required. # ---------------------- # 4. CHART CONFIG (REQUIRED, EXACTLY ONE TYPE) # ---------------------- chart_config: description: > Defines visualization settings. Must match exactly one sub-schema via oneOf: bar/line, scatter, pie, combo, metric, or table. oneOf: - $ref: '#/definitions/bar_line_chart_config' - $ref: '#/definitions/scatter_chart_config' - $ref: '#/definitions/pie_chart_config' - $ref: '#/definitions/combo_chart_config' - $ref: '#/definitions/metric_chart_config' - $ref: '#/definitions/table_chart_config' # ---------------------- # 5. DATA METADATA (REQUIRED) # ---------------------- data_metadata: type: array description: > An array describing each column in the metric's dataset. Each item has a 'name' and a 'dataType'. items: type: object properties: name: type: string description: 'Column name.' data_type: type: string description: 'Data type of the column (e.g., 'string', 'number', 'date').' required: - name - data_typerequired: - title - sql - chart_configdefinitions: goal_line: type: object description: 'A line drawn on the chart to represent a goal/target.' properties: show: type: boolean description: > If true, display the goal line. If you don't need it, omit the property. value: type: number description: > Numeric value of the goal line. Omit if unused. show_goal_line_label: type: boolean description: > If true, show a label on the goal line. Omit if you want the default behavior. goal_line_label: type: string description: > The label text to display near the goal line (if show_goal_line_label = true). goal_line_color: type: string description: > Color for the goal line (e.g., '#FF0000'). Omit if not specified. trendline: type: object description: 'A trendline overlay (e.g. average line, regression).' properties: show: type: boolean show_trendline_label: type: boolean trendline_label: type: string description: 'Label text if show_trendline_label is true (e.g., 'Slope').' type: type: string enum: - average - linear_regression - logarithmic_regression - exponential_regression - polynomial_regression - min - max - median description: > Trendline algorithm to use. Required. trend_line_color: type: string description: 'Color for the trendline (e.g. '#000000').' column_id: type: string description: > Column ID to which this trendline applies. Required. required: - type - column_id bar_and_line_axis: type: object description: > Axis definitions for bar or line charts: x, y, category, and optional tooltip. properties: x: type: array items: type: string description: 'Column ID(s) for the x-axis.' y: type: array items: type: string description: 'Column ID(s) for the y-axis.' category: type: array items: type: string description: 'Column ID(s) representing categories/groups.' tooltip: type: array items: type: string description: 'Columns used in tooltips. Omit if you want the defaults.' required: - x - y - category scatter_axis: type: object description: 'Axis definitions for scatter charts: x, y, optional category/size/tooltip.' properties: x: type: array items: type: string y: type: array items: type: string category: type: array items: type: string description: 'Optional. Omit if not used.' size: type: array maxItems: 1 items: type: string description: 'If omitted, no size-based variation. If present, exactly one column ID.' tooltip: type: array items: type: string description: 'Columns used in tooltips.' required: - x - y pie_chart_axis: type: object description: 'Axis definitions for pie charts: x, y, optional tooltip.' properties: x: type: array items: type: string y: type: array items: type: string tooltip: type: array items: type: string required: - x - y combo_chart_axis: type: object description: 'Axis definitions for combo charts: x, y, optional y2/category/tooltip.' properties: x: type: array items: type: string y: type: array items: type: string y2: type: array items: type: string description: 'Optional secondary y-axis. Omit if unused.' category: type: array items: type: string tooltip: type: array items: type: string required: - x - y i_column_label_format: type: object description: > Describes how a column's data is formatted (currency, percent, date, etc.). If you do not need special formatting for a column, omit it from 'column_label_formats'. properties: column_type: type: string description: 'e.g., 'number', 'string', 'date'' style: type: string enum: - currency - percent - number - date - string description: 'Defines how values are displayed.' display_name: type: string description: 'Override for the column label. Omit if unused.' number_separator_style: type: string description: 'E.g., ',' for thousands separator or omit if no special style.' minimum_fraction_digits: type: number description: 'Min decimal places. Omit if default is fine.' maximum_fraction_digits: type: number description: 'Max decimal places. Omit if default is fine.' multiplier: type: number description: 'E.g., 100 for percents. Omit if default is 1.' prefix: type: string description: 'String to add before each value (e.g. '$').' suffix: type: string description: 'String to add after each value (e.g. '%').' replace_missing_data_with: type: [ 'number', 'string' ] description: 'If data is missing, use this value. Omit if default 0 is fine.' compact_numbers: type: boolean description: 'If true, 10000 => 10K. Omit if not needed.' currency: type: string description: 'ISO code for style=currency. Default 'USD' if omitted.' date_format: type: string description: 'Dayjs format if style=date. Default 'LL' if omitted.' use_relative_time: type: boolean description: 'If true, e.g., '2 days ago' might be used. Omit if not used.' is_utc: type: boolean description: 'If true, interpret date as UTC. Omit if local time.' convert_number_to: type: string description: 'Used if style=number but want day_of_week, etc. Omit if not used.' required: - column_type - style column_settings: type: object description: 'Overrides per-column for visualization (bar, line, dot, etc.).' properties: show_data_labels: type: boolean show_data_labels_as_percentage: type: boolean column_visualization: type: string enum: [ 'bar', 'line', 'dot' ] description: > If omitted, chart-level default is used. line_width: type: number description: 'Thickness of the line. Omit if default is OK.' line_style: type: string enum: [ 'area', 'line' ] line_type: type: string enum: [ 'normal', 'smooth', 'step' ] line_symbol_size: type: number description: 'Size of dots on a line. Omit if default is OK.' bar_roundness: type: number description: 'Roundness of bar corners (0-50). Omit if default is OK.' line_symbol_size_dot: type: number description: 'If column_visualization='dot', size of the dots. Omit if default is OK.' base_chart_config: type: object properties: selected_chart_type: type: string description: > Must match the chart type in the sub-schema. E.g., 'bar', 'line', 'scatter', 'pie', 'combo', 'metric', 'table'. column_label_formats: type: object description: > A map of columnId => label format object (i_column_label_format). If you truly have no column formatting, you can provide an empty object, but do not omit this field. additionalProperties: $ref: '#/definitions/i_column_label_format' column_settings: type: object description: > A map of columnId => column_settings. Omit columns if no special customization is needed. additionalProperties: $ref: '#/definitions/column_settings' colors: type: array items: type: string description: > Array of color hex codes or color names. If omitted, use defaults. show_legend: type: boolean description: 'Whether to display the legend. Omit if defaults apply.' grid_lines: type: boolean description: 'Toggle grid lines. Omit if defaults apply.' show_legend_headline: type: string description: 'Additional legend headline text. Omit if not used.' goal_lines: type: array description: 'Array of goal_line objects. Omit if none.' items: $ref: '#/definitions/goal_line' trendlines: type: array description: 'Array of trendline objects. Omit if none.' items: $ref: '#/definitions/trendline' disable_tooltip: type: boolean description: 'If true, tooltips are disabled. Omit if not needed.' y_axis_config: type: object description: 'If omitted, defaults apply.' additionalProperties: true x_axis_config: type: object additionalProperties: true category_axis_style_config: type: object additionalProperties: true y2_axis_config: type: object additionalProperties: true required: - selected_chart_type - selected_view - column_label_formats bar_line_chart_config: allOf: - $ref: '#/definitions/base_chart_config' - type: object properties: selected_chart_type: enum: [ 'bar', 'line' ] bar_and_line_axis: $ref: '#/definitions/bar_and_line_axis' bar_layout: type: string enum: [ 'horizontal', 'vertical' ] bar_sort_by: type: string bar_group_type: type: string enum: [ 'stack', 'group', 'percentage-stack' ] bar_show_total_at_top: type: boolean line_group_type: type: string enum: [ 'stack', 'percentage-stack' ] required: - bar_and_line_axis scatter_chart_config: allOf: - $ref: '#/definitions/base_chart_config' - type: object properties: selected_chart_type: enum: [ 'scatter' ] scatter_axis: $ref: '#/definitions/scatter_axis' scatter_dot_size: type: array minItems: 2 maxItems: 2 items: type: number description: 'If omitted, scatter dot sizes may follow a default range.' required: - scatter_axis pie_chart_config: allOf: - $ref: '#/definitions/base_chart_config' - type: object properties: selected_chart_type: enum: [ 'pie' ] pie_chart_axis: $ref: '#/definitions/pie_chart_axis' pie_display_label_as: type: string enum: [ 'percent', 'number' ] pie_show_inner_label: type: boolean pie_inner_label_aggregate: type: string enum: [ 'sum', 'average', 'median', 'max', 'min', 'count' ] pie_inner_label_title: type: string pie_label_position: type: string enum: [ 'inside', 'outside', 'none' ] pie_donut_width: type: number pie_minimum_slice_percentage: type: number required: - pie_chart_axis combo_chart_config: allOf: - $ref: '#/definitions/base_chart_config' - type: object properties: selected_chart_type: enum: [ 'combo' ] combo_chart_axis: $ref: '#/definitions/combo_chart_axis' required: - combo_chart_axis metric_chart_config: allOf: - $ref: '#/definitions/base_chart_config' - type: object properties: selected_chart_type: enum: [ 'metric' ] metric_column_id: type: string description: 'Required. The column used for the metric's numeric value.' metric_value_aggregate: type: string enum: [ 'sum', 'average', 'median', 'max', 'min', 'count', 'first' ] metric_header: type: string description: 'If omitted, the column_id is used as default label.' metric_sub_header: type: string metric_value_label: type: string description: 'If omitted, the label is derived from metric_column_id + aggregator.' required: - metric_column_id table_chart_config: allOf: - $ref: '#/definitions/base_chart_config' - type: object properties: selected_chart_type: enum: [ 'table' ] table_column_order: type: array items: type: string table_column_widths: type: object additionalProperties: type: number table_header_background_color: type: string table_header_font_color: type: string table_column_font_color: type: string required: [] description: > For table type, the axis concept is irrelevant; user may specify column order, widths, colors, etc." - }, - "line_numbers": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "Array of line numbers where modifications should be applied" - } - } + "name": "bulk_modify_metrics", + "description": "Modifies existing metric configuration files by applying specified modifications to their content", + "strict": true, + "parameters": { + "type": "object", + "required": [ + "files" + ], + "properties": { + "files": { + "type": "array", + "description": "List of files to modify with their corresponding modifications", + "items": { + "type": "object", + "required": [ + "id", + "file_name", + "modifications" + ], + "properties": { + "id": { + "type": "string", + "description": "UUID of the file to modify" }, - "description": "List of modifications to apply to the file" - } - }, - "additionalProperties": false - }, - "description": "List of files to modify with their corresponding modifications" - } - }, - "additionalProperties": false - } - }) + "file_name": { + "type": "string", + "description": "Name of the file to modify" + }, + "modifications": { + "type": "array", + "description": "List of modifications to apply to the file", + "items": { + "type": "object", + "required": [ + "new_content", + "line_numbers" + ], + "properties": { + "new_content": { + "type": "string", + "description": "The new content to be inserted at the specified line numbers." + }, + "line_numbers": { + "type": "array", + "description": "Array of line numbers where modifications should be applied", + "items": { + "type": "integer" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }) } } diff --git a/api/src/utils/tools/file_tools/send_assets_to_user.rs b/api/src/utils/tools/file_tools/send_assets_to_user.rs new file mode 100644 index 000000000..acbd23b79 --- /dev/null +++ b/api/src/utils/tools/file_tools/send_assets_to_user.rs @@ -0,0 +1,89 @@ +use std::sync::Arc; + +use anyhow::Result; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use uuid::Uuid; + +use crate::utils::{agent::Agent, tools::ToolExecutor}; +use litellm::ToolCall; + +#[derive(Debug, Serialize, Deserialize)] +pub struct SendToUserParams { + metric_id: String, +} + +#[derive(Debug, Serialize)] +pub struct SendToUserOutput { + success: bool, +} + +pub struct SendAssetsToUserTool { + agent: Arc, +} + +impl SendAssetsToUserTool { + pub fn new(agent: Arc) -> Self { + Self { agent } + } +} + +#[async_trait] +impl ToolExecutor for SendAssetsToUserTool { + type Output = SendToUserOutput; + type Params = SendToUserParams; + + async fn execute(&self, params: Self::Params) -> Result { + // TODO: Implement actual send to user logic + Ok(SendToUserOutput { success: true }) + } + + fn get_name(&self) -> String { + "decide_assets_to_return".to_string() + } + + fn get_schema(&self) -> Value { + serde_json::json!({ + "name": "decide_assets_to_return", + "description": "Use after you have created or modified any assets (metrics or dashboards) to specify exactly which assets to present in the final response. If you have not created or modified any assets, do not call this action.", + "strict": true, + "parameters": { + "type": "object", + "required": [ + "assets_to_return", + "ticket_description" + ], + "properties": { + "assets_to_return": { + "type": "array", + "description": "List of assets to present in the final response, each with an ID and a name", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the asset" + }, + "name": { + "type": "string", + "description": "Name of the asset" + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": false + } + }, + "ticket_description": { + "type": "string", + "description": "Description of the ticket related to the assets" + } + }, + "additionalProperties": false + } + }) + } +} diff --git a/api/src/utils/tools/file_tools/send_files_to_user.rs b/api/src/utils/tools/file_tools/send_files_to_user.rs deleted file mode 100644 index a2a4a16db..000000000 --- a/api/src/utils/tools/file_tools/send_files_to_user.rs +++ /dev/null @@ -1,60 +0,0 @@ -use anyhow::Result; -use async_trait::async_trait; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use uuid::Uuid; - -use crate::utils::tools::ToolExecutor; -use litellm::ToolCall; - -#[derive(Debug, Serialize, Deserialize)] -pub struct SendToUserParams { - metric_id: String, -} - -#[derive(Debug, Serialize)] -pub struct SendToUserOutput { - success: bool, -} - -pub struct SendFilesToUserTool; - -impl SendFilesToUserTool { - pub fn new() -> Self { - Self - } -} - -#[async_trait] -impl ToolExecutor for SendFilesToUserTool { - type Output = SendToUserOutput; - type Params = SendToUserParams; - - async fn execute(&self, params: Self::Params) -> Result { - // TODO: Implement actual send to user logic - Ok(SendToUserOutput { success: true }) - } - - fn get_name(&self) -> String { - "send_to_user".to_string() - } - - fn get_schema(&self) -> Value { - serde_json::json!({ - "name": "send_to_user", - "strict": true, - "parameters": { - "type": "object", - "required": ["metric_id"], - "properties": { - "metric_id": { - "type": "string", - "description": "The ID of the metric to send to the user" - } - }, - "additionalProperties": false - }, - "description": "Sends a metric to the user by its ID." - }) - } -}