Merge branch 'evals' into big-nate/bus-939-create-new-structure-for-chats

This commit is contained in:
Nate Kelley 2025-03-04 13:12:03 -07:00
commit 2eb938f597
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
7 changed files with 157 additions and 110 deletions

22
api/CLAUDE.md Normal file
View File

@ -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.

View File

@ -42,8 +42,9 @@ pub struct Message {
pub request_message: String, pub request_message: String,
pub response_messages: Value, pub response_messages: Value,
pub reasoning: Value, pub reasoning: Value,
pub final_reasoning_message: String,
pub title: String, pub title: String,
pub raw_llm_messages: Value,
pub final_reasoning_message: String,
pub chat_id: Uuid, pub chat_id: Uuid,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,

View File

@ -320,6 +320,7 @@ diesel::table! {
response_messages -> Jsonb, response_messages -> Jsonb,
reasoning -> Jsonb, reasoning -> Jsonb,
title -> Text, title -> Text,
raw_llm_messages -> Jsonb,
final_reasoning_message -> Text, final_reasoning_message -> Text,
chat_id -> Uuid, chat_id -> Uuid,
created_at -> Timestamptz, created_at -> Timestamptz,

View File

@ -261,6 +261,7 @@ pub async fn post_chat_handler(
reasoning: serde_json::to_value(&reasoning_messages)?, reasoning: serde_json::to_value(&reasoning_messages)?,
final_reasoning_message, final_reasoning_message,
title: title.title.clone().unwrap_or_default(), title: title.title.clone().unwrap_or_default(),
raw_llm_messages: Value::Array(vec![]),
}; };
// Insert message into database // Insert message into database
@ -359,16 +360,19 @@ async fn process_completed_files(
for container in transformed_messages { for container in transformed_messages {
match container { match container {
BusterContainer::ReasoningMessage(msg) => match &msg.reasoning { BusterContainer::ReasoningMessage(msg) => match &msg.reasoning {
BusterReasoningMessage::File(file) if file.file_type == "metric" => { BusterReasoningMessage::File(file) if file.message_type == "files" => {
if let Some(file_content) = &file.file { // 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 { let metric_file = MetricFile {
id: Uuid::new_v4(), id: Uuid::new_v4(),
name: file.file_name.clone(), name: file_content.file_name.clone(),
file_name: format!( file_name: format!(
"{}", "{}",
file.file_name.to_lowercase().replace(' ', "_") file_content.file_name.to_lowercase().replace(' ', "_")
), ),
content: serde_json::to_value(&file_content)?, content: serde_json::to_value(&file_content.content)?,
verification: Verification::NotRequested, verification: Verification::NotRequested,
evaluation_obj: None, evaluation_obj: None,
evaluation_summary: None, evaluation_summary: None,
@ -399,17 +403,15 @@ async fn process_completed_files(
.execute(conn) .execute(conn)
.await?; .await?;
} }
} "dashboard" => {
BusterReasoningMessage::File(file) if file.file_type == "dashboard" => {
if let Some(file_content) = &file.file {
let dashboard_file = DashboardFile { let dashboard_file = DashboardFile {
id: Uuid::new_v4(), id: Uuid::new_v4(),
name: file.file_name.clone(), name: file_content.file_name.clone(),
file_name: format!( file_name: format!(
"{}", "{}",
file.file_name.to_lowercase().replace(' ', "_") file_content.file_name.to_lowercase().replace(' ', "_")
), ),
content: serde_json::to_value(&file_content)?, content: serde_json::to_value(&file_content.content)?,
filter: None, filter: None,
organization_id: organization_id.clone(), organization_id: organization_id.clone(),
created_by: user_id.clone(), created_by: user_id.clone(),
@ -437,6 +439,9 @@ async fn process_completed_files(
.execute(conn) .execute(conn)
.await?; .await?;
} }
_ => (),
}
}
} }
_ => (), _ => (),
}, },
@ -525,20 +530,28 @@ pub struct BusterThoughtPill {
} }
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
pub struct BusterReasoningFile { pub struct BusterFileContent {
pub id: String, pub id: String,
#[serde(rename = "type")]
pub message_type: String,
pub file_type: String, pub file_type: String,
pub file_name: String, pub file_name: String,
pub version_number: i32, pub version_number: i32,
pub version_id: String, pub version_id: String,
pub status: String, pub status: String,
pub file: Option<Vec<BusterFileLine>>, pub content: Vec<BusterFileLine>,
pub filter_version_id: Option<String>,
pub metadata: Option<Vec<BusterFileMetadata>>, 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)] #[derive(Debug, Serialize, Clone)]
pub struct BusterFileLine { pub struct BusterFileLine {
pub line_number: usize, pub line_number: usize,

View File

@ -4,7 +4,7 @@ use uuid::Uuid;
use super::post_chat_handler::{ use super::post_chat_handler::{
BusterFileLine, BusterReasoningFile, BusterReasoningMessage, BusterReasoningPill, BusterFileLine, BusterReasoningFile, BusterReasoningMessage, BusterReasoningPill,
BusterReasoningText, BusterThoughtPill, BusterThoughtPillContainer, BusterReasoningText, BusterThoughtPill, BusterThoughtPillContainer, BusterFileContent,
}; };
pub struct StreamingParser { pub struct StreamingParser {
@ -245,13 +245,16 @@ impl StreamingParser {
file_type: String, file_type: String,
) -> Result<Option<BusterReasoningMessage>> { ) -> Result<Option<BusterReasoningMessage>> {
if let Some(files) = value.get("files").and_then(Value::as_array) { 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 mut file_contents = Vec::new();
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(); 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();
if has_name && has_yml_content { if has_name && has_yml_content {
let name = last_file.get("name").and_then(Value::as_str).unwrap_or(""); let name = file_obj.get("name").and_then(Value::as_str).unwrap_or("");
let yml_content = last_file let yml_content = file_obj
.get("yml_content") .get("yml_content")
.and_then(Value::as_str) .and_then(Value::as_str)
.unwrap_or(""); .unwrap_or("");
@ -265,21 +268,31 @@ impl StreamingParser {
}); });
} }
return Ok(Some(BusterReasoningMessage::File(BusterReasoningFile { file_contents.push(BusterFileContent {
id, id: Uuid::new_v4().to_string(),
message_type: "file".to_string(), file_type: file_type.clone(),
file_type,
file_name: name.to_string(), file_name: name.to_string(),
version_number: 1, version_number: 1,
version_id: Uuid::new_v4().to_string(), version_id: Uuid::new_v4().to_string(),
status: "loading".to_string(), status: "loading".to_string(),
file: Some(current_lines), content: current_lines,
filter_version_id: None,
metadata: 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) Ok(None)
} }
} }

View File

@ -11,6 +11,7 @@ CREATE TABLE messages (
response_messages JSONB NOT NULL, response_messages JSONB NOT NULL,
reasoning JSONB NOT NULL, reasoning JSONB NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,
raw_llm_messages JSONB NOT NULL,
final_reasoning_message TEXT NOT NULL, final_reasoning_message TEXT NOT NULL,
chat_id UUID NOT NULL REFERENCES chats(id), chat_id UUID NOT NULL REFERENCES chats(id),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),

View File

@ -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 { match is_user_workspace_admin_or_data_admin(auth_user, &user_organization_id).await {
Ok(true) => (), Ok(true) => (),
Ok(false) => return Err(anyhow::anyhow!("Insufficient permissions")), Ok(false) => return Err(anyhow::anyhow!("Insufficient permissions")),