修复: 协议层与体验缺陷六件

- temperature 按模型适配,仅收 1 的模型不再 400
- 流式 error 帧不再被 choices 必填整帧丢弃
- 只读缓存写后失效
- git diff 中文截断撞 UTF-8 边界 panic
- 非视觉模型纯图片直接拒绝
- 全局快捷键补输入焦点守卫
This commit is contained in:
lxy
2026-10-04 16:27:31 +08:00
parent 01d231c39d
commit 7aa0da32f2
28 changed files with 491 additions and 52 deletions
+12
View File
@@ -138,6 +138,14 @@ pub struct ModelConfig {
#[serde(default = "default_context_window")]
pub context_window: usize,
/// 是否支持 temperature 参数(默认 true)。
///
/// 部分模型仅接受固定温度值(如商汤 sensenova 系列仅允许 1.0),透传其它温度(标题 0.3/
/// 压缩 0.2 等)→ 上游 HTTP 400,实测 9 次。false 时协议转换层省略 temperature 字段
/// (交由上游默认),避免无效往返;true 保持原样透传(零回归)。
#[serde(default = "default_true")]
pub temperature_supported: bool,
// ── 探测元数据(只读,由 ModelProbe 填充,Phase 2)──
#[serde(default, skip_serializing_if = "Option::is_none")]
pub probe_source: Option<ProbeSource>,
@@ -173,6 +181,9 @@ impl ModelConfig {
intelligence: IntelligenceTier::Standard,
weight: default_weight(),
context_window: default_context_window(),
// 默认支持 temperature(老格式升级/未知模型保持原样透传,零回归);
// 明确不支持的模型由 presets 表 temperature_supported=false 标注。
temperature_supported: true,
probe_source: None,
}
}
@@ -339,6 +350,7 @@ mod tests {
intelligence: IntelligenceTier::Plus,
weight: 70,
context_window: 128_000,
temperature_supported: true,
probe_source: Some(ProbeSource::PresetTable),
};
let json = serde_json::to_string(&mc).unwrap();
+1
View File
@@ -472,6 +472,7 @@ mod tests {
usage: None,
error: None,
reasoning_content: Some("thought".to_string()),
truncated_by_max_tokens: false,
};
let json = serde_json::to_string(&chunk).unwrap();
assert!(json.contains("reasoning_content"));
+5
View File
@@ -350,6 +350,11 @@ pub struct StreamChunk {
/// 非空表示流中途出错,不应视为正常完成(finished 路径),由 stream_llm 转 AiError。
#[serde(skip)]
pub error: Option<String>,
/// 本流因 provider 输出上限截断收尾(openai finish_reason=length /
/// anthropic stop_reason=max_tokens)。finished 仍为 true(正常终止非断连),但文本/
/// tool_calls 可能不完整;消费方跨帧 OR 累积该标志,轮产出据此标记截断,不冒充正常完成。
#[serde(default)]
pub truncated_by_max_tokens: bool,
/// DeepSeek thinking 模式推理内容增量
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning_content: Option<String>,