mirror of https://github.com/buster-so/buster.git
Merge branch 'evals' into big-nate/bus-939-create-new-structure-for-chats
This commit is contained in:
commit
2eb938f597
|
@ -0,0 +1,22 @@
|
|||
# Buster API Development Guide
|
||||
|
||||
## Build Commands
|
||||
- `make dev` - Start the development environment
|
||||
- `make stop` - Stop the development environment
|
||||
- `cargo test -- --test-threads=1 --nocapture` - Run all tests
|
||||
- `cargo test <test_name> -- --nocapture` - Run a specific test
|
||||
- `cargo clippy` - Run the linter
|
||||
- `cargo build` - Build the project
|
||||
- `cargo watch -x run` - Watch for changes and run
|
||||
|
||||
## Code Style Guidelines
|
||||
- **Error Handling**: Use `anyhow::Result` for functions that can fail. Create specialized errors with `thiserror`.
|
||||
- **Naming**: Use snake_case for variables/functions, CamelCase for types/structs.
|
||||
- **Types**: Put shared types in `types/`, route-specific types in route files.
|
||||
- **Organization**: Follow the repo structure in README.md.
|
||||
- **Imports**: Group imports by std lib, external crates, and internal modules.
|
||||
- **Testing**: Write tests directly in route files. Use `tokio::test` attribute for async tests.
|
||||
- **Documentation**: Document public APIs. Use rustdoc-style comments.
|
||||
- **Async**: Use async/await with Tokio. Handle futures properly.
|
||||
- **Validation**: Validate inputs with proper error messages.
|
||||
- **Security**: Never log secrets or sensitive data.
|
|
@ -42,8 +42,9 @@ pub struct Message {
|
|||
pub request_message: String,
|
||||
pub response_messages: Value,
|
||||
pub reasoning: Value,
|
||||
pub final_reasoning_message: String,
|
||||
pub title: String,
|
||||
pub raw_llm_messages: Value,
|
||||
pub final_reasoning_message: String,
|
||||
pub chat_id: Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
|
|
|
@ -320,6 +320,7 @@ diesel::table! {
|
|||
response_messages -> Jsonb,
|
||||
reasoning -> Jsonb,
|
||||
title -> Text,
|
||||
raw_llm_messages -> Jsonb,
|
||||
final_reasoning_message -> Text,
|
||||
chat_id -> Uuid,
|
||||
created_at -> Timestamptz,
|
||||
|
|
|
@ -261,6 +261,7 @@ pub async fn post_chat_handler(
|
|||
reasoning: serde_json::to_value(&reasoning_messages)?,
|
||||
final_reasoning_message,
|
||||
title: title.title.clone().unwrap_or_default(),
|
||||
raw_llm_messages: Value::Array(vec![]),
|
||||
};
|
||||
|
||||
// Insert message into database
|
||||
|
@ -359,83 +360,87 @@ async fn process_completed_files(
|
|||
for container in transformed_messages {
|
||||
match container {
|
||||
BusterContainer::ReasoningMessage(msg) => match &msg.reasoning {
|
||||
BusterReasoningMessage::File(file) if file.file_type == "metric" => {
|
||||
if let Some(file_content) = &file.file {
|
||||
let metric_file = MetricFile {
|
||||
id: Uuid::new_v4(),
|
||||
name: file.file_name.clone(),
|
||||
file_name: format!(
|
||||
"{}",
|
||||
file.file_name.to_lowercase().replace(' ', "_")
|
||||
),
|
||||
content: serde_json::to_value(&file_content)?,
|
||||
verification: Verification::NotRequested,
|
||||
evaluation_obj: None,
|
||||
evaluation_summary: None,
|
||||
evaluation_score: None,
|
||||
organization_id: organization_id.clone(),
|
||||
created_by: user_id.clone(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
};
|
||||
BusterReasoningMessage::File(file) if file.message_type == "files" => {
|
||||
// Process each file in the files array
|
||||
for file_content in &file.files {
|
||||
match file_content.file_type.as_str() {
|
||||
"metric" => {
|
||||
let metric_file = MetricFile {
|
||||
id: Uuid::new_v4(),
|
||||
name: file_content.file_name.clone(),
|
||||
file_name: format!(
|
||||
"{}",
|
||||
file_content.file_name.to_lowercase().replace(' ', "_")
|
||||
),
|
||||
content: serde_json::to_value(&file_content.content)?,
|
||||
verification: Verification::NotRequested,
|
||||
evaluation_obj: None,
|
||||
evaluation_summary: None,
|
||||
evaluation_score: None,
|
||||
organization_id: organization_id.clone(),
|
||||
created_by: user_id.clone(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
};
|
||||
|
||||
insert_into(metric_files::table)
|
||||
.values(&metric_file)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
insert_into(metric_files::table)
|
||||
.values(&metric_file)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
let message_to_file = MessageToFile {
|
||||
id: Uuid::new_v4(),
|
||||
message_id: message.id,
|
||||
file_id: metric_file.id,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
};
|
||||
let message_to_file = MessageToFile {
|
||||
id: Uuid::new_v4(),
|
||||
message_id: message.id,
|
||||
file_id: metric_file.id,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
};
|
||||
|
||||
insert_into(messages_to_files::table)
|
||||
.values(&message_to_file)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
BusterReasoningMessage::File(file) if file.file_type == "dashboard" => {
|
||||
if let Some(file_content) = &file.file {
|
||||
let dashboard_file = DashboardFile {
|
||||
id: Uuid::new_v4(),
|
||||
name: file.file_name.clone(),
|
||||
file_name: format!(
|
||||
"{}",
|
||||
file.file_name.to_lowercase().replace(' ', "_")
|
||||
),
|
||||
content: serde_json::to_value(&file_content)?,
|
||||
filter: None,
|
||||
organization_id: organization_id.clone(),
|
||||
created_by: user_id.clone(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
};
|
||||
insert_into(messages_to_files::table)
|
||||
.values(&message_to_file)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
}
|
||||
"dashboard" => {
|
||||
let dashboard_file = DashboardFile {
|
||||
id: Uuid::new_v4(),
|
||||
name: file_content.file_name.clone(),
|
||||
file_name: format!(
|
||||
"{}",
|
||||
file_content.file_name.to_lowercase().replace(' ', "_")
|
||||
),
|
||||
content: serde_json::to_value(&file_content.content)?,
|
||||
filter: None,
|
||||
organization_id: organization_id.clone(),
|
||||
created_by: user_id.clone(),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
};
|
||||
|
||||
insert_into(dashboard_files::table)
|
||||
.values(&dashboard_file)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
insert_into(dashboard_files::table)
|
||||
.values(&dashboard_file)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
let message_to_file = MessageToFile {
|
||||
id: Uuid::new_v4(),
|
||||
message_id: message.id,
|
||||
file_id: dashboard_file.id,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
};
|
||||
let message_to_file = MessageToFile {
|
||||
id: Uuid::new_v4(),
|
||||
message_id: message.id,
|
||||
file_id: dashboard_file.id,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
deleted_at: None,
|
||||
};
|
||||
|
||||
insert_into(messages_to_files::table)
|
||||
.values(&message_to_file)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
insert_into(messages_to_files::table)
|
||||
.values(&message_to_file)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
@ -525,20 +530,28 @@ pub struct BusterThoughtPill {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct BusterReasoningFile {
|
||||
pub struct BusterFileContent {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub message_type: String,
|
||||
pub file_type: String,
|
||||
pub file_name: String,
|
||||
pub version_number: i32,
|
||||
pub version_id: String,
|
||||
pub status: String,
|
||||
pub file: Option<Vec<BusterFileLine>>,
|
||||
pub filter_version_id: Option<String>,
|
||||
pub content: Vec<BusterFileLine>,
|
||||
pub metadata: Option<Vec<BusterFileMetadata>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct BusterReasoningFile {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub message_type: String,
|
||||
pub title: String,
|
||||
pub secondary_title: String,
|
||||
pub status: String,
|
||||
pub files: Vec<BusterFileContent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct BusterFileLine {
|
||||
pub line_number: usize,
|
||||
|
|
|
@ -4,7 +4,7 @@ use uuid::Uuid;
|
|||
|
||||
use super::post_chat_handler::{
|
||||
BusterFileLine, BusterReasoningFile, BusterReasoningMessage, BusterReasoningPill,
|
||||
BusterReasoningText, BusterThoughtPill, BusterThoughtPillContainer,
|
||||
BusterReasoningText, BusterThoughtPill, BusterThoughtPillContainer, BusterFileContent,
|
||||
};
|
||||
|
||||
pub struct StreamingParser {
|
||||
|
@ -245,40 +245,53 @@ impl StreamingParser {
|
|||
file_type: String,
|
||||
) -> Result<Option<BusterReasoningMessage>> {
|
||||
if let Some(files) = value.get("files").and_then(Value::as_array) {
|
||||
if let Some(last_file) = files.last().and_then(Value::as_object) {
|
||||
let has_name = last_file.get("name").and_then(Value::as_str).is_some();
|
||||
let has_yml_content = last_file.get("yml_content").is_some();
|
||||
let mut file_contents = Vec::new();
|
||||
|
||||
if has_name && has_yml_content {
|
||||
let name = last_file.get("name").and_then(Value::as_str).unwrap_or("");
|
||||
let yml_content = last_file
|
||||
.get("yml_content")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
for file in files {
|
||||
if let Some(file_obj) = file.as_object() {
|
||||
let has_name = file_obj.get("name").and_then(Value::as_str).is_some();
|
||||
let has_yml_content = file_obj.get("yml_content").is_some();
|
||||
|
||||
let mut current_lines = Vec::new();
|
||||
for (i, line) in yml_content.lines().enumerate() {
|
||||
current_lines.push(BusterFileLine {
|
||||
line_number: i + 1,
|
||||
text: line.to_string(),
|
||||
modified: Some(false),
|
||||
if has_name && has_yml_content {
|
||||
let name = file_obj.get("name").and_then(Value::as_str).unwrap_or("");
|
||||
let yml_content = file_obj
|
||||
.get("yml_content")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
|
||||
let mut current_lines = Vec::new();
|
||||
for (i, line) in yml_content.lines().enumerate() {
|
||||
current_lines.push(BusterFileLine {
|
||||
line_number: i + 1,
|
||||
text: line.to_string(),
|
||||
modified: Some(false),
|
||||
});
|
||||
}
|
||||
|
||||
file_contents.push(BusterFileContent {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
file_type: file_type.clone(),
|
||||
file_name: name.to_string(),
|
||||
version_number: 1,
|
||||
version_id: Uuid::new_v4().to_string(),
|
||||
status: "loading".to_string(),
|
||||
content: current_lines,
|
||||
metadata: None,
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(Some(BusterReasoningMessage::File(BusterReasoningFile {
|
||||
id,
|
||||
message_type: "file".to_string(),
|
||||
file_type,
|
||||
file_name: name.to_string(),
|
||||
version_number: 1,
|
||||
version_id: Uuid::new_v4().to_string(),
|
||||
status: "loading".to_string(),
|
||||
file: Some(current_lines),
|
||||
filter_version_id: None,
|
||||
metadata: None,
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
if !file_contents.is_empty() {
|
||||
return Ok(Some(BusterReasoningMessage::File(BusterReasoningFile {
|
||||
id,
|
||||
message_type: "files".to_string(),
|
||||
title: format!("Creating {} files...", file_type),
|
||||
secondary_title: String::new(),
|
||||
status: "loading".to_string(),
|
||||
files: file_contents,
|
||||
})));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ CREATE TABLE messages (
|
|||
response_messages JSONB NOT NULL,
|
||||
reasoning JSONB NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
raw_llm_messages JSONB NOT NULL,
|
||||
final_reasoning_message TEXT NOT NULL,
|
||||
chat_id UUID NOT NULL REFERENCES chats(id),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
|
|
|
@ -73,10 +73,6 @@ pub async fn update_user_handler(
|
|||
}
|
||||
};
|
||||
|
||||
if &auth_user.id == user_id {
|
||||
return Err(anyhow::anyhow!("Cannot update self"));
|
||||
};
|
||||
|
||||
match is_user_workspace_admin_or_data_admin(auth_user, &user_organization_id).await {
|
||||
Ok(true) => (),
|
||||
Ok(false) => return Err(anyhow::anyhow!("Insufficient permissions")),
|
||||
|
|
Loading…
Reference in New Issue