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 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>,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,83 +360,87 @@ 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
|
||||||
let metric_file = MetricFile {
|
for file_content in &file.files {
|
||||||
id: Uuid::new_v4(),
|
match file_content.file_type.as_str() {
|
||||||
name: file.file_name.clone(),
|
"metric" => {
|
||||||
file_name: format!(
|
let metric_file = MetricFile {
|
||||||
"{}",
|
id: Uuid::new_v4(),
|
||||||
file.file_name.to_lowercase().replace(' ', "_")
|
name: file_content.file_name.clone(),
|
||||||
),
|
file_name: format!(
|
||||||
content: serde_json::to_value(&file_content)?,
|
"{}",
|
||||||
verification: Verification::NotRequested,
|
file_content.file_name.to_lowercase().replace(' ', "_")
|
||||||
evaluation_obj: None,
|
),
|
||||||
evaluation_summary: None,
|
content: serde_json::to_value(&file_content.content)?,
|
||||||
evaluation_score: None,
|
verification: Verification::NotRequested,
|
||||||
organization_id: organization_id.clone(),
|
evaluation_obj: None,
|
||||||
created_by: user_id.clone(),
|
evaluation_summary: None,
|
||||||
created_at: Utc::now(),
|
evaluation_score: None,
|
||||||
updated_at: Utc::now(),
|
organization_id: organization_id.clone(),
|
||||||
deleted_at: None,
|
created_by: user_id.clone(),
|
||||||
};
|
created_at: Utc::now(),
|
||||||
|
updated_at: Utc::now(),
|
||||||
|
deleted_at: None,
|
||||||
|
};
|
||||||
|
|
||||||
insert_into(metric_files::table)
|
insert_into(metric_files::table)
|
||||||
.values(&metric_file)
|
.values(&metric_file)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let message_to_file = MessageToFile {
|
let message_to_file = MessageToFile {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
message_id: message.id,
|
message_id: message.id,
|
||||||
file_id: metric_file.id,
|
file_id: metric_file.id,
|
||||||
created_at: Utc::now(),
|
created_at: Utc::now(),
|
||||||
updated_at: Utc::now(),
|
updated_at: Utc::now(),
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
insert_into(messages_to_files::table)
|
insert_into(messages_to_files::table)
|
||||||
.values(&message_to_file)
|
.values(&message_to_file)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
"dashboard" => {
|
||||||
BusterReasoningMessage::File(file) if file.file_type == "dashboard" => {
|
let dashboard_file = DashboardFile {
|
||||||
if let Some(file_content) = &file.file {
|
id: Uuid::new_v4(),
|
||||||
let dashboard_file = DashboardFile {
|
name: file_content.file_name.clone(),
|
||||||
id: Uuid::new_v4(),
|
file_name: format!(
|
||||||
name: file.file_name.clone(),
|
"{}",
|
||||||
file_name: format!(
|
file_content.file_name.to_lowercase().replace(' ', "_")
|
||||||
"{}",
|
),
|
||||||
file.file_name.to_lowercase().replace(' ', "_")
|
content: serde_json::to_value(&file_content.content)?,
|
||||||
),
|
filter: None,
|
||||||
content: serde_json::to_value(&file_content)?,
|
organization_id: organization_id.clone(),
|
||||||
filter: None,
|
created_by: user_id.clone(),
|
||||||
organization_id: organization_id.clone(),
|
created_at: Utc::now(),
|
||||||
created_by: user_id.clone(),
|
updated_at: Utc::now(),
|
||||||
created_at: Utc::now(),
|
deleted_at: None,
|
||||||
updated_at: Utc::now(),
|
};
|
||||||
deleted_at: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
insert_into(dashboard_files::table)
|
insert_into(dashboard_files::table)
|
||||||
.values(&dashboard_file)
|
.values(&dashboard_file)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let message_to_file = MessageToFile {
|
let message_to_file = MessageToFile {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
message_id: message.id,
|
message_id: message.id,
|
||||||
file_id: dashboard_file.id,
|
file_id: dashboard_file.id,
|
||||||
created_at: Utc::now(),
|
created_at: Utc::now(),
|
||||||
updated_at: Utc::now(),
|
updated_at: Utc::now(),
|
||||||
deleted_at: None,
|
deleted_at: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
insert_into(messages_to_files::table)
|
insert_into(messages_to_files::table)
|
||||||
.values(&message_to_file)
|
.values(&message_to_file)
|
||||||
.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,
|
||||||
|
|
|
@ -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,40 +245,53 @@ 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();
|
|
||||||
|
|
||||||
if has_name && has_yml_content {
|
for file in files {
|
||||||
let name = last_file.get("name").and_then(Value::as_str).unwrap_or("");
|
if let Some(file_obj) = file.as_object() {
|
||||||
let yml_content = last_file
|
let has_name = file_obj.get("name").and_then(Value::as_str).is_some();
|
||||||
.get("yml_content")
|
let has_yml_content = file_obj.get("yml_content").is_some();
|
||||||
.and_then(Value::as_str)
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
let mut current_lines = Vec::new();
|
if has_name && has_yml_content {
|
||||||
for (i, line) in yml_content.lines().enumerate() {
|
let name = file_obj.get("name").and_then(Value::as_str).unwrap_or("");
|
||||||
current_lines.push(BusterFileLine {
|
let yml_content = file_obj
|
||||||
line_number: i + 1,
|
.get("yml_content")
|
||||||
text: line.to_string(),
|
.and_then(Value::as_str)
|
||||||
modified: Some(false),
|
.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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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")),
|
||||||
|
|
Loading…
Reference in New Issue