mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Merge pull request #617 from fabro-sh/feat/expose-model-controls
Expose model reasoning effort controls
This commit is contained in:
commit
415e9e5cd8
17 changed files with 205 additions and 10 deletions
|
|
@ -8289,6 +8289,20 @@ components:
|
|||
description: Cost per million cached input tokens in USD.
|
||||
example: 1.50
|
||||
|
||||
ModelControls:
|
||||
description: Request-control values accepted by a provider/model offering.
|
||||
type: object
|
||||
required:
|
||||
- reasoning_effort
|
||||
properties:
|
||||
reasoning_effort:
|
||||
type: array
|
||||
description: >-
|
||||
Exact reasoning-effort values accepted by this offering. An empty
|
||||
array means the request control is unsupported.
|
||||
items:
|
||||
$ref: "#/components/schemas/ReasoningEffort"
|
||||
|
||||
Model:
|
||||
description: |
|
||||
One provider's offering of an LLM model. The `id` is unique within
|
||||
|
|
@ -8303,6 +8317,7 @@ components:
|
|||
- training
|
||||
- knowledge_cutoff
|
||||
- features
|
||||
- controls
|
||||
- costs
|
||||
- estimated_output_tps
|
||||
- aliases
|
||||
|
|
@ -8336,6 +8351,8 @@ components:
|
|||
example: "May 2025"
|
||||
features:
|
||||
$ref: "#/components/schemas/ModelFeatures"
|
||||
controls:
|
||||
$ref: "#/components/schemas/ModelControls"
|
||||
costs:
|
||||
$ref: "#/components/schemas/ModelCosts"
|
||||
estimated_output_tps:
|
||||
|
|
|
|||
|
|
@ -517,7 +517,9 @@ impl Default for ModelsCommand {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fabro_model::{ModelCosts, ModelFeatures, ModelLimits, ReasoningEffortFeature};
|
||||
use fabro_model::{
|
||||
ModelControls, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffortFeature,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -546,6 +548,7 @@ mod tests {
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls::default(),
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(1.0),
|
||||
output_cost_per_mtok: Some(2.0),
|
||||
|
|
@ -581,6 +584,7 @@ mod tests {
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls::default(),
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(1.0),
|
||||
output_cost_per_mtok: Some(2.0),
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ fn list_with_filters_renders_server_models_table() {
|
|||
"vision": false,
|
||||
"reasoning": false
|
||||
},
|
||||
"controls": {
|
||||
"reasoning_effort": []
|
||||
},
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 1.2,
|
||||
"output_cost_per_mtok": 3.4,
|
||||
|
|
@ -134,6 +137,9 @@ fn list_with_filters_renders_server_models_table() {
|
|||
"vision": true,
|
||||
"reasoning": true
|
||||
},
|
||||
"controls": {
|
||||
"reasoning_effort": []
|
||||
},
|
||||
"costs": {
|
||||
"input_cost_per_mtok": null,
|
||||
"output_cost_per_mtok": null,
|
||||
|
|
@ -206,6 +212,9 @@ fn list_uses_configured_server_target_without_server_flag() {
|
|||
"vision": false,
|
||||
"reasoning": false
|
||||
},
|
||||
"controls": {
|
||||
"reasoning_effort": []
|
||||
},
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 1.0,
|
||||
"output_cost_per_mtok": 2.0,
|
||||
|
|
@ -260,6 +269,9 @@ fn list_uses_fabro_config_for_machine_settings() {
|
|||
"vision": false,
|
||||
"reasoning": false
|
||||
},
|
||||
"controls": {
|
||||
"reasoning_effort": []
|
||||
},
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 1.0,
|
||||
"output_cost_per_mtok": 2.0,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ fn model_json(id: &str, provider: &str, configured: bool) -> serde_json::Value {
|
|||
"vision": false,
|
||||
"reasoning": false
|
||||
},
|
||||
"controls": {
|
||||
"reasoning_effort": []
|
||||
},
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 1.0,
|
||||
"output_cost_per_mtok": 2.0,
|
||||
|
|
|
|||
|
|
@ -6768,6 +6768,35 @@ async fn list_models_filters_by_provider() {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_models_exposes_reasoning_effort_controls() {
|
||||
let app = test_app_with();
|
||||
|
||||
let req = Request::builder()
|
||||
.method("GET")
|
||||
.uri(api("/models?provider=kimi"))
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = app.oneshot(req).await.unwrap();
|
||||
let body = response_json!(response, StatusCode::OK).await;
|
||||
let models = body["data"].as_array().unwrap();
|
||||
let kimi_k3 = models
|
||||
.iter()
|
||||
.find(|model| model["id"] == "kimi-k3")
|
||||
.expect("Kimi K3 should be listed");
|
||||
let kimi_k2_5 = models
|
||||
.iter()
|
||||
.find(|model| model["id"] == "kimi-k2.5")
|
||||
.expect("Kimi K2.5 should be listed");
|
||||
|
||||
assert_eq!(
|
||||
kimi_k3["controls"]["reasoning_effort"],
|
||||
json!(["low", "high", "max"])
|
||||
);
|
||||
assert_eq!(kimi_k2_5["controls"]["reasoning_effort"], json!([]));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_models_marks_configured_true_when_provider_has_credential_material() {
|
||||
let state = test_app_state_with_env_lookup(
|
||||
|
|
|
|||
|
|
@ -169,7 +169,9 @@ fn validate_deep_result(result: &GenerateResult) -> Result<(), String> {
|
|||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use fabro_model::{ModelCosts, ModelFeatures, ModelLimits, ProviderId, ReasoningEffortFeature};
|
||||
use fabro_model::{
|
||||
ModelControls, ModelCosts, ModelFeatures, ModelLimits, ProviderId, ReasoningEffortFeature,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::types::{FinishReason, Message, Response, StepResult, TokenCounts, ToolResult};
|
||||
|
|
@ -187,6 +189,7 @@ mod tests {
|
|||
training: None,
|
||||
knowledge_cutoff: None,
|
||||
features,
|
||||
controls: ModelControls::default(),
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: None,
|
||||
output_cost_per_mtok: None,
|
||||
|
|
|
|||
|
|
@ -484,6 +484,7 @@ fn main() {
|
|||
),
|
||||
("ReasoningEffort", "fabro_model::ReasoningEffort", &[]),
|
||||
("ModelFeatures", "fabro_model::ModelFeatures", &[]),
|
||||
("ModelControls", "fabro_model::ModelControls", &[]),
|
||||
("ModelCosts", "fabro_model::ModelCosts", &[]),
|
||||
("ModelTestMode", "fabro_model::ModelTestMode", &[]),
|
||||
("RunProjection", "fabro_types::RunProjection", &[]),
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ pub mod types {
|
|||
};
|
||||
pub use fabro_environment::Environment;
|
||||
pub use fabro_model::{
|
||||
CostSource, Model, ModelCosts, ModelFeatures, ModelLimits, ModelRef as BillingModelRef,
|
||||
ModelTestMode, Provider, ReasoningEffort, ReasoningEffortFeature, Speed as BillingSpeed,
|
||||
TokenCounts as CompletionUsage,
|
||||
CostSource, Model, ModelControls, ModelCosts, ModelFeatures, ModelLimits,
|
||||
ModelRef as BillingModelRef, ModelTestMode, Provider, ReasoningEffort,
|
||||
ReasoningEffortFeature, Speed as BillingSpeed, TokenCounts as CompletionUsage,
|
||||
};
|
||||
pub use fabro_types::run_event::AgentSessionActivatedProps;
|
||||
pub use fabro_types::settings::run::McpHttpProtocol;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
use std::any::{TypeId, type_name};
|
||||
|
||||
use fabro_api::types::Model as ApiModel;
|
||||
use fabro_api::types::{Model as ApiModel, ModelControls as ApiModelControls};
|
||||
use fabro_model::{
|
||||
Model, ModelCosts, ModelFeatures, ModelLimits, ProviderId, ReasoningEffortFeature,
|
||||
Model, ModelControls, ModelCosts, ModelFeatures, ModelLimits, ProviderId, ReasoningEffort,
|
||||
ReasoningEffortFeature,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn model_reuses_canonical_type() {
|
||||
assert_same_type::<ApiModel, Model>();
|
||||
assert_same_type::<ApiModelControls, ModelControls>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -32,6 +34,13 @@ fn model_json_matches_openapi_shape() {
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: vec![
|
||||
ReasoningEffort::Low,
|
||||
ReasoningEffort::High,
|
||||
ReasoningEffort::Max,
|
||||
],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(5.0),
|
||||
output_cost_per_mtok: Some(25.0),
|
||||
|
|
@ -50,6 +59,10 @@ fn model_json_matches_openapi_shape() {
|
|||
assert_eq!(json["knowledge_cutoff"], "May 2025");
|
||||
assert_eq!(json["features"]["reasoning_effort"], "levels");
|
||||
assert_eq!(json["features"]["prompt_cache"], true);
|
||||
assert_eq!(
|
||||
json["controls"]["reasoning_effort"],
|
||||
serde_json::json!(["low", "high", "max"])
|
||||
);
|
||||
assert_eq!(json["estimated_output_tps"], 25.0);
|
||||
assert_eq!(json["small_default"], true);
|
||||
assert_eq!(json["configured"], true);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use std::any::{TypeId, type_name};
|
|||
|
||||
use fabro_api::types::Model as ApiModel;
|
||||
use fabro_model::{
|
||||
Model, ModelCosts, ModelFeatures, ModelLimits, ProviderId, ReasoningEffortFeature,
|
||||
Model, ModelControls, ModelCosts, ModelFeatures, ModelLimits, ProviderId,
|
||||
ReasoningEffortFeature,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ fn provider_id_json_matches_openapi_shape_through_model() {
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls::default(),
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: None,
|
||||
output_cost_per_mtok: None,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ use crate::codec::CodecKind;
|
|||
use crate::ids::{ModelId, ProviderId};
|
||||
use crate::provider::Provider;
|
||||
use crate::reasoning::ReasoningEffort;
|
||||
use crate::types::{Model, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffortFeature};
|
||||
use crate::types::{
|
||||
Model, ModelControls, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffortFeature,
|
||||
};
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/catalog/providers"]
|
||||
|
|
@ -2228,6 +2230,9 @@ fn build_model(
|
|||
training: settings.training.clone(),
|
||||
knowledge_cutoff: settings.knowledge_cutoff.clone(),
|
||||
features: model_features,
|
||||
controls: ModelControls {
|
||||
reasoning_effort: controls.reasoning_effort.clone(),
|
||||
},
|
||||
costs,
|
||||
estimated_output_tps: settings.estimated_output_tps,
|
||||
aliases: settings.aliases.clone().unwrap_or_default(),
|
||||
|
|
@ -3324,6 +3329,12 @@ enabled = true
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: [
|
||||
High,
|
||||
XHigh,
|
||||
],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(
|
||||
0.784,
|
||||
|
|
@ -3394,6 +3405,13 @@ enabled = true
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: false,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: [
|
||||
Low,
|
||||
High,
|
||||
Max,
|
||||
],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(
|
||||
3.0,
|
||||
|
|
@ -6018,6 +6036,15 @@ sampling_params = false
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: [
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
XHigh,
|
||||
Max,
|
||||
],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(
|
||||
5.0,
|
||||
|
|
@ -6075,6 +6102,9 @@ sampling_params = false
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: false,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: [],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(
|
||||
0.6,
|
||||
|
|
@ -6124,6 +6154,13 @@ sampling_params = false
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: false,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: [
|
||||
Low,
|
||||
High,
|
||||
Max,
|
||||
],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(
|
||||
3.0,
|
||||
|
|
@ -6197,6 +6234,12 @@ sampling_params = false
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: [
|
||||
High,
|
||||
Max,
|
||||
],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(
|
||||
1.4,
|
||||
|
|
@ -6267,6 +6310,15 @@ sampling_params = false
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: [
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
XHigh,
|
||||
Max,
|
||||
],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(
|
||||
0.25,
|
||||
|
|
@ -6324,6 +6376,15 @@ sampling_params = false
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls {
|
||||
reasoning_effort: [
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
XHigh,
|
||||
Max,
|
||||
],
|
||||
},
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(
|
||||
30.0,
|
||||
|
|
|
|||
|
|
@ -27,4 +27,6 @@ pub use model_ref::ModelHandle;
|
|||
pub use model_test::ModelTestMode;
|
||||
pub use provider::Provider;
|
||||
pub use reasoning::ReasoningEffort;
|
||||
pub use types::{Model, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffortFeature};
|
||||
pub use types::{
|
||||
Model, ModelControls, ModelCosts, ModelFeatures, ModelLimits, ReasoningEffortFeature,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ids::{ModelId, ProviderId};
|
||||
use crate::reasoning::ReasoningEffort;
|
||||
|
||||
// --- 2.9 Model ---
|
||||
|
||||
|
|
@ -83,6 +84,14 @@ pub struct ModelCosts {
|
|||
pub cache_input_cost_per_mtok: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ModelControls {
|
||||
/// Exact reasoning-effort values accepted by this provider/model offering.
|
||||
/// An empty list means the request control is unsupported.
|
||||
#[serde(default)]
|
||||
pub reasoning_effort: Vec<ReasoningEffort>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Model {
|
||||
pub id: ModelId,
|
||||
|
|
@ -93,6 +102,10 @@ pub struct Model {
|
|||
pub training: Option<String>,
|
||||
pub knowledge_cutoff: Option<String>,
|
||||
pub features: ModelFeatures,
|
||||
/// Required in API responses; defaulted on deserialization so newer
|
||||
/// clients tolerate older servers that predate this field.
|
||||
#[serde(default)]
|
||||
pub controls: ModelControls,
|
||||
pub costs: ModelCosts,
|
||||
pub estimated_output_tps: Option<f64>,
|
||||
pub aliases: Vec<String>,
|
||||
|
|
@ -236,6 +249,7 @@ mod tests {
|
|||
cache_control_breakpoints: false,
|
||||
sampling_params: true,
|
||||
},
|
||||
controls: ModelControls::default(),
|
||||
costs: ModelCosts {
|
||||
input_cost_per_mtok: Some(1.0),
|
||||
output_cost_per_mtok: Some(2.0),
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ models/mcp-transport.ts
|
|||
models/merge-method.ts
|
||||
models/merge-run-pull-request-request.ts
|
||||
models/merge-run-pull-request-response.ts
|
||||
models/model-controls.ts
|
||||
models/model-costs.ts
|
||||
models/model-features.ts
|
||||
models/model-limits.ts
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ export * from './merge-method';
|
|||
export * from './merge-run-pull-request-request';
|
||||
export * from './merge-run-pull-request-response';
|
||||
export * from './model';
|
||||
export * from './model-controls';
|
||||
export * from './model-costs';
|
||||
export * from './model-features';
|
||||
export * from './model-limits';
|
||||
|
|
|
|||
28
lib/packages/fabro-api-client/src/models/model-controls.ts
generated
Normal file
28
lib/packages/fabro-api-client/src/models/model-controls.ts
generated
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ReasoningEffort } from './reasoning-effort';
|
||||
|
||||
/**
|
||||
* Request-control values accepted by a provider/model offering.
|
||||
*/
|
||||
export interface ModelControls {
|
||||
/**
|
||||
* Exact reasoning-effort values accepted by this offering. An empty array means the request control is unsupported.
|
||||
*/
|
||||
'reasoning_effort': Array<ReasoningEffort>;
|
||||
}
|
||||
|
|
@ -13,6 +13,9 @@
|
|||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ModelControls } from './model-controls';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ModelCosts } from './model-costs';
|
||||
|
|
@ -53,6 +56,7 @@ export interface Model {
|
|||
*/
|
||||
'knowledge_cutoff': string | null;
|
||||
'features': ModelFeatures;
|
||||
'controls': ModelControls;
|
||||
'costs': ModelCosts;
|
||||
/**
|
||||
* Estimated output tokens per second.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue