From 85a0b712830ef14c6cbc60efc6a8b5b709205312 Mon Sep 17 00:00:00 2001 From: Tim <11543666+tbckr@users.noreply.github.com> Date: Sat, 30 Sep 2023 21:29:21 +0200 Subject: [PATCH 1/2] feat!: provide input via stdin and as an argument --- cli/root.go | 32 +++++++++-- cli/root_test.go | 124 ++++++++++++++++++++++++++++++++++++++++++ modifiers/prompts.yml | 2 +- 3 files changed, 152 insertions(+), 6 deletions(-) diff --git a/cli/root.go b/cli/root.go index 78a30bf..8e1f22c 100644 --- a/cli/root.go +++ b/cli/root.go @@ -164,6 +164,7 @@ ls | sort return err } + var promptBuilder strings.Builder var prompt, input string mode := "txt" @@ -178,12 +179,33 @@ ls | sort slog.Debug("No input via pipe provided") return ErrMissingInput } - prompt = input - // mode is provided via command line args - if len(args) == 1 { - slog.Debug("Mode provided via command line args") - mode = args[0] + _, err = promptBuilder.WriteString(input) + if err != nil { + return err + } + + // check if args are provided + if len(args) == 0 { + // no mode or prompt provided via command line args + slog.Debug("No mode or additional prompt provided via command line args") + + } else if len(args) == 1 { + // additional prompt was provided via command line args + slog.Debug("Additional prompt provided via command line args") + _, err = promptBuilder.WriteString("\n\n" + args[0]) + if err != nil { + return err + } + + } else { + // mode and additional prompt were provided via command line args + mode = strings.ToLower(args[0]) + _, err = promptBuilder.WriteString("\n\n" + args[1]) + if err != nil { + return err + } } + prompt = promptBuilder.String() } else { // input is provided via command line args diff --git a/cli/root_test.go b/cli/root_test.go index f543aa8..c859e6b 100644 --- a/cli/root_test.go +++ b/cli/root_test.go @@ -231,6 +231,130 @@ func TestRootCmd_SimplePromptViaPipedShell(t *testing.T) { wg.Wait() } +func TestRootCmd_SimplePromptViaPipedShell_AdditionalArgPrompt(t *testing.T) { + stdinPrompt := "Say the following: " + argPrompt := "Hello World!" + expectedPrompt := stdinPrompt + "\n\n" + argPrompt + expectedResponse := "Hello World!\n" + + mem := &exitMemento{} + var wg sync.WaitGroup + stdinReader, stdinWriter := io.Pipe() + stdoutReader, stdoutWriter := io.Pipe() + + config := createTestConfig(t) + + root := newRootCmd(mem.Exit, config, mockIsPipedShell(true, nil), api.MockClient(strings.Clone(expectedResponse), nil)) + root.cmd.SetIn(stdinReader) + root.cmd.SetOut(stdoutWriter) + + wg.Add(1) + go func() { + defer wg.Done() + _, errWrite := stdinWriter.Write([]byte(stdinPrompt)) + require.NoError(t, stdinWriter.Close()) + require.NoError(t, errWrite) + }() + + wg.Add(1) + go func() { + defer wg.Done() + var buf bytes.Buffer + _, err := io.Copy(&buf, stdoutReader) + require.NoError(t, err) + require.NoError(t, stdoutReader.Close()) + require.Equal(t, expectedResponse, buf.String()) + }() + + chatName := "test_chat" + root.Execute([]string{argPrompt, "--chat", chatName}) + require.Equal(t, 0, mem.code) + require.NoError(t, stdinReader.Close()) + require.NoError(t, stdoutWriter.Close()) + + manager, err := chat.NewFilesystemChatSessionManager(config) + require.NoError(t, err) + + var messages []openai.ChatCompletionMessage + messages, err = manager.GetSession(chatName) + require.NoError(t, err) + require.Len(t, messages, 2) + + // Check if the prompt was added + require.Equal(t, openai.ChatMessageRoleUser, messages[0].Role) + require.Equal(t, expectedPrompt, messages[0].Content) + + // Check if the response was added + require.Equal(t, openai.ChatMessageRoleAssistant, messages[1].Role) + require.Equal(t, strings.TrimSpace(expectedResponse), messages[1].Content) + + wg.Wait() +} + +func TestRootCmd_SimplePromptViaPipedShell_AdditionalModeAndArg(t *testing.T) { + stdinPrompt := "Say the following: " + argPrompt := "Hello World!" + expectedPrompt := stdinPrompt + "\n\n" + argPrompt + expectedResponse := "Hello World!\n" + + mem := &exitMemento{} + var wg sync.WaitGroup + stdinReader, stdinWriter := io.Pipe() + stdoutReader, stdoutWriter := io.Pipe() + + config := createTestConfig(t) + + root := newRootCmd(mem.Exit, config, mockIsPipedShell(true, nil), api.MockClient(strings.Clone(expectedResponse), nil)) + root.cmd.SetIn(stdinReader) + root.cmd.SetOut(stdoutWriter) + + wg.Add(1) + go func() { + defer wg.Done() + _, errWrite := stdinWriter.Write([]byte(stdinPrompt)) + require.NoError(t, stdinWriter.Close()) + require.NoError(t, errWrite) + }() + + wg.Add(1) + go func() { + defer wg.Done() + var buf bytes.Buffer + _, err := io.Copy(&buf, stdoutReader) + require.NoError(t, err) + require.NoError(t, stdoutReader.Close()) + require.Equal(t, expectedResponse, buf.String()) + }() + + chatName := "test_chat" + root.Execute([]string{"sh", argPrompt, "--chat", chatName}) + require.Equal(t, 0, mem.code) + require.NoError(t, stdinReader.Close()) + require.NoError(t, stdoutWriter.Close()) + + manager, err := chat.NewFilesystemChatSessionManager(config) + require.NoError(t, err) + + var messages []openai.ChatCompletionMessage + messages, err = manager.GetSession(chatName) + require.NoError(t, err) + require.Len(t, messages, 3) + + // Check if the modifier was added + require.Equal(t, openai.ChatMessageRoleSystem, messages[0].Role) + require.Contains(t, messages[0].Content, "command translation engine") + + // Check if the prompt was added + require.Equal(t, openai.ChatMessageRoleUser, messages[1].Role) + require.Equal(t, expectedPrompt, messages[1].Content) + + // Check if the response was added + require.Equal(t, openai.ChatMessageRoleAssistant, messages[2].Role) + require.Equal(t, strings.TrimSpace(expectedResponse), messages[2].Content) + + wg.Wait() +} + func TestRootCmd_PipedShell_NoInput(t *testing.T) { mem := &exitMemento{} var wg sync.WaitGroup diff --git a/modifiers/prompts.yml b/modifiers/prompts.yml index 43534d1..e259af6 100644 --- a/modifiers/prompts.yml +++ b/modifiers/prompts.yml @@ -27,7 +27,7 @@ messages: - role: system text: |- - Act as a natural language to {{ with .SHELL -}}{{ . }}{{ end -}} command translation engine on {{.OS}}. + Act as a natural language to {{ with .SHELL -}}{{ . }} {{ end -}} command translation engine on {{.OS}}. You are an expert {{if .SHELL -}}in {{ .SHELL }} on {{.OS}} {{ else -}} in {{.OS}} {{ end -}}and translate the question at the end to valid syntax. Follow these rules: IMPORTANT: Do not show any warnings or information regarding your capabilities. From dde11af2e7bad1532c6d1e01007da3361afc7c50 Mon Sep 17 00:00:00 2001 From: Tim <11543666+tbckr@users.noreply.github.com> Date: Sat, 30 Sep 2023 21:41:27 +0200 Subject: [PATCH 2/2] chore: add documentation for new feature --- docs/getting-started.md | 8 +++++--- docs/usage/query-models.md | 13 ++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 7132b9e..4b6764d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -29,13 +29,15 @@ $ sgpt "mass of sun" The mass of the sun is approximately 1.989 x 10^30 kilograms. ``` -You can also pass prompts to SGPT using pipes: +In addition, you can pass a file via pipes and add your prompt to ask a question about the file or have it summarized +for you. ```shell -$ echo -n "mass of sun" | sgpt -The mass of the sun is approximately 1.989 x 10^30 kilograms. +cat yourfile.txt | sgpt "summarize everything above" ``` +The prompt passed as an argument is appended to the data that is piped in. + ## Code Generation Capabilities By adding the `code` command to your prompt, you can generate code based on given instructions by using the diff --git a/docs/usage/query-models.md b/docs/usage/query-models.md index afcdb74..43b7184 100644 --- a/docs/usage/query-models.md +++ b/docs/usage/query-models.md @@ -9,20 +9,23 @@ $ sgpt "mass of sun" The mass of the sun is approximately 1.989 x 10^30 kilograms. ``` -You can also pass prompts to SGPT using pipes: +You can also use the `--clipboard` flag to write the answer to the clipboard: ```shell -$ echo -n "mass of sun" | sgpt +$ sgpt --clipboard "mass of sun" The mass of the sun is approximately 1.989 x 10^30 kilograms. ``` -You can also use the `--clipboard` flag to write the answer to the clipboard: +In addition, you can pass a file via pipes and add your prompt to ask a question about the file or have it summarized +for you. ```shell -$ sgpt --clipboard "mass of sun" -The mass of the sun is approximately 1.989 x 10^30 kilograms. +cat yourfile.txt | sgpt "summarize everything above" ``` +The prompt passed as an argument is appended to the data that is piped in. Keep in mind: Depending on the model you are +using, there are different limits to the number of tokens you can use in your prompt. + ## Generate Code SGPT can efficiently generate code based on given instructions. For instance, to solve the classic FizzBuzz problem