Skip to content

Commit 7551018

Browse files
Add files via upload
update to reasoning from logic
1 parent 52b0979 commit 7551018

File tree

7 files changed

+709
-346
lines changed

7 files changed

+709
-346
lines changed

SocraticReasoning.py

+316-34
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,321 @@
1-
# SocraticReasoning.py
21
import logging
2+
import os
3+
import pathlib
4+
import ujson
5+
from datetime import datetime
36
from chatter import GPT4o, GroqModel, OllamaModel
7+
from logic import LogicTables
8+
from memory.memory import create_memory_folders, store_in_stm, DialogEntry
9+
from api import APIManager
410

511
class SocraticReasoning:
612
def __init__(self, chatter):
7-
self.premises = []
13+
"""
14+
Initializes the SocraticReasoning instance with necessary configurations.
15+
16+
Args:
17+
chatter: An instance of the model used for generating responses.
18+
"""
19+
self.premises = [] # List to hold premises
820
self.logger = logging.getLogger('SocraticReasoning')
9-
self.logger.setLevel(logging.INFO)
10-
self.max_tokens = 100
11-
self.chatter = chatter
21+
self.logger.setLevel(logging.DEBUG) # Set to DEBUG to capture all SocraticReasoning logs
22+
23+
# Ensure the logs directory exists
24+
logs_dir = './memory/logs'
25+
os.makedirs(logs_dir, exist_ok=True)
26+
27+
# File handler for saving Socratic Reasoning logs
28+
file_handler = logging.FileHandler(os.path.join(logs_dir, 'socraticlogs.txt'))
29+
file_handler.setLevel(logging.DEBUG)
30+
file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
31+
file_handler.setFormatter(file_formatter)
32+
33+
# Stream handler to suppress lower-level logs in the terminal
34+
stream_handler = logging.StreamHandler()
35+
stream_handler.setLevel(logging.CRITICAL) # Show only critical logs in the terminal
36+
stream_formatter = logging.Formatter('%(message)s')
37+
stream_handler.setFormatter(stream_formatter)
38+
39+
# Adding handlers to the logger
40+
self.logger.addHandler(file_handler)
41+
self.logger.addHandler(stream_handler)
42+
43+
# File paths for saving premises, non-premises, conclusions, and truth tables
44+
self.premises_file = './memory/logs/premises.json'
45+
self.not_premises_file = './memory/logs/notpremise.json'
46+
self.conclusions_file = './memory/logs/conclusions.txt' # Path to save conclusions
47+
self.truth_tables_file = './memory/logs/truth_tables.json' # Path to save truth tables
48+
49+
self.max_tokens = 100 # Default max tokens for Socratic premise from add_premise(statement)
50+
self.chatter = chatter # Chatter model for generating responses
51+
self.logic_tables = LogicTables() # Logic tables for reasoning
52+
self.dialogue_history = [] # List to hold the history of dialogues
53+
self.logical_conclusion = "" # Variable to store the conclusion
54+
55+
create_memory_folders() # Ensure memory folders are created
1256

1357
def log(self, message, level='info'):
58+
"""
59+
Logs a message with a specified level.
60+
61+
Args:
62+
message: The message to be logged.
63+
level: The level of logging ('info' or 'error').
64+
"""
1465
if level == 'info':
1566
self.logger.info(message)
1667
elif level == 'error':
1768
self.logger.error(message)
18-
print(message)
69+
self.log_errors(message, level) # Save Socratic reasoning to ./memory/logs/socraticlogs.txt
70+
71+
def log_errors(self, message, level):
72+
"""
73+
Stores error logs in ./memory/logs/errorlogs.txt.
74+
75+
Args:
76+
message: The error message to be logged.
77+
level: The level of the error.
78+
"""
79+
error_logs_path = './memory/logs/errorlogs.txt'
80+
pathlib.Path(error_logs_path).parent.mkdir(parents=True, exist_ok=True)
81+
with open(error_logs_path, 'a') as file:
82+
file.write(f"{level.upper()}: {message}\n")
83+
84+
def log_not_premise(self, message, level='info'):
85+
"""
86+
Logs messages that are not considered premises.
87+
88+
Args:
89+
message: The message to be logged.
90+
level: The level of logging.
91+
"""
92+
not_premises_path = self.not_premises_file
93+
pathlib.Path(not_premises_path).parent.mkdir(parents=True, exist_ok=True)
94+
entry = {"level": level.upper(), "message": message}
95+
try:
96+
with open(not_premises_path, 'r') as file:
97+
logs = ujson.load(file)
98+
except (FileNotFoundError, ValueError):
99+
logs = []
100+
101+
logs.append(entry)
102+
with open(not_premises_path, 'w') as file:
103+
ujson.dump(logs, file, indent=2)
104+
105+
def save_premises(self):
106+
"""
107+
Saves the current list of premises to a JSON file.
108+
"""
109+
pathlib.Path(self.premises_file).parent.mkdir(parents=True, exist_ok=True)
110+
with open(self.premises_file, 'w') as file:
111+
ujson.dump(self.premises, file, indent=2)
19112

