モデル性能のハードコードを消し飛し、Codexのフォーマットの修正
This commit is contained in:
@@ -20,32 +20,17 @@ mod recorder;
|
||||
mod scenarios;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use llm_worker::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||
};
|
||||
use llm_worker::llm_client::scheme::{
|
||||
Scheme, anthropic::AnthropicScheme, gemini::GeminiScheme, openai_chat::OpenAIScheme,
|
||||
};
|
||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||
|
||||
/// 既定の capability: fixture 記録には cache_control を付けない
|
||||
/// (既知モデルの静的テーブルを経由すると scheme 毎に自動設定される)。
|
||||
fn fallback_capability() -> ModelCapability {
|
||||
ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: false,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_transport<S: Scheme>(
|
||||
scheme: S,
|
||||
model: &str,
|
||||
auth: ResolvedAuth,
|
||||
) -> HttpTransport<S> {
|
||||
let cap = scheme.capability_for(model).unwrap_or_else(fallback_capability);
|
||||
let cap = scheme.default_capability();
|
||||
let base_url = scheme.default_base_url().to_string();
|
||||
HttpTransport::new(scheme, model.to_string(), base_url, auth, cap)
|
||||
}
|
||||
@@ -138,7 +123,7 @@ async fn run_scenario_with_ollama(
|
||||
model.to_string(),
|
||||
"http://localhost:11434".to_string(),
|
||||
ResolvedAuth::None,
|
||||
fallback_capability(),
|
||||
AnthropicScheme::new().default_capability(),
|
||||
);
|
||||
|
||||
recorder::record_request(
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
//!
|
||||
//! Example of cancelling from another thread during streaming
|
||||
|
||||
use llm_worker::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||
};
|
||||
use llm_worker::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
|
||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||
use llm_worker::{Worker, WorkerResult};
|
||||
@@ -28,13 +25,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let scheme = AnthropicScheme::new();
|
||||
let model = "claude-sonnet-4-20250514".to_string();
|
||||
let cap = scheme.capability_for(&model).unwrap_or(ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: false,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
});
|
||||
let cap = scheme.default_capability();
|
||||
let base_url = scheme.default_base_url().to_string();
|
||||
let client = HttpTransport::new(scheme, model, base_url, ResolvedAuth::ApiKey(api_key), cap);
|
||||
let worker = Worker::new(client);
|
||||
|
||||
@@ -343,9 +343,7 @@ fn build_transport<S: Scheme>(
|
||||
model: String,
|
||||
auth: ResolvedAuth,
|
||||
) -> Box<dyn LlmClient> {
|
||||
let cap = scheme
|
||||
.capability_for(&model)
|
||||
.unwrap_or_else(default_capability);
|
||||
let cap = scheme.default_capability();
|
||||
let base_url = scheme.default_base_url().to_string();
|
||||
Box::new(HttpTransport::new(scheme, model, base_url, auth, cap))
|
||||
}
|
||||
|
||||
@@ -1,34 +1,17 @@
|
||||
//! `model_id → ModelCapability` 静的テーブル。
|
||||
//! Anthropic scheme の wire-level 既定 capability。
|
||||
//!
|
||||
//! 既知モデルのみ網羅する。未知モデルは `None` を返し、呼び出し側
|
||||
//! (`HttpTransport` 構築時)に scheme 既定へフォールバックさせる。
|
||||
//! モデル ID 固有のテーブル(`claude-*` など)は高レベル構築層
|
||||
//! (`provider::capability`)の責務。ここでは未知モデルでも「この wire で
|
||||
//! 安全に送れる最小共通項」を返すだけに留める。
|
||||
|
||||
use crate::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, ReasoningSupport, StructuredOutput, ToolCallingSupport,
|
||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||
};
|
||||
|
||||
/// Anthropic 公式モデルの既定 capability。
|
||||
///
|
||||
/// `claude-sonnet-*` / `claude-opus-*` / `claude-haiku-*` に対応する。
|
||||
/// `cache_control` は公式のみ有効で、最大 4 breakpoint(公式仕様)。
|
||||
pub(crate) fn lookup(model_id: &str) -> Option<ModelCapability> {
|
||||
if !model_id.starts_with("claude-") {
|
||||
return None;
|
||||
}
|
||||
Some(ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: Some(ReasoningSupport::BudgetTokens),
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Explicit { max_breakpoints: 4 },
|
||||
})
|
||||
}
|
||||
|
||||
/// Scheme 既定の capability。
|
||||
///
|
||||
/// Ollama の `/v1/messages` 流用を想定して `cache_control` を送らない
|
||||
/// `CacheStrategy::Auto` にする。Anthropic 本家の未知モデル(新 Claude)
|
||||
/// も tool_calling / vision を備える想定で Parallel / true を返す。
|
||||
/// `CacheStrategy::Auto` にする。
|
||||
pub(crate) fn default_capability() -> ModelCapability {
|
||||
ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
|
||||
@@ -93,10 +93,6 @@ impl Scheme for AnthropicScheme {
|
||||
Ok(vec![event])
|
||||
}
|
||||
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability> {
|
||||
super::capability::lookup(model_id)
|
||||
}
|
||||
|
||||
fn default_capability(&self) -> ModelCapability {
|
||||
super::capability::default_capability()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
//! `model_id → ModelCapability` 静的テーブル(Google Gemini)。
|
||||
//! Gemini scheme の wire-level 既定 capability。
|
||||
//!
|
||||
//! モデル ID 固有のテーブル(`gemini-*` バージョン別の reasoning 有無)は
|
||||
//! 高レベル構築層(`provider::capability`)の責務。ここでは wire の
|
||||
//! 保守的 default のみ。
|
||||
|
||||
use crate::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, ReasoningSupport, StructuredOutput, ToolCallingSupport,
|
||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||
};
|
||||
|
||||
/// Scheme 既定の capability(未知モデル / 未明示モデル用)。
|
||||
/// Scheme 既定の capability(未知モデル / 未明示モデル用)。
|
||||
pub(crate) fn default_capability() -> ModelCapability {
|
||||
ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
@@ -14,24 +18,3 @@ pub(crate) fn default_capability() -> ModelCapability {
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(model_id: &str) -> Option<ModelCapability> {
|
||||
if !model_id.starts_with("gemini-") {
|
||||
return None;
|
||||
}
|
||||
// 2.5 系以降は thinking / reasoning を持つ
|
||||
let reasoning = if model_id.starts_with("gemini-2.5")
|
||||
|| model_id.starts_with("gemini-3")
|
||||
{
|
||||
Some(ReasoningSupport::BudgetTokens)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning,
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,10 +47,6 @@ impl Scheme for GeminiScheme {
|
||||
Ok(self.parse_event(data)?.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability> {
|
||||
super::capability::lookup(model_id)
|
||||
}
|
||||
|
||||
fn default_capability(&self) -> ModelCapability {
|
||||
super::capability::default_capability()
|
||||
}
|
||||
|
||||
@@ -76,13 +76,10 @@ pub trait Scheme: Clone + Send + Sync + 'static {
|
||||
state: &mut Self::State,
|
||||
) -> Result<Vec<Event>, ClientError>;
|
||||
|
||||
/// 既知モデル ID の能力テーブル引き。未知なら `None` を返す
|
||||
/// ので、呼び出し側は [`Scheme::default_capability`] に
|
||||
/// フォールバックする。
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability>;
|
||||
|
||||
/// scheme 既定の capability。未知モデル ID や未明示モデルでの
|
||||
/// フォールバックに使う。`capability_for` と違って必ず値を返す。
|
||||
/// scheme 既定の capability。モデル ID に関係なく、この wire で
|
||||
/// 安全に送れる最小共通項を返す。既知モデル ID の能力テーブルは
|
||||
/// `provider::capability::lookup` 側(高レベル構築層)の責務で、
|
||||
/// scheme はここには関与しない。
|
||||
fn default_capability(&self) -> ModelCapability;
|
||||
|
||||
/// scheme 側でサポートしていない `RequestConfig` フィールドを
|
||||
|
||||
@@ -1,76 +1,13 @@
|
||||
//! `model_id → ModelCapability` 静的テーブル(OpenAI Chat Completions)。
|
||||
//! OpenAI Chat Completions scheme の wire-level 既定 capability。
|
||||
//!
|
||||
//! OpenAI 本家の主要モデルのみ網羅する。OpenRouter / xAI / Groq 等は
|
||||
//! モデル ID が各社独自なので、マニフェスト側で明示 override する
|
||||
//! 前提。
|
||||
//!
|
||||
//! [`classify`] はモデル ID から family を判定する一次情報で、
|
||||
//! `scheme/openai_responses` からも参照される。
|
||||
//! モデル ID 固有のテーブル(`gpt-5` 系など)は高レベル構築層
|
||||
//! (`provider::capability`)の責務。ここでは wire の保守的 default のみ。
|
||||
|
||||
use crate::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, ReasoningSupport, StructuredOutput, ToolCallingSupport,
|
||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||
};
|
||||
|
||||
/// OpenAI 本家のモデル family 分類。
|
||||
///
|
||||
/// `openai_chat` と `openai_responses` で共有する一次情報。各 scheme は
|
||||
/// この分類に自 scheme 固有の `ReasoningSupport` 等を当てはめて
|
||||
/// `ModelCapability` を組み立てる。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum OpenAiFamily {
|
||||
/// GPT-5 / o1 / o3 / o4 系 — reasoning 対応
|
||||
Reasoning,
|
||||
/// GPT-4o / GPT-4 系
|
||||
Gpt4,
|
||||
/// GPT-3.5 系(旧式)
|
||||
Gpt35,
|
||||
}
|
||||
|
||||
/// モデル ID の prefix から family を判定する。未知は `None`。
|
||||
pub(crate) fn classify(model_id: &str) -> Option<OpenAiFamily> {
|
||||
if model_id.starts_with("gpt-5")
|
||||
|| model_id.starts_with("o1")
|
||||
|| model_id.starts_with("o3")
|
||||
|| model_id.starts_with("o4")
|
||||
{
|
||||
return Some(OpenAiFamily::Reasoning);
|
||||
}
|
||||
if model_id.starts_with("gpt-4") {
|
||||
return Some(OpenAiFamily::Gpt4);
|
||||
}
|
||||
if model_id.starts_with("gpt-3.5") {
|
||||
return Some(OpenAiFamily::Gpt35);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(model_id: &str) -> Option<ModelCapability> {
|
||||
classify(model_id).map(|family| match family {
|
||||
OpenAiFamily::Reasoning => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: Some(ReasoningSupport::Effort),
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
OpenAiFamily::Gpt4 => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
OpenAiFamily::Gpt35 => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonObject,
|
||||
reasoning: None,
|
||||
vision: false,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Scheme 既定の capability。OpenAI 互換ルーター系(xAI / Groq / OpenRouter 等)
|
||||
/// Scheme 既定の capability。OpenAI 互換ルーター系(xAI / Groq / OpenRouter 等)
|
||||
/// で未知モデル ID を受けたときのフォールバックに使う。
|
||||
pub(crate) fn default_capability() -> ModelCapability {
|
||||
ModelCapability {
|
||||
|
||||
@@ -52,10 +52,6 @@ impl Scheme for OpenAIScheme {
|
||||
Ok(self.parse_event(data)?.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability> {
|
||||
super::capability::lookup(model_id)
|
||||
}
|
||||
|
||||
fn default_capability(&self) -> ModelCapability {
|
||||
super::capability::default_capability()
|
||||
}
|
||||
|
||||
@@ -1,75 +1,11 @@
|
||||
//! `model_id → ModelCapability` 静的テーブル(OpenAI Responses API)。
|
||||
//! OpenAI Responses scheme の wire-level 既定 capability。
|
||||
//!
|
||||
//! モデル family 判定は `scheme/openai_chat/capability.rs::classify` を
|
||||
//! 共有する。Responses 側は `ReasoningSupport::Effort` 固定で、prompt
|
||||
//! caching はサーバ側自動(`CacheStrategy::Auto`)。
|
||||
//!
|
||||
//! `gpt-5-codex` は `gpt-5` prefix 経由で Reasoning 扱いされるが、
|
||||
//! `codex-mini-latest` 等 `codex-` prefix のモデルは ChatGPT backend
|
||||
//! 経由(CodexOAuth)でしか使えないため、このテーブルでだけ Reasoning
|
||||
//! にフォールバックする。
|
||||
//! モデル ID 固有のテーブル(`gpt-5` / `codex-` 系など)は高レベル構築層
|
||||
//! (`provider::capability`)の責務。ここでは wire の保守的 default のみ。
|
||||
|
||||
use crate::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, ReasoningSupport, StructuredOutput, ToolCallingSupport,
|
||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||
};
|
||||
use crate::llm_client::scheme::openai_chat::capability::{OpenAiFamily, classify};
|
||||
|
||||
pub(crate) fn lookup(model_id: &str) -> Option<ModelCapability> {
|
||||
let family = classify(model_id).or_else(|| {
|
||||
if model_id.starts_with("codex-") {
|
||||
Some(OpenAiFamily::Reasoning)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
Some(match family {
|
||||
OpenAiFamily::Reasoning => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: Some(ReasoningSupport::Effort),
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
OpenAiFamily::Gpt4 => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonSchema,
|
||||
reasoning: None,
|
||||
vision: true,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
OpenAiFamily::Gpt35 => ModelCapability {
|
||||
tool_calling: ToolCallingSupport::Parallel,
|
||||
structured_output: StructuredOutput::JsonObject,
|
||||
reasoning: None,
|
||||
vision: false,
|
||||
prompt_caching: CacheStrategy::Auto,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn gpt_5_codex_is_reasoning() {
|
||||
// `gpt-5` prefix で classify される
|
||||
let cap = lookup("gpt-5-codex").unwrap();
|
||||
assert!(cap.reasoning.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_mini_latest_is_reasoning() {
|
||||
// ChatGPT backend 専用モデル。`codex-` prefix で Reasoning にフォールバック
|
||||
let cap = lookup("codex-mini-latest").unwrap();
|
||||
assert!(cap.reasoning.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_model_returns_none() {
|
||||
assert!(lookup("foo-bar-3000").is_none());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_capability() -> ModelCapability {
|
||||
ModelCapability {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! item 配列で reasoning / function_call / function_call_output が
|
||||
//! first-class。`Item` を素に近い形で `input[]` に投影できる。
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::llm_client::{
|
||||
@@ -125,11 +125,28 @@ pub(crate) struct ResponseTool {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
/// OpenAI Responses API は `type:"object"` のパラメータスキーマに
|
||||
/// `properties` が存在することを要求する。schemars は引数なし struct
|
||||
/// から `properties` を含まない最小スキーマを出すので、serialize
|
||||
/// 時に空オブジェクトを補う。
|
||||
#[serde(serialize_with = "serialize_parameters")]
|
||||
pub parameters: Value,
|
||||
/// Structured output モード制御。デフォルト false。
|
||||
pub strict: bool,
|
||||
}
|
||||
|
||||
fn serialize_parameters<S: Serializer>(value: &Value, s: S) -> Result<S::Ok, S::Error> {
|
||||
if let Some(obj) = value.as_object()
|
||||
&& obj.get("type").and_then(Value::as_str) == Some("object")
|
||||
&& !obj.contains_key("properties")
|
||||
{
|
||||
let mut patched = obj.clone();
|
||||
patched.insert("properties".to_string(), Value::Object(Default::default()));
|
||||
return Value::Object(patched).serialize(s);
|
||||
}
|
||||
value.serialize(s)
|
||||
}
|
||||
|
||||
impl OpenAIResponsesScheme {
|
||||
/// `Request` から wire 形式の body を組み立てる。
|
||||
pub(crate) fn build_request(
|
||||
@@ -438,6 +455,46 @@ mod tests {
|
||||
assert_eq!(body.max_output_tokens, Some(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_schema_without_properties_is_normalized() {
|
||||
// schemars は引数なし struct から `type:"object"` だけのスキーマを
|
||||
// 吐く。OpenAI Responses は `properties` 欠落を 400 で拒否するので
|
||||
// 送る直前に空オブジェクトを補うのを確認。
|
||||
let scheme = OpenAIResponsesScheme::new();
|
||||
let raw_schema = serde_json::json!({ "type": "object" });
|
||||
let req = Request::new().tool(
|
||||
ToolDefinition::new("empty")
|
||||
.description("no args")
|
||||
.input_schema(raw_schema),
|
||||
);
|
||||
let body = scheme.build_request("gpt-5", &req, &cap_with_reasoning());
|
||||
let json = serde_json::to_value(&body).unwrap();
|
||||
assert_eq!(json["tools"][0]["parameters"]["type"], "object");
|
||||
assert!(
|
||||
json["tools"][0]["parameters"]["properties"].is_object(),
|
||||
"properties must be present as an object, got: {}",
|
||||
json["tools"][0]["parameters"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_schema_with_properties_is_untouched() {
|
||||
let scheme = OpenAIResponsesScheme::new();
|
||||
let raw_schema = serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": { "path": { "type": "string" } },
|
||||
"required": ["path"]
|
||||
});
|
||||
let req = Request::new().tool(
|
||||
ToolDefinition::new("t")
|
||||
.description("d")
|
||||
.input_schema(raw_schema.clone()),
|
||||
);
|
||||
let body = scheme.build_request("gpt-5", &req, &cap_with_reasoning());
|
||||
let json = serde_json::to_value(&body).unwrap();
|
||||
assert_eq!(json["tools"][0]["parameters"], raw_schema);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_body_has_expected_shape() {
|
||||
// wire 形式が崩れていないかのスモークテスト
|
||||
|
||||
@@ -19,11 +19,14 @@ impl Scheme for OpenAIResponsesScheme {
|
||||
type State = OpenAIResponsesState;
|
||||
|
||||
fn default_base_url(&self) -> &'static str {
|
||||
"https://api.openai.com"
|
||||
// `/v1` は base_url 側に寄せる。ChatGPT OAuth 経由のときは
|
||||
// `https://chatgpt.com/backend-api/codex` を base にすれば同じ
|
||||
// `/responses` path で両系統を吸収できる(Codex CLI 準拠)。
|
||||
"https://api.openai.com/v1"
|
||||
}
|
||||
|
||||
fn path(&self, _model_id: &str) -> String {
|
||||
"/v1/responses".to_string()
|
||||
"/responses".to_string()
|
||||
}
|
||||
|
||||
fn required_auth(&self) -> AuthRequirement {
|
||||
@@ -49,10 +52,6 @@ impl Scheme for OpenAIResponsesScheme {
|
||||
super::events::parse_sse(event_type, data, state)
|
||||
}
|
||||
|
||||
fn capability_for(&self, model_id: &str) -> Option<ModelCapability> {
|
||||
super::capability::lookup(model_id)
|
||||
}
|
||||
|
||||
fn default_capability(&self) -> ModelCapability {
|
||||
super::capability::default_capability()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user