674 lines · 24.7 KB · Rust
RawDownload
mod assertions;mod example;mod example2;mod examples;mod ids;mod instance;mod templates;mod tool_metrics; use assertions::display_error_row;use instance::{ExampleInstance, JudgeOutput, RunOutput, run_git};pub(crate) use tool_metrics::*; use ::fs::RealFs;use anyhow::anyhow;use clap::Parser;use client::{Client, ProxySettings, UserStore};use collections::{HashMap, HashSet};use extension::ExtensionHostProxy;use futures::future;use gpui::http_client::{Uri, read_proxy_from_env};use gpui::{App, AppContext, Application, AsyncApp, Entity, SemanticVersion, UpdateGlobal};use gpui_tokio::Tokio;use language::LanguageRegistry;use language_model::{ConfiguredModel, LanguageModel, LanguageModelRegistry};use node_runtime::{NodeBinaryOptions, NodeRuntime};use project::Project;use project::project_settings::ProjectSettings;use prompt_store::PromptBuilder;use release_channel::AppVersion;use reqwest_client::ReqwestClient;use settings::{Settings, SettingsStore};use std::cell::RefCell;use std::collections::VecDeque;use std::env;use std::path::{Path, PathBuf};use std::rc::Rc;use std::sync::Arc;use util::ResultExt as _; #[derive(Parser, Debug)]#[command(name = "eval", disable_version_flag = true)]struct Args {    /// Runs all examples and threads that contain these substrings. If unspecified, all examples and threads are run.    #[arg(value_name = "EXAMPLE_SUBSTRING")]    filter: Vec<String>,    /// Model to use (default: "claude-3-7-sonnet-latest")    #[arg(long, default_value = "claude-3-7-sonnet-latest")]    model: String,    #[arg(long, value_delimiter = ',', default_value = "rs,ts")]    languages: Vec<String>,    /// How many times to run each example.    #[arg(long, default_value = "1")]    repetitions: usize,    /// Maximum number of examples to run concurrently.    #[arg(long, default_value = "10")]    concurrency: usize,} fn main() {    env_logger::init();     let system_id = ids::get_or_create_id(&ids::eval_system_id_path()).ok();    let installation_id = ids::get_or_create_id(&ids::eval_installation_id_path()).ok();    let session_id = uuid::Uuid::new_v4().to_string();    let run_timestamp = chrono::Local::now().format("%Y-%m-%d_%H-%M-%S");    let run_id = match env::var("GITHUB_RUN_ID") {        Ok(run_id) => format!("github/{}", run_id),        Err(_) => format!("local/{}", run_timestamp),    };     let root_dir = Path::new(std::env!("CARGO_MANIFEST_DIR"))        .parent()        .unwrap()        .parent()        .unwrap()        .canonicalize()        .unwrap();    let eval_crate_dir = root_dir.join("crates").join("eval");    let repos_dir = eval_crate_dir.join("repos");    let worktrees_dir = eval_crate_dir.join("worktrees");    let examples_dir = eval_crate_dir.join("src").join("examples");    let run_dir = eval_crate_dir        .join("runs")        .join(format!("{}", run_timestamp));    std::fs::create_dir_all(&run_dir).unwrap();    std::fs::create_dir_all(&repos_dir).unwrap();    std::fs::create_dir_all(&worktrees_dir).unwrap();    std::fs::create_dir_all(&examples_dir).unwrap();    std::fs::create_dir_all(&paths::config_dir()).unwrap();     let zed_commit_sha = commit_sha_for_path(&root_dir);    let zed_branch_name = git_branch_for_path(&root_dir);    let args = Args::parse();    let languages: HashSet<String> = args.languages.into_iter().collect();     let http_client = Arc::new(ReqwestClient::new());    let app = Application::headless().with_http_client(http_client.clone());    let all_threads = examples::all(&examples_dir);     app.run(move |cx| {        let app_state = init(cx);         let telemetry = app_state.client.telemetry();        telemetry.start(system_id, installation_id, session_id, cx);         let enable_telemetry = env::var("ZED_EVAL_TELEMETRY").map_or(false, |value| value == "1")            && telemetry.has_checksum_seed();        if enable_telemetry {            println!("Telemetry enabled");            telemetry::event!(                "Agent Eval Started",                zed_commit_sha = zed_commit_sha,                zed_branch_name = zed_branch_name,                run_id = run_id,            );        }         let mut cumulative_tool_metrics = ToolMetrics::default();         let model_registry = LanguageModelRegistry::read_global(cx);        let model = find_model("claude-3-7-sonnet-latest", model_registry, cx).unwrap();        let model_provider_id = model.provider_id();        let model_provider = model_registry.provider(&model_provider_id).unwrap();         LanguageModelRegistry::global(cx).update(cx, |registry, cx| {            registry.set_default_model(                Some(ConfiguredModel {                    provider: model_provider.clone(),                    model: model.clone(),                }),                cx,            );        });         let authenticate_task = model_provider.authenticate(cx);         cx.spawn(async move |cx| {            authenticate_task.await.unwrap();             let mut examples = Vec::new();             const COLORS: [&str; 12] = [                "\x1b[31m", // Red                "\x1b[32m", // Green                "\x1b[33m", // Yellow                "\x1b[34m", // Blue                "\x1b[35m", // Magenta                "\x1b[36m", // Cyan                "\x1b[91m", // Bright Red                "\x1b[92m", // Bright Green                "\x1b[93m", // Bright Yellow                "\x1b[94m", // Bright Blue                "\x1b[95m", // Bright Magenta                "\x1b[96m", // Bright Cyan            ];             let mut skipped = Vec::new();             for thread in all_threads {                let meta = thread.meta();                if !args.filter.is_empty() && !args.filter.iter().any(|sub| meta.name.contains(sub))                {                    skipped.push(meta.name);                    continue;                }                 if meta.language_server.map_or(false, |language| {                    !languages.contains(&language.file_extension)                }) {                    skipped.push(meta.name);                    continue;                }                 // TODO: This creates a worktree per repetition. Ideally these examples should                // either be run sequentially on the same worktree, or reuse worktrees when there                // are more examples to run than the concurrency limit.                for repetition_number in 0..args.repetitions {                    let example_instance = ExampleInstance::new(                        thread.clone(),                        &repos_dir,                        &run_dir,                        &worktrees_dir,                        repetition_number,                    );                     examples.push(example_instance);                }            }             if !skipped.is_empty() {                println!("Skipped threads: {}", skipped.join(", "));            }             if examples.is_empty() {                eprintln!("Filter matched no examples");                return cx.update(|cx| cx.quit());            }             let mut repo_urls = HashSet::default();            let mut clone_tasks = Vec::new();             let max_name_width = examples                .iter()                .map(|e| e.worktree_name().len())                .max()                .unwrap_or(0);             for (i, example_instance) in examples.iter_mut().enumerate() {                let color = COLORS[i % COLORS.len()].to_string();                example_instance.set_log_prefix_style(&color, max_name_width);                 println!(                    "{}Logging to: {}",                    example_instance.log_prefix,                    example_instance.run_directory.display()                );                 let repo_url = example_instance.repo_url();                if repo_urls.insert(repo_url.clone()) {                    let repo_path = example_instance.repo_path.clone();                     if !repo_path.join(".git").is_dir() {                        println!(                            "{:<width$} < {}",                            "↓ Cloning",                            repo_url,                            width = max_name_width                        );                         let git_task = cx.spawn(async move |_cx| {                            std::fs::create_dir_all(&repo_path)?;                            run_git(&repo_path, &["init"]).await?;                            run_git(&repo_path, &["remote", "add", "origin", &repo_url]).await                        });                         clone_tasks.push(git_task);                    } else {                        println!(                            "{:<width$}  < {}",                            "✔︎ Already cloned",                            repo_url,                            width = max_name_width                        );                         let actual_origin =                            run_git(&repo_path, &["remote", "get-url", "origin"]).await?;                        if actual_origin != repo_url {                            return Err(anyhow!(                                "remote origin {} does not match expected origin {}",                                actual_origin,                                repo_url,                            ));                        }                    }                }            }             future::join_all(clone_tasks).await;             for example_instance in examples.iter_mut() {                example_instance.fetch().await?;            }             let examples = Rc::new(RefCell::new(VecDeque::from(examples)));            let results_by_example_name = Rc::new(RefCell::new(HashMap::default()));             future::join_all((0..args.concurrency).map(|_| {                let app_state = app_state.clone();                let model = model.clone();                let zed_commit_sha = zed_commit_sha.clone();                let zed_branch_name = zed_branch_name.clone();                let run_id = run_id.clone();                let examples = examples.clone();                let results = results_by_example_name.clone();                cx.spawn(async move |cx| {                    loop {                        let Some(mut example) = examples.borrow_mut().pop_front() else {                            break;                        };                        let result = async {                            example.setup().await?;                            let run_output = cx                                .update(|cx| example.run(model.clone(), app_state.clone(), cx))?                                .await?;                            let judge_output = judge_example(                                example.clone(),                                model.clone(),                                &zed_commit_sha,                                &zed_branch_name,                                &run_id,                                &run_output,                                enable_telemetry,                                cx,                            )                            .await;                            anyhow::Ok((run_output, judge_output))                        }                        .await;                        results                            .borrow_mut()                            .entry(example.name.clone())                            .or_insert(Vec::new())                            .push((example.clone(), result));                    }                })            }))            .await;             print_h1("EVAL RESULTS");             let mut diff_scores = Vec::new();            let mut thread_scores = Vec::new();            let mut programmatic_scores = Vec::new();            let mut error_count = 0;             for (example_name, results) in results_by_example_name.borrow_mut().iter_mut() {                print_h2(&example_name);                 results.sort_unstable_by_key(|(example, _)| example.repetition);                let mut example_cumulative_tool_metrics = ToolMetrics::default();                 let mut table_rows = String::new();                 for (example, result) in results.iter() {                    match result {                        Err(err) => {                            display_error_row(                                &mut table_rows,                                example.repetition,                                err.to_string(),                            )?;                            error_count += 1;                        }                        Ok((run_output, judge_output)) => {                            cumulative_tool_metrics.merge(&run_output.tool_metrics);                            example_cumulative_tool_metrics.merge(&run_output.tool_metrics);                             if !run_output.programmatic_assertions.total_count() > 0 {                                for assertion in &run_output.programmatic_assertions.ran {                                    assertions::display_table_row(                                        &mut table_rows,                                        example.repetition,                                        assertion,                                    )?;                                }                                 programmatic_scores                                    .push(run_output.programmatic_assertions.passed_percentage())                            }                             if !judge_output.diff.is_empty() {                                diff_scores.push(judge_output.diff.passed_percentage());                                 for assertion in &judge_output.diff.ran {                                    assertions::display_table_row(                                        &mut table_rows,                                        example.repetition,                                        assertion,                                    )?;                                }                            }                             if !judge_output.thread.is_empty() {                                thread_scores.push(judge_output.thread.passed_percentage());                                 for assertion in &judge_output.thread.ran {                                    assertions::display_table_row(                                        &mut table_rows,                                        example.repetition,                                        assertion,                                    )?;                                }                            }                        }                    }                }                 if !table_rows.is_empty() {                    assertions::print_table_header();                    print!("{}", table_rows);                     assertions::print_table_divider();                     for (example, result) in results.iter() {                        if let Ok((run_output, judge_output)) = result {                            assertions::print_table_round_summary(                                &example.repetition.to_string(),                                [                                    &run_output.programmatic_assertions,                                    &judge_output.diff,                                    &judge_output.thread,                                ]                                .into_iter(),                            )                        }                    }                     assertions::print_table_divider();                     assertions::print_table_round_summary(                        "avg",                        results.iter().flat_map(|(_, result)| {                            result.iter().flat_map(|(run_output, judge_output)| {                                [                                    &run_output.programmatic_assertions,                                    &judge_output.diff,                                    &judge_output.thread,                                ]                                .into_iter()                            })                        }),                    );                     assertions::print_table_footer();                }                 if !example_cumulative_tool_metrics.is_empty() {                    println!("{}", &example_cumulative_tool_metrics);                }            }             if results_by_example_name.borrow().len() > 1 {                print_h1("AGGREGATE");                 if error_count > 0 {                    println!("\n{error_count} examples failed to run!");                }                 let programmatic_score_count = programmatic_scores.len();                if programmatic_score_count > 0 {                    let average_programmatic_score = (programmatic_scores.into_iter().sum::<f32>()                        / (programmatic_score_count as f32))                        .floor();                    println!("Average programmatic score: {average_programmatic_score}%");                }                 let diff_score_count = diff_scores.len();                if diff_score_count > 0 {                    let average_diff_score =                        (diff_scores.into_iter().sum::<f32>() / (diff_score_count as f32)).floor();                    println!("Average diff score: {average_diff_score}%");                }                 let thread_score_count = thread_scores.len();                 if thread_score_count > 0 {                    let average_thread_score = (thread_scores.into_iter().sum::<f32>()                        / (thread_score_count as f32))                        .floor();                    println!("Average thread score: {average_thread_score}%");                }                 println!("");                 print_h2("CUMULATIVE TOOL METRICS");                println!("{}", cumulative_tool_metrics);            }             app_state.client.telemetry().flush_events().await;             cx.update(|cx| cx.quit())        })        .detach_and_log_err(cx);    });} /// Subset of `workspace::AppState` needed by `HeadlessAssistant`, with additional fields.pub struct AgentAppState {    pub languages: Arc<LanguageRegistry>,    pub client: Arc<Client>,    pub user_store: Entity<UserStore>,    pub fs: Arc<dyn fs::Fs>,    pub node_runtime: NodeRuntime,     // Additional fields not present in `workspace::AppState`.    pub prompt_builder: Arc<PromptBuilder>,} pub fn init(cx: &mut App) -> Arc<AgentAppState> {    release_channel::init(SemanticVersion::default(), cx);    gpui_tokio::init(cx);     let mut settings_store = SettingsStore::new(cx);    settings_store        .set_default_settings(settings::default_settings().as_ref(), cx)        .unwrap();    cx.set_global(settings_store);    client::init_settings(cx);     // Set User-Agent so we can download language servers from GitHub    let user_agent = format!(        "Zed/{} ({}; {})",        AppVersion::global(cx),        std::env::consts::OS,        std::env::consts::ARCH    );    let proxy_str = ProxySettings::get_global(cx).proxy.to_owned();    let proxy_url = proxy_str        .as_ref()

Lines 1–500 of 674.