llm-model-config完了
This commit is contained in:
@@ -24,3 +24,17 @@ pub(crate) fn lookup(model_id: &str) -> Option<ModelCapability> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Scheme 既定の capability。
|
||||
///
|
||||
/// Ollama の `/v1/messages` 流用を想定して `cache_control` を送らない
|
||||
/// `CacheStrategy::Auto` にする。Anthropic 本家の未知モデル(新 Claude)
|
||||
/// も tool_calling / vision を備える想定で Parallel / true を返す。
|
||||
pub(crate) fn default_capability() -> ModelCapability {
|
||||
ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: false,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +96,8 @@ impl Scheme for AnthropicScheme {
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability> {
|
||||
super::capability::lookup(model_id)
|
||||
}
|
||||
|
||||
fn default_capability(&self) -> ModelCapability {
|
||||
super::capability::default_capability()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,17 @@ use crate::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, ReasoningSupport, StructuredOutput, ToolCallingSupport,
|
||||
};
|
||||
|
||||
/// Scheme 既定の capability(未知モデル / 未明示モデル用)。
|
||||
pub(crate) fn default_capability() -> ModelCapability {
|
||||
ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(model_id: &str) -> Option<ModelCapability> {
|
||||
if !model_id.starts_with("gemini-") {
|
||||
return None;
|
||||
|
||||
@@ -50,4 +50,8 @@ impl Scheme for GeminiScheme {
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability> {
|
||||
super::capability::lookup(model_id)
|
||||
}
|
||||
|
||||
fn default_capability(&self) -> ModelCapability {
|
||||
super::capability::default_capability()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ use serde_json::Value;
|
||||
|
||||
use super::auth::AuthRequirement;
|
||||
use super::capability::ModelCapability;
|
||||
use super::client::ConfigWarning;
|
||||
use super::error::ClientError;
|
||||
use super::event::Event;
|
||||
use super::types::Request;
|
||||
use super::types::{Request, RequestConfig};
|
||||
|
||||
/// wire scheme の抽象。各プロバイダの API 仕様ごとに 1 つ実装する。
|
||||
///
|
||||
@@ -44,7 +45,7 @@ pub trait Scheme: Clone + Send + Sync + 'static {
|
||||
fn path(&self, model_id: &str) -> String;
|
||||
|
||||
/// この scheme が要求する認証形式。`build_client` 時に
|
||||
/// [`AuthRef`](../../../manifest/enum.AuthRef.html) と照合する。
|
||||
/// `manifest::AuthRef` と照合する。
|
||||
fn required_auth(&self) -> AuthRequirement;
|
||||
|
||||
/// `Content-Type` 以外の追加ヘッダ。`anthropic-version` / `anthropic-beta` 等。
|
||||
@@ -75,8 +76,20 @@ pub trait Scheme: Clone + Send + Sync + 'static {
|
||||
) -> Result<Vec<Event>, ClientError>;
|
||||
|
||||
/// 既知モデル ID の能力テーブル引き。未知なら `None` を返す
|
||||
/// ので、呼び出し側は scheme ごとの安全側デフォルト
|
||||
/// ([`ModelCapability::minimal`])にフォールバックする。
|
||||
/// ので、呼び出し側は [`Scheme::default_capability`] に
|
||||
/// フォールバックする。
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability>;
|
||||
|
||||
/// scheme 既定の capability。未知モデル ID や未明示モデルでの
|
||||
/// フォールバックに使う。`capability_for` と違って必ず値を返す。
|
||||
fn default_capability(&self) -> ModelCapability;
|
||||
|
||||
/// scheme 側でサポートしていない `RequestConfig` フィールドを
|
||||
/// 警告として返す(例: OpenAI Chat は `top_k` 非対応)。
|
||||
/// デフォルトは空 Vec。
|
||||
fn validate_config(&self, config: &RequestConfig) -> Vec<ConfigWarning> {
|
||||
let _ = config;
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,3 +45,15 @@ pub(crate) fn lookup(model_id: &str) -> Option<ModelCapability> {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Scheme 既定の capability。OpenAI 互換ルーター系(xAI / Groq / OpenRouter 等)
|
||||
/// で未知モデル ID を受けたときのフォールバックに使う。
|
||||
pub(crate) fn default_capability() -> ModelCapability {
|
||||
ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: false,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ use serde_json::Value;
|
||||
|
||||
use crate::llm_client::{
|
||||
ClientError,
|
||||
capability::ModelCapability,
|
||||
event::Event,
|
||||
auth::AuthRequirement,
|
||||
capability::ModelCapability,
|
||||
client::ConfigWarning,
|
||||
event::Event,
|
||||
scheme::Scheme,
|
||||
types::Request,
|
||||
types::{Request, RequestConfig},
|
||||
};
|
||||
|
||||
use super::OpenAIScheme;
|
||||
@@ -54,4 +55,17 @@ impl Scheme for OpenAIScheme {
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability> {
|
||||
super::capability::lookup(model_id)
|
||||
}
|
||||
|
||||
fn default_capability(&self) -> ModelCapability {
|
||||
super::capability::default_capability()
|
||||
}
|
||||
|
||||
fn validate_config(&self, config: &RequestConfig) -> Vec<ConfigWarning> {
|
||||
let mut warnings = Vec::new();
|
||||
// OpenAI Chat Completions API は top_k を受け付けない
|
||||
if config.top_k.is_some() {
|
||||
warnings.push(ConfigWarning::unsupported("top_k", "OpenAI Chat"));
|
||||
}
|
||||
warnings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ use eventsource_stream::Eventsource;
|
||||
use futures::{Stream, StreamExt, TryStreamExt};
|
||||
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
|
||||
|
||||
use super::auth::AuthRequirement;
|
||||
use super::capability::ModelCapability;
|
||||
use super::client::LlmClient;
|
||||
use super::client::{ConfigWarning, LlmClient};
|
||||
use super::error::ClientError;
|
||||
use super::event::Event;
|
||||
use super::auth::AuthRequirement;
|
||||
use super::scheme::Scheme;
|
||||
use super::types::Request;
|
||||
use super::types::{Request, RequestConfig};
|
||||
|
||||
/// `AuthRef` を解決したランタイム表現。`crates/provider` が構築する。
|
||||
///
|
||||
@@ -155,6 +155,10 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn validate_config(&self, config: &RequestConfig) -> Vec<ConfigWarning> {
|
||||
self.scheme.validate_config(config)
|
||||
}
|
||||
|
||||
async fn stream(
|
||||
&self,
|
||||
request: Request,
|
||||
|
||||
@@ -5,6 +5,7 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
llm-worker = { version = "0.2.1", path = "../llm-worker" }
|
||||
protocol = { version = "0.1.0", path = "../protocol" }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_ignored = "0.1.14"
|
||||
|
||||
@@ -50,6 +50,8 @@ pub struct ModelConfigPartial {
|
||||
pub model_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub auth: Option<AuthRef>,
|
||||
#[serde(default)]
|
||||
pub capability: Option<crate::model::ModelCapability>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
@@ -205,6 +207,7 @@ impl ModelConfigPartial {
|
||||
base_url: upper.base_url.or(self.base_url),
|
||||
model_id: upper.model_id.or(self.model_id),
|
||||
auth: upper.auth.or(self.auth),
|
||||
capability: upper.capability.or(self.capability),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,6 +313,7 @@ fn resolve_model(
|
||||
base_url: cfg.base_url,
|
||||
model_id,
|
||||
auth,
|
||||
capability: cfg.capability,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// `ModelCapability` は `llm-worker` 側に定義される runtime 構造だが、
|
||||
// マニフェストで任意に override できるよう型だけ再エクスポートする。
|
||||
pub use llm_worker::llm_client::capability::ModelCapability;
|
||||
|
||||
/// Pod が使う LLM モデルの宣言。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ModelConfig {
|
||||
@@ -21,6 +25,12 @@ pub struct ModelConfig {
|
||||
/// 認証方式
|
||||
#[serde(default)]
|
||||
pub auth: AuthRef,
|
||||
/// モデル能力の明示指定。`None` のときは `crates/provider` が
|
||||
/// scheme 静的テーブル → scheme 既定値の順でフォールバックする。
|
||||
/// OpenAI 互換ルーター(OpenRouter / xAI / Groq 等)で scheme テーブル
|
||||
/// に載っていないモデル ID を使うときに指定する。
|
||||
#[serde(default)]
|
||||
pub capability: Option<ModelCapability>,
|
||||
}
|
||||
|
||||
/// サポートする wire scheme の種類。
|
||||
|
||||
@@ -361,6 +361,7 @@ fn build_overlay_toml(
|
||||
base_url: model.base_url.clone(),
|
||||
model_id: Some(model.model_id.clone()),
|
||||
auth: Some(model.auth.clone()),
|
||||
capability: model.capability.clone(),
|
||||
},
|
||||
worker: WorkerManifestConfig {
|
||||
instruction: Some(instruction.to_string()),
|
||||
@@ -494,6 +495,7 @@ mod tests {
|
||||
env: None,
|
||||
file: Some(PathBuf::from("/etc/keys/anthropic")),
|
||||
},
|
||||
capability: None,
|
||||
};
|
||||
|
||||
let toml_str = build_overlay_toml("child", "$insomnia/default", &[], &model).unwrap();
|
||||
|
||||
@@ -141,6 +141,7 @@ fn dummy_model() -> ModelConfig {
|
||||
base_url: None,
|
||||
model_id: "claude-test".into(),
|
||||
auth: AuthRef::None,
|
||||
capability: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,3 +12,4 @@ thiserror = "2.0"
|
||||
[dev-dependencies]
|
||||
serial_test = "3.4.0"
|
||||
tempfile = "3.27.0"
|
||||
toml = "1.1.2"
|
||||
|
||||
+40
-32
@@ -12,7 +12,7 @@
|
||||
|
||||
use llm_worker::llm_client::{
|
||||
LlmClient,
|
||||
capability::{CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport},
|
||||
capability::ModelCapability,
|
||||
scheme::{
|
||||
Scheme, anthropic::AnthropicScheme, gemini::GeminiScheme, openai_chat::OpenAIScheme,
|
||||
},
|
||||
@@ -79,34 +79,6 @@ fn resolve_auth(
|
||||
}
|
||||
}
|
||||
|
||||
/// `SchemeKind` ごとに固定のデフォルト capability(未知モデル用)。
|
||||
fn default_capability(scheme: SchemeKind) -> ModelCapability {
|
||||
match scheme {
|
||||
SchemeKind::Anthropic => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: false,
|
||||
// Ollama の /v1/messages 流用時に cache_control を拒否されないよう Auto
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
SchemeKind::OpenaiChat | SchemeKind::OpenaiResponses => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: false,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
SchemeKind::Gemini => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn build_transport<S: Scheme>(
|
||||
scheme: S,
|
||||
config: &ModelConfig,
|
||||
@@ -117,9 +89,16 @@ fn build_transport<S: Scheme>(
|
||||
scheme: config.scheme,
|
||||
});
|
||||
}
|
||||
let capability = scheme
|
||||
.capability_for(&config.model_id)
|
||||
.unwrap_or_else(|| default_capability(config.scheme));
|
||||
// capability の優先順位:
|
||||
// 1. `ModelConfig.capability` の明示指定(OpenAI 互換ルーターの
|
||||
// 未知モデル等、マニフェストで完全に上書きしたいケース)
|
||||
// 2. scheme 静的テーブル(既知モデル)
|
||||
// 3. `Scheme::default_capability()`(scheme ごとの安全側デフォルト)
|
||||
let capability: ModelCapability = config
|
||||
.capability
|
||||
.clone()
|
||||
.or_else(|| scheme.capability_for(&config.model_id))
|
||||
.unwrap_or_else(|| scheme.default_capability());
|
||||
let base_url = config
|
||||
.base_url
|
||||
.clone()
|
||||
@@ -162,6 +141,7 @@ mod tests {
|
||||
env: None,
|
||||
file: None,
|
||||
},
|
||||
capability: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,6 +227,33 @@ mod tests {
|
||||
assert!(matches!(result, Err(ProviderError::ApiKeyMissing { .. })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_config_capability_overrides_scheme_default() {
|
||||
// 未知モデル ID でも `ModelConfig.capability` が指定されていれば
|
||||
// scheme の静的テーブル / デフォルトではなくその値が採用される。
|
||||
use llm_worker::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, ReasoningEffort, ReasoningSupport, StructuredOutput,
|
||||
ToolCallingSupport,
|
||||
};
|
||||
|
||||
let explicit = ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: Some(ReasoningSupport::Effort),
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
};
|
||||
|
||||
// TOML 経由の往復(`[model.capability]` が正しくパースできる)
|
||||
let toml_str = toml::to_string(&explicit).unwrap();
|
||||
let round_trip: ModelCapability = toml::from_str(&toml_str).unwrap();
|
||||
assert_eq!(round_trip, explicit);
|
||||
|
||||
// `_ = ReasoningEffort` は serde derive が欠けていると失敗する
|
||||
// ほぼ確実なコンパイル時ガード。
|
||||
let _ = ReasoningEffort::Medium;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ollama_succeeds_without_key() {
|
||||
// Ollama = Anthropic scheme + base_url 差し替え + AuthRef::None
|
||||
@@ -255,6 +262,7 @@ mod tests {
|
||||
base_url: Some("http://localhost:11434".into()),
|
||||
model_id: "llama3".into(),
|
||||
auth: AuthRef::None,
|
||||
capability: None,
|
||||
};
|
||||
// scheme.required_auth() が XApiKey でも ResolvedAuth::None は許容する
|
||||
// (None は全 scheme で受け入れるため)
|
||||
|
||||
Reference in New Issue
Block a user