Skip to content

Commit

Permalink
Merge pull request #1 from osangenis/hello_world_service
Browse files Browse the repository at this point in the history
Hello world service
  • Loading branch information
osangenis authored Sep 4, 2023
2 parents 960afe1 + ceafa6e commit f9e7e49
Show file tree
Hide file tree
Showing 14 changed files with 403 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: Generate services

on:
push:
branches: [main]
pull_request:
branches: [main]

env:
GPT_API: https://api.github.com

permissions:
contents: write
pull-requests: write

jobs:
hello-service:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v2
with:
go-version: '1.19.3' # Important to have the minor
- name: generate_prompt


3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

#In this project, gpt files are generated from templates
*.gpt
66 changes: 66 additions & 0 deletions cmd/openai-render/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Command line tool to render a GPT-3 prompt result into files
package main

import (
"context"
"flag"
"fmt"
"io"
"os"
"strings"

"github.com/osangenis/pap/v2/cmd/openai-render/response"
openai "github.com/sashabaranov/go-openai"
)

const openaiKeyEnv = "OPENAI_API_KEY"

func main() {
pOutputDir := flag.String("output_dir", "", "The directory in where files/code blocks from the response will be saved")
flag.Parse()

if pOutputDir == nil || *pOutputDir == "" {
panic("The flag output_dir is required")
}

apiKey := os.Getenv(openaiKeyEnv)
if apiKey == "" {
panic(fmt.Sprintf("%v is not set", openaiKeyEnv))
}

client := openai.NewClient(apiKey)
resp, err := client.CreateChatCompletion(
context.Background(),
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: stdin(),
},
},
},
)

if err != nil {
panic(fmt.Sprintf("ChatCompletion error: %v\n", err))
}

files := response.FilesFromChat(resp.Choices[0].Message.Content, "go")
for _, file := range files {
err = os.WriteFile(*pOutputDir+"/"+file.Path, []byte(file.Content), 0644)
if err != nil {
panic(fmt.Sprintf("Error writing file %v : %v\n", file.Path, err))
}
}

fmt.Println(resp.Choices[0].Message.Content)
}

func stdin() string {
buf, err := io.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
return strings.TrimSuffix(string(buf), "\n")
}
45 changes: 45 additions & 0 deletions cmd/openai-render/response/files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package response

import (
"regexp"
)

// OutputFiles is a list of code blocks extracted as files
type OutputFiles []*OutputFile

// OutputFile is a code block extracted as a file with a relative path and content
type OutputFile struct {
Path string
Content string
}

// Filenames just returns the filenames of the output files as []string
func (o OutputFiles) Filenames() []string {
res := []string{}
for _, file := range o {
res = append(res, file.Path)
}
return res
}

// FilesFromChat extracts files from a ChatCompletionResponse code blocks.
// If lang is specified, it will be used to determine which code blocks to extract as
// files. If lang is empty, all code blocks will be extracted to files
func FilesFromChat(resp string, lang string) OutputFiles {
filesGroup := OutputFiles{}
// you can check this regex at
// https: //regex101.com/r/Rs5m3T/1
codeBlockRegex := regexp.MustCompile(`\x60([^\x60]+)\x60:\W\x60\x60\x60(\w+)([^\x60]+)\x60\x60\x60`)
for _, match := range codeBlockRegex.FindAllStringSubmatch(resp, -1) {
fPath := match[1]
fLang := match[2]
fContent := match[3]
if lang == "" || fLang == lang {
filesGroup = append(filesGroup, &OutputFile{
Path: fPath,
Content: fContent,
})
}
}
return filesGroup
}
42 changes: 42 additions & 0 deletions cmd/openai-render/response/files_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package response