20113
def add_premise(self, premise):
21-
self.premises.append(premise)
22-
self.log(f'Added premise: {premise}')
114+
"""
115+
Adds a premise to the list if valid and saves the premises.
116+
117+
Args:
118+
premise: The premise to be added.
119+
"""
120+
if self.parse_statement(premise): # Check if the premise is valid
121+
self.premises.append(premise) # Add the premise to the list
122+
self.save_premises() # Save the updated list of premises
123+
else:
124+
self.log_not_premise(f'Invalid premise: {premise}', level='error') # Log invalid premise
125+
126+
def parse_statement(self, statement):
127+
"""
128+
Validates a statement to check if it can be a premise.
129+
130+
Args:
131+
statement: The statement to be validated.
132+
133+
Returns:
134+
bool: True if the statement is valid, False otherwise.
135+
"""
136+
return isinstance(statement, str) and len(statement) > 0 # Check if the statement is a non-empty string
137+
138+
def generate_new_premise(self, current_premises):
139+
"""
140+
Generates a new premise based on the current premises.
141+
142+
Args:
143+
current_premises: The list of current premises.
144+
145+
Returns:
146+
str: A new premise generated from the current premises.
147+
"""
148+
premise_text = " ".join(f"{premise}" for premise in current_premises)
149+
new_premise = self.chatter.generate_response(premise_text)
150+
return new_premise.strip()
23151

24152
def challenge_premise(self, premise):
25-
if premise in self.premises:
26-
self.premises.remove(premise)
27-
self.log(f'Challenged and removed premise: {premise}')
153+
"""
154+
Challenges and removes a premise from the list if it exists.
155+
156+
Args:
157+
premise: The premise to be challenged.
158+
"""
159+
if premise in self.premises: # Check if the premise exists in the list
160+
self.premises.remove(premise) # Remove the premise from the list
161+
self.log(f'Challenged and removed premise: {premise}') # Log the removal
162+
self.remove_equivalent_premises(premise) # Remove equivalent premises
163+
self.save_premises() # Save the updated list of premises
28164
else:
29-
self.log(f'Premise not found: {premise}', level='error')
165+
self.log_not_premise(f'Premise not found: {premise}', level='error') # Log if premise not found
166+
167+
def remove_equivalent_premises(self, premise):
168+
"""
169+
Removes premises that are logically equivalent to the challenged premise.
170+
171+
Args:
172+
premise: The premise to be checked for equivalence.
173+
"""
174+
equivalent_premises = [p for p in self.premises if self.logic_tables.unify_variables(premise, p)]
175+
for p in equivalent_premises:
176+
self.premises.remove(p) # Remove equivalent premise
177+
self.log_not_premise(f'Removed equivalent premise: {p}') # Log removal of equivalent premise
178+
self.save_premises() # Save the updated list of premises
30179

31180
def draw_conclusion(self):
32-
if not self.premises:
33-
self.log('No premises available for logic as conclusion.', level='error')
34-
return
181+
"""
182+
Draws a conclusion based on the current list of premises.
183+
184+
Returns:
185+
str: The conclusion derived from the premises.
186+
"""
187+
if not self.premises: # Check if there are no premises
188+
self.log('No premises available for logic as conclusion.', level='error') # Log the absence of premises
189+
return "No premises available for logic as conclusion."
35190

