Skip to content

Commit 33fd8b5

Browse files
committed
Add configuration command and update key references
This commit introduces a new 'configure' command in the build workflow, which creates or updates the config file. The 'openai_api_key' is now fetched from the config file instead of the 'OPENAI_API_KEY'. The 'personality' option in the config file has also been updated to use the 'Her' personality by default. The 'setup' command has been removed as it's no longer needed. Additionally, the 'DEFAULT_PERSONALITY' has been set to 'Her' and the 'PERSONALITIES' keys have been updated to match the case of the personality names. The tests have been updated to reflect these changes. The 'test_configure' function now tests the 'configure' command, and the 'test_setup_with_openai_key' function has been removed as it's no longer relevant. These changes make the configuration process more streamlined and user-friendly. 🚀"
1 parent 4465984 commit 33fd8b5

File tree

4 files changed

+135
-147
lines changed

4 files changed

+135
-147
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
- name: Install dependencies
3939
run: pip install -r requirements/requirements-test.txt
4040

41+
- name: Run configure to create the config file
42+
run: python -m aicodebot.cli configure
43+
4144
- name: Run the tests with coverage
4245
run: pytest --cov=./ --cov-report=xml
4346

aicodebot/cli.py

Lines changed: 109 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
logger,
99
read_config,
1010
)
11-
from aicodebot.prompts import PERSONALITIES, generate_files_context, get_prompt
11+
from aicodebot.prompts import DEFAULT_PERSONALITY, PERSONALITIES, generate_files_context, get_prompt
1212
from langchain.callbacks.base import BaseCallbackHandler
1313
from langchain.chains import LLMChain
1414
from langchain.chat_models import ChatOpenAI
@@ -73,7 +73,7 @@ def alignment(response_token_size, verbose):
7373
llm = ChatOpenAI(
7474
model=model,
7575
temperature=CREATIVE_TEMPERATURE,
76-
openai_api_key=config["OPENAI_API_KEY"],
76+
openai_api_key=config["openai_api_key"],
7777
max_tokens=response_token_size,
7878
verbose=verbose,
7979
streaming=True,
@@ -136,7 +136,7 @@ def commit(verbose, response_token_size, yes, skip_pre_commit):
136136
# Set up the language model
137137
llm = ChatOpenAI(
138138
model=model,
139-
openai_api_key=config["OPENAI_API_KEY"],
139+
openai_api_key=config["openai_api_key"],
140140
temperature=PRECISE_TEMPERATURE,
141141
max_tokens=DEFAULT_MAX_TOKENS,
142142
verbose=verbose,
@@ -168,6 +168,99 @@ def commit(verbose, response_token_size, yes, skip_pre_commit):
168168
Path.unlink(temp_file_name)
169169

170170

171+
@cli.command()
172+
@click.option("-v", "--verbose", count=True)
173+
@click.option("--openai-api-key", envvar="OPENAI_API_KEY", help="Your OpenAI API key")
174+
def configure(verbose, openai_api_key):
175+
"""Create or update the config file"""
176+
177+
# --------------- Check for an existing key or set up defaults --------------- #
178+
179+
config_data_defaults = {
180+
"version": 1.1,
181+
"openai_api_key": openai_api_key,
182+
"personality": DEFAULT_PERSONALITY.name,
183+
}
184+
185+
config_data = config_data_defaults.copy()
186+
config_file = get_config_file()
187+
188+
existing_config = read_config()
189+
if existing_config:
190+
console.print(f"Config file already exists at {get_config_file()}.")
191+
click.confirm("Do you want to rerun configure and overwrite it?", default=False, abort=True)
192+
config_data.update(
193+
{"openai_api_key": existing_config["openai_api_key"], "personality": existing_config["personality"]}
194+
)
195+
196+
config_data = config_data_defaults.copy()
197+
198+
def write_config_file(config_data):
199+
with Path.open(config_file, "w") as f:
200+
yaml.dump(config_data, f)
201+
console.print(f"✅ Created config file at {config_file}")
202+
203+
is_terminal = sys.stdout.isatty()
204+
if not is_terminal:
205+
if config_data["openai_api_key"] is None:
206+
raise click.ClickException(
207+
"🛑 No OpenAI API key found.\n"
208+
"Please set the OPENAI_API_KEY environment variable or call configure with --openai-api-key set."
209+
)
210+
else:
211+
# If we are not in a terminal, then we can't ask for input, so just use the defaults and write the file
212+
write_config_file(config_data)
213+
return
214+
215+
# ---------------- Collect the OPENAI_API_KEY and validate it ---------------- #
216+
217+
if config_data["openai_api_key"] is None:
218+
console.print(
219+
"An OpenAI API key is required to use AICodeBot. You can get one for free on the OpenAI website.\n"
220+
)
221+
openai_api_key_url = "https://platform.openai.com/account/api-keys"
222+
if click.confirm("Open the OpenAI API keys page for you in a browser?", default=False):
223+
webbrowser.open(openai_api_key_url)
224+
225+
config_data["openai_api_key"] = click.prompt(
226+
"Please enter your OpenAI API key", default=config_data["openai_api_key"]
227+
)
228+
229+
# Validate the API key
230+
try:
231+
openai.api_key = config_data["openai_api_key"]
232+
click.echo("Validating the OpenAI API key")
233+
engine.Engine.list()
234+
except Exception as e:
235+
raise click.ClickException(f"Failed to validate the API key: {str(e)}") from e
236+
click.echo("✅ The API key is valid.")
237+
238+
# ---------------------- Collect the personality choice ---------------------- #
239+
240+
# Pull the choices from the name from each of the PERSONALITIES
241+
personality_choices = "\nHow would you like your AI to act? You can choose from the following personalities:\n"
242+
for key, personality in PERSONALITIES.items():
243+
personality_choices += f"\t{key} - {personality.description}\n"
244+
console.print(personality_choices)
245+
246+
config_data["personality"] = click.prompt(
247+
"Please choose a personality",
248+
type=click.Choice(PERSONALITIES.keys(), case_sensitive=False),
249+
default=DEFAULT_PERSONALITY.name,
250+
)
251+
252+
write_config_file(config_data)
253+
console.print("✅ Configuration complete, you're ready to run aicodebot!\n")
254+
255+
# After writing the config file, print the usage for the top-level group
256+
ctx = click.get_current_context()
257+
while ctx.parent is not None:
258+
ctx = ctx.parent
259+
console.print(ctx.get_help())
260+
261+
console.print("\nDon't know where to start? Try running `aicodebot alignment`.")
262+
263+
171264
@cli.command(context_settings={"ignore_unknown_options": True})
172265
@click.argument("command", nargs=-1)
173266
@click.option("-v", "--verbose", count=True)
@@ -208,7 +301,7 @@ def debug(command, verbose):
208301
llm = ChatOpenAI(
209302
model=model,
210303
temperature=PRECISE_TEMPERATURE,
211-
openai_api_key=config["OPENAI_API_KEY"],
304+
openai_api_key=config["openai_api_key"],
212305
max_tokens=DEFAULT_MAX_TOKENS,
213306
verbose=verbose,
214307
streaming=True,
@@ -241,7 +334,7 @@ def fun_fact(verbose, response_token_size):
241334
model=model,
242335
temperature=PRECISE_TEMPERATURE,
243336
max_tokens=response_token_size,
244-
openai_api_key=config["OPENAI_API_KEY"],
337+
openai_api_key=config["openai_api_key"],
245338
verbose=verbose,
246339
streaming=True,
247340
callbacks=[RichLiveCallbackHandler(live)],
@@ -271,7 +364,7 @@ def review(commit, verbose):
271364
logger.trace(f"Prompt: {prompt}")
272365

273366
# Check the size of the diff context and adjust accordingly
274-
response_token_size = DEFAULT_MAX_TOKENS
367+
response_token_size = DEFAULT_MAX_TOKENS * 2
275368
request_token_size = get_token_length(diff_context) + get_token_length(prompt.template)
276369
model = get_llm_model(request_token_size)
277370
if model is None:
@@ -281,7 +374,7 @@ def review(commit, verbose):
281374
llm = ChatOpenAI(
282375
model=model,
283376
temperature=PRECISE_TEMPERATURE,
284-
openai_api_key=config["OPENAI_API_KEY"],
377+
openai_api_key=config["openai_api_key"],
285378
max_tokens=response_token_size,
286379
verbose=verbose,
287380
streaming=True,
@@ -294,28 +387,6 @@ def review(commit, verbose):
294387
chain.run(diff_context)
295388

296389

297-
@cli.command()
298-
@click.option("--openai-api-key", "-k", help="Your OpenAI API key")
299-
@click.option("--gpt-4-supported", "-4", help="Whether you have access to GPT-4", is_flag=True)
300-
def setup(openai_api_key, gpt_4_supported):
301-
"""Set up the configuration file with your OpenAI API key
302-
If the config file already exists, it will ask you if you want to remove it and recreate it.
303-
"""
304-
config_file = get_config_file()
305-
if config_file.exists():
306-
if not click.confirm(
307-
f"The config file already exists at {config_file}. Do you want to remove it and recreate it?"
308-
):
309-
console.print("Setup cancelled. 🚫")
310-
sys.exit(1)
311-
312-
# Remove the existing config file
313-
config_file.unlink()
314-
315-
# Call the setup_config function with the provided arguments
316-
setup_config(openai_api_key, gpt_4_supported)
317-
318-
319390
@cli.command
320391
@click.option("--request", "-r", help="What to ask your sidekick to do")
321392
@click.option("-v", "--verbose", count=True)
@@ -349,7 +420,7 @@ def sidekick(request, verbose, response_token_size, files):
349420

350421
llm = ChatOpenAI(
351422
model=model,
352-
openai_api_key=config["OPENAI_API_KEY"],
423+
openai_api_key=config["openai_api_key"],
353424
temperature=PRECISE_TEMPERATURE,
354425
max_tokens=response_token_size,
355426
verbose=verbose,
@@ -398,73 +469,14 @@ def sidekick(request, verbose, response_token_size, files):
398469
# ---------------------------------------------------------------------------- #
399470

400471

401-
def setup_config(openai_api_key=None, gpt_4_supported=None):
402-
config = read_config()
403-
openai.api_key = openai_api_key
404-
if config:
405-
openai.api_key = config["OPENAI_API_KEY"]
406-
logger.success(f"Using OpenAI API key from {get_config_file()}")
407-
return config
408-
elif os.getenv("OPENAI_API_KEY"):
409-
logger.info("Using OPENAI_API_KEY environment variable")
410-
openai.api_key = os.getenv("OPENAI_API_KEY")
411-
412-
config_file = get_config_file()
413-
console.print(f"[bold red]The config file does not exist.[/bold red]\nLet's set that up for you at {config_file}\n")
414-
415-
if not openai.api_key:
416-
openai_api_key_url = "https://platform.openai.com/account/api-keys"
417-
418-
console.print(
419-
"First, an OpenAI API key is required to use AICodeBot. You can get one for free on the OpenAI website.\n"
420-
)
421-
422-
if click.confirm("Open the OpenAI API keys page for you in a browser?"):
423-
webbrowser.open(openai_api_key_url)
424-
425-
openai.api_key = click.prompt("Please enter your OpenAI API key")
426-
427-
# Validate the API key and check if it supports GPT-4
428-
if gpt_4_supported is None:
429-
try:
430-
click.echo("Validating the API key, and checking if GPT-4 is supported...")
431-
engines = engine.Engine.list()
432-
logger.trace(f"Engines: {engines}")
433-
gpt_4_supported = "gpt-4" in [engine.id for engine in engines.data]
434-
if gpt_4_supported:
435-
click.echo("✅ The API key is valid and supports GPT-4.")
436-
else:
437-
click.echo("✅ The API key is valid, but does not support GPT-4. GPT-3.5 will be used instead.")
438-
except Exception as e:
439-
raise click.ClickException(f"Failed to validate the API key: {str(e)}") from e
440-
441-
# Pull the choices from the name from each of the PERSONALITIES
442-
personality_choices = "\nHow would you like your AI to act? You can choose from the following personalities:\n"
443-
for key, personality in PERSONALITIES.items():
444-
personality_choices += f"\t{key} - {personality.description}\n"
445-
console.print(personality_choices)
446-
447-
personality = click.prompt(
448-
"Please choose a personality",
449-
type=click.Choice(PERSONALITIES.keys(), case_sensitive=False),
450-
default=list(PERSONALITIES.keys())[0],
451-
)
452-
453-
config_data = {
454-
"config_version": 1,
455-
"OPENAI_API_KEY": openai.api_key,
456-
"gpt_4_supported": gpt_4_supported,
457-
"personality": personality,
458-
}
459-
460-
with Path.open(config_file, "w") as f:
461-
yaml.dump(config_data, f)
462-
463-
console.print(
464-
f"[bold green]Created {config_file} with your OpenAI API key.[/bold green] "
465-
"Now, please re-run aicodebot and let's get started!"
466-
)
467-
sys.exit(0)
472+
def setup_config():
473+
existing_config = read_config()
474+
if not existing_config:
475+
console.print("No config file found. Running configure...\n")
476+
configure.callback(openai_api_key=None, verbose=0)
477+
sys.exit()
478+
else:
479+
return existing_config
468480

469481

470482
class RichLiveCallbackHandler(BaseCallbackHandler):

aicodebot/prompts.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,6 @@
2929
Speak like Sherlock.
3030
"""
3131

32-
THE_DUDE = """
33-
Your personality is The Dude from The Big Lebowski. You're laid-back, easygoing, and you
34-
prefer to take life as it comes. You're not one for formalities or complications. You're
35-
here to help the developer, but you're not going to stress about it. You might use a bit
36-
of profanity, but nothing too harsh. Speak like The Dude.
37-
"""
38-
3932
MORPHEUS = """
4033
Your personality is Morpheus from The Matrix. You're wise, calm, and you believe in the
4134
potential of others. You're here to guide the developer, to help them realize their own
@@ -44,17 +37,19 @@
4437
"""
4538

4639
PERSONALITIES = {
47-
"HER": SimpleNamespace(name="Her", prompt=HER, description="The AI character from the movie Her"),
48-
"JULES": SimpleNamespace(name="Jules", prompt=JULES, description="Samuel L. Jackson's character from Pulp Fiction"),
49-
"SHERLOCK": SimpleNamespace(name="Sherlock", prompt=SHERLOCK, description="Sherlock Holmes"),
50-
"THE_DUDE": SimpleNamespace(name="The Dude", prompt=THE_DUDE, description="The Dude from The Big Lebowski"),
51-
"MORPHEUS": SimpleNamespace(name="Morpheus", prompt=MORPHEUS, description="Morpheus from The Matrix"),
40+
"Her": SimpleNamespace(name="Her", prompt=HER, description="The AI character from the movie Her"),
41+
"Jules": SimpleNamespace(
42+
name="Jules", prompt=JULES, description="Samuel L. Jackson's character from Pulp Fiction (warning: profanity))"
43+
),
44+
"Sherlock": SimpleNamespace(name="Sherlock", prompt=SHERLOCK, description="Sherlock Holmes"),
45+
"Morpheus": SimpleNamespace(name="Morpheus", prompt=MORPHEUS, description="Morpheus from The Matrix"),
5246
}
47+
DEFAULT_PERSONALITY = PERSONALITIES["Her"]
5348

5449

5550
def get_personality_prompt():
5651
"""Generates a prompt for the sidekick personality."""
57-
default_personality = "HER"
52+
default_personality = DEFAULT_PERSONALITY.name
5853
if os.getenv("AICODEBOT_PERSONALITY"):
5954
personality = os.getenv("AICODEBOT_PERSONALITY")
6055
else:

0 commit comments

Comments
 (0)