Create simple calculator

This commit is contained in:
2025-06-09 15:03:04 +02:00
parent c0aafe41b4
commit a469746af1
8 changed files with 82 additions and 469 deletions

View File

@ -1,79 +1,34 @@
# Agenten Plattform
#
# (c) 2024 Magnus Bender
# Institute of Humanities-Centered Artificial Intelligence (CHAI)
# Universitaet Hamburg
# https://www.chai.uni-hamburg.de/~bender
#
# (c) 2025 Magnus Bender
#
# source code released under the terms of GNU Public License Version 3
# https://www.gnu.org/licenses/gpl-3.0.txt
import re
from typing import Callable
from ums.agent import ExtractTextAgent
from ums.utils import RiddleData, ExtractedData, ExtractedContent
from ums.agent import ExtractAudioAgent, ExtractImageAgent, ExtractTextAgent
from ums.utils import RiddleData, AgentMessage, ExtractedData, ExtractedContent, ExtractedPositions
class SimpleExtractAudioAgent(ExtractAudioAgent):
# here we do not have an implementation for extracting audio,
# normally, we would not have this class, but here as example
def handle(self, data: RiddleData) -> RiddleData:
print("Audio Process:", data.file_plain)
return data
class SimpleExtractImageAgent(ExtractImageAgent):
# equally, we would not have this class without implementation
def before_response(self, response: AgentMessage, send_it: Callable[[], None]) -> bool:
# agents are able to prevent sending response messages to the management
# or send this messages later via `send_it()``
print("The response would be:", response)
# just stop the response from being sent
return False
def handle(self, data: RiddleData) -> RiddleData:
print("Image Process:", data.file_plain)
return data
class SimpleExtractTextAgent(ExtractTextAgent):
class CalculatorExtractVariables(ExtractTextAgent):
def handle(self, data: RiddleData) -> RiddleData:
print("Text Process:", data.file_plain)
# here we extract the variables assigned with numbers
found = False
# here we extract the variables assigned with numbers in the .txt files
with open(data.file_plain) as f:
for i, line in enumerate(f):
if "=" in line:
match = re.match(r"([a-z]{1})\s*=\s*(\d+)", line.strip())
if not match is None:
variable = match.group(1)
value = int(match.group(2))
found = True
line_no = i
if found:
extracted = ExtractedData(
contents=[
ExtractedContent(type="variable",content=variable),
ExtractedContent(type="value",content=value)
],
positions=[
ExtractedPositions(type="line",position=line_no),
ExtractedPositions(type="line",position=line_no)
],
other={
"variable" : variable,
"value" : value
}
)
data.file_extracted = self.store_extracted(data, extracted)
for line in f:
match = re.match(r"([a-z]{1})\s*=\s*(\d+)", line.strip())
if not match is None:
variable, value = match.group(1), int(match.group(2))
data.file_extracted = self.store_extracted(data, ExtractedData(
contents=[
ExtractedContent(type="variable", content=variable),
ExtractedContent(type="value", content=value)
]
))
return data
AGENT_CLASSES = [
SimpleExtractAudioAgent, SimpleExtractImageAgent, SimpleExtractTextAgent
CalculatorExtractVariables
]

View File