import (
"os"
"testing"

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

func Test_FilesFromChat(t *testing.T) {
tests := []struct {
name string
filePath string
lang string
want []string
}{
{
name: "extract go files from code blocks like In `hello/ping.go`: ```...",
filePath: "fixtures/sample.output",
lang: "go",
want: []string{
"hello/ping.go",
"cmd/main.go",
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fixture := readFileAsString(t, test.filePath)
got := FilesFromChat(fixture, test.lang)
assert.ElementsMatch(t, test.want, got.Filenames())
})
}
}

func readFileAsString(t *testing.T, filePath string) string {
bytes, err := os.ReadFile(filePath)
require.NoError(t, err)
return string(bytes)
}
94 changes: 94 additions & 0 deletions cmd/openai-render/response/fixtures/sample.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
Sure! Here's an example implementation of the gRPC API service based on the given `hello.proto` file.

In `hello.proto`:
```protobuf
/**
* Hello World API service
*
* Just an experimenting API that will ping what you send
*/
syntax = "proto3";

package hello;

option go_package = "github.com/osangenis/hello";

message PingRequest {
string in = 1;
}

message PingResponse {
string out = 1;
}

// Hello Service
service HelloService {
// Returns the same string that was sent
rpc Ping(PingRequest) returns (PingResponse);
}
```

In `cmd/main.go`:
```go
...
package main

import (
"fmt"
"log"
"net"
"os"

"github.com/osangenis/hello/hello"
"google.golang.org/grpc"
)

func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

listenAddr := fmt.Sprintf(":%s", port)
lis, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

grpcServer := grpc.NewServer()
helloService := hello.NewHelloServiceServer()
hello.RegisterHelloServiceServer(grpcServer, helloService)

log.Printf("Server listening on port %s", port)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
```

In `hello/ping.go`:
```go
...
package hello

import (
"context"
)

type HelloServiceServer struct {
}

// NewHelloServiceServer creates a new instance of HelloServiceServer.
func NewHelloServiceServer() *HelloServiceServer {
return &HelloServiceServer{}
}

// Ping implements the Ping gRPC method.
func (s *HelloServiceServer) Ping(ctx context.Context, req *PingRequest) (*PingResponse, error) {
return &PingResponse{
Out: req.In,
}, nil
}
```

To run the service, use the command `go run cmd/main.go` in the root directory of the project.
3 changes: 3 additions & 0 deletions common/code-rules.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Add source code comments
- File names must be in snake case
- Don't include any explanations in your responses
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/osangenis/pap/v2

go 1.20

require (
github.com/sashabaranov/go-openai v1.15.1
github.com/stretchr/testify v1.8.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sashabaranov/go-openai v1.15.1 h1:BAV5LCVEzvZ3rN/Lh5NRVs2z6AahPt/jn5s2/cEEG0M=
github.com/sashabaranov/go-openai v1.15.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
25 changes: 25 additions & 0 deletions hello/hello.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Hello World API service
*
* Just an experimenting API that will ping what you send
*/
syntax = "proto3";

package hello;

option go_package = "github.com/osangenis/hello";

message PingRequest {
string in = 1;
}

message PingResponse {
string out = 1;
}

// Hello Service
service HelloService {
// Returns the same string that was sent
rpc Ping(PingRequest) returns (PingResponse);
}

32 changes: 32 additions & 0 deletions hello/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

package main

import (
"fmt"
"log"
"net"
"os"

"github.com/osangenis/hello/hello"
"google.golang.org/grpc"
)

func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}

lis, err := net.Listen("tcp", fmt.Sprintf(":%s", port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

s := grpc.NewServer()
hello.RegisterHelloServiceServer(s, &hello.Server{})

log.Printf("Server listening on port %s", port)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
14 changes: 14 additions & 0 deletions hello/ping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

package hello

import (
"context"
)

type Server struct{}

func (s *Server) Ping(ctx context.Context, req *PingRequest) (*PingResponse, error) {
return &PingResponse{
Out: req.In,
}, nil
}
Loading

0 comments on commit f9e7e49

Please sign in to comment.