36-
premise_text = "\n".join(f"- {premise}" for premise in self.premises)
37-
prompt = f"Based on the premises:\n{premise_text}\nProvide a logical conclusion."
191+
additional_premises_count = 0 # Counter for additional premises
38192

39-
# Use the appropriate model to generate the response
40-
conclusion = self.chatter.generate_response(prompt)
41-
self.log(f"Conclusion:\n{conclusion}")
193+
# Generate new premises until a valid conclusion is drawn or the maximum limit is reached
194+
while additional_premises_count < 5:
195+
new_premise = self.generate_new_premise(self.premises)
196+
if not self.parse_statement(new_premise):
197+
self.log_not_premise(f'Invalid generated premise: {new_premise}', level='error')
198+
continue
199+
self.premises.append(new_premise)
200+
self.save_premises()
201+
additional_premises_count += 1
202+
203+
# Create a single string from the premises
204+
premise_text = " ".join(f"{premise}" for premise in self.premises)
205+
206+
# Use the premise_text as the input (knowledge) for generating a response
207+
raw_response = self.chatter.generate_response(premise_text)
208+
209+
# Process the response to get the conclusion
210+
conclusion = raw_response.strip()
211+
212+
self.logical_conclusion = conclusion # Store the conclusion
213+
214+
if self.validate_conclusion(): # Validate the conclusion
215+
break
216+
else:
217+
self.log_not_premise('Invalid conclusion. Generating more premises.', level='error')
218+
219+
# Save the conclusion along with premises
220+
conclusion_entry = {"premises": self.premises, "conclusion": self.logical_conclusion}
221+
pathlib.Path(self.premises_file).parent.mkdir(parents=True, exist_ok=True)
222+
with open(self.premises_file, 'w') as file:
223+
ujson.dump(conclusion_entry, file, indent=2)
224+
225+
# Log the conclusion to conclusions.txt
226+
pathlib.Path(self.conclusions_file).parent.mkdir(parents=True, exist_ok=True)
227+
with open(self.conclusions_file, 'a') as file:
228+
file.write(f"Premises: {self.premises}\nConclusion: {self.logical_conclusion}\n")
229+
230+
# Log the final premises to conclusion
231+
self.log(f"Final Premises to Conclusion:\nPremises: {self.premises}\nConclusion: {self.logical_conclusion}")
232+
233+
# Save the valid conclusion as a truth
234+
self.save_truth(self.logical_conclusion)
235+
236+
# Clear the premises list for the next round
237+
self.premises = []
238+
239+
return self.logical_conclusion # Return the conclusion
240+
241+
def validate_conclusion(self):
242+
"""
243+
Validates the logical conclusion.
244+
245+
Returns:
246+
bool: True if the conclusion is valid, False otherwise.
247+
"""
248+
return self.logic_tables.tautology(self.logical_conclusion) # Validate using logic tables
249+
250+
def save_truth(self, truth):
251+
"""
252+
Saves the valid conclusion as a truth in the truth tables.
253+
254+
Args:
255+
truth: The truth to be saved.
256+
"""
257+
truth_tables_entry = {
258+
"truth": truth,
259+
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
260+
}
261+
pathlib.Path(self.truth_tables_file).parent.mkdir(parents=True, exist_ok=True)
262+
with open(self.truth_tables_file, 'a') as file:
263+
ujson.dump(truth_tables_entry, file, indent=2)
264+
file.write("\n")
265+
266+
def update_logic_tables(self, variables, expressions, valid_truths):
267+
"""
268+
Updates the logic tables with new variables, expressions, and valid truths.
269+
270+
Args:
271+
variables: The logical variables.
272+
expressions: The logical expressions.
273+
valid_truths: The valid truths for the logic table.
274+
"""
275+
self.logic_tables.variables = variables
276+
self.logic_tables.expressions = expressions
277+
self.logic_tables.valid_truths = valid_truths
278+
279+
# Log the truth tables to truth_tables.json
280+
truth_tables_entry = {
281+
"variables": variables,
282+
"expressions": expressions,
283+
"valid_truths": valid_truths
284+
}
285+
pathlib.Path(self.truth_tables_file).parent.mkdir(parents=True, exist_ok=True)
286+
with open(self.truth_tables_file, 'w') as file:
287+
ujson.dump(truth_tables_entry, file, indent=2)
288+
289+
# Save a timestamped file in ./memory/truth
290+
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
291+
belief_timestamp_file = f'./memory/truth/belief_{timestamp}.json'
292+
pathlib.Path(belief_timestamp_file).parent.mkdir(parents=True, exist_ok=True)
293+
with open(belief_timestamp_file, 'w') as file:
294+
ujson.dump(truth_tables_entry, file, indent=2)
295+
296+
# Add a log entry to confirm the update
297+
self.logger.info("Updated logic tables: %s", truth_tables_entry)
42298

