优化: 供应商级采样温度覆盖配置

受限模型(经路由透传)仅接受特定 temperature 其它值 400;
provider config JSON 带 temperature 字段则覆盖默认,未配置走 0.7,设置页可编辑。
This commit is contained in:
lxy
2026-09-29 13:33:49 +08:00
parent 7cb32d1380
commit 378c363632
5 changed files with 155 additions and 17 deletions
+117 -16
View File
@@ -342,7 +342,7 @@ use context_lifecycle::maybe_auto_compress;
mod title_lifecycle; mod title_lifecycle;
#[allow(unused_imports)] #[allow(unused_imports)]
mod knowledge_lifecycle; mod knowledge_lifecycle;
mod workflow_context; pub(crate) mod workflow_context;
// ============================================================ // ============================================================
// L2 统一状态机(ConvState enum + 转换守卫,单一真相源)。 // L2 统一状态机(ConvState enum + 转换守卫,单一真相源)。
@@ -360,7 +360,10 @@ pub mod command_lock;
pub(crate) mod helpers; pub(crate) mod helpers;
pub(crate) use helpers::try_continue_agent_loop; pub(crate) use helpers::try_continue_agent_loop;
pub(crate) use helpers::infer_goal_from_tool_calls; pub(crate) use helpers::infer_goal_from_tool_calls;
pub(crate) use helpers::{tool_call_signature, is_repetitive_exploration, is_pure_greeting, is_tool_call_over_limit}; pub(crate) use helpers::{
tool_call_signature, is_repetitive_exploration, is_pure_greeting, is_tool_call_over_limit,
DestructiveLoopSample, destructive_loop_sample, detect_destructive_loop,
};
// ============================================================ // ============================================================
// 单 Provider 流式结果 + fallback 辅助 // 单 Provider 流式结果 + fallback 辅助
@@ -490,11 +493,21 @@ async fn stream_with_retry(
resolved_model: &str, resolved_model: &str,
last_reasoning_content: &Option<String>, last_reasoning_content: &Option<String>,
) -> StreamOutcome { ) -> StreamOutcome {
// 供应商级采样温度覆盖:provider config JSON 带 "temperature" 字段则用之,
// 治受限模型(如 Kimi 系经 u-switch 透传)仅接受 temperature=1、其它值 400 的问题;
// 未配置/非法 JSON 走默认 0.7。candidate 在重试循环内不变,解析提到循环外只做一次。
let temperature = candidate
.config
.as_deref()
.and_then(|cfg| serde_json::from_str::<serde_json::Value>(cfg).ok())
.and_then(|v| v.get("temperature").and_then(|t| t.as_f64()))
.map(|t| t as f32)
.or(Some(0.7));
for retry_attempt in 0..=max_retries { for retry_attempt in 0..=max_retries {
let retry_request = CompletionRequest { let retry_request = CompletionRequest {
model: resolved_model.to_string(), model: resolved_model.to_string(),
messages: messages.to_vec(), messages: messages.to_vec(),
temperature: Some(0.7), temperature,
max_tokens: Some(8192), max_tokens: Some(8192),
stream: true, stream: true,
tools: if tool_defs.is_empty() { None } else { Some(tool_defs.to_vec()) }, tools: if tool_defs.is_empty() { None } else { Some(tool_defs.to_vec()) },
@@ -1367,6 +1380,11 @@ pub(crate) async fn run_agentic_loop(
let mut stall_count: u32 = 0; let mut stall_count: u32 = 0;
let mut stall_warned: bool = false; let mut stall_warned: bool = false;
// G2 破坏性回环计数器(独立于 stall_count,互不干扰):写删同路径回环对检出累计,
// 治「写完就删」自毁循环(签名范式对写删交替漏网)。警示/阈值复用 STALL_BREAKER_*。
let mut loop_count: u32 = 0;
let mut loop_warned: bool = false;
// 目标验证续轮计数(GOAL_VERIFY_ENABLED 门控):末轮无 tool_calls 但 pinned_goals 未达成 // 目标验证续轮计数(GOAL_VERIFY_ENABLED 门控):末轮无 tool_calls 但 pinned_goals 未达成
// 且无 applied.matched_target 证据时强制续轮,每续 +1。达 GOAL_VERIFY_MAX_ROUNDS 强制收敛 // 且无 applied.matched_target 证据时强制续轮,每续 +1。达 GOAL_VERIFY_MAX_ROUNDS 强制收敛
// + AiCompleted 标注 goal_unverified。续轮内调了工具则重置(说明 LLM 在行动非空转)。 // + AiCompleted 标注 goal_unverified。续轮内调了工具则重置(说明 LLM 在行动非空转)。
@@ -2079,12 +2097,14 @@ pub(crate) async fn run_agentic_loop(
// G2 探索熔断:连续空结果无进展检查。抽至 check_stall_breaker helper(扁平重构)。 // G2 探索熔断:连续空结果无进展检查。抽至 check_stall_breaker helper(扁平重构)。
// 返回 true=已熔断(loop 应 return),false=继续。stall_count/stall_warned 传入传出。 // 返回 true=已熔断(loop 应 return),false=继续。stall_count/stall_warned 传入传出。
if STALL_BREAKER_ENABLED { if STALL_BREAKER_ENABLED {
let (tripped, new_count, new_warned) = check_stall_breaker( let (tripped, new_count, new_warned, new_loop_count, new_loop_warned) = check_stall_breaker(
&session_arc, &app_handle, &conv_id, iteration, &session_arc, &app_handle, &conv_id, iteration,
stall_count, stall_warned, stall_count, stall_warned, loop_count, loop_warned,
).await; ).await;
stall_count = new_count; stall_count = new_count;
stall_warned = new_warned; stall_warned = new_warned;
loop_count = new_loop_count;
loop_warned = new_loop_warned;
if tripped { if tripped {
guard.reset().await; guard.reset().await;
return; return;
@@ -2533,15 +2553,20 @@ fn emit_circuit_breaker_tripped(app_handle: &AppHandle, conv_id: &str, max_count
} }
// ── check_stall_breaker: G2 探索熔断检查(扁平抽自原嵌套 6 层块) ── // ── check_stall_breaker: G2 探索熔断检查(扁平抽自原嵌套 6 层块) ──
// 返回 (tripped, new_stall_count, new_stall_warned): // 返回 (tripped, new_stall_count, new_stall_warned, new_loop_count, new_loop_warned):
// - tripped=true → 已 emit AiHelpRequired 且 loop 应 return(调用方需先 guard.reset 再 return) // - tripped=true → 已 emit AiHelpRequired 且 loop 应 return(调用方需先 guard.reset 再 return)
// - tripped=false → 正常继续,新 stall_count/warned 供调用方回写。 // - tripped=false → 正常继续,新 stall/loop 计数与警示位供调用方回写。
// //
// 2026-08-01 根本性重构:判定从「末尾 Tool 结果是否空(is_empty_tool_result)」改为 // 2026-08-01 根本性重构:判定从「末尾 Tool 结果是否空(is_empty_tool_result)」改为
// 「最近 N 个 assistant tool_calls 签名是否重复(tool_call_signature + is_repetitive_exploration)」。 // 「最近 N 个 assistant tool_calls 签名是否重复(tool_call_signature + is_repetitive_exploration)」。
// 治旧范式误熔断正常排除式搜索(grep 无匹配是有效排除信号,非漂移)。熔断骨架(计数/警示/ // 治旧范式误熔断正常排除式搜索(grep 无匹配是有效排除信号,非漂移)。熔断骨架(计数/警示/
// 阈值/emit/return)不变,只换判定输入。 // 阈值/emit/return)不变,只换判定输入。
// //
// 2026-09-29 补破坏性回环检测:签名范式对「写完就删」自毁循环漏网(write_file/delete_file
// 走签名兜底分支,写删交替每步签名都不同,唯一率≈0.83 不触发)。采样处并行维护
// (工具, 路径) 结构化缓冲喂 detect_destructive_loop,独立计数器与 stall 并存互不干扰,
// 两段式(警示/阈值)复用 STALL_BREAKER_WARN_FIRST/THRESHOLD。
//
// 嵌套 ≤ 3 层:取签名→计数/警示→达阈值 emit,各段早返回。 // 嵌套 ≤ 3 层:取签名→计数/警示→达阈值 emit,各段早返回。
async fn check_stall_breaker( async fn check_stall_breaker(
session_arc: &Arc<Mutex<AiSession>>, session_arc: &Arc<Mutex<AiSession>>,
@@ -2550,11 +2575,14 @@ async fn check_stall_breaker(
iteration: usize, iteration: usize,
stall_count: u32, stall_count: u32,
stall_warned: bool, stall_warned: bool,
) -> (bool, u32, bool) { loop_count: u32,
loop_warned: bool,
) -> (bool, u32, bool, u32, bool) {
// 1) 取最近 N 个 assistant tool_calls 签名:倒序扫 messages,遇 Assistant 且有 tool_calls // 1) 取最近 N 个 assistant tool_calls 签名:倒序扫 messages,遇 Assistant 且有 tool_calls
// 就抽取每工具签名(name + arguments JSON parse),累积到 STALL_BREAKER_SAMPLE_SIZE 个。 // 就抽取每工具签名(name + arguments JSON parse),累积到 STALL_BREAKER_SAMPLE_SIZE 个。
// arguments 非合法 JSON 时 to_string 兜底(签名仍可比,只是兜底全量)。 // arguments 非合法 JSON 时 to_string 兜底(签名仍可比,只是兜底全量)。
let signatures: Vec<String> = { // 同时并行抽取回环样本(写/删工具记路径,其他占位),两缓冲同序位对齐。
let (signatures, loop_samples): (Vec<String>, Vec<DestructiveLoopSample>) = {
let session = session_arc.lock().await; let session = session_arc.lock().await;
// BE2只取尾部 BREAKER_TAIL_N 条(签名抽取只需最近几轮工具调用, // BE2只取尾部 BREAKER_TAIL_N 条(签名抽取只需最近几轮工具调用,
// 全量 clone 纯浪费;STALL_BREAKER_SAMPLE_SIZE=12 ≪ 40,样本充足)。 // 全量 clone 纯浪费;STALL_BREAKER_SAMPLE_SIZE=12 ≪ 40,样本充足)。
@@ -2563,6 +2591,7 @@ async fn check_stall_breaker(
None => Vec::new(), None => Vec::new(),
}; };
let mut sigs: Vec<String> = Vec::new(); let mut sigs: Vec<String> = Vec::new();
let mut loops: Vec<DestructiveLoopSample> = Vec::new();
for m in messages.iter().rev() { for m in messages.iter().rev() {
if sigs.len() >= STALL_BREAKER_SAMPLE_SIZE { if sigs.len() >= STALL_BREAKER_SAMPLE_SIZE {
break; break;
@@ -2579,18 +2608,20 @@ async fn check_stall_breaker(
let args: serde_json::Value = let args: serde_json::Value =
serde_json::from_str(&tc.function.arguments).unwrap_or(serde_json::Value::Null); serde_json::from_str(&tc.function.arguments).unwrap_or(serde_json::Value::Null);
sigs.push(tool_call_signature(&tc.function.name, &args)); sigs.push(tool_call_signature(&tc.function.name, &args));
loops.push(destructive_loop_sample(&tc.function.name, &args));
} }
} }
} }
// 倒序收集的签名需反转为时间正序,保持 is_repetitive_exploration 的语义稳定 // 倒序收集的签名需反转为时间正序,保持 is_repetitive_exploration 的语义稳定
// (虽纯函数对顺序不敏感,但反转后日志/调试更直观)。 // (虽纯函数对顺序不敏感,但反转后日志/调试更直观)。回环检测依赖序位间隔,必须正序。
sigs.reverse(); sigs.reverse();
sigs loops.reverse();
(sigs, loops)
}; };
// 本轮无工具调用(纯文本轮或首轮)→ 不计入也不重置,保持 stall_count。 // 本轮无工具调用(纯文本轮或首轮)→ 不计入也不重置,保持 stall_count/loop_count。
if signatures.is_empty() { if signatures.is_empty() {
return (false, stall_count, stall_warned); return (false, stall_count, stall_warned, loop_count, loop_warned);
} }
// 2) 签名重复判定 → 更新 stall_count / stall_warned(沿用原 reset 语义) // 2) 签名重复判定 → 更新 stall_count / stall_warned(沿用原 reset 语义)
@@ -2602,6 +2633,16 @@ async fn check_stall_breaker(
new_warned = false; new_warned = false;
} }
// 2.5) 破坏性回环判定(独立计数器):窗口内有同路径写删回环对 → 累计,无 → 清零重来。
let (mut new_loop_count, mut new_loop_warned) = (loop_count, loop_warned);
let loop_hit_path = detect_destructive_loop(&loop_samples);
if loop_hit_path.is_some() {
new_loop_count += 1;
} else {
new_loop_count = 0;
new_loop_warned = false;
}
// 3) 两段式警示:达 THRESHOLD-1 首次警示(给 LLM 自纠机会) // 3) 两段式警示:达 THRESHOLD-1 首次警示(给 LLM 自纠机会)
if STALL_BREAKER_WARN_FIRST if STALL_BREAKER_WARN_FIRST
&& new_count == STALL_BREAKER_THRESHOLD.saturating_sub(1) && new_count == STALL_BREAKER_THRESHOLD.saturating_sub(1)
@@ -2611,9 +2652,40 @@ async fn check_stall_breaker(
insert_stall_warning(session_arc, conv_id, iteration, new_count).await; insert_stall_warning(session_arc, conv_id, iteration, new_count).await;
} }
// 4) 熔断:达 THRESHOLD → emit AiHelpRequired + 返回 tripped=true // 3.5) 回环两段式警示(对齐 stall WARN_FIRST,独立警示位)
if STALL_BREAKER_WARN_FIRST
&& new_loop_count == STALL_BREAKER_THRESHOLD.saturating_sub(1)
&& !new_loop_warned
{
new_loop_warned = true;
insert_destructive_loop_warning(session_arc, conv_id, iteration, new_loop_count, loop_hit_path.as_deref()).await;
}
// 4) 回环熔断:独立达 THRESHOLD → emit(与 stall 熔断谁先达谁触发)
if new_loop_count >= STALL_BREAKER_THRESHOLD {
tracing::warn!(
conv_id = %conv_id,
loop_count = new_loop_count,
hit_path = %loop_hit_path.as_deref().unwrap_or("?"),
sample_size = loop_samples.len(),
"[ai] G2 破坏性回环熔断:连续 {} 轮检测到同路径写删回环,疑似自毁循环停止", new_loop_count
);
let path_desc = loop_hit_path.as_deref().unwrap_or("未知路径");
let _ = app_handle.emit(
"ai-chat-event",
AiChatEvent::AiHelpRequired {
reason: format!("连续 {} 轮检测到对同一文件({})反复写删,疑似「写完就删」自毁循环已停止", new_loop_count, path_desc),
context: "写文件后又删除同一文件的紧凑交替,可能在反复销毁自己的工作成果而无进展。".into(),
options: vec!["回顾目标".into(), "换思路".into(), "停止".into()],
conversation_id: Some(conv_id.to_string()),
},
);
return (true, new_count, new_warned, new_loop_count, new_loop_warned);
}
// 5) stall 熔断:达 THRESHOLD → emit AiHelpRequired + 返回 tripped=true
if new_count < STALL_BREAKER_THRESHOLD { if new_count < STALL_BREAKER_THRESHOLD {
return (false, new_count, new_warned); return (false, new_count, new_warned, new_loop_count, new_loop_warned);
} }
tracing::warn!( tracing::warn!(
conv_id = %conv_id, conv_id = %conv_id,
@@ -2630,7 +2702,36 @@ async fn check_stall_breaker(
conversation_id: Some(conv_id.to_string()), conversation_id: Some(conv_id.to_string()),
}, },
); );
(true, new_count, new_warned) (true, new_count, new_warned, new_loop_count, new_loop_warned)
}
// ── insert_destructive_loop_warning: 破坏性回环软提示 insert system 消息(对齐 insert_stall_warning) ──
async fn insert_destructive_loop_warning(
session_arc: &Arc<Mutex<AiSession>>,
conv_id: &str,
iteration: usize,
loop_count: u32,
path: Option<&str>,
) {
let goal_text = if STALL_BREAKER_GOAL_REMIND {
fetch_goal_summary(session_arc, conv_id).await
} else {
String::new()
};
let path_desc = path.unwrap_or("同一文件");
let warn_text = format!(
"⚠ 检测到对 {} 反复写删(写完又删的回环),可能在销毁自己的工作成果{},请回顾目标停止删除或换思路。",
path_desc, goal_text
);
let mut session = session_arc.lock().await;
if session.per_conv.contains_key(conv_id) {
let conv = session.conv(conv_id);
conv.messages.insert_at(0, ChatMessage::system(&warn_text));
tracing::info!(
conv_id = %conv_id, iteration, loop_count,
"[ai] G2 破坏性回环:写删回环警示已 insert(软提示,给 LLM 自纠机会)"
);
}
} }
// ── insert_stall_warning: G2 软提示 insert system 消息(给 LLM 自纠机会) ── // ── insert_stall_warning: G2 软提示 insert system 消息(给 LLM 自纠机会) ──
@@ -97,6 +97,11 @@ pub async fn ai_list_providers(state: State<'_, AppState>) -> Result<Vec<AiProvi
} }
/// 保存/更新 AI 提供商配置 /// 保存/更新 AI 提供商配置
///
/// `temperature_override`:供应商级采样温度覆盖(可选)。Some(t) 落库进 config JSON
/// `{"temperature": t}`,agentic 主链路读取覆盖默认 0.7 —— 治受限模型(如 Kimi 系
/// 经 u-switch 透传)仅接受 temperature=1、其它值 400 "field Temperature invalid" 的问题。
/// None(前端不传/留空)→ config 保持 None,走默认 0.7。
#[tauri::command] #[tauri::command]
pub async fn ai_save_provider( pub async fn ai_save_provider(
state: State<'_, AppState>, state: State<'_, AppState>,
@@ -107,6 +112,7 @@ pub async fn ai_save_provider(
default_model: String, default_model: String,
provider_type: String, provider_type: String,
model_configs: Vec<ModelConfig>, model_configs: Vec<ModelConfig>,
temperature_override: Option<f32>,
) -> Result<String, String> { ) -> Result<String, String> {
// 接收前端传入的 model_configs 落库(含用户在 Settings 调的 weight/enabled/label), // 接收前端传入的 model_configs 落库(含用户在 Settings 调的 weight/enabled/label),
// 不再硬塞 Vec::new() 丢弃用户配置。新建传 []、编辑传回填+改动后的 providerForm.models。 // 不再硬塞 Vec::new() 丢弃用户配置。新建传 []、编辑传回填+改动后的 providerForm.models。
@@ -178,7 +184,9 @@ pub async fn ai_save_provider(
models: None, models: None,
model_configs, model_configs,
is_default, is_default,
config: None, // 采样温度覆盖非空才写 config JSON;None 保持 config=None(config 列历史恒 None 无旧数据,直接覆盖安全)
config: temperature_override
.map(|t| serde_json::json!({ "temperature": t }).to_string()),
created_at, created_at,
updated_at: now_millis(), updated_at: now_millis(),
// c: enabled/weight 编辑保留原值(负载均衡池可编辑层),新建走默认。 // c: enabled/weight 编辑保留原值(负载均衡池可编辑层),新建走默认。
+25
View File
@@ -101,6 +101,12 @@
Gemini 2.5→3.5 等),提示用户点「测试连接」拉真实模型列表覆盖,降低踩过时默认值的困惑。 --> Gemini 2.5→3.5 等),提示用户点「测试连接」拉真实模型列表覆盖,降低踩过时默认值的困惑。 -->
<div class="form-field-hint">{{ $t('settings.defaultModelHint') }}</div> <div class="form-field-hint">{{ $t('settings.defaultModelHint') }}</div>
</div> </div>
<!-- 供应商级采样温度覆盖:部分受限模型(如 Kimi 系)仅接受 temperature=1,留空走默认 0.7 -->
<div class="form-field">
<label class="form-label">{{ $t('settings.labelSamplingTemp') }}</label>
<input type="number" v-model.number="providerForm.temperature" class="setting-input"
:placeholder="$t('settings.phSamplingTemp')" step="0.1" min="0" max="2" />
</div>
<!-- P0-2: 保存失败持久错误横幅(常驻表单内,非自隐 toast)。 <!-- P0-2: 保存失败持久错误横幅(常驻表单内,非自隐 toast)。
含分类建议:keyring/迁移失败 → 查系统钥匙串权限;其它 → 通用重试。 含分类建议:keyring/迁移失败 → 查系统钥匙串权限;其它 → 通用重试。
用户看到错误同时表单保持编辑态,改完点保存即可重试。 --> 用户看到错误同时表单保持编辑态,改完点保存即可重试。 -->
@@ -221,6 +227,8 @@ const providerForm = reactive({
baseUrl: '', baseUrl: '',
apiKey: '', apiKey: '',
defaultModel: '', defaultModel: '',
// 供应商级采样温度覆盖(null=未配置,保存不带该参,主链路走默认 0.7;清空输入可能残留 '' ,保存时防御过滤)
temperature: null as number | null,
// 项 D:厂商预设下拉当前值(__custom=自定义;命中预设 id 时仅作展示与回填源头, // 项 D:厂商预设下拉当前值(__custom=自定义;命中预设 id 时仅作展示与回填源头,
// 实际字段值由 onPresetChange 写入;用户手改任一字段后 presetId 不自动复位, // 实际字段值由 onPresetChange 写入;用户手改任一字段后 presetId 不自动复位,
// 避免编辑态抖动,提交时也不读 presetId —— 以表单实际字段值为准)。 // 避免编辑态抖动,提交时也不读 presetId —— 以表单实际字段值为准)。
@@ -286,6 +294,7 @@ function formBusinessSnapshot(): string {
baseUrl: providerForm.baseUrl, baseUrl: providerForm.baseUrl,
apiKey: providerForm.apiKey, apiKey: providerForm.apiKey,
defaultModel: providerForm.defaultModel, defaultModel: providerForm.defaultModel,
temperature: providerForm.temperature,
models: providerForm.models.map(m => ({ models: providerForm.models.map(m => ({
model_id: m.model_id, model_id: m.model_id,
enabled: m.enabled, enabled: m.enabled,
@@ -385,6 +394,16 @@ function openProviderForm(p?: AiProviderConfig) {
providerForm.apiKey = '' // 编辑不回填 api_key(list 返回 mask,留空=不改,FR-S1) providerForm.apiKey = '' // 编辑不回填 api_key(list 返回 mask,留空=不改,FR-S1)
providerForm.defaultModel = p.default_model providerForm.defaultModel = p.default_model
providerForm.providerType = p.provider_type || 'openai_compat' providerForm.providerType = p.provider_type || 'openai_compat'
// 从 config JSON 解析采样温度回显(config 可能为 null/非法 JSON,防御处理兜底 null)
providerForm.temperature = (() => {
try {
const cfg = p.config ? JSON.parse(p.config) : null
const tv = cfg?.temperature
return typeof tv === 'number' && Number.isFinite(tv) ? tv : null
} catch {
return null
}
})()
// 项 D:编辑态不反推预设(避免用户已手改 baseUrl/model 却被下拉误标成某预设误导); // 项 D:编辑态不反推预设(避免用户已手改 baseUrl/model 却被下拉误标成某预设误导);
// 统一标"自定义",下拉仅作新建快捷填充入口。 // 统一标"自定义",下拉仅作新建快捷填充入口。
providerForm.presetId = CUSTOM_PRESET_ID providerForm.presetId = CUSTOM_PRESET_ID
@@ -399,6 +418,7 @@ function openProviderForm(p?: AiProviderConfig) {
providerForm.defaultModel = '' providerForm.defaultModel = ''
providerForm.presetId = CUSTOM_PRESET_ID providerForm.presetId = CUSTOM_PRESET_ID
providerForm.models = [] providerForm.models = []
providerForm.temperature = null
} }
providerForm.fetching = false providerForm.fetching = false
providerForm.saveError = null // 重开表单清旧错误横幅 providerForm.saveError = null // 重开表单清旧错误横幅
@@ -525,6 +545,11 @@ async function saveProvider() {
apiKey: providerForm.apiKey, apiKey: providerForm.apiKey,
defaultModel: providerForm.defaultModel, defaultModel: providerForm.defaultModel,
modelConfigs: providerForm.models, modelConfigs: providerForm.models,
// 采样温度非空且为有效数字才带(清空输入 v-model.number 可能残留 ''/NaN,不过滤会污染后端 config)
temperatureOverride:
typeof providerForm.temperature === 'number' && Number.isFinite(providerForm.temperature)
? providerForm.temperature
: undefined,
}) })
// 成功明确反馈——清错误横幅 + 关表单 + 绿色「已保存」toast // 成功明确反馈——清错误横幅 + 关表单 + 绿色「已保存」toast
providerForm.saveError = null providerForm.saveError = null
+2
View File
@@ -49,6 +49,8 @@ export default {
labelBaseUrl: 'Base URL', labelBaseUrl: 'Base URL',
labelApiKey: 'API Key', labelApiKey: 'API Key',
labelDefaultModel: 'Default', labelDefaultModel: 'Default',
labelSamplingTemp: 'Sampling temperature',
phSamplingTemp: 'default 0.7',
phProviderName: 'e.g. DeepSeek, GLM-4', phProviderName: 'e.g. DeepSeek, GLM-4',
phBaseUrl: 'https://api.deepseek.com', phBaseUrl: 'https://api.deepseek.com',
phApiKey: 'sk-...', phApiKey: 'sk-...',
+2
View File
@@ -49,6 +49,8 @@ export default {
labelBaseUrl: 'Base URL', labelBaseUrl: 'Base URL',
labelApiKey: 'API Key', labelApiKey: 'API Key',
labelDefaultModel: '默认模型', labelDefaultModel: '默认模型',
labelSamplingTemp: '采样温度',
phSamplingTemp: '默认 0.7',
phProviderName: '如 DeepSeek、GLM-4', phProviderName: '如 DeepSeek、GLM-4',
phBaseUrl: 'https://api.deepseek.com', phBaseUrl: 'https://api.deepseek.com',
phApiKey: 'sk-...', phApiKey: 'sk-...',