agi.aichat_backend_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package agi
  2. import (
  3. "io"
  4. "net/http"
  5. "net/http/httptest"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "testing"
  10. "github.com/robertkrimen/otto"
  11. "imuslab.com/arozos/mod/agi/static"
  12. user "imuslab.com/arozos/mod/user"
  13. )
  14. /*
  15. Backend script tests for the AI Chat demo app (web/AIChat/backend/*.agi).
  16. These execute the real .agi scripts inside an otto VM with the real llm
  17. library injected (pointed at a mock OpenAI-compatible server), so the demo
  18. app's backend logic is verified without a running arozos server or a real
  19. model endpoint.
  20. */
  21. // runAIChatBackend loads a backend script, injects the llm lib + stubs for
  22. // requirelib/sendJSONResp, sets the given POST params and returns whatever the
  23. // script passed to sendJSONResp.
  24. func runAIChatBackend(t *testing.T, g *Gateway, scriptRelPath string, params map[string]string) string {
  25. t.Helper()
  26. vm := otto.New()
  27. g.injectLLMFunctions(&static.AgiLibInjectionPayload{VM: vm, User: &user.User{Username: "tester"}})
  28. //requirelib is a no-op here: the lib is already injected above.
  29. vm.Set("requirelib", func(call otto.FunctionCall) otto.Value {
  30. v, _ := vm.ToValue(true)
  31. return v
  32. })
  33. var captured string
  34. vm.Set("sendJSONResp", func(call otto.FunctionCall) otto.Value {
  35. captured, _ = call.Argument(0).ToString()
  36. return otto.UndefinedValue()
  37. })
  38. for k, v := range params {
  39. vm.Set(k, v)
  40. }
  41. scriptPath := filepath.Join("..", "..", "web", scriptRelPath)
  42. content, err := os.ReadFile(scriptPath)
  43. if err != nil {
  44. t.Fatalf("cannot read backend script %s: %v", scriptPath, err)
  45. }
  46. if _, err := vm.Run(string(content)); err != nil {
  47. t.Fatalf("backend script %s errored: %v", scriptRelPath, err)
  48. }
  49. return captured
  50. }
  51. func TestAIChatBackend_Chat(t *testing.T) {
  52. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  53. body, _ := io.ReadAll(r.Body)
  54. //The system prompt set via options must reach the endpoint.
  55. if !strings.Contains(string(body), "be a pirate") {
  56. t.Errorf("system prompt was not forwarded; body=%s", string(body))
  57. }
  58. w.Header().Set("Content-Type", "application/json")
  59. io.WriteString(w, `{"model":"test-model",
  60. "choices":[{"message":{"role":"assistant","content":"Arr, hello!"}}],
  61. "usage":{"prompt_tokens":12,"completion_tokens":4,"total_tokens":16}}`)
  62. }))
  63. defer srv.Close()
  64. g := dbGateway(t)
  65. sysdb := g.Option.UserHandler.GetDatabase()
  66. sysdb.Write(llmDBTable, "config", LLMConfig{Endpoint: srv.URL, DefaultModel: "test-model", Currency: "USD"})
  67. out := runAIChatBackend(t, g, "AIChat/backend/chat.agi", map[string]string{
  68. "messages": `[{"role":"user","content":"hi"}]`,
  69. "options": `{"model":"test-model","system":"be a pirate"}`,
  70. })
  71. if !strings.Contains(out, `"ok":true`) {
  72. t.Fatalf("expected ok:true, got: %s", out)
  73. }
  74. if !strings.Contains(out, "Arr, hello!") {
  75. t.Errorf("assistant content missing from response: %s", out)
  76. }
  77. if !strings.Contains(out, `"total_tokens":16`) {
  78. t.Errorf("usage missing from response: %s", out)
  79. }
  80. }
  81. func TestAIChatBackend_ChatNoEndpointReturnsError(t *testing.T) {
  82. g := dbGateway(t) //no config written -> endpoint unset
  83. out := runAIChatBackend(t, g, "AIChat/backend/chat.agi", map[string]string{
  84. "messages": `[{"role":"user","content":"hi"}]`,
  85. "options": `{}`,
  86. })
  87. if !strings.Contains(out, `"ok":false`) {
  88. t.Fatalf("expected ok:false when endpoint missing, got: %s", out)
  89. }
  90. if !strings.Contains(strings.ToLower(out), "endpoint") {
  91. t.Errorf("expected an endpoint-related error message, got: %s", out)
  92. }
  93. }
  94. func TestAIChatBackend_Models(t *testing.T) {
  95. g := dbGateway(t)
  96. sysdb := g.Option.UserHandler.GetDatabase()
  97. sysdb.Write(llmDBTable, "config", LLMConfig{DefaultModel: "test-model", Currency: "USD"})
  98. sysdb.Write(llmDBTable, "pricing", map[string]LLMPricing{
  99. "test-model": {InputPrice: 1, OutputPrice: 2},
  100. "other": {InputPrice: 3, OutputPrice: 4},
  101. })
  102. out := runAIChatBackend(t, g, "AIChat/backend/models.agi", map[string]string{})
  103. if !strings.Contains(out, `"default":"test-model"`) {
  104. t.Errorf("default model missing: %s", out)
  105. }
  106. if !strings.Contains(out, "test-model") || !strings.Contains(out, "other") {
  107. t.Errorf("configured models missing: %s", out)
  108. }
  109. }