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,15 +0,0 @@
ARG IMAGE_FROM
FROM $IMAGE_FROM
# become root
USER root
# do somthing as root, e.g. install packages, set up things ...
# RUN ...
# copy the source of the agent in this repo
COPY --chown=user:user ./src/ /ums-agenten/project/src/
# fix permissions
RUN chown -R user:user /ums-agenten
# switch back to user
USER user

View File

@ -1,88 +0,0 @@
# Beispiel: Rätsel & Agent
Der Beispielagent kann Rechenaufgaben lösen, dabei ist das Rätsel eine einfache Aufgabe mit Variablen und in den Daten (Textdateien) werden den Variablen Werte zugewiesen.
> Der Beispielagent ist nicht sonderlich *schlau*, sondern funktioniert nur Rätseln, die genau einen bestimmten Aufbau haben.
## Beispiel Rätsel (`AgentMessage`)
- Daten (als Dateien in `./data/share`)
- `./example/x.txt`: `x = 10`
- `./example/y.txt`: `y = 15`
- `./example/z.txt`: `z = 20`
- Rätsel z.B.
- Kontext: "[Taschenrechner]"
- Frage: "x * y ="
- Eingabe unter <http://localhost:8080/app/new> möglich
- Für die Dateien ist eine Liste vorhandener Dateien hinterlegt, sodass Eingaben vervollständigt werden.
- Es wird eine JSON-Vorschau angezeigt und das Rätsel kann an das Management geschickt werden
- Beispiel für JSON Darstellung [&darr;](#json-darstellung)
- Nachrichten könnten unter <http://localhost:8080/app/table> angesehen werden
- Nachricht mit `Solution=True` sollte dann die Lösung beinhalten
## Ablauf der Bestimmung der Lösung
> Siehe auch [&rarr; Implementierung](./Implement.md#agent-handle)
1. Nachricht mit Rätsel an Management senden
2. Management wertet Nachricht und sendet Nachricht an alle ihm bekannten Extract-Agenten
3. Extract-Agenten extrahieren Daten und senden Ergebnis zurück an Management (`status.extract.finished == True`)
4. Management senden Nachricht (mit Extraktionen) an alle ihm bekannten Solve-Agenten
5. Solve-Agenten versuchen das Rätsel zu lösen
- Können Sub-Rätsel erstellen (<http://localhost:8080/docs/ums/agent/agent.html#BasicAgent.sub_riddle>)
- Bekommen ein Rätsel evtl. mehrfach (jeweils wenn ein Extract-Agent) fertig geworden ist. (Evtl. Lösen mit Teilen der Infos schon möglich oder auch nicht.)
- Können Antworten an das Management stoppen (<http://localhost:8080/docs/ums/agent/agent.html#BasicAgent.before_response>)
6. Management bekommt Ergebnis von Solve-Agenten und sendet dies an Gatekeeper-Agenten
7. Gatekeeper-Agent prüft Lösung und akzeptiert oder lehnt diese ab. Sendet Entscheidung an Management.
8. Management prüft, ob Lösung angenommen. Falls Lösung nicht akzeptiert, so wird Rätsel erneut gestellt (maximal `SOLUTION_MAX_TRIALS` mal)
## Agenten Quellcode
1. Extract [`./src/extract/agent.py`](./src/extract/agent.py)
2. Solve [`./src/solve/agent.py`](./src/solve/agent.py)
3. Validate [`./src/validate/agent.py`](./src/validate/agent.py)
### JSON-Darstellung
```json
{
"id": "ex1",
"sub_ids": [],
"riddle": {
"context": "[Taschenrechner]",
"question": "x * y =",
"solutions_before": []
},
"solution": [],
"data": [
{
"type": "text",
"file_plain": "example/x.txt",
"file_extracted": null
},
{
"type": "text",
"file_plain": "example/y.txt",
"file_extracted": null
},
{
"type": "text",
"file_plain": "example/z.txt",
"file_extracted": null
}
],
"status": {
"extract": {
"required": true,
"finished": false
},
"solve": {
"required": true,
"finished": false
},
"validate": {
"required": true,
"finished": false
},
"trial": 0,
"solved": false
},
"contacts": 0
}
```

View File

@ -1,61 +0,0 @@
# Implementierung
> Bisher ist das hier noch eher eine Sammlung von Tipps.
## Grundsätzlich
Dieses Git-Repository ist so erstellt, dass es als Template dienen kann.
Die Agenten sollten im Ordner `./src/` und damit dem Paket `src` implementiert werden.
Aktuell ist es so angelegt, dass alle Agenten sich das Paket `src` teilen, d.h., auch wenn man zwei Agenten implementiert, dann ist aller Code in `src` und je nach Agent z.B. jeweils in Unterpaketen.
Über `AGENTS_LIST` wird beim Start eines Containers dann jeweils spezifiziert, welchen Agenten der Container laden/ ausführen soll.
Es ist theoretisch auch möglich, alle Agenten in einem Docker-Container auszuführen (wenn `AGENTS_LIST` Klassen für Typen von Agenten enthält), dabei wird aber die Performance und Nebenläufigkeit leiden.
## Dinge *nachinstallieren*
Eigene Programme, Bibliotheken etc. können in Docker installiert werden, dazu liegt eine `Dockerfile` im Repository, diese wird auf die Agenten angewandt.
Zum Beispiel würde ein `RUN pip3 install super-duper-toll` das Pakete ` super-duper-toll` dann auch im Docker-Container des Agenten verfügbar machen.
Nach Änderungen an der `Dockerfile` baut man mit `docker compose build` das Docker-Image für die Agenten neu.
## Einzelne Teile Ausführen
Manchmal kann es nervig sein, beim Arbeiten an einer Komponente eines Agenten immer das ganze System mit Management etc. nutzen zu müssen und jedes mal auch noch Nachrichten ans Management senden zu müssen.
Zur Abhilfe gibt es die Möglichkeit einzelne Tasks per Terminal direkt an an einzelne Agenten zu senden, siehe <http://localhost:8080/docs/ums/agent.html#run-single-task> (Link zu Dokumentation im Management, Dokumentation auch als HTML unter <https://git.chai.uni-hamburg.de/UMS-Agenten/Agenten-Plattform/src/branch/master/web/public/docs>).
Z.B. eine Dummy-Message an den `agent_solve` senden, wenn der Container selbst nicht läuft:
`docker compose run --rm --entrypoint "" agent_solve python -m ums.agent -d`
## Agent `handle`
Die Methode `handle` ist die Hauptmethode eines Agenten, das Framework kümmert sich darum, dass `handle` aufgerufen wird.
Genauere Spezifikationen finden sich hier <http://localhost:8080/docs/ums/agent/agent.html>.
Bei der Ausführung von `handle` kann ein Agent entscheiden, eine Unteraufgabe (via <http://localhost:8080/docs/ums/agent/agent.html#BasicAgent.sub_riddle>) zu erstellen. Eine solche Unteraufgabe wird wie eine normale Aufgabe an das Management gesendet und gelöst.
Auch der Agent, der die Unteraufgabe erstellt hat, kann/ wird seine eigene Unteraufgabe zum Lösen bekommen.
Das ist in soweit nützlich, dass der Agent z.B. eine andere Datenauswahl getroffen haben kann und nun bei der Unteraufgabe andere Extraktionen bekommt.
Möchte der Agent das Gesamtergebnis einer Unteraufgabe *sehen*, so muss das Management angefragt werden (siehe [&darr;](#requests-an-das-management))
Insbesondere bei Aufgabe, die der Agent nicht lösen kann, kann der Agent die Antwort an das Management stoppen (via <http://localhost:8080/docs/ums/agent/agent.html#BasicAgent.before_response>).
Damit wird das Lösung des Rätsels (zumindest von diesem Agenten) aufgegeben.
Sollte z.B. der Agent zur Extraktion von Bilder keine Antwort geben und gibt aber der Agent zur Extraktion von Audio eine Antwort, so wird die Lösung des Rätsels nicht aufgegeben, aber es sind nur die Daten des Typs Audio extrahiert vorhanden und nutzbar genutzt werden.
Hieraus lässt sich auch ablesen, dass ein Agent evtl. das selbe Rätsel mehrfach bekommt, jeweils mit unterschiedlichen Daten die bereits extrahiert sind.
Dadurch kann ein Agent direkt versuchen ein Rätsel zu lösen und falls die Daten nicht reichen, dies später mit mehr Daten wiederholen.
Das Framework führt die verschiedenen Extraktionen der verschiedenen Agenten zu Audio, Image, Text zusammen, d.h., das Framework stellt immer alle vorhandenen Extraktionen für ein Datensatz in den Nachrichten zusammen (dabei gilt ein Datensatz als gleich, wenn Datei, Typ und Prompt übereinstimmen.)
Die Zusammenführung findet nur innerhalb von Rätseln mit der gleichen ID statt.
Über den Parameter `REQUIRE_FULL_EXTRACT` kann das Management angewiesen werden, zuerst alle Extraktionen aller Agenten abzuwarten, bevor das Rätsel zum Lösen weitergegeben wird.
Falls `REQUIRE_FULL_EXTRACT=true` so muss sichergestellt werden, dass auch alle Agenten (also für jeden Datensatz) irgendwann eine Extraktion bestimmt wird, andernfalls wartet das Management ewig.
Bei `REQUIRE_FULL_EXTRACT=false` bekommt der Agent zum Lösen ein Rätsel dann evtl. doppelt, jeweils mit weiteren Extraktionen.
Mit `REQUIRE_FULL_SOLVE` kann (ähnlich zu `REQUIRE_FULL_EXTRACT`) das Management angewiesen werden, von allen Agenten (in `AGENTS_SOLVE`) eine Lösung abzuwarten, bevor diese Lösungen (gebündelt) an den Gatekeeper zum Prüfen weitergeleitet werden.
Auch hier muss bei `REQUIRE_FULL_SOLVE=true` sichergestellt werden, dass alle Agenten auch (in `AGENTS_SOLVE`) irgendwann eine Lösung ausgeben.
Außerdem darf die Lösung von zwei Agenten nicht komplett identisch sein, d.h., wenn Lösung, Erklärung und benutzte Dateien einer Lösung gleich sind, so wird sie als die selbe Lösung angesehen und das Management würde z.B. auf eine *weitere* Lösung warten.
Ein Agent kann eine Antwort aber auch nicht nur stoppen, sondern auch für später aufheben (via <http://localhost:8080/docs/ums/agent/agent.html#BasicAgent.before_response>).
So kann ein Agent z.B. auf die Lösung einer Unteraufgabe warten und erst anschließend seine Antwort an das Management senden.
## Requests an das Management
Eigentlich sendet das Framework alle Request an das Management.
Sollte ein Agent beim Lösen einer Aufgabe weiter Unteraufgabe erstellen möchten, so kann <http://localhost:8080/docs/ums/agent/agent.html#BasicAgent.sub_riddle> benutzt werden.
In seltenen Fällen, möchte ein Agent aber vielleicht auf die Liste aller Nachrichten im Management zugreifen (Inhalte der Tabelle).
Dazu kann <http://localhost:8080/docs/ums/utils/request.html#ManagementRequest> benutzt werden.
## Typen und Schema
- <http://localhost:8080/docs/ums/utils/types.html>
- <http://localhost:8080/docs/ums/utils/schema.html>

View File

@ -1,83 +1,8 @@
# Template für das Agentensystem
# Calculator-Example
## Einstieg
0. Rechner vorbereiten
1. Docker Desktop installieren <https://docs.docker.com/desktop/>
2. Editor (IDE) installieren (freie Auswahl, Beispiele für <https://code.visualstudio.com/>)
1. Repository einrichten
1. Template laden `git clone -o UHH https://git.chai.uni-hamburg.de/UMS-Agenten/Agent-Template.git`
2. *Eigenes* Repository hinzufügen `git remote add UMS https://git.uni-muenster.de/example/my-group.git`
3. In *eigenes* Repository pushen `git push UMS master`
4. Später dann auch immer `git push UMS ...`, `git pull UMS ...`
5. Updates vom Template `git pull UHH master` (Achtung: Merge-Konflikt)
2. Lokale Umgebung (kann übersprungen werden, mach aber das Entwickeln netter; nur für VS Code)
- Python-Paket `src`: Eigene Implementierung
- Python-Paket `ums`: Agenten-Plattform ([Quelle](https://git.chai.uni-hamburg.de/UMS-Agenten/Agenten-Plattform/src/branch/master/ums))
- VS Code kann leider kein Autocomplete/ IntelliSense im Docker Container anbieten, daher müssen die Quellen auch auf dem Host verfügbar sein.
- VS Code erkennt das Verzeichnis `./src/` als Paket `src`
- Wir brauchen zusätzlich ein paar Abhängigkeiten und `ums`
1. Virtuelles Env. erstellen `python3 -m venv .`
2. Virtuelles Env. aktivieren `source ./bin/activate`
3. Pakete installieren `pip install requests fastapi pdoc` (evtl. später weitere, damit diese auch IntelliSense unterstützen)
4. In VS Code (in einer Python-Datei unten rechts) das Virtuelle Env. auswählen (`./bin/...`)
5. Verzeichnis `ums` aus dem Docker Container extrahieren:
```bash
docker create --name "management" "git.chai.uni-hamburg.de/ums-agenten/management:arm64" # oder :amd64
docker cp "management:/ums-agenten/plattform/ums/" ./ums/
docker rm "management"
```
- Virtuelles Env. und das Verzeichnis `./ums` werden von git ignoriert (siehe `./.gitignore`)
3. Agenten und Management starten
- Die Konfiguration des Managements und er verschiedenen Agenten erfolgt über die Datei `docker-compose.yml`
- Es ist sehr sinnvoll die Datei einmal durchzugehen und die Kommentare dort anzusehen.
1. Für jeden Container/ Service die Images prüfen und anpassen (`:arm64` or `:amd64`, siehe [&darr;](#docker-images))
2. `docker compose up` startet alle Container wie in der `docker-compose.yml` angegeben
- Anschließend hängt das Terminal an der Ausgabe der verschiedenen Container
- Fehler erscheinen dort im Terminal oder/ und in `./data/persist-*/ums.log`
3. Das Management kann über <http://localhost:8080> erreicht werden, es bietet:
- Dokumentation: <http://localhost:8080/docs/ums.html>
- Übersicht der Nachrichten zwischen Agenten und Management: <http://localhost:8080/app/table>
- Senden von Nachrichten/ Erstellen von Rätseln: <http://localhost:8080/app/new>
- Web API: <http://localhost:8080/api> (siehe auch <http://localhost:8080/docs/ums/utils/request.html>)
4. Im Ordner `src` ist ein sehr einfaches Agentensystem implementiert [&rarr; Beispiel: Rätsel & Agent](./Example.md)
5. Die Implementierung kann auf dem Beispiel aufbauen [&rarr; Implementierung](./Implement.md)
This is a simple example of an calculator agent system using the [Agents Platform](https://git.chai.uni-hamburg.de/UMS-Agenten/Agenten-Plattform) and based on [Agent Template](https://git.chai.uni-hamburg.de/UMS-Agenten/Agent-Template).
> **Generell gilt:**
> Die Images sind größtenteils neu.
> Auch das Management und Agenten-Framework wurde neu entworfen, d.h., es können (und werden) noch ein paar Käfer irgendwo lauern.
> Bugs also bitte melden und bei Problemen mit dem System nachfragen (magnus.bender@uni-hamburg.de).
Start by `docker compose up` and open web interface of management at <http://localhost:8080/>.
## Update Images
- Image für Management
- `docker compose pull` (aktualisiert neu)
- Images für Agenten
- `docker pull git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-arm64` (oder `:cpu-amd64`)
- `docker compose build`
## Riddle Example
## Docker Images
Es gibt unter <https://git.chai.uni-hamburg.de/UMS-Agenten/-/packages> viele verschiedene Docker Images.
Die Images stellen die vorbereitete Umgebung da.
Folgende Images sind verfügbar:
- `base-image`
- Basis für alle Agenten, beinhaltet eine Menge von relevanten Tools (Python und Pakete mit z.B. PyTorch, ...)
- Tags `:cpu-arm64 :cpu-amd64 :gpu-amd64`
(Die `cpu-*` Variante sind für das lokale Entwickeln und beinhalten keine NVIDIA CUDA Treiber, sind aber sonst identisch.
Die `gpu-*` Variate kann später auf dem Evaluationsserver ausgeführt werden, oder wenn man lokal einen geeignete GPU hat, das Image ist durch die Treiber sehr groß.
Die `*-arm64` Variante ist insb. für aktuelle Apple-Prozessoren gedacht.)
- Das Image wird als Basis für `base-agent` benutzt, muss also i.A. nicht direkt genutzt werden.
- `management`
- Das Image für den Management Container.
- Tags `:arm64, :amd64`
(Alles CPU Varianten, `arm64` ist wieder insb. für aktuelle Apple-Prozessoren und `amd64` für alle anderen.)
- Hier muss nichts angepasst werden. Der Container muss nur lokal und später auf dem Server laufen und stellt dann das Management für die einzelnen Agenten (auch wieder einzelne Container) bereit.
- `base-agent`
- In diesem Image sind die Agenten zu implementieren bzw. darauf aufzubauen.
- Tags `:cpu-arm64 :cpu-amd64 :gpu-amd64`
(Analog zu `base-image`)
- Dieses Repository bildet einen einfachen und beispielhaften Agenten ab und soll als Basis dienen.
Es wird im Laufe der Zeit sicherlich Updates der verschiedenen Images geben.
Aus diesem Grund gibt bei den Tags Suffixe wie z.B. `2024-10-04` mit dem Datum des Build eines Images.
Somit bleiben auch alte Versionen erreichbar, auch wenn am Ende die aktuelle Version ohne Suffix genutzt werden soll.

View File

@ -1,76 +1,43 @@
services:
# first the management
management:
# select the correct one base on platform this is running on
image: git.chai.uni-hamburg.de/ums-agenten/management:arm64
#image: git.chai.uni-hamburg.de/ums-agenten/management:amd64
ports:
# external (to host) http port
# open: http://localhost:8080/ !
- 8080:80
environment:
# limit number of trials for solving a riddle
- SOLUTION_MAX_TRIALS=5
# limit to prevent messages in never ending cycles
- MESSAGE_MAX_CONTACTS=100
# wait for all extraction agents to return results before solving a riddle
- REQUIRE_FULL_EXTRACT=true
# wait for all solve agents to return a solutions before giving to gatekeeper
- REQUIRE_FULL_SOLVE=true
# how to access management (the host name is the name of the service in this file)
- MANAGEMENT_URL=http://management
# *register* the agents to the management
- AGENTS_PROCESS=http://agent_extract:8000
- AGENTS_SOLVE=http://agent_solve:8000
- AGENTS_GATEKEEPER=http://agent_gatekeeper:8000
# divide multiple agents of same type by comma
#- AGENTS_PROCESS=http://agent_extract_1:8000,http://agent_extract_2:8000
volumes:
# all data is bind-mounted from ./data on the host into the containers
# the folder *share* is shared with all agents, it can be used to pass the data via files
- ./data/share/:/ums-agenten/share/
# the folder *persist* is different for each container and is used to store data permanently
- ./data/persist-management/:/ums-agenten/persist/
# afterwards the agents
agent_extract:
# this allow to do installs etc. in the docker image (a new image will be built on top of the provided one)
build:
context: .
dockerfile: Dockerfile
args:
# select the correct one base on platform this is running on
- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-arm64
#- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-amd64
#- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:gpu-amd64
image: git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-arm64
# image: git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-amd64
ports:
# this port is only for access from the host, the management can always use 8000
- 8081:8000
environment:
# python package:variable_name of the list of agents implemeted here
- AGENTS_LIST=src.extract.agent:AGENT_CLASSES
# tell the agent where the management is accessible
- MANAGEMENT_URL=http://management
volumes:
- ./data/share/:/ums-agenten/share/
- ./data/persist-extract/:/ums-agenten/persist/
# this is for development (s.t. the changes in ./src/ are directly applied)
- ./src/:/ums-agenten/project/src/:ro
# for development: will detect file changes and reload server with new source
entrypoint: bash -c "SERVE=true uvicorn ums.agent.main:app --host 0.0.0.0 --port 8000 --reload --reload-dir /ums-agenten/project/src/"
agent_solve:
build:
context: .
dockerfile: Dockerfile
args:
- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-arm64
#- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-amd64
#- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:gpu-amd64
image: git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-arm64
# image: git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-amd64
ports:
- 8082:8000
environment:
@ -83,13 +50,8 @@ services:
entrypoint: bash -c "SERVE=true uvicorn ums.agent.main:app --host 0.0.0.0 --port 8000 --reload --reload-dir /ums-agenten/project/src/"
agent_gatekeeper:
build:
context: .
dockerfile: Dockerfile
args:
- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-arm64
#- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-amd64
#- IMAGE_FROM=git.chai.uni-hamburg.de/ums-agenten/base-agent:gpu-amd64
image: git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-arm64
# image: git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-amd64
ports:
- 8083:8000
environment:

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
class CalculatorExtractVariables(ExtractTextAgent):
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):
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:
for line in f:
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)
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,45 +1,30 @@
# 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 = {}
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
@ -59,43 +44,14 @@ class SimpleSolveAgent(SolveAgent):
return RiddleSolution(solution="Error", explanation="Missing data or faulty expression")
try:
# using eval is a bad idea, but this is only for demonstration
# 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")
# 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]!")
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 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
]