Skip to content

Commit b2d6b6c

Browse files
authored
Merge pull request #3 from ajhous44/feature/inputconsolidation
Cody Consolidation + Rework
2 parents 2218109 + 5e1e7f0 commit b2d6b6c

File tree

7 files changed

+85
-204
lines changed

7 files changed

+85
-204
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
.venv
2+
.env

.local.env

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OPENAI_API_KEY=YOUR_API_KEY_HERE

README.md

+10-11
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ Cody continuously updates its knowledge base every time you save a file, ensurin
1212

1313
## 🚀 Getting Started
1414

15-
1. Set the environment variable `OPENAI_API_KEY` in a `.env` file with your OpenAI API key.
16-
2. Modify the `ignore_list` in the `if __name__ == "__main__":` section of the script to specify directories and files you wish to exclude from monitoring.
17-
3. Run the script using Python: python codyv4.py or codyv5.py
18-
19-
4. Once the script is running, type 'Q' and press enter to switch to question mode. Cody is ready to answer your queries!
15+
1. Clone the repo
16+
2. (Optionally) Setup virtual environment by running `pip install -m venv .venv` and then `pip install -r requirements.txt` in terminal from the root of your directory
17+
3. Rename the `.local.env` file to `.env`` and replace `YOUR_API_KEY_HERE` with your OpenAI API Key.
18+
4. Modify the `IGNORE_THESE` global var at the top of the script to specify directories and files you wish to exclude from monitoring. (You should comment out any large files like a virtual environment, cache, js libraries you have downloaded, etc...)
19+
5. Run the script using Python: python cody.py and follow terminal for setup. It will prompt you for if you want to use text chat (terminal) or conversational (speech i/o). It will also warn you if you remove .env from the ignore list.
2020

2121
## 🎯 Features
2222

2323
- **File Monitoring**: Real-time monitoring of all files in your project's directory and subdirectories. 👀
2424
- **Embedding-based Knowledge Base**: Create a knowledge base using OpenAI Embeddings. Cody collects the contents of all text and JSON files and adds them to this knowledge base. 📚
25-
- **Interactive Querying**: Listen to user inputs. Ask questions, and Cody will generate a response using the knowledge base. 🧠
25+
- **Interactive Q&A**: Listen to user inputs. Ask questions, and Cody will generate a response using the knowledge base. 🧠
2626
- **Customizable**: Easily specify files or directories to ignore during monitoring.
2727

2828
## 🛠 Dependencies
@@ -37,17 +37,16 @@ Cody continuously updates its knowledge base every time you save a file, ensurin
3737

3838
## 💡 Usage
3939

40-
- For version V4 (Text Interaction), type 'Q' and press enter. Cody will prompt you to input your question. Once you've entered your query, Cody will generate a response based on its knowledge base. Use Cody to debug code, troubleshoot errors, ask for help in adding new features, understand how functions interact across files, and more.
41-
42-
- For version V5 (Voice Interaction), simply speak to Cody, and it will respond accordingly. Cody is here to assist you with various programming tasks, making it a valuable tool in your coding journey.
43-
44-
To stop the script, type 'exit' or speak the word 'exit' and press enter. Cody will gracefully terminate the program.
40+
- To stop the script, type 'exit' or speak the word 'exit' and press enter. Cody will gracefully terminate the program.
4541

4642
## ⚠️ Notes & Tips
4743

4844
- Cody uses the FAISS library for efficient similarity search in storing vectors. Please ensure you have sufficient memory available, especially when monitoring a large number of files.
4945
- Additionally, be sure to monitor your OpenAI api usage. A helpful tip is to set a monthly spend limit inside of your OpenAI account to prevent anything crazy from happening. As an additional helper, it prints the number of tokens used in each call you make.
5046
- "LIVE" coding questions. To use to it's full potential. I recommend opening a seperate terminal or even command prompt cd'ing into your project directory, and then launching python cody.py. Then place it split screen with your code in a small viewing window on the far left or right. This way, you can use a seperate terminal for actually running your code without worrying about Cody or having to run him (er... it) each time! This will still continue to update with each file save you do on any file so it always is using the latest data.
5147

48+
## Contributing
49+
50+
Contributions are welcome. Please submit a pull request or open an issue for any bugs or feature requests.
5251

5352
Happy Coding with Cody! 💡🚀🎉

audio/response.mp3

-25.4 KB
Binary file not shown.

codyV5.py renamed to cody.py

+73-54
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from langchain.vectorstores import FAISS
55
from watchdog.observers import Observer
66
from watchdog.events import FileSystemEventHandler
7+
import tempfile
78
import json
89
import time
910
import threading
@@ -15,6 +16,11 @@
1516

1617
load_dotenv()
1718
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
19+
20+
### USER OPTIONS ###
21+
### MAX TOKENS PER CALL: MAX TOKENS TO USE FOR CALL
22+
MAX_TOKENS_PER_CALL = 2500 # MAX TOKENS TO USE FOR CALL
23+
IGNORE_THESE = ['.venv', '.env', 'static', 'dashboard/static', 'audio', 'license.md', '.github', '__pycache__']
1824
r = sr.Recognizer()
1925

2026
class FileChangeHandler(FileSystemEventHandler):
@@ -38,12 +44,18 @@ def should_ignore(self, filename):
3844
def on_modified(self, event):
3945
if "response.mp3" not in event.src_path:
4046
if not self.should_ignore(event.src_path):
41-
print(f'\n \U0001F4BE The file {event.src_path} has changed!')
47+
print(f'\n\U0001F4BE The file {event.src_path} has changed!')
4248
self.update_file_content()
4349

4450
def update_file_content(self):
45-
print("\U0001F4C1 Collecting files...")
51+
print("\n\U0001F4C1 Collecting files...")
4652
all_files_data = {}
53+
# Check if ".env" is in ignore list, if not prompt warning "Are you sure you want to include your .env in your api call to OpenAI?"
54+
if ".env" not in self.ignore_list:
55+
response = input("😨 You removed .env from ignore list. This may expose .env variables to OpenAI. Confirm? (1 for Yes, 2 for exit):")
56+
if response != "1":
57+
print("\n😅 Phew. Close one... Operation aborted. Please add '.env' to your ignore list and try again.")
58+
exit()
4759
for root, dirs, files in os.walk('.'):
4860
# Remove directories in the ignore list
4961
dirs[:] = [d for d in dirs if d not in self.ignore_list]
@@ -54,6 +66,7 @@ def update_file_content(self):
5466
with open(file_path, 'r') as file:
5567
if filename.endswith('.json'):
5668
json_data = json.load(file)
69+
all_files_data[file_path] = json_data # Store JSON data in the dictionary
5770
else:
5871
lines = file.readlines()
5972
line_data = {}
@@ -81,8 +94,8 @@ def update_file_content(self):
8194
self.knowledge_base = FAISS.from_texts(chunks, self.embeddings)
8295

8396
print("\U00002705 All set!")
84-
create_audio("Files updated. Ready for questions", "audio/response.mp3")
85-
play_audio("audio/response.mp3")
97+
audio_stream = create_audio("Files updated. Ready for questions")
98+
play_audio(audio_stream)
8699

87100
def play_audio(file_path):
88101
"""
@@ -96,83 +109,90 @@ def play_audio(file_path):
96109
continue
97110

98111
pygame.mixer.music.unload()
112+
os.unlink(file_path) # Delete the temporary file
113+
print("Deleted temp audio file in: " + file_path)
99114

100-
def create_audio(text, filename):
115+
def create_audio(text):
101116
"""
102-
Create an audio file from text
117+
Create an audio file from text and return the path to a temporary file
103118
"""
104-
try:
119+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
120+
print(f"\nCreated temp audio file in : {temp_file.name}")
121+
try:
105122
speech = gTTS(text=text, lang='en', slow=False)
106-
speech.save(filename)
123+
speech.save(temp_file.name)
107124
except Exception as e:
108-
print(f"Error in creating audio: {e}")
125+
print(f"\nError in creating audio: {e}")
126+
127+
return temp_file.name
109128

110-
def generate_response(prompt):
129+
def generate_response(prompt, speak_response=True):
111130
openai.api_key = OPENAI_API_KEY
112-
# # For debugging if you want to view the full data being passed
113-
# print("\U00002753 Received question: " + str(prompt))
114131
try:
115132
completion = openai.ChatCompletion.create(
116133
model="gpt-3.5-turbo",
117-
messages=[{"role": "user", "content": prompt}]
134+
messages=[{"role": "user", "content": prompt}],
135+
max_tokens=MAX_TOKENS_PER_CALL,
118136
)
119-
print("\U0001F4B0 Tokens used:", completion.usage.total_tokens)
120-
# print total tokens used al time
121-
print('\U0001F916', completion.choices[0].message.content)
122-
create_audio(completion.choices[0].message.content, "audio/response.mp3")
123-
play_audio("audio/response.mp3")
137+
print("\n\U0001F4B0 Tokens used:", completion.usage.total_tokens)
138+
response_text = completion.choices[0].message.content
139+
print('\U0001F916', response_text)
140+
if speak_response:
141+
audio_stream = create_audio(response_text)
142+
play_audio("audio/response.mp3")
124143
except Exception as e:
125144
print(f"\U000026A0 Error in generating response: {e}")
126145

127-
def collect_files(handler):
128-
# print("\U0001F4C1 Collecting files...")
129-
handler.update_file_content()
130-
# print("\U00002705 Initial file collection done.")
131-
132-
def monitor_input(handler):
133-
146+
def monitor_input(handler, terminal_input=True):
134147
while True:
135148
try:
136-
with sr.Microphone() as source:
137-
print("Listening...")
138-
audio_data = r.listen(source)
139-
text = r.recognize_google(audio_data)
140-
141-
142-
if text == "exit":
143-
print("\U0001F44B Exiting the program...")
144-
os._exit(0)
145-
elif text:
146-
print(f"You said: {text}")
147-
question = text
148-
print("\U0001F9E0 You asked: " + question)
149-
docs = handler.knowledge_base.similarity_search(question)
150-
response = f"You are an expert programmer who is aware of this much of the code base:{str(docs)}. \n"
151-
response += "Please answer this: " + question + " Keep your answer under 20 words if possible. Speak in bullet points if you can to help with conciseness. Your main priority is to answer their questions using the info provided including line numbers if possible. Also note that when you give answers please include the file path if it makes sense. If the question is not relevant or not a question simply respond with 'Skipping'. Do not include any special text like _'s or ''s as this will be read by text to speech. Only include text in the response without non character letters. Even function names with _ in them should be replaced with a space so it is more readable audibly."
152-
ai_response = generate_response(response)
153-
text = ""
149+
if terminal_input:
150+
text = input("\U00002753 Please type your question (or 'exit' to quit): ")
151+
else:
152+
with sr.Microphone() as source:
153+
print("\nListening...")
154+
audio_data = r.listen(source)
155+
text = r.recognize_google(audio_data)
156+
157+
if text.lower() == 'exit':
158+
print("\n\U0001F44B Exiting the program...")
159+
os._exit(0)
160+
else:
161+
print(f"You said: {text}")
162+
question = text
163+
print("\n\U0001F9E0 You asked: " + question)
164+
docs = handler.knowledge_base.similarity_search(question)
165+
response = f"You are an expert programmer who is aware of this much of the code base:{str(docs)}. \n"
166+
response += "Please answer this: " + question + "..." # Add the rest of your instructions here
167+
generate_response(response, speak_response=not terminal_input)
154168
except sr.UnknownValueError:
155-
print("Could not understand audio")
169+
print("\nCould not understand audio")
156170
except sr.RequestError as e:
157-
print("Could not request results; {0}".format(e))
158-
171+
print("\nCould not request results; {0}".format(e))
172+
except Exception as e:
173+
print(f"An error occurred: {e}")
159174

160175
def start_cody(ignore_list=[]):
161-
handler = FileChangeHandler(ignore_list)
176+
handler = FileChangeHandler(ignore_list=IGNORE_THESE)
162177

163178
# Collect files before starting the observer
164-
collect_files(handler)
179+
handler.update_file_content() # Directly call the update_file_content method
165180

166-
# Start a new thread to monitor user input
167-
input_thread = threading.Thread(target=monitor_input, args=(handler,))
181+
# Prompt user for interaction method
182+
interaction_method = input("\nHow should I talk to you? Enter 1 for Terminal or 2 for Speech I/O: ")
183+
184+
terminal_input = interaction_method == '1'
185+
186+
# Start a new thread to monitor input
187+
input_thread = threading.Thread(target=monitor_input, args=(handler, terminal_input))
168188
input_thread.start()
169189

170190
# Initialize the observer
171191
observer = Observer()
172192
observer.schedule(handler, path='.', recursive=True)
173193
observer.start()
174194

175-
# Continue to observe for file changes. Adding time.sleep to reduce CPU usage as well as prevent 'duplicate' file change events (false flags)
195+
# Continue to observe for file changes
176196
try:
177197
while True:
178198
time.sleep(5)
@@ -181,6 +201,5 @@ def start_cody(ignore_list=[]):
181201

182202
observer.join()
183203

184-
if __name__ == "__main__": # Set this to False to ignore .env file
185-
ignore_list = ['static', 'dashboard/static', 'audio', 'license.md', '.github', '__pycache__']
186-
start_cody(ignore_list)
204+
if __name__ == "__main__":
205+
start_cody(ignore_list=IGNORE_THESE)

0 commit comments

Comments
 (0)