@ -1,101 +1,57 @@
# Agenten Plattform
#
# (c) 2024 Magnus Bender
# Institute of Humanities-Centered Artificial Intelligence (CHAI)
# Universitaet Hamburg
# https://www.chai.uni-hamburg.de/~bender
# (c) 2025 Magnus Bender
#
# source code released under the terms of GNU Public License Version 3
# https://www.gnu.org/licenses/gpl-3.0.txt
import re
import re, random
from typing import Callable
from typing import List
from ums.agent import SolveAgent
from ums.utils import Riddle, RiddleData, RiddleSolution, RiddleDataType, AgentMessage
from ums.utils import Riddle, RiddleData, RiddleSolution
class SimpleSolveAgent(SolveAgent):
class CalculatorSolveExpression(SolveAgent):
def before_response(self, response: AgentMessage, send_it: Callable[[], None]) -> bool:
# do not send a response, if this is not a calculator riddle!
return not self.stop_response
def handle(self, riddle: Riddle, data: RiddleData) -> RiddleSolution:
def handle(self, riddle: Riddle, data: List[RiddleData]) -> RiddleSolution:
# remove whitespace
expression = riddle.question.strip()
# this is a very simple calculator, if the riddle it not for the calculator
# just do not try to solve it and do not answer management!
if "[Taschenrechner]" in riddle.context:
self.stop_response = False
# get all the extracted values
var_vals = {}
for d in data:
e = self.get_extracted(d)
if not e is None \
and "variable" in e.other and "value" in e.other \
and e.other["variable"] in expression:
var_vals[e.other["variable"]] = e.other["value"]
# get all the extracted values
var_vals = {}
used_data = []
for d in data:
e = self.get_extracted(d)
if not e is None \
and "variable" in e.other and "value" in e.other \
and e.other["variable"] in expression:
used_data.append(d)
var_vals[e.other["variable"]] = e.other["value"]
# require "=" at the end
if not expression[-1] == "=":
return RiddleSolution(solution="Error", explanation="No = at the end of the expression!")
# solve the expression
# remove the = and whitespace
expression = expression[:-1].strip()
# require "=" at the end
if not expression[-1] == "=":
return RiddleSolution(solution="Error", explanation="No = at the end of the expression!")
# solve the expression
# remove the = and whitespace
expression = expression[:-1].strip()
for var, val in var_vals.items():
# replace the variables by values
expression = expression.replace(var, str(val))
for var, val in var_vals.items():
# replace the variables by values
expression = expression.replace(var, str(val))
# check the expression
if re.match(r"^[0-9+\-*\/ ]+$", expression) is None:
return RiddleSolution(solution="Error", explanation="Missing data or faulty expression")
# check the expression
if re.match(r"^[0-9+\-*\/ ]+$", expression) is None:
return RiddleSolution(solution="Error", explanation="Missing data or faulty expression")
try:
# using eval is a bad idea, but this is only for demonstration
# and expression may only contain "0-9 +-*/"
result = eval(expression)
except:
return RiddleSolution(solution="Error", explanation="Unable to calculate value of expression")
# add some noise and invalidate result (for gatekeeper to check)
if random.random() > 0.5:
print("UPPS UPPS")
result += 1 + int(random.random()*9)
return RiddleSolution(
solution=str(result),
explanation="{} = {}".format(expression, result),
used_data=used_data
)
else:
self.stop_response = True
# but we will start a nice riddle, we can solve :D
self.sub_riddle(
riddle=Riddle(
context="[Taschenrechner]",
question="x * x ="
),
data=[
RiddleData(
type=RiddleDataType.TEXT,
file_plain="./example/x.txt"
)
]
)
return RiddleSolution(solution="Error", explanation="No context [Taschenrechner]!")
try:
# using eval is a usually bad idea, but this is only for demonstration
# and expression may only contain "0-9 +-*/"
result = eval(expression)
except:
return RiddleSolution(solution="Error", explanation="Unable to calculate value of expression")
return RiddleSolution(solution=str(result), explanation="{} = {}".format(expression, result))
AGENT_CLASSES = [
SimpleSolveAgent
CalculatorSolveExpression
]

View File

@ -1,71 +1,50 @@
# Agenten Plattform
#
# (c) 2024 Magnus Bender
# Institute of Humanities-Centered Artificial Intelligence (CHAI)
# Universitaet Hamburg
# https://www.chai.uni-hamburg.de/~bender
# (c) 2025 Magnus Bender
#
# source code released under the terms of GNU Public License Version 3
# https://www.gnu.org/licenses/gpl-3.0.txt
import re
from typing import Callable, List
from typing import List
from ums.agent import GatekeeperAgent
from ums.utils import Riddle, RiddleSolution, AgentMessage
from ums.utils import Riddle, RiddleSolution
class SimpleSolveAgent(GatekeeperAgent):
def before_response(self, response: AgentMessage, send_it: Callable[[], None]) -> bool:
# do not send a response, if this is not a calculator riddle!
return not self.stop_response
class CalculatorCheckResult(GatekeeperAgent):
def handle(self, solution: List[RiddleSolution], riddle: Riddle) -> List[RiddleSolution]:
self.stop_response = False
# iterate over all poss. solutions
return [self._check_single_solution(s) for s in solution]
def _check_single_solution(self, solution:RiddleSolution) -> RiddleSolution:
# first check for errors
if solution.solution == "Error":
solution.accepted = True
solution.review = "An error of the riddle can not be fixed!"
return solution
# this is just a simple check, we check if solution and explanation match to the expression
match = re.match(r"^([0-9+\-*\/ ]+)\s*=\s*(\d+)$", solution.explanation)
if match is None:
self.stop_response = True
return solution
return self._update_solution(solution, False, "Invalid math expression")
expression, result = match.group(1), match.group(2)
if result != solution.solution:
solution.accepted = False
solution.review = "Inconsistent values"
return solution
return self._update_solution(solution, False, "Inconsistent values")
try:
# using eval is a bad idea, but this is only for demonstration
# and expression may only contain "0-9 +-*/"
own_result = eval(expression)
except:
solution.accepted = False
solution.review = "Unsolvable expression"
return solution
return self._update_solution(solution, False, "Unsolvable expression")
# check the values
if str(own_result) != solution.solution:
solution.accepted = False
solution.review = "Value of expression does not match solution!"
return self._update_solution(solution, False, "Value of expression does not match solution!")
else:
solution.accepted = True
solution.review = "Yes, {}".format(solution.explanation)
return solution
return self._update_solution(solution, True, "Yes, {}".format(solution.explanation))
def _update_solution(self, solution:RiddleSolution, accepted:bool, review:str="") -> RiddleSolution:
solution.accepted = accepted
solution.review = review
return solution
AGENT_CLASSES = [
SimpleSolveAgent
CalculatorCheckResult
]