feat: multi-backend LLM layer with per-request choice on case page
Replace the single [llm] block in settings.toml with a curated catalog of LLM "backends" (provider + model + sampling params + system prompt) in server/src/analyze/backend.rs. Two backends ship initially: gpt_oss_120b (default, reasoning_effort=medium) and llama_3_1_405b (uses response_format: json_schema to sidestep the <|eot_id|> leak). The case page now renders one submit button per available backend; the clicked button's name=backend value rides through AnalysisInput.backend_id to the worker, which looks it up via find_backend() with default fallback. A submitted unknown backend id is rejected as 400. .analysis_failed.json records the failed backend and the failure banner surfaces its label. The API key now comes from IONOS_API_KEY (env), not settings.toml; the [llm] section is read into a discarded field so old configs still load. Backends without a satisfied requires_api_key are filtered from the UI (any_backend_available()). Three end-to-end tests that mocked the LLM endpoint via settings.llm.url are #[ignore]'d with a clear note — they need per-test backend injection (Arc<Vec<LlmBackend>> through the worker) before they can be re-enabled.
This commit is contained in:
@@ -8,6 +8,7 @@ use doctate_common::timestamp::now_rfc3339;
|
||||
use serde::Deserialize;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::analyze::backend::any_backend_available;
|
||||
use crate::analyze::{ANALYSIS_INPUT_FILE, AnalyzeJob, AnalyzeSender, DOCUMENT_FILE};
|
||||
use crate::auth::AuthenticatedWebUser;
|
||||
use crate::config::Config;
|
||||
@@ -20,7 +21,6 @@ use crate::routes::case_actions::{
|
||||
build_analysis_input, reset_case_artefacts, write_input_create_new,
|
||||
};
|
||||
use crate::routes::user_web::locate_case;
|
||||
use crate::settings::Settings;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BulkForm {
|
||||
@@ -45,7 +45,6 @@ impl HasCsrfToken for BulkForm {
|
||||
pub async fn handle_bulk_action(
|
||||
user: AuthenticatedWebUser,
|
||||
State(config): State<Arc<Config>>,
|
||||
State(settings): State<Arc<Settings>>,
|
||||
State(analyze_tx): State<AnalyzeSender>,
|
||||
State(events_tx): State<EventSender>,
|
||||
State(locks): State<OnelinerLocks>,
|
||||
@@ -72,7 +71,6 @@ pub async fn handle_bulk_action(
|
||||
match action {
|
||||
BulkAction::Analyze => {
|
||||
bulk_analyze(
|
||||
&settings,
|
||||
&analyze_tx,
|
||||
&events_tx,
|
||||
&user.slug,
|
||||
@@ -91,15 +89,14 @@ pub async fn handle_bulk_action(
|
||||
}
|
||||
|
||||
async fn bulk_analyze(
|
||||
settings: &Settings,
|
||||
analyze_tx: &AnalyzeSender,
|
||||
events_tx: &EventSender,
|
||||
slug: &str,
|
||||
user_root: &std::path::Path,
|
||||
case_ids: &[String],
|
||||
) {
|
||||
if !settings.llm_configured() {
|
||||
warn!(slug = %slug, "bulk-analyze: LLM not configured, skipping all");
|
||||
if !any_backend_available() {
|
||||
warn!(slug = %slug, "bulk-analyze: no LLM backend available, skipping all");
|
||||
return;
|
||||
}
|
||||
let mut ok = 0usize;
|
||||
@@ -127,7 +124,7 @@ async fn bulk_analyze(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let input = match build_analysis_input(&case_dir).await {
|
||||
let input = match build_analysis_input(&case_dir, "").await {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
warn!(slug = %slug, case_id = %case_id, "bulk-analyze: build_analysis_input failed");
|
||||
|
||||
Reference in New Issue
Block a user