fix(llm): 测试连接校验模型确有回复,并按配置的收发模式测
测试连接此前只判 err:请求通了但模型一个字不吐(思考烧光预算、正文被 安全策略吞掉、兼容层丢 content)照样报成功,而这种配置在会话里就是不 回话——正是「测试成功、对话不成功」的来源。 - TestConnection 返回回复文本,空回复直接判失败 - 回复回传前端,成功 toast 里展示,测试通过有据可查 - 测试连接改用该配置真实的收发模式:此前恒走流式(ConfigFrom 默认 Stream=true),选了非流式的配置测的是另一条通道;请求省略 streaming 时仍默认流式,老客户端不受影响 Closes #65
This commit is contained in:
1 parent
68a221eda1
commit
7b1f7e141b
5 files changed
+62
-11
No files matched your search
+23
-2
@@ -1337,6 +1337,7 @@ func (s *Server) testLLM(w http.ResponseWriter, r *http.Request) {
|
||||
ThinkingType string `json:"thinking_type"`
|
||||
ReasoningEffort string `json:"reasoning_effort"`
|
||||
ProfileID *int64 `json:"profile_id"` // 测已存 profile 时传入:api_key 为空则用它存的 key
|
||||
Streaming *bool `json:"streaming"` // 省略=流式,与保存 profile 时同一套默认
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeErr(w, 400, err.Error())
|
||||
@@ -1347,6 +1348,11 @@ func (s *Server) testLLM(w http.ResponseWriter, r *http.Request) {
|
||||
// reasoning_effort/thinking field fails the test too (no false "test ok, run 400").
|
||||
cfg.ThinkingType = req.ThinkingType
|
||||
cfg.ReasoningEffort = req.ReasoningEffort
|
||||
// 同理,收发模式也照该 profile 的选择来:只支持其中一种通道的端点必须在这里就
|
||||
// 暴露,而不是等会话里才发现"测试通过的配置根本跑不动"。
|
||||
if req.Streaming != nil {
|
||||
cfg.Stream = *req.Streaming
|
||||
}
|
||||
// API Key 解析优先级:表单输入 > 指定 profile 存的 key > 全局配置的 key。
|
||||
// 已存 profile 的 key 不回传浏览器,所以测试已存配置时表单为空,需从 DB 取。
|
||||
if cfg.APIKey == "" && req.ProfileID != nil {
|
||||
@@ -1363,12 +1369,27 @@ func (s *Server) testLLM(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, 200, map[string]any{"ok": false, "error": "未提供 API Key"})
|
||||
return
|
||||
}
|
||||
lat, err := agent.TestConnection(r.Context(), cfg)
|
||||
lat, reply, err := agent.TestConnection(r.Context(), cfg)
|
||||
if err != nil {
|
||||
writeJSON(w, 200, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{"ok": true, "latency_ms": lat.Milliseconds(), "model": cfg.Model})
|
||||
// 回传模型实际回复,让"测试通过"有据可查:看得见它确实说了话,而不只是 HTTP 200。
|
||||
writeJSON(w, 200, map[string]any{
|
||||
"ok": true, "latency_ms": lat.Milliseconds(), "model": cfg.Model, "reply": truncateReply(reply),
|
||||
})
|
||||
}
|
||||
|
||||
// truncateReply clips a connection-test reply for display. A model told to answer
|
||||
// "OK" can still ramble (or think out loud); the UI only needs enough to show it
|
||||
// really said something. Rune-based so multibyte text never splits mid-character.
|
||||
func truncateReply(s string) string {
|
||||
const max = 200
|
||||
r := []rune(s)
|
||||
if len(r) <= max {
|
||||
return s
|
||||
}
|
||||
return string(r[:max]) + "…"
|
||||
}
|
||||
|
||||
type createTaskReq struct {
|
||||
|
||||
Reference in new issue
Block a user