diff --git a/README.md b/README.md index 907c681b969b..d6cc8c23e4d1 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,16 @@ local-ai run https://gist.githubusercontent.com/.../phi-2.yaml local-ai run oci://localai/phi-2:latest ``` +To test a running LocalAI server from the terminal, open an interactive chat session from another shell. Inside the prompt, `/models` lists installed models and `/model ` switches between them. + +```bash +# Terminal 1 +local-ai run llama-3.2-1b-instruct:q4_k_m + +# Terminal 2 +local-ai chat --model llama-3.2-1b-instruct:q4_k_m +``` + > **Automatic Backend Detection**: LocalAI automatically detects your GPU capabilities and downloads the appropriate backend. For advanced options, see [GPU Acceleration](https://localai.io/features/gpu-acceleration/). For more details, see the [Getting Started guide](https://localai.io/basics/getting_started/). diff --git a/core/cli/chat/chat.go b/core/cli/chat/chat.go new file mode 100644 index 000000000000..071d3a7858ad --- /dev/null +++ b/core/cli/chat/chat.go @@ -0,0 +1,30 @@ +package chat + +import ( + "context" + "io" + "strings" +) + +type Options struct { + Model string + BaseURL string + APIKey string + In io.Reader + Out io.Writer +} + +func Run(ctx context.Context, opts Options) error { + if opts.In == nil { + opts.In = strings.NewReader("") + } + if opts.Out == nil { + opts.Out = io.Discard + } + + session, err := newChatSession(ctx, newLocalAIChatClient(opts.BaseURL, opts.APIKey), opts.Model) + if err != nil { + return err + } + return runTerminalChat(ctx, session, opts.In, opts.Out) +} diff --git a/core/cli/chat/chat_suite_test.go b/core/cli/chat/chat_suite_test.go new file mode 100644 index 000000000000..1f4d79aafab6 --- /dev/null +++ b/core/cli/chat/chat_suite_test.go @@ -0,0 +1,13 @@ +package chat + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestChat(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Chat Suite") +} diff --git a/core/cli/chat/chat_test.go b/core/cli/chat/chat_test.go new file mode 100644 index 000000000000..a5c9a1f3c82c --- /dev/null +++ b/core/cli/chat/chat_test.go @@ -0,0 +1,161 @@ +package chat + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Run chat", func() { + It("streams a single chat response", func() { + var capturedModel string + var capturedAuth string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/v1/models" { + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, `{"object":"list","data":[{"id":"test-model","object":"model"}]}`) + return + } + + Expect(r.URL.Path).To(Equal("/v1/chat/completions")) + capturedAuth = r.Header.Get("Authorization") + + var body struct { + Model string `json:"model"` + Messages []struct { + Role string `json:"role"` + Content string `json:"content"` + } `json:"messages"` + } + Expect(json.NewDecoder(r.Body).Decode(&body)).To(Succeed()) + capturedModel = body.Model + Expect(body.Messages).To(HaveLen(1)) + Expect(body.Messages[0].Role).To(Equal("user")) + Expect(body.Messages[0].Content).To(Equal("hello")) + + w.Header().Set("Content-Type", "text/event-stream") + fmt.Fprint(w, "data: {\"choices\":[{\"index\":0,\"delta\":{\"content\":\"hi\"}}]}\n\n") + fmt.Fprint(w, "data: {\"choices\":[{\"index\":0,\"delta\":{\"content\":\"!\"}}]}\n\n") + fmt.Fprint(w, "data: [DONE]\n\n") + })) + defer server.Close() + + var out bytes.Buffer + err := Run(GinkgoT().Context(), Options{ + Model: "test-model", + BaseURL: server.URL + "/v1", + APIKey: "secret", + In: strings.NewReader("hello\n/exit\n"), + Out: &out, + }) + + Expect(err).ToNot(HaveOccurred()) + Expect(capturedModel).To(Equal("test-model")) + Expect(capturedAuth).To(Equal("Bearer secret")) + Expect(out.String()).To(ContainSubstring("assistant: hi!")) + Expect(out.String()).To(ContainSubstring("bye")) + }) + + It("auto-selects the only available model", func() { + server := chatTestServer([]string{"solo"}, nil) + defer server.Close() + + var out bytes.Buffer + err := Run(GinkgoT().Context(), Options{ + BaseURL: server.URL + "/v1", + In: strings.NewReader("/exit\n"), + Out: &out, + }) + + Expect(err).ToNot(HaveOccurred()) + Expect(out.String()).To(ContainSubstring("LocalAI chat (solo)")) + }) + + It("returns an actionable error when no models are installed", func() { + server := chatTestServer(nil, nil) + defer server.Close() + + err := Run(GinkgoT().Context(), Options{ + BaseURL: server.URL + "/v1", + In: strings.NewReader(""), + }) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no chat models are installed")) + Expect(err.Error()).To(ContainSubstring("local-ai models install ")) + }) + + It("returns an actionable error when multiple models are available without a selection", func() { + server := chatTestServer([]string{"alpha", "beta"}, nil) + defer server.Close() + + err := Run(GinkgoT().Context(), Options{ + BaseURL: server.URL + "/v1", + In: strings.NewReader(""), + }) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("multiple models are available")) + Expect(err.Error()).To(ContainSubstring("--model")) + Expect(err.Error()).To(ContainSubstring("alpha")) + Expect(err.Error()).To(ContainSubstring("beta")) + }) + + It("lists and switches models inside the chat", func() { + requestedModels := []string{} + server := chatTestServer([]string{"alpha", "beta"}, func(model string) { + requestedModels = append(requestedModels, model) + }) + defer server.Close() + + var out bytes.Buffer + err := Run(GinkgoT().Context(), Options{ + Model: "alpha", + BaseURL: server.URL + "/v1", + In: strings.NewReader("/models\n/model beta\nhello\n/exit\n"), + Out: &out, + }) + + Expect(err).ToNot(HaveOccurred()) + Expect(out.String()).To(ContainSubstring("* alpha")) + Expect(out.String()).To(ContainSubstring(" beta")) + Expect(out.String()).To(ContainSubstring("switched to beta; conversation cleared")) + Expect(requestedModels).To(Equal([]string{"beta"})) + }) +}) + +func chatTestServer(models []string, onChat func(model string)) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v1/models": + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, `{"object":"list","data":[`) + for i, model := range models { + if i > 0 { + fmt.Fprint(w, ",") + } + fmt.Fprintf(w, `{"id":%q,"object":"model"}`, model) + } + fmt.Fprint(w, `]}`) + case "/v1/chat/completions": + var body struct { + Model string `json:"model"` + } + Expect(json.NewDecoder(r.Body).Decode(&body)).To(Succeed()) + if onChat != nil { + onChat(body.Model) + } + w.Header().Set("Content-Type", "text/event-stream") + fmt.Fprint(w, "data: {\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ok\"}}]}\n\n") + fmt.Fprint(w, "data: [DONE]\n\n") + default: + w.WriteHeader(http.StatusNotFound) + } + })) +} diff --git a/core/cli/chat/client.go b/core/cli/chat/client.go new file mode 100644 index 000000000000..93910de7e13c --- /dev/null +++ b/core/cli/chat/client.go @@ -0,0 +1,112 @@ +package chat + +import ( + "context" + "errors" + "fmt" + "io" + "sort" + "strings" + + openai "github.com/sashabaranov/go-openai" +) + +type chatClient interface { + ListModels(ctx context.Context) ([]string, error) + StreamChat(ctx context.Context, model string, messages []chatMessage, out io.Writer) (string, error) +} + +type localAIChatClient struct { + client *openai.Client +} + +func newLocalAIChatClient(baseURL string, apiKey string) *localAIChatClient { + cfg := openai.DefaultConfig(apiKey) + cfg.BaseURL = baseURL + return &localAIChatClient{client: openai.NewClientWithConfig(cfg)} +} + +func (c *localAIChatClient) ListModels(ctx context.Context) ([]string, error) { + resp, err := c.client.ListModels(ctx) + if err != nil { + return nil, err + } + + models := make([]string, 0, len(resp.Models)) + for _, model := range resp.Models { + if model.ID != "" { + models = append(models, model.ID) + } + } + sort.Strings(models) + return models, nil +} + +func (c *localAIChatClient) StreamChat(ctx context.Context, model string, messages []chatMessage, out io.Writer) (string, error) { + stream, err := c.client.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{ + Model: model, + Messages: openAIChatMessages(messages), + }) + if err != nil { + return "", friendlyChatError(err, model) + } + defer stream.Close() + + var answer strings.Builder + for { + resp, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return answer.String(), friendlyChatError(err, model) + } + if len(resp.Choices) == 0 { + continue + } + + token := resp.Choices[0].Delta.Content + if token == "" { + continue + } + answer.WriteString(token) + if _, err := fmt.Fprint(out, token); err != nil { + return answer.String(), err + } + } + + return answer.String(), nil +} + +func openAIChatMessages(messages []chatMessage) []openai.ChatCompletionMessage { + converted := make([]openai.ChatCompletionMessage, len(messages)) + for i, message := range messages { + converted[i] = openai.ChatCompletionMessage{ + Role: message.Role, + Content: message.Content, + } + } + return converted +} + +func friendlyChatError(err error, model string) error { + var apiErr *openai.APIError + if errors.As(err, &apiErr) { + switch apiErr.HTTPStatusCode { + case 404: + return fmt.Errorf("model %q is not available. Run `local-ai models list`, install a model with `local-ai models install `, or switch with `/model `", model) + case 403: + return fmt.Errorf("model %q is disabled. Enable it from LocalAI settings or choose another model with `/model `", model) + } + if apiErr.Message != "" { + return errors.New(apiErr.Message) + } + } + + msg := err.Error() + if strings.Contains(msg, "model") && strings.Contains(msg, "not found") { + return fmt.Errorf("model %q is not available. Run `local-ai models list`, install a model with `local-ai models install `, or switch with `/model `", model) + } + + return err +} diff --git a/core/cli/chat/models.go b/core/cli/chat/models.go new file mode 100644 index 000000000000..291ec15aa8c6 --- /dev/null +++ b/core/cli/chat/models.go @@ -0,0 +1,17 @@ +package chat + +import "strings" + +func formatChatModelList(models []string, current string) string { + var b strings.Builder + for _, model := range models { + prefix := " " + if model == current { + prefix = "* " + } + b.WriteString(prefix) + b.WriteString(model) + b.WriteByte('\n') + } + return b.String() +} diff --git a/core/cli/chat/session.go b/core/cli/chat/session.go new file mode 100644 index 000000000000..e3075a8af0dc --- /dev/null +++ b/core/cli/chat/session.go @@ -0,0 +1,120 @@ +package chat + +import ( + "context" + "errors" + "fmt" + "io" + "strings" +) + +const ( + chatRoleUser = "user" + chatRoleAssistant = "assistant" +) + +type chatMessage struct { + Role string + Content string +} + +type chatSession struct { + client chatClient + model string + models []string + messages []chatMessage +} + +func newChatSession(ctx context.Context, client chatClient, requestedModel string) (*chatSession, error) { + models, err := client.ListModels(ctx) + if err != nil { + return nil, fmt.Errorf("list models: %w", err) + } + + model, err := resolveChatModel(requestedModel, models) + if err != nil { + return nil, err + } + + return &chatSession{ + client: client, + model: model, + models: models, + }, nil +} + +func (s *chatSession) CurrentModel() string { + return s.model +} + +func (s *chatSession) Models() []string { + models := make([]string, len(s.models)) + copy(models, s.models) + return models +} + +func (s *chatSession) Clear() { + s.messages = nil +} + +func (s *chatSession) SwitchModel(model string) error { + if !modelExists(s.models, model) { + return fmt.Errorf("model %q is not available. Use /models to see installed models", model) + } + s.model = model + s.Clear() + return nil +} + +func (s *chatSession) Send(ctx context.Context, prompt string, out io.Writer) error { + s.messages = append(s.messages, chatMessage{ + Role: chatRoleUser, + Content: prompt, + }) + + answer, err := s.client.StreamChat(ctx, s.model, s.messages, out) + if err != nil { + return err + } + + s.messages = append(s.messages, chatMessage{ + Role: chatRoleAssistant, + Content: answer, + }) + return nil +} + +func resolveChatModel(requested string, models []string) (string, error) { + switch { + case requested == "" && len(models) == 0: + return "", errors.New(`no chat models are installed. + +Install a model first, for example: + local-ai models list + local-ai models install + local-ai run + +Then start a chat session: + local-ai chat --model `) + case requested == "" && len(models) == 1: + return models[0], nil + case requested == "" && len(models) > 1: + var b strings.Builder + b.WriteString("multiple models are available; choose one with --model:\n") + b.WriteString(formatChatModelList(models, "")) + return "", errors.New(b.String()) + case !modelExists(models, requested): + return "", fmt.Errorf("model %q is not available. Use `local-ai models list` and `local-ai models install `, or pass an installed model with --model", requested) + default: + return requested, nil + } +} + +func modelExists(models []string, name string) bool { + for _, model := range models { + if model == name { + return true + } + } + return false +} diff --git a/core/cli/chat/session_test.go b/core/cli/chat/session_test.go new file mode 100644 index 000000000000..dcf274805e3d --- /dev/null +++ b/core/cli/chat/session_test.go @@ -0,0 +1,56 @@ +package chat + +import ( + "context" + "io" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Chat session", func() { + It("keeps model switching and message history out of the terminal adapter", func() { + client := &fakeChatClient{ + models: []string{"alpha", "beta"}, + answer: "pong", + } + + session, err := newChatSession(context.Background(), client, "alpha") + Expect(err).ToNot(HaveOccurred()) + Expect(session.CurrentModel()).To(Equal("alpha")) + + Expect(session.SwitchModel("beta")).To(Succeed()) + Expect(session.CurrentModel()).To(Equal("beta")) + Expect(session.Send(context.Background(), "ping", io.Discard)).To(Succeed()) + + Expect(client.requests).To(HaveLen(1)) + Expect(client.requests[0].model).To(Equal("beta")) + Expect(client.requests[0].messages).To(HaveLen(1)) + Expect(client.requests[0].messages[0].Content).To(Equal("ping")) + }) +}) + +type fakeChatClient struct { + models []string + answer string + requests []fakeChatRequest +} + +type fakeChatRequest struct { + model string + messages []chatMessage +} + +func (c *fakeChatClient) ListModels(context.Context) ([]string, error) { + return c.models, nil +} + +func (c *fakeChatClient) StreamChat(_ context.Context, model string, messages []chatMessage, out io.Writer) (string, error) { + copied := make([]chatMessage, len(messages)) + copy(copied, messages) + c.requests = append(c.requests, fakeChatRequest{model: model, messages: copied}) + if _, err := io.WriteString(out, c.answer); err != nil { + return "", err + } + return c.answer, nil +} diff --git a/core/cli/chat/terminal.go b/core/cli/chat/terminal.go new file mode 100644 index 000000000000..c9aebdbe3911 --- /dev/null +++ b/core/cli/chat/terminal.go @@ -0,0 +1,70 @@ +package chat + +import ( + "bufio" + "context" + "fmt" + "io" + "strings" +) + +func runTerminalChat(ctx context.Context, session *chatSession, in io.Reader, out io.Writer) error { + scanner := bufio.NewScanner(in) + scanner.Buffer(make([]byte, 0, 64*1024), 4*1024*1024) + + fmt.Fprintf(out, "LocalAI chat (%s)\n", session.CurrentModel()) + fmt.Fprintln(out, "Type /exit to quit, /clear to reset the conversation, /models to list models.") + + for { + fmt.Fprint(out, "\n> ") + if !scanner.Scan() { + break + } + + prompt := strings.TrimSpace(scanner.Text()) + switch prompt { + case "": + continue + case "/bye", "/exit", "/quit": + fmt.Fprintln(out, "bye") + return nil + case "/clear": + session.Clear() + fmt.Fprintln(out, "conversation cleared") + continue + case "/models": + printChatModels(out, session.Models(), session.CurrentModel()) + continue + } + + if nextModel, ok := strings.CutPrefix(prompt, "/model "); ok { + nextModel = strings.TrimSpace(nextModel) + if nextModel == "" { + fmt.Fprintln(out, "usage: /model ") + continue + } + if err := session.SwitchModel(nextModel); err != nil { + fmt.Fprintln(out, err) + continue + } + fmt.Fprintf(out, "switched to %s; conversation cleared\n", session.CurrentModel()) + continue + } + + fmt.Fprint(out, "assistant: ") + if err := session.Send(ctx, prompt, out); err != nil { + return err + } + fmt.Fprintln(out) + } + + return scanner.Err() +} + +func printChatModels(out io.Writer, models []string, current string) { + if len(models) == 0 { + fmt.Fprintln(out, "no models installed") + return + } + fmt.Fprint(out, formatChatModelList(models, current)) +} diff --git a/core/cli/chat_cmd.go b/core/cli/chat_cmd.go new file mode 100644 index 000000000000..65228ff1f511 --- /dev/null +++ b/core/cli/chat_cmd.go @@ -0,0 +1,25 @@ +package cli + +import ( + "context" + "os" + + chatcli "github.com/mudler/LocalAI/core/cli/chat" + cliContext "github.com/mudler/LocalAI/core/cli/context" +) + +type ChatCMD struct { + Model string `short:"m" help:"Model name to use. Defaults to the only model returned by the server when exactly one is available"` + Endpoint string `env:"LOCALAI_CHAT_ENDPOINT" default:"http://127.0.0.1:8080" help:"LocalAI server endpoint. The /v1 path is added automatically when omitted"` + APIKey string `env:"LOCALAI_API_KEY,API_KEY" help:"API key to use when the LocalAI server requires authentication"` +} + +func (c *ChatCMD) Run(ctx *cliContext.Context) error { + return chatcli.Run(context.Background(), chatcli.Options{ + Model: c.Model, + BaseURL: chatAPIBaseURL(c.Endpoint), + APIKey: c.APIKey, + In: os.Stdin, + Out: os.Stdout, + }) +} diff --git a/core/cli/chat_cmd_test.go b/core/cli/chat_cmd_test.go new file mode 100644 index 000000000000..55ce5014b97e --- /dev/null +++ b/core/cli/chat_cmd_test.go @@ -0,0 +1,27 @@ +package cli + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Chat command wiring", func() { + Describe("chatAPIBaseURL", func() { + It("adds /v1 to a root endpoint", func() { + Expect(chatAPIBaseURL("http://127.0.0.1:8080")).To(Equal("http://127.0.0.1:8080/v1")) + }) + + It("keeps endpoints that already include /v1", func() { + Expect(chatAPIBaseURL("http://127.0.0.1:8080/v1")).To(Equal("http://127.0.0.1:8080/v1")) + Expect(chatAPIBaseURL("http://127.0.0.1:8080/v1/")).To(Equal("http://127.0.0.1:8080/v1")) + }) + + It("adds a default http scheme", func() { + Expect(chatAPIBaseURL("127.0.0.1:8080")).To(Equal("http://127.0.0.1:8080/v1")) + }) + + It("preserves non-root paths before /v1", func() { + Expect(chatAPIBaseURL("http://127.0.0.1:8080/localai")).To(Equal("http://127.0.0.1:8080/localai/v1")) + }) + }) +}) diff --git a/core/cli/chat_endpoint.go b/core/cli/chat_endpoint.go new file mode 100644 index 000000000000..94f460ae826f --- /dev/null +++ b/core/cli/chat_endpoint.go @@ -0,0 +1,29 @@ +package cli + +import ( + "net/url" + "strings" +) + +func chatAPIBaseURL(endpoint string) string { + if !strings.Contains(endpoint, "://") { + endpoint = "http://" + endpoint + } + + u, err := url.Parse(endpoint) + if err != nil { + return strings.TrimRight(endpoint, "/") + "/v1" + } + + path := strings.TrimRight(u.Path, "/") + if path == "" { + u.Path = "/v1" + } else if path != "/v1" && !strings.HasSuffix(path, "/v1") { + u.Path = path + "/v1" + } else { + u.Path = path + } + u.RawQuery = "" + u.Fragment = "" + return u.String() +} diff --git a/core/cli/cli.go b/core/cli/cli.go index 88c224ce7edb..8bf4b207a673 100644 --- a/core/cli/cli.go +++ b/core/cli/cli.go @@ -9,6 +9,7 @@ var CLI struct { cliContext.Context `embed:""` Run RunCMD `cmd:"" help:"Run LocalAI, this the default command if no other command is specified. Run 'local-ai run --help' for more information" default:"withargs"` + Chat ChatCMD `cmd:"" help:"Open an interactive chat session against a running LocalAI server"` Federated FederatedCLI `cmd:"" help:"Run LocalAI in federated mode"` Models ModelsCMD `cmd:"" help:"Manage LocalAI models and definitions"` Backends BackendsCMD `cmd:"" help:"Manage LocalAI backends and definitions"` diff --git a/core/cli/run.go b/core/cli/run.go index a2a72077f93c..7b0450d3b0e7 100644 --- a/core/cli/run.go +++ b/core/cli/run.go @@ -652,12 +652,12 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error { // waitForServerReady polls the given address until the HTTP server is // accepting connections or the context is cancelled. func waitForServerReady(address string, ctx context.Context) { - // Ensure the address has a host component for dialing. - // Echo accepts ":8080" but net.Dial needs a resolvable host. host, port, err := net.SplitHostPort(address) if err == nil && host == "" { address = "127.0.0.1:" + port } + ticker := time.NewTicker(250 * time.Millisecond) + defer ticker.Stop() for { select { @@ -665,11 +665,17 @@ func waitForServerReady(address string, ctx context.Context) { return default: } + conn, err := net.DialTimeout("tcp", address, 500*time.Millisecond) if err == nil { conn.Close() return } - time.Sleep(250 * time.Millisecond) + + select { + case <-ctx.Done(): + return + case <-ticker.C: + } } } diff --git a/docs/content/getting-started/try-it-out.md b/docs/content/getting-started/try-it-out.md index 8c2395e894a1..a56e2fca94d2 100644 --- a/docs/content/getting-started/try-it-out.md +++ b/docs/content/getting-started/try-it-out.md @@ -20,7 +20,29 @@ With the CLI you can list the models with `local-ai models list` and install the You can also [run models manually]({{%relref "getting-started/models" %}}) by copying files into the `models` directory. {{% /notice %}} -You can test out the API endpoints using `curl`, few examples are listed below. The models we are referring here (`gpt-4`, `gpt-4-vision-preview`, `tts-1`, `whisper-1`) are examples - replace them with the model names you have installed. +You can test chat models from the CLI without keeping a separate `curl` command around: + +```bash +# Terminal 1 +local-ai run + +# Terminal 2 +local-ai chat --model gpt-4 +``` + +`local-ai chat` connects to a running LocalAI server, opens an interactive chat prompt, and exits when you type `/exit`, `/quit`, or `/bye`. Use `/models` to list installed models, `/model ` to switch models, and `/clear` to reset the current conversation. If the server exposes exactly one model, LocalAI uses that model automatically: + +```bash +# Terminal 1 +local-ai run llama-3.2-1b-instruct:q4_k_m + +# Terminal 2 +local-ai chat +``` + +When more than one model is configured, pass `--model` with the installed model name to avoid ambiguity. Use `--endpoint` to connect to a non-default server, for example `local-ai chat --endpoint http://127.0.0.1:8081 --model gpt-4`. + +You can also test out the API endpoints using `curl`, few examples are listed below. The models we are referring here (`gpt-4`, `gpt-4-vision-preview`, `tts-1`, `whisper-1`) are examples - replace them with the model names you have installed. ### Text Generation diff --git a/docs/content/reference/cli-reference.md b/docs/content/reference/cli-reference.md index 556cf5a995e9..685b873773f6 100644 --- a/docs/content/reference/cli-reference.md +++ b/docs/content/reference/cli-reference.md @@ -118,6 +118,21 @@ For more information on VRAM management, see [VRAM and Memory Management]({{%rel See [Authentication & Authorization]({{%relref "features/authentication" %}}) for full documentation. +## Chat Flags + +Use `local-ai chat` to open an interactive terminal chat session against a running LocalAI server. + +| Parameter | Default | Description | Environment Variable | +|-----------|---------|-------------|----------------------| +| `--endpoint` | `http://127.0.0.1:8080` | LocalAI server endpoint. The `/v1` path is added automatically when omitted. | `$LOCALAI_CHAT_ENDPOINT` | +| `--model` | | Model name to use. If omitted, LocalAI uses the only model returned by the server when exactly one is available. | | +| `--api-key` | | API key to use when the LocalAI server requires authentication. | `$LOCALAI_API_KEY`, `$API_KEY` | + +- Inside the chat prompt: + - Use `/models` to list installed models. + - Use `/model ` to switch to a different model and clear the conversation. + - Use `/clear` to reset the current conversation. + ## P2P Flags | Parameter | Default | Description | Environment Variable | @@ -181,4 +196,3 @@ export LOCALAI_F16=true - See [Advanced Usage]({{%relref "advanced/advanced-usage" %}}) for configuration examples - See [VRAM and Memory Management]({{%relref "advanced/vram-management" %}}) for memory management options -