Skip to content

Commit

Permalink
Added bulk mode
Browse files Browse the repository at this point in the history
Added bulk mode that reads a list of prompts from a file and runs them
one by one.
  • Loading branch information
igolaizola committed Jul 6, 2023
1 parent 6651727 commit 8959628
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 0 deletions.
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ This allows more interaction in fewer steps.

Connect one chat with another chat and let them talk to each other.

### Bulk mode

Generate a list of prompts and run all of them one by one.

### Chats that implement `io.ReadWriter`

You can import the libraries in the `pkg` directory to use Bing or ChatGPT as `io.ReadWriter` in your own projects.
Expand Down Expand Up @@ -126,6 +130,62 @@ Use standard input and output to communicate with the chatbot.
igogpt chat --config igogpt.yaml
```

#### Bulk mode

Bulk mode reads a list of prompts from a file and runs them one by one. You can group prompts so that each group runs in a different chat.

```bash
igogpt bulk --config igogpt.yaml --bulk-in prompts.txt --bulk-out output.json
```

The input file can be a JSON file with a list (or lists) of prompts.
Each sublist will be launched in a different chat.

```json
[
["prompt 1 in chat 1", "prompt 2 in chat 1"],
["prompt 1 in chat 2", "prompt 2 in chat 2"],
]
```

Alternatively, the input can be a text file with one prompt per line.
Use an empty line to separate prompts in different chats.

```text
prompt 1 in chat 1
prompt 2 in chat 1
prompt 1 in chat 2
prompt 2 in chat 2
```

The output file will be a JSON file containing all the prompts and their corresponding responses.

```json
[
[
{
"in": "prompt 1 in chat 1",
"out": "response 1 in chat 1"
},
{
"in": "prompt 2 in chat 1",
"out": "response 2 in chat 1"
},
],
[
{
"in": "prompt 1 in chat 1",
"out": "response 1 in chat 1"
},
{
"in": "prompt 2 in chat 1",
"out": "response 2 in chat 1"
},
],
]
```

### Create bing session (only for the first time)

If you want to use the Bing search engine, you need to create a session file with the Bing cookies and other information retrieved from your browser.
Expand Down
5 changes: 5 additions & 0 deletions cmd/igogpt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func newCommand() *ffcli.Command {
newRunCommand("auto"),
newRunCommand("pair"),
newRunCommand("cmd"),
newRunCommand("bulk"),
newCreateBingSessionCommand(),
newVersionCommand(),
},
Expand All @@ -69,6 +70,10 @@ func newRunCommand(action string) *ffcli.Command {
fs.StringVar(&cfg.LogDir, "log", "logs", "log path, if empty, only logs to stdout (optional)")
fs.IntVar(&cfg.Steps, "steps", 0, "number of steps to run, if unset, it will run until it exits (optional)")

// Bulk files
fs.StringVar(&cfg.BulkInput, "bulk-in", "", "bulk input file")
fs.StringVar(&cfg.BulkOutput, "bulk-out", "", "bulk output file")

// Google
fs.StringVar(&cfg.GoogleKey, "google-key", "", "google api key, see https://developers.google.com/custom-search/v1/introduction")
fs.StringVar(&cfg.GoogleCX, "google-cx", "", "google cx (search engine ID), see https://cse.google.com/cse/all")
Expand Down
183 changes: 183 additions & 0 deletions igogpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ type Config struct {
LogDir string `yaml:"log-dir"`
Steps int `yaml:"steps"`

// Bulk parameters
BulkInput string `yaml:"bulk-input"`
BulkOutput string `yaml:"bulk-output"`

// Google parameters
GoogleKey string `yaml:"google-key"`
GoogleCX string `yaml:"google-cx"`
Expand Down Expand Up @@ -56,6 +60,8 @@ func Run(ctx context.Context, action string, cfg *Config) error {
return Auto(ctx, cfg)
case "chat":
return Chat(ctx, cfg)
case "bulk":
return Bulk(ctx, cfg)
case "cmd":
return Cmd(ctx, cfg)
default:
Expand Down Expand Up @@ -313,6 +319,183 @@ func Pair(ctx context.Context, cfg *Config) error {
}
}

type BulkOutput [][]inOut

type inOut struct {
In string `json:"in"`
Out string `json:"out"`
}

func Bulk(ctx context.Context, cfg *Config) error {
if cfg.BulkInput == "" {
return fmt.Errorf("igogpt: bulk input is required")
}
if cfg.BulkOutput == "" {
return fmt.Errorf("igogpt: bulk output is required")
}

// Read bulk input file
b, err := os.ReadFile(cfg.BulkInput)
if err != nil {
return fmt.Errorf("igogpt: couldn't read bulk input file: %w", err)
}
var inputs [][]string

if filepath.Ext(cfg.BulkInput) == ".json" {
var list []any
if err := json.Unmarshal(b, &list); err != nil {
return fmt.Errorf("igogpt: couldn't unmarshal bulk input file: %w", err)
}
if len(list) == 0 {
return fmt.Errorf("igogpt: no inputs found in bulk input file")
}
for _, elem := range list {
// Check if input is a string or an array of strings
switch vv := elem.(type) {
case string:
if vv == "" {
continue
}
inputs = append(inputs, []string{vv})
case []any:
var group []string
for _, v := range vv {
s, ok := v.(string)
if !ok {
return fmt.Errorf("igogpt: bulk input file must contain strings or arrays of strings")
}
if s == "" {
continue
}
group = append(group, s)
}
if len(group) == 0 {
continue
}
inputs = append(inputs, group)
default:
return fmt.Errorf("igogpt: bulk input file must contain strings or arrays of strings")
}
}
} else {
// Split by double newlines
list := strings.Split(string(b), "\n\n")
for _, elem := range list {
// Split group by newlines
group := strings.Split(elem, "\n")
if len(group) == 0 {
continue
}
// Remove empty lines
var filtered []string
for _, s := range group {
if s != "" {
filtered = append(filtered, s)
}
}
inputs = append(inputs, filtered)
}
}

// Create main chat
var chatFunc func() (io.ReadWriter, func(), error)
switch cfg.AI {
case "bing":
// Create bing client
bingClient, err := bing.New(cfg.BingWait, &cfg.BingSession, cfg.BingSessionFile, cfg.Proxy)
if err != nil {
return fmt.Errorf("igogpt: couldn't create bing client: %w", err)
}
chatFunc = func() (io.ReadWriter, func(), error) {
c, err := bingClient.Chat(ctx)
if err != nil {
return nil, nil, err
}
return c, func() { _ = c.Close }, nil
}
case "chatgpt":
// Create chatgpt client
client, err := chatgpt.New(ctx, cfg.ChatgptWait, cfg.ChatgptRemote, cfg.Proxy, true)
if err != nil {
return fmt.Errorf("igogpt: couldn't create chatgpt client: %w", err)
}
defer client.Close()
chatFunc = func() (io.ReadWriter, func(), error) {
c, err := client.Chat(ctx, cfg.Model)
if err != nil {
return nil, nil, err
}
return c, func() { _ = c.Close }, nil
}
case "openai":
// Create openai client
client := openai.New(cfg.OpenaiKey, cfg.OpenaiWait, cfg.OpenaiMaxTokens)
chatFunc = func() (io.ReadWriter, func(), error) {
return client.Chat(ctx, cfg.Model, "system", fixed.NewFixedMemory(1, cfg.OpenaiMaxTokens)), func() {}, nil
}
default:
return fmt.Errorf("igogpt: invalid ai: %s", cfg.AI)
}

var exit bool
var output BulkOutput
for _, prompts := range inputs {
if exit {
break
}
chat, close, err := chatFunc()
if err != nil {
return fmt.Errorf("igogpt: couldn't create chat: %w", err)
}
var msgs []inOut
for _, prmpt := range prompts {
if exit {
break
}
// Check context
select {
case <-ctx.Done():
exit = true
continue
default:
}

// Write to chat
log.Println(prmpt)
if _, err := chat.Write([]byte(prmpt)); err != nil {
return fmt.Errorf("igogpt: couldn't write message to chatgpt: %w", err)
}

// Read from chat
buf := make([]byte, 1024*64)
n, err := chat.Read(buf)
if err != nil {
return fmt.Errorf("igogpt: couldn't read message from chatgpt: %w", err)
}
recv := string(buf[:n])
log.Println(recv)

msgs = append(msgs, inOut{In: prmpt, Out: recv})
}
close()
output = append(output, msgs)
}
// Marshal output
b, err = json.MarshalIndent(output, "", " ")
if err != nil {
return fmt.Errorf("igogpt: couldn't marshal output: %w", err)
}
// Create output directory if it doesn't exist
if err := os.MkdirAll(filepath.Dir(cfg.BulkOutput), 0755); err != nil {
return fmt.Errorf("igogpt: couldn't create output directory: %w", err)
}
// Write output to file
if err := os.WriteFile(cfg.BulkOutput, b, 0644); err != nil {
return fmt.Errorf("igogpt: couldn't write output: %w", err)
}
return nil
}

// Cmd runs a command and returns the result
func Cmd(ctx context.Context, cfg *Config) error {
// Bing chat not being available in this mode
Expand Down

0 comments on commit 8959628

Please sign in to comment.