Create simple calculator
This commit is contained in:
15
Dockerfile
15
Dockerfile
@ -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
|
88
Example.md
88
Example.md
@ -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 [↓](#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 [→ 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
|
||||
}
|
||||
```
|
61
Implement.md
61
Implement.md
@ -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 [↓](#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>
|
83
Readme.md
83
Readme.md
@ -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 [↓](#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 [→ Beispiel: Rätsel & Agent](./Example.md)
|
||||
5. Die Implementierung kann auf dem Beispiel aufbauen [→ 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.
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
]
|
@ -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
|
||||
]
|
@ -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
|
||||
]
|
Reference in New Issue
Block a user