feat: several models for the assistant, each with its own name, kind, address and key; a model menu in the chat when there are several (or always, from Аккаунт › Вид), the choice kept for the user
This commit is contained in:
1 parent
ded86e1c3f
commit
983809714b
12 files changed
+411
-44
No files matched your search
@@ -0,0 +1,109 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The model set before there could be several is the first of the list.
|
||||
func TestAIModelsFromLegacy(t *testing.T) {
|
||||
s, _ := newTestServer(t)
|
||||
_ = s.db.SetConfig(cfgAIType, "openai")
|
||||
_ = s.db.SetConfig(cfgAIURL, "https://api.example/v1/")
|
||||
_ = s.db.SetConfig(cfgAIModel, "gpt-x")
|
||||
_ = s.db.SetConfig(cfgAIKey, "k1")
|
||||
ms := s.aiModels()
|
||||
if len(ms) != 1 || ms[0].Type != "openai" || ms[0].URL != "https://api.example/v1" || ms[0].Model != "gpt-x" || ms[0].Key != "k1" || ms[0].ID == "" {
|
||||
t.Fatalf("models: %+v", ms)
|
||||
}
|
||||
if got := s.aiModel("nope"); got.ID != ms[0].ID {
|
||||
t.Fatalf("an unknown id is not the first model: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// postAIModels sends the settings form with the given models.
|
||||
func postAIModels(t *testing.T, s *Server, c *http.Cookie, models [][6]string) {
|
||||
t.Helper()
|
||||
f := url.Values{"csrf": {"tok"}}
|
||||
for _, m := range models {
|
||||
f.Add("m_id", m[0])
|
||||
f.Add("m_name", m[1])
|
||||
f.Add("m_type", m[2])
|
||||
f.Add("m_url", m[3])
|
||||
f.Add("m_model", m[4])
|
||||
f.Add("m_key", m[5])
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/settings/ai", strings.NewReader(f.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.AddCookie(c)
|
||||
req.AddCookie(&http.Cookie{Name: csrfCookie, Value: "tok"})
|
||||
w := httptest.NewRecorder()
|
||||
s.Handler().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusSeeOther {
|
||||
t.Fatalf("save: %d %s", w.Code, w.Body)
|
||||
}
|
||||
}
|
||||
|
||||
// Настройки keep several models with their names; the chat learns the
|
||||
// names and ids only, never a key.
|
||||
func TestAIModelsSaveAndList(t *testing.T) {
|
||||
s, c := newTestServer(t)
|
||||
postAIModels(t, s, c, [][6]string{
|
||||
{"", "Qwen", "gradio", "", "", ""},
|
||||
{"", "Claude", "anthropic", "https://api.anthropic.com", "claude-x", "secret-key"},
|
||||
{"", "", "", "", "", ""}, // an empty card is dropped
|
||||
})
|
||||
ms := s.aiModels()
|
||||
if len(ms) != 2 || ms[0].Name != "Qwen" || ms[1].Name != "Claude" || ms[1].Key != "secret-key" || ms[0].ID == "" || ms[0].ID == ms[1].ID {
|
||||
t.Fatalf("models: %+v", ms)
|
||||
}
|
||||
body := get(t, s, c, "/api/panel/ai").Body.String()
|
||||
if strings.Contains(body, "secret-key") || !strings.Contains(body, `"name":"Claude"`) || !strings.Contains(body, ms[1].ID) {
|
||||
t.Fatalf("state: %s", body)
|
||||
}
|
||||
// Saved again with its id, a model keeps it.
|
||||
postAIModels(t, s, c, [][6]string{{ms[1].ID, "Claude 2", "anthropic", "https://api.anthropic.com", "claude-x", "secret-key"}})
|
||||
if got := s.aiModels(); len(got) != 1 || got[0].ID != ms[1].ID || got[0].Name != "Claude 2" {
|
||||
t.Fatalf("after resave: %+v", got)
|
||||
}
|
||||
if !strings.Contains(get(t, s, c, "/settings?frag=1").Body.String(), `value="Claude 2"`) {
|
||||
t.Fatal("the settings page does not show the model's name")
|
||||
}
|
||||
}
|
||||
|
||||
// The chat asks the model it names.
|
||||
func TestAIChatUsesChosenModel(t *testing.T) {
|
||||
s, c := newTestServer(t)
|
||||
srv := func(word string) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "data: {\"choices\":[{\"delta\":{\"content\":\"%s\"}}]}\n\ndata: [DONE]\n\n", word)
|
||||
}))
|
||||
}
|
||||
one, two := srv("first"), srv("second")
|
||||
defer one.Close()
|
||||
defer two.Close()
|
||||
postAIModels(t, s, c, [][6]string{
|
||||
{"", "One", "openai", one.URL, "m", ""},
|
||||
{"", "Two", "openai", two.URL, "m", ""},
|
||||
})
|
||||
ms := s.aiModels()
|
||||
ask := func(model string) string {
|
||||
b, _ := json.Marshal(map[string]any{"message": "hi", "context": "x", "model": model})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/panel/ai/chat", strings.NewReader(string(b)))
|
||||
req.AddCookie(c)
|
||||
w := httptest.NewRecorder()
|
||||
s.Handler().ServeHTTP(w, req)
|
||||
return w.Body.String()
|
||||
}
|
||||
if got := ask(ms[1].ID); !strings.Contains(got, "second") {
|
||||
t.Fatalf("model Two: %q", got)
|
||||
}
|
||||
if got := ask(""); !strings.Contains(got, "first") {
|
||||
t.Fatalf("no model named - the first: %q", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user