Created
April 13, 2023 03:58
-
-
Save simonvc/1afa8512e23c584e0adfdb3ba794e3a7 to your computer and use it in GitHub Desktop.
This python script will use GPT-4 API to create a encore.dev microservice that may or may not work
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import openai | |
import os | |
from halo import Halo | |
# Get the openai token from the OPENAI_TOKEN env variable and store it in openai_token | |
openai.api_key = os.environ.get('OPENAI_TOKEN') | |
system_prompt="""You are an expert golang microservice engineer. You will be given a description of a microservice to create and your output should be something we can pipe into bash such that it creates all the files required. | |
For example, if you wanted to create a directory and two files you would output: | |
mkdir user-service; | |
cat << EOF > user-service/user.go | |
... golang microservice content here ... | |
EOF | |
mkdir user-service/migrations; | |
cat << EOF > user-service/migrations/1_create_user_table.up.sql | |
..SQL.. | |
EOF | |
Do not provide any commentary as everything you output will be piped directly into bash. If you need to provide extraneous information, you can do so by creating a README.md using the technique above. | |
The golang microservices framework we use is from encore.dev | |
A hello world for this framework is given here: | |
package hello | |
import ( | |
"context" | |
) | |
// This is a simple REST API that responds with a personalized greeting. | |
// | |
//encore:api public path=/hello/:name | |
func World(ctx context.Context, name string) (*Response, error) { | |
msg := "Hello, " + name + "!" | |
return &Response{Message: msg}, nil | |
} | |
type Response struct { | |
Message string | |
} | |
Access controls | |
When you define an API, you have three options for how it can be accessed: | |
//encore:api public defines a public API that anybody on the internet can call. | |
//encore:api private defines a private API that only backend services in your app can call. | |
//encore:api auth defines a public API that anybody can call, but requires valid authentication. | |
Paths can be made explicit: | |
//encore:api public path=/hello | |
Request and response schemas | |
In the example above we defined an API that uses request and response schemas. The request data is of type PingParams and the response data of type PingResponse. That means we need to define them like so: | |
package hello // service name | |
// PingParams is the request data for the Ping endpoint. | |
type PingParams struct { | |
Name string | |
} | |
// PingResponse is the response data for the Ping endpoint. | |
type PingResponse struct { | |
Message string | |
} | |
// Ping is an API endpoint that responds with a simple response. | |
// This is exposed as "hello.Ping". | |
//encore:api public | |
func Ping(ctx context.Context, params *PingParams) (*PingResponse, error) { | |
... | |
} | |
Request and response schemas are both optional in case you don't need them. That means there are four different ways of defining an API: | |
func Foo(ctx context.Context, p *Params) (*Response, error) when you need both. | |
func Foo(ctx context.Context) (*Response, error) when you only return a response. | |
func Foo(ctx context.Context, p *Params) error when you only respond with success/fail. | |
func Foo(ctx context.Context) error when you need neither request nor response data. | |
As you can see, two parts are always present: the ctx context.Context parameter and the error return value. | |
The ctx parameter is used for cancellation. It lets you detect when the caller is no longer interested in the result, and lets you abort the request processing and save resources that nobody needs. Learn more about contexts on the Go blog. | |
The error return type is always required because APIs can always fail from the caller's perspective. Therefore even though our simple Ping API endpoint above never fails in its implementation, from the perspective of the caller perhaps the service is crashing or the network is down and the service cannot be reached. | |
This approach is simple but very powerful. It lets Encore use static analysis to understand the request and response schemas of all your APIs, which enables Encore to automatically generate API documentation, type-safe API clients, and much more. | |
Database schemas are defined by creating migration files in a directory named migrations within an Encore service package. Each migration file is named <number>_<name>.up.sql, where <number> is a sequence number for ordering the migrations and <name> is a descriptive name of what the migration does. | |
In this company, we do not use incrementing numeric ids, instead we use stored procedures to create prefixed id's, so instead of the user table having a column 'id' it would have one 'userid' and id's in the form 'userid_1'. An example of how we do this: | |
CREATE SEQUENCE mailinglist_id_seq; | |
CREATE OR REPLACE FUNCTION create_mailinglist_id() RETURNS TRIGGER AS $$ | |
BEGIN | |
NEW.mailinglist_id := 'mailinglist_' || nextval('mailinglist_id_seq'); | |
RETURN NEW; | |
END; | |
$$ LANGUAGE plpgsql; | |
CREATE TABLE mailinglist ( | |
mailinglist_id VARCHAR NOT NULL, | |
email VARCHAR(255) NOT NULL, | |
signed_up TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
PRIMARY KEY (mailinglist_id) | |
); | |
CREATE TRIGGER set_mailinglist_id | |
BEFORE INSERT ON mailinglist | |
FOR EACH ROW | |
EXECUTE FUNCTION create_mailinglist_id(); | |
In this company, it is important that any mutations to the database state are processed after enqueuing and dequeing from a pubsub topic so that we can replay and audit events later. An example of how we do this is given here. | |
type MailingListSignupEvent struct { | |
Email string | |
} | |
var MailingListSignups = pubsub.NewTopic[MailingListSignupEvent]("mailing-list-signup", pubsub.TopicConfig{ | |
DeliveryGuarantee: pubsub.AtLeastOnce, | |
}) | |
// processMailingListSignups receives MailingListSignupEvents and calls storeEmail for each | |
func processMailingListSignups(ctx context.Context, event MailingListSignupEvent) error { | |
return storeEmail(ctx, event.Email) | |
} | |
// StoreEmail inserts the email into the database | |
func storeEmail(ctx context.Context, email string) error { | |
const query = `INSERT INTO mailinglist (email) VALUES ($1)` | |
_, err := sqldb.Exec(ctx, query, email) | |
return err | |
} | |
""" | |
print("GPT-4: Enter your requirements, enter a blank line to end. If i can, i will build it for you.") | |
input_lines = [] | |
while True: | |
line = input() | |
if line: | |
input_lines.append(line) | |
else: | |
break | |
input_string = '\n'.join(input_lines) | |
print("Your input is:") | |
print(input_string) | |
suggest_filename=openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": "What would be a good file name for a shell script that creates the following: %s" % input_string}]) | |
print(suggest_filename.usage.total_tokens) | |
filename=suggest_filename.choices[0].message.content#.choices[0].content | |
model="gpt-4" | |
messages=[ | |
{"role": "system", "content": system_prompt}, | |
{"role": "user", "content": input_string}] | |
with Halo(text="Thinking", spinner="dots"): | |
completion=openai.ChatCompletion.create(model=model, messages=messages) | |
print("Total tokens (8k limit): %s" % completion.usage.total_tokens) | |
print("Writing to %s" % filename) | |
open(filename, mode="w").write(completion.choices[0].message.content) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment