Skip to content

Commit

Permalink
feat: adds support for stream mode (#183)
Browse files Browse the repository at this point in the history
* chore: refactor package structure

* feat(openai): add stream support, WIP

* chore(refactor): refactor api_test.go

* chore(refactor): add stream test to api_test.go

* chore: print final linebreak + print message when not streamed

* chore(fix): fixes remaining tests in api_test.go

* chore: start refactoring for root_test.go

* chore: WIP refactoring for cli tests

* chore: finish tests

* chore: add comment and remove trailing linebreak

* chore: add comments

* chore: update docs with stream flas

* chore(fix,test): fix test for streamed output

* chore(fix,lint): fix linting error

* chore(fix,test): fix panic error

* chore: remove github script part in release pipeline
  • Loading branch information
tbckr authored Nov 27, 2023
1 parent 789baa1 commit 463fa8b
Show file tree
Hide file tree
Showing 41 changed files with 989 additions and 518 deletions.
20 changes: 0 additions & 20 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,23 +95,3 @@ jobs:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
SCOOP_TAP_GITHUB_TOKEN: ${{ secrets.SCOOP_TAP_GITHUB_TOKEN }}

# Update release PR
# - uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
# if: ${{ steps.release.outputs.release_created }}
# with:
# github-token: ${{ steps.generate_token.outputs.token }}
# script: |
# github.rest.issues.removeLabel({
# owner: context.repo.owner,
# repo: context.repo.repo,
# issue_number: ${{ steps.release.outputs.pr }},
# name: 'autorelease: tagged'
# });
#
# github.rest.issues.addLabels({
# owner: context.repo.owner,
# repo: context.repo.repo,
# issue_number: ${{ steps.release.outputs.pr }},
# labels: ['autorelease: published']
# });
124 changes: 0 additions & 124 deletions cli/config_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion cmd/sgpt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ package main
import (
"os"

"github.com/tbckr/sgpt/v2/cli"
"github.com/tbckr/sgpt/v2/pkg/cli"
)

func main() {
Expand Down
3 changes: 3 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ $ echo -n "mass of sun" | sgpt
The mass of the sun is approximately 1.989 x 10^30 kilograms.
```

If you want to stream the completion to the command line, you can add the `--stream` flag. This will stream the output
to the command line as it is generated.

## Code Generation Capabilities

By adding the `code` command to your prompt, you can generate code based on given instructions by using the
Expand Down
3 changes: 3 additions & 0 deletions docs/usage/query-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ $ sgpt "mass of sun"
The mass of the sun is approximately 1.989 x 10^30 kilograms.
```

If you want to stream the completion to the command line, you can add the `--stream` flag. This will stream the output
to the command line as it is generated.

You can also pass prompts to SGPT using pipes:

```shell
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jarcoal/httpmock v1.3.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/mango v0.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
Expand Down
38 changes: 38 additions & 0 deletions internal/testlib/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2023 Tim <tbckr>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// SPDX-License-Identifier: MIT

package testlib

import (
"os"
"testing"

"github.com/stretchr/testify/require"
)

func SetAPIKey(t *testing.T) {
err := os.Setenv("OPENAI_API_KEY", "test")
require.NoError(t, err)

t.Cleanup(func() {
_ = os.Unsetenv("OPENAI_API_KEY")
})
}
95 changes: 95 additions & 0 deletions internal/testlib/httmock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) 2023 Tim <tbckr>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// SPDX-License-Identifier: MIT

package testlib

import (
"bytes"
"fmt"
"net/http"

"github.com/jarcoal/httpmock"
)

const (
baseURL = "https://api.openai.com/v1"
chatCompletionSuffix = "/chat/completions"
)

func RegisterExpectedChatResponse(response string) {
httpmock.RegisterResponder(
"POST",
fmt.Sprintf("%s%s", baseURL, chatCompletionSuffix),
httpmock.NewStringResponder(
200,
fmt.Sprintf(`{
"choices": [
{
"index": 0,
"finish_reason": "length",
"message": {
"role": "assistant",
"content": "%s"
}
}
]
}`, response),
),
)
}

func RegisterExpectedChatResponseStream(response string) {
httpmock.RegisterResponder(
"POST",
fmt.Sprintf("%s%s", baseURL, chatCompletionSuffix),
func(request *http.Request) (*http.Response, error) {
// Reference: https://github.com/sashabaranov/go-openai/blob/a09cb0c528c110a6955a9ee9a5d021a57ed44b90/chat_stream_test.go#L39
data := createStreamedMessages(response)
resp := httpmock.NewBytesResponse(200, data)
resp.Header.Set("Content-Type", "text/event-stream")
return resp, nil
},
)
}

func createStreamedMessages(response string) []byte {
// Data is written in the format of Server-Sent Events (SSE).
// The first message is the event name, followed by the data.
// Right now, there are two events: "message" and "done". The "done" event is sent when the completion is finished.
const (
eventMessage = "event: message\n"

dataMessageTemplate = "data: %s\n\n"
messageTemplate = `{"id":"1","object":"completion","created":1598069254,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"content":"%c"},"finish_reason":"max_tokens"}]}`

eventDone = "event: done\n"
dataDone = "data: [DONE]\n\n"
)
var buff bytes.Buffer
for _, char := range response {
buff.WriteString(eventMessage)
buff.WriteString(fmt.Sprintf(dataMessageTemplate, fmt.Sprintf(messageTemplate, char)))
}
buff.WriteString(eventDone)
buff.WriteString(dataDone)

return buff.Bytes()
}
57 changes: 57 additions & 0 deletions internal/testlib/testctx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) 2023 Tim <tbckr>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// SPDX-License-Identifier: MIT

package testlib

import (
"testing"

"github.com/spf13/viper"
)

type TestCtx struct {
Config *viper.Viper

ConfigDir string
CacheDir string
PersonasDir string
}

func NewTestCtx(t *testing.T) *TestCtx {
cacheDir := t.TempDir()
configDir := t.TempDir()
personasDir := t.TempDir()

config := viper.New()
config.AddConfigPath(configDir)
config.SetConfigName("config")
config.SetConfigType("yaml")
config.Set("cacheDir", cacheDir)
config.Set("personas", personasDir)
config.Set("TESTING", 1)

return &TestCtx{
Config: config,
ConfigDir: configDir,
CacheDir: cacheDir,
PersonasDir: personasDir,
}
}
Loading

0 comments on commit 463fa8b

Please sign in to comment.