Package: compaipair
compaiA CLI for pair programming (or whatever other creative uses you can give it!) powered by AI.The CLI currently uses theGoogle Generative AI APIs, from here on referred to as PaLM.InstallationThe tool is available as apython package.Install via pip:pipinstallcompaipairOnce installed the package will install acompaiscript that serves as entry point to the CLI.ConfigurationCache directoryIn order to access the API-key and use persisted data, like prompt templates, the package will create a directory.compaion your home directory.You can change the default location of this directory by setting the environment variableCOMPAI_CACHE_PATHto a different location.Configuring the PaLM API keySince the CLI relies on the PaLM APIs you need an API key, which you can gethere, it will need to be accessed by the CLI when it's executed.Currently, there are two ways to access the API key:Via anapi_keyfileVia environment variablesStoring your API-key using theinitcommandOnce the package is installed you can run theinitcommand with your api key:completeinit--api-key"<your api key>"The package will create the directory~/.compaiin your home-directory, where it will store your API-key in a file calledapi_key.Storing your API-key in an environment variableAlternatively, you can save your API-key in the environment variableGOOGLE_GENERATIVEAI_API_KEY.CAUTION:The file is saved as plain text, and it's not encrypted or anything like that! Use at your own discretion, in an environment you trust!UsageIn order to explore the CLI, you can# Run the script with no argumentscompai# Or with the --help optioncompai--helpEssential commandsThe CLI exposes a few commands to prompt one of the Google Generative AI models:completeUsage:compai complete [OPTIONS] QUESTIONThis command prompts the user for aquestion, and uses the PaLM APIs to generate completions for the question. The completions are written to a file specified by the-oor--outputflag.The following options are available:--priming: A string to prime the LLM prompt.--decorator: A decorator to add to the prompt.-o,--output: The file to write the completions to.-i,--input-file: A file with input to append to the question.-t,--temperature: The model temperature, use 0 for more deterministic completions.-m,--model-name: The name of the LLM model to use.-v,--verbose: If set, includes the prompt in the output.--plain-text: Flag to output the results in plain text, otherwise will be formatted as Markdown.--template: A template to prime and decorate the prompt.complete-templateUsage:compai complete-template [OPTIONS] TEMPLATEThis command prompts the user for atemplate,  and uses the PaLM APIs to generate completions for the question. The completions are written to a file specified by the-oor--outputflag.The following options are available:-q,--question: An optional question that will be appended to the template.-o,--output: The file to write the completions to.-i,--input-file: A file with input to append to the question.-t,--temperature: The model temperature, use 0 for more deterministic completions.-m,--model-name: The name of the LLM model to use.-v,--verbose: Whether to include the prompt in the output.--plain-text: Whether to print the output in plain text, or format it as Markdown.templates newUsage:compai templates new [OPTIONS] NAMECreates a new template with priming and decoratorsOptions:-p,--priming:    Text for priming this template's prompts-d,--decorator:  Text for decorating this template's promptstemplates editUsage:compai complete-template [OPTIONS] TEMPLATEEdit an existing template.Options:-q,--questionOptional question that will be appended to the template.-o,--outputFile to write output-i,--input-fileFile with input to append to the question.-t,--temperatureModel temperature, use 0 for more deterministic completions.-m,--model-nameLLM model for this query-v,--verboseVerbose. Include prompt in output--plain-textPrint output in plain text, otherwise will be formatted as Markdowntemplates showUsage:compai templates show [OPTIONS]Show the list of available templates.-n,--nameName of the template to show.-v,--verboseShow all the templates' contents--helpShow this message and exit.ExampleSay you want to use an LLM to help you with your documentation.
You could installcompaiand use it on your terminal next to your code editor to easily prompt an LLM.
You can then prompt the LLM with code from your own codebase and write the results to a file on your disk.Simple usageLet's see a simple example. I'm going to use code from this package, for instance the classCompaiCompletion:# ./compaipair/types/compaicompletion.py# imports and preamble...classCompaiCompletion:model:Modelpriming:strquestion:strdecorator:strresult:Completionapi_key:strtemplate:CompletionTemplate|Nonedef__init__(self,model:Model|str,template:str=None,question:str="",priming:str="",decorator:str="",temperature:float=0.7,api_key:str=None,):self.temperature=temperatureself.question=questioniftemplateisNone:self.priming=primingself.decorator=decoratoriftemplateisnotNone:template=CompletionTemplate.find_template(template)self.priming=template.primingiftemplate.primingelseprimingself.decorator=template.decoratoriftemplate.decoratorelsedecoratorifapi_keyisNone:self.api_key=get_api_key()configure_palm_api(api_key=self.api_key)ifisinstance(model,Model):self.model=modelelifmodelisNone:self.model=self.get_model()else:self.model=self.get_model(model)defcomplete(self)->Completion:self.result=palm.generate_text(prompt=self.prompt,model=self.model,temperature=self.temperature)returnself.result# ...more codeI'm going to use compai to generate docstrings for this class:# cd "/../compaipair_dir"compaicomplete"Can you generate docstrings for the following python class?"-i./compaipair/types/compaicompletion.pyFrom this I just got the following (just a part of the output):classCompaiCompletion:"""Class to generate text completions using the Palm API.Args:model (Model | str): The model to use for completion. If a string isprovided, it will be used to select a model from the list ofavailable models. If None, the default model will be used.template (str | CompletionTemplate | None): The template to use forgenerating the completion prompt. If a string is provided, it willbe used to find a template by name. If None, the default templatewill be used.question (str): The question to be completed.priming (str): The priming text to be used before the question.decorator (str): The decorator text to be used after the question.temperature (float): The temperature to use for completion.api_key (str): The API key to use for the Palm API. If None, thedefault API key will be used."""# ...It's important to always check what the model does, in this case the explanation of the argumenttemplateis not quite
right, because I know (because I wrote the code) that if no template is provided, then the CLI just prepends and appends the priming and decorators, respectively.
Apart from that the result is kind of reasonable.Priming and decorating your promptsSometimes, priming or decorating your prompts might improve the quality of the results.Let's use priming and decorators and see if we get a different result.For priming, I'll use: "You're an experienced Python programmer who writes excellent documentation".
For a decorator, I'll tell the model to also comment the code and provide an explanation for the code.# cd "/../compaipair_dir"compaicomplete--priming"You're an experienced Python programmer who writes excellent documentation."\--decorator"Please additionally provide a detailed explanation of what the code does."\"Can you generate docstrings and comments for the following python class?"-i./compaipair/types/compaicompletion.pyIn this case the result included docstrings for the class as well as a brief explanation of the class below.The CompaiCompletion class is a Python class for generating completions using thePalm API. It takes a number of arguments, including the model to use, thecompletion template to use, the question to generate completions for, the primingtext to use, the decorator text to use, and the temperature to use for thecompletion.Using templatesIn your work, you might use different languages, or you might want to prompt the model for different purposes.
Templates provide a bit of convenience for these cases. They allow you to save primings and decorators that can be retrieved
for new questions you might ask an LLM.In the previous example we were working with python and producing docstrings. We could create a templatepython-docthat we can use whenever we want to produce documentation for python code:compaitemplatesnewpython-doc--priming"You're an experienced Python programmer who writes excellent documentation."\--decorator"Please write docstrings for the preceding code."# Check that the template was savedcompaitemplatesshowThe templates are saved as documents in a TinyDB database that lives in the cache directory, by default located
at~/.compai/db.json.You can now use your template like this:compaicomplete-templatepython-doc-i./compaipair/types/compaicompletion.pyThe output in this case looks very similar to the case above.Let's say you're also working on a spring boot project with Kotlin and you want to generate test cases for your web
controllers. You could then save another template:compaitemplatesnewkotlin-springboot-test--priming"You are an experienced Kotlin programmer with vast experience in the Spring Boot framework. Please generate test cases for the following code"--decorator"Please explain what each test case is supposed to test and write comments for your code"And use it with the following code.// ./spring-boot-project/UsersController.kt@RestController@RequestMapping("/users")@CrossOrigin(origins=["http://localhost:3000"])classUsersController(privatevalusersRepository:UsersRepository,privatevalusersService:UsersService){@GetMappingfungetUsers():List<User>=usersRepository.findAll()@GetMapping("{id}")fungetUserById(@PathVariableid:String)=usersRepository.findById(id)@GetMapping("/email_or_username/")fungetUsersByUsernameOrEmail(@RequestParamterm:String):List<User>=usersService.findUsersByUsernameOrEmail(term)}# cd ./spring-boot-projectcompaicomplete-templatekotlin-springboot-test-iUsersControllers.ktIf it's some code from somewhere else that you can just copy-paste, you could use the-qor--questionoption to paste the code to the command:compaicomplete-templatekotlin-springboot-test-q'@RestController@RequestMapping("/users")@CrossOrigin(origins = ["http://localhost:3000"])class UsersController(private val usersRepository: UsersRepository, private val usersService: UsersService) {@GetMappingfun getUsers(): List<User> = usersRepository.findAll()@GetMapping("{id}")fun getUserById(@PathVariable id: String) = usersRepository.findById(id)@GetMapping("/email_or_username/")fun getUsersByUsernameOrEmail(@RequestParam term: String): List<User> =usersService.findUsersByUsernameOrEmail(term)}'In  this case this was the result (which hasn't been tested at all!!):packagecom.example.demo.controllerimportcom.example.demo.model.Userimportcom.example.demo.repository.UsersRepositoryimportcom.example.demo.service.UsersServiceimportorg.junit.jupiter.api.Assertions.assertEqualsimportorg.junit.jupiter.api.Testimportorg.junit.jupiter.api.extension.ExtendWithimportorg.mockito.Mockimportorg.mockito.Mockito.`when`importorg.mockito.junit.jupiter.MockitoExtensionimportjava.util.*@ExtendWith(MockitoExtension::class)classUsersControllerTest{@MockprivatelateinitvarusersRepository:UsersRepository@MockprivatelateinitvarusersService:UsersService@Testfun`getUsers returns all users`(){// Givenvalusers=listOf(User(1,"John Doe","john.doe@example.com"),User(2,"Jane Doe","jane.doe@example.com"))`when`(usersRepository.findAll()).thenReturn(users)// Whenvalresult=UsersController(usersRepository,usersService).getUsers()// ThenassertEquals(users,result)}@Testfun`getUserById returns the user with the given id`(){// Givenvalid="1"valuser=User(1,"John Doe","john.doe@example.com")`when`(usersRepository.findById(id)).thenReturn(Optional.of(user))// Whenvalresult=UsersController(usersRepository,usersService).getUserById(id)// ThenassertEquals(user,result)}@Testfun`getUsersByUsernameOrEmail returns the users with the given username or email`(){// Givenvalterm="john.doe"valusers=listOf(User(1,"John Doe","john.doe@example.com"),User(2,"Jane Doe","jane.doe@example.com"))`when`(usersService.findUsersByUsernameOrEmail(term)).thenReturn(users)// Whenvalresult=UsersController(usersRepository,usersService).getUsersByUsernameOrEmail(term)// ThenassertEquals(users,result)}}DisclaimerThis is a personal project designed according to my taste and needs! Further development and maintenance of the project
is subject to my time constraints.Also, LLMs often hallucinate, so please check and testeverythingyou get from an LLM.