From 533b9fed6d26255ae00d794886a00e112694af11 Mon Sep 17 00:00:00 2001 From: KIMB-technologies Date: Tue, 29 Oct 2024 23:57:04 +0100 Subject: [PATCH] Agent should work --- .gitea/workflows/docker-build.yml | 4 +- Readme.md | 1 + build-agent.sh | 50 ++++++ docker-agent/Dockerfile | 24 ++- docker-agent/requirements-frozen.txt | 16 ++ docker-agent/requirements.txt | 15 ++ docker-compose.yml | 26 ++- docker-mgmt/supervisor.conf | 2 + ums/agent/__init__.py | 11 +- ums/agent/agent.py | 243 +++++++++++++++++++++++++++ ums/agent/main.py | 65 +++++++ ums/agent/process.py | 95 +++++++++++ ums/example/__init__.py | 9 + ums/{ => example}/__main__.py | 2 + ums/example/example.py | 71 ++++++++ ums/management/process.py | 15 +- ums/utils/__init__.py | 19 ++- ums/utils/const.py | 8 +- ums/utils/functions.py | 10 ++ vars.sh | 1 + web/public/static/main.css | 8 + web/public/static/new.js | 9 + web/public/static/table.js | 9 + web/templates/modal.html | 9 + 24 files changed, 703 insertions(+), 19 deletions(-) create mode 100755 build-agent.sh create mode 100644 docker-agent/requirements-frozen.txt create mode 100644 docker-agent/requirements.txt create mode 100644 ums/agent/agent.py create mode 100644 ums/agent/main.py create mode 100644 ums/agent/process.py create mode 100644 ums/example/__init__.py rename ums/{ => example}/__main__.py (93%) create mode 100644 ums/example/example.py diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index d5c4c17..9b55c3e 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -24,8 +24,8 @@ jobs: - name: Build the Management run: bash ./build-mgmt.sh -no-updates - #- name: Build the Agent - # run: bash ./build-agent.sh -no-updates + - name: Build the Agent + run: bash ./build-agent.sh -no-updates - name: Docker login uses: docker/login-action@v3 diff --git a/Readme.md b/Readme.md index cd16038..21f33c2 100644 --- a/Readme.md +++ b/Readme.md @@ -12,6 +12,7 @@ - `./docker-agent/` - `./ums/agent/` +- `./build-agent.sh` ## Development diff --git a/build-agent.sh b/build-agent.sh new file mode 100755 index 0000000..44e9e83 --- /dev/null +++ b/build-agent.sh @@ -0,0 +1,50 @@ +#/bin/bash + +# Agenten Plattform +# +# (c) 2024 Magnus Bender +# Institute of Humanities-Centered Artificial Intelligence (CHAI) +# Universitaet Hamburg +# https://www.chai.uni-hamburg.de/~bender +# +# source code released under the terms of GNU Public License Version 3 +# https://www.gnu.org/licenses/gpl-3.0.txt + +# https://stackoverflow.com/a/4774063 +SCRIPTPATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" + +source "$SCRIPTPATH/vars.sh" + +requirements="requirements-frozen.txt" +if [ "$1" != "-no-updates" ]; then + echo "Update the depencendies in requirements.txt? [may break app] (y/n)" + read newlockfile + if [ "$newlockfile" == "y" ]; then + requirements="requirements.txt" + fi; +fi; + +for platform in $PLATFORMS; do + if [ "$platform" == "gpu" ]; then + platform="amd64" + tag="gpu-amd64" + else + tag="cpu-$platform" + fi; + + docker build \ + --pull \ + --platform "linux/$platform" \ + --file "$SCRIPTPATH/docker-agent/Dockerfile" \ + --build-arg FROM_IMAGE="$IMAGE_REGISTRY/$IMAGE_OWNER/$IMAGE_AGENT_BASE:$tag" \ + --build-arg PIP_REQ_FILE="$requirements" \ + --tag "$IMAGE_REGISTRY/$IMAGE_OWNER/$IMAGE_NAME_AGENT:$tag" \ + "$SCRIPTPATH" +done; + +if [ "$requirements" == "requirements.txt" ]; then + # extract requirements-frozen.txt + cid=$(docker create "$IMAGE_REGISTRY/$IMAGE_OWNER/$IMAGE_NAME_AGENT:cpu-arm64") + docker cp "$cid:/ums-agenten/requirements-frozen.txt" "$SCRIPTPATH/docker-agent/requirements-frozen.txt" + docker rm "$cid" +fi; diff --git a/docker-agent/Dockerfile b/docker-agent/Dockerfile index 0470cd9..c66a2c4 100644 --- a/docker-agent/Dockerfile +++ b/docker-agent/Dockerfile @@ -8,8 +8,28 @@ # source code released under the terms of GNU Public License Version 3 # https://www.gnu.org/licenses/gpl-3.0.txt -ARG FROM_IMAGE= - +ARG FROM_IMAGE FROM $FROM_IMAGE +ARG PIP_REQ_FILE +USER root +RUN mkdir -p /ums-agenten/plattform/ && mkdir -p /ums-agenten/persist/ + +COPY ./docker-agent/$PIP_REQ_FILE /ums-agenten/requirements.txt +RUN pip3 install --break-system-packages --no-cache-dir -r /ums-agenten/requirements.txt \ + && pip3 freeze -q -r /ums-agenten/requirements.txt > /ums-agenten/requirements-frozen.txt + +# install the code of the repo +COPY ./docker-mgmt/setup.py /ums-agenten/plattform/ +RUN pip3 install --break-system-packages -e /ums-agenten/plattform/ + +COPY --chown=user:user ./ums/ /ums-agenten/plattform/ums/ +COPY --chown=user:user ./web/ /ums-agenten/plattform/web/ + +WORKDIR /ums-agenten/plattform/ums/ +RUN chown -R user:user /ums-agenten +USER user + +ENV SERVE=true +CMD ["/usr/local/bin/uvicorn", "ums.agent.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/docker-agent/requirements-frozen.txt b/docker-agent/requirements-frozen.txt new file mode 100644 index 0000000..12ad90e --- /dev/null +++ b/docker-agent/requirements-frozen.txt @@ -0,0 +1,16 @@ +# Agenten Plattform +# +# (c) 2024 Magnus Bender +# Institute of Humanities-Centered Artificial Intelligence (CHAI) +# Universitaet Hamburg +# https://www.chai.uni-hamburg.de/~bender +# source code released under the terms of GNU Public License Version 3 +# https://www.gnu.org/licenses/gpl-3.0.txt + +# non frozen dependecies, to use latest +requests==2.32.3 +fastapi==0.115.4 +uvicorn==0.32.0 +python-multipart==0.0.16 +## The following requirements were added by pip freeze: +# ... \ No newline at end of file diff --git a/docker-agent/requirements.txt b/docker-agent/requirements.txt new file mode 100644 index 0000000..cb3c900 --- /dev/null +++ b/docker-agent/requirements.txt @@ -0,0 +1,15 @@ +# Agenten Plattform +# +# (c) 2024 Magnus Bender +# Institute of Humanities-Centered Artificial Intelligence (CHAI) +# Universitaet Hamburg +# https://www.chai.uni-hamburg.de/~bender +# +# source code released under the terms of GNU Public License Version 3 +# https://www.gnu.org/licenses/gpl-3.0.txt + +# non frozen dependecies, to use latest +requests +fastapi +uvicorn[standard] +python-multipart \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e4c728e..0ce6269 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,10 +20,10 @@ services: - 8000:80 environment: - SOLUTION_MAX_TRIALS=5 - - MANAGEMENT_URL=http://management:8000 - - AGENTS_PROCESS=http://agent_process_1:3001,http://agent_process_2:3001 - - AGENTS_SOLVE=http://agent_solve_1:3001 - - AGENTS_GATEKEEPER=http://agent_gatekeeper_1:3001 + - MANAGEMENT_URL=http://management + - AGENTS_PROCESS=http://agent_all:8000 + - AGENTS_SOLVE=http://agent_all:8000 + - AGENTS_GATEKEEPER=http://agent_all:8000 volumes: - ./data/share/:/ums-agenten/share/ - ./data/persist-management/:/ums-agenten/persist/ @@ -33,3 +33,21 @@ services: # enable auto reloading (for development) entrypoint: bash -c "nginx; SERVE=true uvicorn ums.management.main:app --uds /tmp/uvicorn.sock --proxy-headers --reload" + agent_all: + image: git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-arm64 + #image: git.chai.uni-hamburg.de/ums-agenten/base-agent:cpu-amd64 + #image: git.chai.uni-hamburg.de/ums-agenten/base-agent:gpu-amd64 + ports: + - 8001:8000 + environment: + - AGENTS_LIST=ums.example.example:AGENT_CLASSES + - MANAGEMENT_URL=http://management + volumes: + - ./data/share/:/ums-agenten/share/ + - ./data/persist-all/:/ums-agenten/persist/ + # bind code from host to container (for development) + - ./ums/:/ums-agenten/plattform/ums/:ro + - ./web/:/ums-agenten/plattform/web/ + # enable auto reloading (for development) + entrypoint: bash -c "SERVE=true uvicorn ums.agent.main:app --host 0.0.0.0 --port 8000 --reload" + diff --git a/docker-mgmt/supervisor.conf b/docker-mgmt/supervisor.conf index 3278f04..cb1b68d 100644 --- a/docker-mgmt/supervisor.conf +++ b/docker-mgmt/supervisor.conf @@ -11,6 +11,8 @@ [supervisord] nodaemon=true user=root +logfile=/dev/stdout +logfile_maxbytes = 0 [program:setup] command=/bin/sh -c "chown user:user -R /ums-agenten" diff --git a/ums/agent/__init__.py b/ums/agent/__init__.py index bc35628..918a4b1 100644 --- a/ums/agent/__init__.py +++ b/ums/agent/__init__.py @@ -6,4 +6,13 @@ # https://www.chai.uni-hamburg.de/~bender # # source code released under the terms of GNU Public License Version 3 -# https://www.gnu.org/licenses/gpl-3.0.txt \ No newline at end of file +# https://www.gnu.org/licenses/gpl-3.0.txt + +from ums.agent.agent import ( + AgentCapability, + BasicAgent, + ExtractAgent, + ExtractAudioAgent, ExtractImageAgent, ExtractTextAgent, + SolveAgent, + GatekeeperAgent +) \ No newline at end of file diff --git a/ums/agent/agent.py b/ums/agent/agent.py new file mode 100644 index 0000000..319f65e --- /dev/null +++ b/ums/agent/agent.py @@ -0,0 +1,243 @@ + +import random + +from abc import abstractmethod, ABC +from enum import Enum +from typing import List, Callable + +from ums.utils import ( + RiddleInformation, AgentMessage, RiddleDataType, RiddleData, Riddle, + RiddleStatus, RiddleSolution, + logger +) + +class AgentCapability(Enum): + """ + The three different capabilities an agent can have. + """ + + EXTRACT="extract" + SOLVE="solve" + GATEKEEPER="gatekeeper" + + +class BasicAgent(ABC): + """ + A basic agent, each agent will be a subclass of this class. + """ + + @staticmethod + @abstractmethod + def agent_capability() -> AgentCapability: + """ + Represents the capabilities of this agent, for messages/ tasks of this capability, the `handle` method will be called. + """ + pass + + def __init__(self, message:AgentMessage, send_message:Callable[[AgentMessage], bool]): + self._send_message = send_message + self._sub_cnt = 0 + + self._message = message + self._response = message.model_copy(deep=True) + + self._do_response = False + + self._process() + self._respond() + + @abstractmethod + def _process(self): + pass + + def _respond(self): + send_it = lambda: self._send_message(self._response) + if self.before_response(self._response, send_it) and self._do_response: + send_it() + logger.debug(f"Response sent {self._response.id}") + else: + logger.debug(f"Stopped response {self._response.id}") + + def before_response(self, response:AgentMessage, send_it:Callable[[], None]) -> bool: + """ + This method is called before the response is sent. + If the method returns `False` no response will be sent. + Thus, by overwriting this method, a response can be prevented. + + The response to be sent is in `response` and `send_it` is a callable, which sends the response to the management if it gets called. + (Hence, one may stop sending the response and later call `send_it()` to send the response.) + """ + return True + + def message(self) -> AgentMessage: + """ + Get the message this agent object is working on. + """ + return self._message; + + def sub_riddle(self, + riddle:Riddle, data:List[RiddleData]=[], status:RiddleStatus=None + ) -> AgentMessage|bool: + """ + Create a new sub-riddle for solving details of the current riddle. + For the sub-riddle, give a `riddle` and optionally, a selection of `data` items (default none) and a `status` (default `ums.utils.types.RiddleStatus()`). + By changing a status, different steps can be (de-)selected. + + Return the message of the sub-riddle or `false` on error. + """ + + if status is None: + status = RiddleStatus() + + # new sub riddle id + self._sub_cnt += 1 + new_id = "{}-sub-{}.{}".format(self._message.id, self._sub_cnt, int(random.random()*100)) + + self._message.sub_ids.append(new_id) + self._response.sub_ids.append(new_id) + + # create the riddle's message + sub_msg = AgentMessage( + id=new_id, + riddle=riddle, + data=data, + status=status + ) + logger.debug(f"Created sub-riddle {sub_msg.id}") + + # send it + if self._send_message(sub_msg): + return sub_msg + else: + return False + + @abstractmethod + def handle(self, *args:RiddleInformation) -> RiddleInformation: + """ + Handle a single task of the agent, the arguments and return value depends on the actual task (see subclass)! + + **This is the method to implement!** + + The full message is available via `message()`, a sub riddle can be created with `sub_riddle()`. + """ + pass + +class ExtractAgent(BasicAgent): + """ + An extraction agent. + """ + + def agent_capability() -> AgentCapability: + return AgentCapability.EXTRACT + + @staticmethod + @abstractmethod + def extract_type() -> RiddleDataType: + """ + Represents the data this agent can process. + """ + pass + + def _process(self): + for i, data in enumerate(self._response.data): + if data.type == self.__class__.extract_type(): + logger.debug(f"Start extraction '{data.file_plain}'") + result = self.handle(data) + logger.debug(f"End extraction '{data.file_plain}' ('{result.file_extracted}')") + + if result.file_extracted is None: + logger.info(f"Riddle {self._response.id}: 'file_extracted' for data '{data.file_plain}' still empty after handling") + + self._response.data[i] = result + self._do_response = True + + self._response.status.extract.finished = True + + @abstractmethod + def handle(self, data:RiddleData) -> RiddleData: + """ + Process the item `data`, create extraction file and return `data` with populated `data.file_extracted`. + """ + +class ExtractTextAgent(ExtractAgent): + """ + An extraction agent for text, create a subclass for your agent. + """ + + def extract_type() -> RiddleDataType: + return RiddleDataType.TEXT + +class ExtractAudioAgent(ExtractAgent): + """ + An extraction agent for audio, create a subclass for your agent. + """ + + def extract_type() -> RiddleDataType: + return RiddleDataType.AUDIO + +class ExtractImageAgent(ExtractAgent): + """ + An extraction agent for images, create a subclass for your agent. + """ + + def extract_type() -> RiddleDataType: + return RiddleDataType.IMAGE + + +class SolveAgent(BasicAgent): + """ + A solve agent, create a subclass for your agent. + """ + + def agent_capability() -> AgentCapability: + return AgentCapability.SOLVE + + def _process(self): + logger.debug(f"Start solve: {self._response.id}") + solution = self.handle(self._response.riddle, self._response.data) + logger.debug(f"End solve: {self._response.id} ({solution.solution}, {solution.explanation})") + + if len(solution.solution) == 0 or len(solution.explanation) == 0: + logger.info(f"Riddle {self._response.id}: Empty solution/ explanation after handling") + + self._response.solution = solution + self._response.status.solve.finished = True + + self._do_response = True + + @abstractmethod + def handle(self, riddle:Riddle, data:RiddleData) -> RiddleSolution: + """ + Solve the `riddle` using `data` and return a solution. + """ + +class GatekeeperAgent(BasicAgent): + """ + A gatekeeper agent, create a subclass for your agent. + """ + + def agent_capability() -> AgentCapability: + return AgentCapability.GATEKEEPER + + def _process(self): + if self._response.solution is None: + self._response.solution = RiddleSolution(solution="", explanation="") + + logger.debug(f"Start validate: {self._response.id}") + solution = self.handle(self._response.solution, self._response.riddle) + logger.debug(f"End validate: {self._response.id} ({solution.review}, {solution.accepted})") + + if solution.review is None or len(solution.review) == 0: + logger.info(f"Riddle {self._response.id}: Empty review after handling") + + self._response.solution = solution + self._response.status.validate.finished = True + self._response.status.solved = solution.accepted + + self._do_response = True + + @abstractmethod + def handle(self, solution:RiddleSolution, riddle:Riddle) -> RiddleSolution: + """ + Check the `solution` of `riddle` and return solution with populated `solution.accepted` and `solution.review`. + """ \ No newline at end of file diff --git a/ums/agent/main.py b/ums/agent/main.py new file mode 100644 index 0000000..91938e3 --- /dev/null +++ b/ums/agent/main.py @@ -0,0 +1,65 @@ +# Agenten Plattform +# +# (c) 2024 Magnus Bender +# Institute of Humanities-Centered Artificial Intelligence (CHAI) +# Universitaet Hamburg +# https://www.chai.uni-hamburg.de/~bender +# +# source code released under the terms of GNU Public License Version 3 +# https://www.gnu.org/licenses/gpl-3.0.txt + +import os + +from fastapi import FastAPI, Request, BackgroundTasks +from fastapi.staticfiles import StaticFiles +from fastapi.responses import JSONResponse + +from ums.agent.process import MessageProcessor +from ums.utils import AgentMessage, AgentResponse, const + +class WebMain(): + + def __init__(self): + self.msg_process = MessageProcessor() + + self._init_app() + self._add_routes() + + def _init_app(self): + self.app = FastAPI( + title="Agenten Plattform", + description="Agenten Plattform – Agent", + openapi_url="/api/schema.json", + docs_url='/api', + redoc_url=None + ) + + self.app.mount( + "/static", + StaticFiles(directory=os.path.join(const.PUBLIC_PATH, 'static')), + name="static" + ) + self.app.mount( + "/docs", + StaticFiles(directory=os.path.join(const.PUBLIC_PATH, 'docs'), html=True), + name="docs" + ) + + def _add_routes(self): + + @self.app.get("/", response_class=JSONResponse, summary="Link list") + def index(): + return { + "title" : "Agenten Plattform – Agent", + "./message" : "Messaged from the Management", + "./api" : "API Overview", + "./docs" : "Documentation" + } + + @self.app.post("/message", summary="Send a message to this agent") + def message(request: Request, message:AgentMessage, background_tasks: BackgroundTasks) -> AgentResponse: + return self.msg_process.new_message(message, background_tasks) + +if __name__ == "ums.agent.main" and os.environ.get('SERVE', 'false') == 'true': + main = WebMain() + app = main.app \ No newline at end of file diff --git a/ums/agent/process.py b/ums/agent/process.py new file mode 100644 index 0000000..1930dc9 --- /dev/null +++ b/ums/agent/process.py @@ -0,0 +1,95 @@ +# Agenten Plattform +# +# (c) 2024 Magnus Bender +# Institute of Humanities-Centered Artificial Intelligence (CHAI) +# Universitaet Hamburg +# https://www.chai.uni-hamburg.de/~bender +# +# source code released under the terms of GNU Public License Version 3 +# https://www.gnu.org/licenses/gpl-3.0.txt + +import os, importlib +from typing import List + +import requests +from fastapi import BackgroundTasks + +from ums.agent.agent import BasicAgent, AgentCapability, ExtractAgent, SolveAgent, GatekeeperAgent +from ums.utils import AgentMessage, AgentResponse, logger + +class MessageProcessor(): + + MANAGEMENT_URL = os.environ.get('MANAGEMENT_URL', 'http://127.0.0.1:80').strip().strip('/') + AGENTS_LIST = os.environ.get('AGENTS_LIST', 'ums.example.example:AGENT_CLASSES').strip() + + def __init__(self): + self.counts = 0 + + module_name, var_name = self.AGENTS_LIST.split(':') + agents_module = importlib.import_module(module_name) + + self.agent_classes:List[BasicAgent] = getattr(agents_module, var_name) + self.extract_agents:List[ExtractAgent] = list(filter( + lambda ac: ac.agent_capability() == AgentCapability.EXTRACT, + self.agent_classes + )) + self.solve_agents:List[SolveAgent] = list(filter( + lambda ac: ac.agent_capability() == AgentCapability.SOLVE, + self.agent_classes + )) + self.gatekeeper_agents:List[GatekeeperAgent] = list(filter( + lambda ac: ac.agent_capability() == AgentCapability.GATEKEEPER, + self.agent_classes + )) + + def new_message(self, message:AgentMessage, background_tasks: BackgroundTasks) -> AgentResponse: + enqueued = False + + if message.status.extract.required and not message.status.extract.finished: + # send to extract agents + if len(self.extract_agents) > 0: + data_types = set( d.type for d in message.data ) + for ac in self.extract_agents: + if ac.extract_type() in data_types: + background_tasks.add_task(ac, message, self._send_message) + enqueued = True + + elif message.status.solve.required and not message.status.solve.finished: + # send to solve agents + if len(self.solve_agents) > 0: + for sa in self.solve_agents: + background_tasks.add_task(sa, message, self._send_message) + enqueued = True + + elif message.status.validate.required and not message.status.validate.finished: + # send to solve agents + if len(self.gatekeeper_agents) > 0: + for ga in self.gatekeeper_agents: + background_tasks.add_task(ga, message, self._send_message) + enqueued = True + + logger.debug( + ("Added to queue" if enqueued else "No agent found to queue message.") + + f"ID: {message.id} Count: {self.counts}" + ) + + self.counts += 1 + return AgentResponse( + count=self.counts-1, + msg="Added to queue" if enqueued else "", + error=not enqueued, + error_msg=None if enqueued else "No agent found to queue message." + ) + + def _send_message(self, message:AgentMessage) -> bool: + r = requests.post( + "{}/message".format(self.MANAGEMENT_URL), + data=message.model_dump_json(), + headers={"accept" : "application/json", "content-type" : "application/json"} + ) + + if r.status_code == 200: + return True + else: + logger.warning(f"Error sending message to management! {(r.text, r.headers)}") + return False \ No newline at end of file diff --git a/ums/example/__init__.py b/ums/example/__init__.py new file mode 100644 index 0000000..bc35628 --- /dev/null +++ b/ums/example/__init__.py @@ -0,0 +1,9 @@ +# Agenten Plattform +# +# (c) 2024 Magnus Bender +# Institute of Humanities-Centered Artificial Intelligence (CHAI) +# Universitaet Hamburg +# https://www.chai.uni-hamburg.de/~bender +# +# source code released under the terms of GNU Public License Version 3 +# https://www.gnu.org/licenses/gpl-3.0.txt \ No newline at end of file diff --git a/ums/__main__.py b/ums/example/__main__.py similarity index 93% rename from ums/__main__.py rename to ums/example/__main__.py index ec6f2c7..68fce6d 100644 --- a/ums/__main__.py +++ b/ums/example/__main__.py @@ -11,6 +11,8 @@ if __name__ == "__main__": + ## Example: Sending messages to management via python + from ums.utils import AgentMessage, RiddleData, RiddleDataType, RiddleSolution, ManagementRequest ex = AgentMessage( diff --git a/ums/example/example.py b/ums/example/example.py new file mode 100644 index 0000000..822856f --- /dev/null +++ b/ums/example/example.py @@ -0,0 +1,71 @@ +# Agenten Plattform +# +# (c) 2024 Magnus Bender +# Institute of Humanities-Centered Artificial Intelligence (CHAI) +# Universitaet Hamburg +# https://www.chai.uni-hamburg.de/~bender +# +# source code released under the terms of GNU Public License Version 3 +# https://www.gnu.org/licenses/gpl-3.0.txt + +from typing import Callable +from ums.agent import ExtractAudioAgent, ExtractImageAgent, ExtractTextAgent, SolveAgent, GatekeeperAgent + +from ums.utils.types import AgentMessage, Riddle, RiddleData, RiddleSolution, RiddleStatus + +""" + Examples for simple agents. + + Each agent is represented by its own class. The handling of tasks is done by `handle()` in each agent. + + Finally `AGENT_CLASSES` contains the classes of the agents in a list. Via environmental variables this list is specified to the ums.agent system. +""" + +class MyExtractAudioAgent(ExtractAudioAgent): + + def handle(self, data: RiddleData) -> RiddleData: + print("Audio Process:", data.file_plain) + return data + +class MyExtractImageAgent(ExtractImageAgent): + + def handle(self, data: RiddleData) -> RiddleData: + print("Image Process:", data.file_plain) + return data + +class MyExtractTextAgent(ExtractTextAgent): + + def before_response(self, response: AgentMessage, send_it: Callable[[], None]) -> bool: + print("The response will be:", response) + return True + + def handle(self, data: RiddleData) -> RiddleData: + print("Text Process:", data.file_plain) + return data + + +class MySolveAgent(SolveAgent): + + def handle(self, riddle: Riddle, data: RiddleData) -> RiddleSolution: + + if self.message().id == "test": + status = RiddleStatus() + status.extract.required = False + self.sub_riddle(riddle=Riddle(context="Haha", question="Blubber"), status=status) + + return RiddleSolution(solution="Huii", explanation="Blubb") + + +class MyGatekeeperAgent(GatekeeperAgent): + + def handle(self, solution: RiddleSolution, riddle: Riddle) -> RiddleSolution: + solution.accepted = True + solution.review = "Ok" + + return solution + +AGENT_CLASSES = [ + MyExtractAudioAgent, MyExtractImageAgent, MyExtractTextAgent, + MySolveAgent, + MyGatekeeperAgent +] diff --git a/ums/management/process.py b/ums/management/process.py index 6f9bbf0..0fc1904 100644 --- a/ums/management/process.py +++ b/ums/management/process.py @@ -15,7 +15,7 @@ import requests from fastapi import BackgroundTasks from ums.management.db import DB -from ums.utils import AgentMessage, AgentResponse +from ums.utils import AgentMessage, AgentResponse, logger class MessageProcessor(): @@ -41,11 +41,11 @@ class MessageProcessor(): self.management_name = self._get_name(self.MANAGEMENT_URL) if len(self.AGENTS_PROCESS) == 0: - print("Not Process Agent (AGENTS_PROCESS) found, this may be a problem!") + logger.warning(f"Not Process Agent (AGENTS_PROCESS) found, this may be a problem!") if len(self.AGENTS_SOLVE) == 0: - print("Not Solve Agent (AGENTS_SOLVE) found, this may be a problem!") + logger.warning(f"Not Solve Agent (AGENTS_SOLVE) found, this may be a problem!") if len(self.AGENTS_GATEKEEPER) == 0: - print("Not Gatekeeper Agent (AGENTS_GATEKEEPER) found, this may be a problem!") + logger.warning(f"Not Gatekeeper Agent (AGENTS_GATEKEEPER) found, this may be a problem!") def _get_name(self, url:str) -> str: m = re.match(r'^https?://([^:]*)(?::(\d+))?$', url) @@ -92,6 +92,11 @@ class MessageProcessor(): self._send_messages(self.AGENTS_GATEKEEPER, db_message.message) else: # all steps "done" + + # validate not required? (then solved will never be set to true, thus set it here) + if not db_message.message.status.validate.required: + db_message.message.status.solved = True + if db_message.message.status.solved: # yay, message is solved self.db.set_solution(count=count, solution=True); @@ -155,6 +160,6 @@ class MessageProcessor(): self.db.set_processed(db_count, processed=True) return True else: - print("Error sending message to:", recipient, (r.text, r.headers)) + logger.warning(f"Error sending message to: {recipient} {(r.text, r.headers)}") return False \ No newline at end of file diff --git a/ums/utils/__init__.py b/ums/utils/__init__.py index e5797e0..4201d10 100644 --- a/ums/utils/__init__.py +++ b/ums/utils/__init__.py @@ -8,6 +8,21 @@ # source code released under the terms of GNU Public License Version 3 # https://www.gnu.org/licenses/gpl-3.0.txt +from ums.utils.const import * + +import logging, os +if os.environ.get('SERVE', 'false') == 'true': + logging.basicConfig( + handlers=[ + logging.FileHandler(LOG_FILE), + logging.StreamHandler() + ], + level=LOG_LEVEL, + format='%(asctime)s %(levelname)s %(name)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) +logger = logging.getLogger('UMS Agenten') + from ums.utils.types import ( RiddleInformation, AgentMessage, @@ -20,8 +35,6 @@ from ums.utils.types import ( MessageDbRow ) -from ums.utils.const import * - from ums.utils.request import ManagementRequest -from ums.utils.functions import list_shared_data, list_shared_schema \ No newline at end of file +from ums.utils.functions import list_shared_data, list_shared_schema diff --git a/ums/utils/const.py b/ums/utils/const.py index 78e53c7..ac21b03 100644 --- a/ums/utils/const.py +++ b/ums/utils/const.py @@ -13,9 +13,13 @@ See the content ... """ -import os +import os, logging BASE_PATH = '/ums-agenten' SHARE_PATH = os.path.join(BASE_PATH, 'share') PERSIST_PATH = os.path.join(BASE_PATH, 'persist') -TEMPLATE_PATH = os.path.join(BASE_PATH, 'plattform', 'web', 'templates') \ No newline at end of file +PUBLIC_PATH = os.path.join(BASE_PATH, 'plattform', 'web', 'public') +TEMPLATE_PATH = os.path.join(BASE_PATH, 'plattform', 'web', 'templates') + +LOG_FILE = os.path.join(PERSIST_PATH, 'ums.log') +LOG_LEVEL = logging.INFO \ No newline at end of file diff --git a/ums/utils/functions.py b/ums/utils/functions.py index f7d0978..84cdd7d 100644 --- a/ums/utils/functions.py +++ b/ums/utils/functions.py @@ -1,3 +1,13 @@ +# Agenten Plattform +# +# (c) 2024 Magnus Bender +# Institute of Humanities-Centered Artificial Intelligence (CHAI) +# Universitaet Hamburg +# https://www.chai.uni-hamburg.de/~bender +# +# source code released under the terms of GNU Public License Version 3 +# https://www.gnu.org/licenses/gpl-3.0.txt + import os from typing import List, Callable diff --git a/vars.sh b/vars.sh index 78fe87d..ab63d8a 100755 --- a/vars.sh +++ b/vars.sh @@ -13,5 +13,6 @@ IMAGE_REGISTRY="git.chai.uni-hamburg.de" IMAGE_OWNER="ums-agenten" IMAGE_NAME_AGENT="base-agent" +IMAGE_AGENT_BASE="base-image" IMAGE_NAME_MGMT="management" PLATFORMS="amd64 arm64 gpu" diff --git a/web/public/static/main.css b/web/public/static/main.css index 40327f4..d500dcb 100644 --- a/web/public/static/main.css +++ b/web/public/static/main.css @@ -1,3 +1,11 @@ +/** Agenten Plattform + (c) 2024 Magnus Bender + Institute of Humanities-Centered Artificial Intelligence (CHAI) + Universitaet Hamburg + https://www.chai.uni-hamburg.de/~bender + source code released under the terms of GNU Public License Version 3 + https://www.gnu.org/licenses/gpl-3.0.txt +**/ .value_filter { width: 150px; diff --git a/web/public/static/new.js b/web/public/static/new.js index 5d91dbd..b0277a6 100644 --- a/web/public/static/new.js +++ b/web/public/static/new.js @@ -1,3 +1,12 @@ +/** Agenten Plattform + (c) 2024 Magnus Bender + Institute of Humanities-Centered Artificial Intelligence (CHAI) + Universitaet Hamburg + https://www.chai.uni-hamburg.de/~bender + source code released under the terms of GNU Public License Version 3 + https://www.gnu.org/licenses/gpl-3.0.txt +**/ + var fileLast = 0; function renderFile(cnt, type="", file_extracted="", file_plain=""){ var template = $("#filesTemplate").html(); diff --git a/web/public/static/table.js b/web/public/static/table.js index 8446f84..b491c61 100644 --- a/web/public/static/table.js +++ b/web/public/static/table.js @@ -1,3 +1,12 @@ +/** Agenten Plattform + (c) 2024 Magnus Bender + Institute of Humanities-Centered Artificial Intelligence (CHAI) + Universitaet Hamburg + https://www.chai.uni-hamburg.de/~bender + source code released under the terms of GNU Public License Version 3 + https://www.gnu.org/licenses/gpl-3.0.txt +**/ + function pagination_link(name, value){ let link_args = db_args; if (name && value){ diff --git a/web/templates/modal.html b/web/templates/modal.html index 7ac4c2d..d8beb26 100644 --- a/web/templates/modal.html +++ b/web/templates/modal.html @@ -1,3 +1,12 @@ +{#- +Agenten Plattform + (c) 2024 Magnus Bender + Institute of Humanities-Centered Artificial Intelligence (CHAI) + Universitaet Hamburg + https://www.chai.uni-hamburg.de/~bender + source code released under the terms of GNU Public License Version 3 + https://www.gnu.org/licenses/gpl-3.0.txt +-#}