43299
def set_max_tokens(self, max_tokens):
300+
"""
301+
Sets the maximum number of tokens for generating a response.
302+
303+
Args:
304+
max_tokens: The maximum number of tokens.
305+
"""
44306
self.max_tokens = max_tokens
45307
self.log(f"Max tokens set to: {max_tokens}")
46308

47309
def interact(self):
310+
"""
311+
Interacts with the user to add, challenge premises, and draw conclusions.
312+
"""
48313
while True:
49314
self.log("\nCommands: add, challenge, conclude, set_tokens, exit")
50315
cmd = input("> ").strip().lower()
51-
316+
52317
if cmd == 'exit':
53-
self.log('Exiting SocraticReasoning.')
318+
self.log('Exiting Socratic Reasoning.')
54319
break
55320
elif cmd == 'add':
56321
premise = input("Enter the premise: ").strip()
@@ -59,25 +324,42 @@ def interact(self):
59324
premise = input("Enter the premise to challenge: ").strip()
60325
self.challenge_premise(premise)
61326
elif cmd == 'conclude':
62-
self.draw_conclusion()
327+
conclusion = self.draw_conclusion()
328+
print(conclusion)
63329
elif cmd == 'set_tokens':
64330
tokens = input("Enter the maximum number of tokens for the conclusion: ").strip()
65331
if tokens.isdigit():
66332
self.set_max_tokens(int(tokens))
67333
else:
68334
self.log("Invalid number of tokens.", level='error')
335+
self.log_not_premise("Invalid number of tokens.", level='error')
69336
else:
70-
self.log('Invalid command.', level='error')
337+
self.log_not_premise('Invalid command.', level='error')
338+
339+
if __name__ == "__main__":
340+
logging.basicConfig(level=logging.INFO)
341+
api_manager = APIManager() # Initialize API manager to handle API keys
342+
openai_key = api_manager.get_api_key('openai')
343+
groq_key = api_manager.get_api_key('groq')
344+
345+
if openai_key:
346+
chatter = GPT4o(openai_key) # Use OpenAI model if key is available
347+
elif groq_key:
348+
chatter = GroqModel(groq_key) # Use Groq model if key is available
349+
else:
350+
raise ValueError("No suitable API key found. Please add an API key.")
351+
352+
socratic_reasoning = SocraticReasoning(chatter) # Initialize SocraticReasoning with the selected model
71353

72-
def main():
73-
# Replace 'your_api_key_here' with the actual API key or retrieve it as needed
74-
api_key = 'your_api_key_here'
75-
api_provider = 'openai' # or 'groq' or 'ollama'
76-
chatter = GPT4o(api_key) # or GroqModel(api_key), OllamaModel()
77-
reasoner = SocraticReasoning(chatter)
78-
reasoner.log('SocraticReasoning initialized.')
79-
reasoner.interact()
354+
# Example usage
355+
statements = [
356+
"All humans are mortal.",
357+
"Socrates is a human."
358+
]
80359

81-
if __name__ == '__main__':
82-
main()
360+
for statement in statements:
361+
socratic_reasoning.add_premise(statement) # Add each statement as a premise
83362

363+
conclusion = socratic_reasoning.draw_conclusion() # Draw a conclusion based on the premises
364+
print(conclusion)
365+
socratic_reasoning.interact() # Start the interactive loop

0 commit comments

Comments
 (0)