From 5e0945f9ae8fd156bda976ee79788eb0170685b0 Mon Sep 17 00:00:00 2001 From: KIMB-technologies Date: Wed, 9 Oct 2024 19:04:19 +0200 Subject: [PATCH] Accept Messages --- ums/__cli__.py | 9 ------ ums/__main__.py | 41 ++++++++++++++++++++++++++ ums/management/db.py | 49 +++++--------------------------- ums/management/main.py | 52 +++++++++++++++------------------ ums/management/process.py | 44 ++++++++++++++++++++++++++++ ums/utils/__init__.py | 8 ++++-- ums/utils/request.py | 45 +++++++++++++++++++++++++++++ ums/utils/types.py | 60 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 226 insertions(+), 82 deletions(-) delete mode 100644 ums/__cli__.py create mode 100644 ums/__main__.py create mode 100644 ums/management/process.py create mode 100644 ums/utils/request.py diff --git a/ums/__cli__.py b/ums/__cli__.py deleted file mode 100644 index bc35628..0000000 --- a/ums/__cli__.py +++ /dev/null @@ -1,9 +0,0 @@ -# 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/__main__.py new file mode 100644 index 0000000..b3f2896 --- /dev/null +++ b/ums/__main__.py @@ -0,0 +1,41 @@ +# 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 + + +if __name__ == "__main__": + + from ums.utils import AgentMessage, RiddleData, RiddleDataType, RiddleSolution, ManagementRequest + + ex = AgentMessage( + id="ex1", + riddle={ + "context":"Example 1", + "question":"Get the name of the person." + }, + data=[ + RiddleData( + type=RiddleDataType.TEXT, + file_plain="./cv.txt" + ) + ] + ) + ex.status.extract.required = False + + ex.solution = RiddleSolution( + solution="Otto", + explanation="Written in line 6 after 'Name:'" + ) + + mr = ManagementRequest("localhost") + + print( mr.send_message(ex) ) + + print( mr.get_status(20)) + \ No newline at end of file diff --git a/ums/management/db.py b/ums/management/db.py index c205787..4bd8c19 100644 --- a/ums/management/db.py +++ b/ums/management/db.py @@ -15,44 +15,9 @@ from datetime import datetime from threading import Lock from typing import Generator -from pydantic import validate_call, BaseModel +from pydantic import validate_call -from ums.utils import PERSIST_PATH, AgentMessage - -class RowObject(BaseModel): - """ - Object representing a database row. - """ - - count : int - """ - The count (primary key) of the item. - """ - - sender : str - """ - The sender of the message. - """ - - recipient : str - """ - The recipient of the message - """ - - time : int - """ - The time (unix timestamp) the message was received/ sent. - """ - - message : AgentMessage - """ - The message received/ sent. - """ - - processed : bool - """ - Did the management process the message, i.e., did the tasks necessary for this message (mostly only relevant for received messages). - """ +from ums.utils import PERSIST_PATH, AgentMessage, MessageDbRow class DB(): @@ -117,7 +82,7 @@ class DB(): finally: self.db_lock.release() - def __iter__(self) -> Generator[RowObject, None, None]: + def __iter__(self) -> Generator[MessageDbRow, None, None]: yield from self.iterate() @validate_call @@ -126,7 +91,7 @@ class DB(): processed:bool|None=None, time_after:int|None=None, time_before:int|None=None, limit:int=20, offset:int=0, _count_only:bool=False - ) -> Generator[RowObject|int, None, None]: + ) -> Generator[MessageDbRow|int, None, None]: where = [] params = { @@ -177,8 +142,8 @@ class DB(): kwargs['_count_only'] = True return next(self.iterate(**kwargs)) - def _create_row_object(self, row:sqlite3.Row) -> RowObject: - return RowObject( + def _create_row_object(self, row:sqlite3.Row) -> MessageDbRow: + return MessageDbRow( count=row['count'], sender=row['sender'], recipient=row['recipient'], @@ -187,7 +152,7 @@ class DB(): processed=row['processed'] ) - def by_count(self, count:int) -> RowObject|None: + def by_count(self, count:int) -> MessageDbRow|None: with self.db: try: return self._create_row_object( diff --git a/ums/management/main.py b/ums/management/main.py index b04cd41..84f0a59 100644 --- a/ums/management/main.py +++ b/ums/management/main.py @@ -12,16 +12,17 @@ import os from datetime import datetime -from fastapi import FastAPI, Request +from fastapi import FastAPI, Request, BackgroundTasks, HTTPException from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates from jinja2.runtime import Undefined as JinjaUndefined from ums.management.interface import Interface -from ums.management.db import DB +from ums.management.db import DB, MessageDbRow +from ums.management.process import MessageProcessor -from ums.utils import AgentMessage, RiddleData, RiddleDataType, RiddleSolution, TEMPLATE_PATH +from ums.utils import AgentMessage, AgentResponse, TEMPLATE_PATH class WebMain(): @@ -32,6 +33,7 @@ class WebMain(): self._init_templates() self.db = DB() + self.msg_process = MessageProcessor(self.db) self._add_routes() self._add_routers() @@ -75,33 +77,25 @@ class WebMain(): 'index.html', {"request" : request} ) + + @self.app.post("/message", summary="Send a message to the management") + def message(request: Request, message:AgentMessage, background_tasks: BackgroundTasks) -> AgentResponse: + + receiver = request.headers['host'] + if ':' in receiver: + receiver = receiver[:receiver.rindex(':')] + + sender = request.headers['x-forwarded-for'] + + return self.msg_process.new_message(sender, receiver, message, background_tasks) - @self.app.get("/test", summary="Test") - def huhu(request: Request) -> AgentMessage: - ex = AgentMessage( - id="ex1", - riddle={ - "context":"Example 1", - "question":"Get the name of the person." - }, - data=[ - RiddleData( - type=RiddleDataType.TEXT, - file_plain="./cv.txt" - ) - ] - ) - ex.status.extract.required = False - - ex.solution = RiddleSolution( - solution="Otto", - explanation="Written in line 6 after 'Name:'" - ) - - ins_count = self.db.add_message('from', 'to', ex) - self.db.set_processed(ins_count) - - return ex + @self.app.get("/status", summary="Get status of a message") + def status(count:int) -> MessageDbRow: + msg = self.db.by_count(count) + if msg is None: + raise HTTPException(status_code=404, detail="Message not found") + + return msg if __name__ == "ums.management.main" and os.environ.get('SERVE', 'false') == 'true': main = WebMain() diff --git a/ums/management/process.py b/ums/management/process.py new file mode 100644 index 0000000..3cb1f29 --- /dev/null +++ b/ums/management/process.py @@ -0,0 +1,44 @@ +# 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 fastapi import BackgroundTasks + +from ums.management.db import DB + +from ums.utils import AgentMessage, AgentResponse + +class MessageProcessor(): + + def __init__(self, db:DB): + self.db = db + + def new_message(self, + sender:str, receiver:str, message:AgentMessage, + background_tasks: BackgroundTasks + ) -> AgentResponse: + + try: + db_count = self.db.add_message(sender, receiver, message) + background_tasks.add_task(self._process_message, db_count) + + return AgentResponse( + count=db_count, + msg="Added message to queue" + ) + except Exception as e: + return AgentResponse( + count=-1, + error=True, + error_msg=str(e) + ) + + def _process_message(self, count:int): + # TODO !!! + self.db.set_processed(count=count, processed=True) \ No newline at end of file diff --git a/ums/utils/__init__.py b/ums/utils/__init__.py index 980f90b..820c931 100644 --- a/ums/utils/__init__.py +++ b/ums/utils/__init__.py @@ -15,7 +15,11 @@ from ums.utils.types import ( RiddleSolution, RiddleData, RiddleDataType, - RiddleStatus + RiddleStatus, + AgentResponse, + MessageDbRow ) -from ums.utils.const import * \ No newline at end of file +from ums.utils.const import * + +from ums.utils.request import ManagementRequest \ No newline at end of file diff --git a/ums/utils/request.py b/ums/utils/request.py new file mode 100644 index 0000000..c40b536 --- /dev/null +++ b/ums/utils/request.py @@ -0,0 +1,45 @@ +# 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 requests + +from ums.utils.types import AgentMessage, AgentResponse, MessageDbRow + +class RequestException(Exception): + pass + +class ManagementRequest(): + + def __init__(self, hostname:str, port:int=80): + self.url = "http://{hostname}:{port}".format(hostname=hostname, port=port) + + def get_status(self, count:int) -> MessageDbRow: + r = requests.get( + "{}/status".format(self.url), + params={"count": count} + ) + + if r.status_code == 200: + return MessageDbRow.model_validate_json(r.text) + else: + raise RequestException(str(r.text)+str(r.headers)) + + + def send_message(self, message:AgentMessage) -> AgentResponse: + r = requests.post( + "{}/message".format(self.url), + data=message.model_dump_json(), + headers={"accept" : "application/json", "content-type" : "application/json"} + ) + + if r.status_code == 200: + return AgentResponse.model_validate_json(r.text) + else: + return AgentResponse(count=-1, error=True, error_msg=str(r.text)+str(r.headers)) \ No newline at end of file diff --git a/ums/utils/types.py b/ums/utils/types.py index a414f55..5808b79 100644 --- a/ums/utils/types.py +++ b/ums/utils/types.py @@ -289,4 +289,64 @@ class AgentMessage(RiddleInformation): status: RiddleStatus = RiddleStatus() """ The status of the riddle. + """ + +class AgentResponse(RiddleInformation): + """ + Returned by the management when receiving an `AgentMessage`. + """ + + count : int + """ + The count of the message (overall numeric id). + """ + + msg: str|None = None + """ + An additional message. + """ + + error: bool = False + """ + If an error occurred. + """ + + error_msg: str|None = None + """ + Error message (if `error` ) + """ + +class MessageDbRow(BaseModel): + """ + Object representing a database row. + """ + + count : int + """ + The count (primary key) of the item. + """ + + sender : str + """ + The sender of the message. + """ + + recipient : str + """ + The recipient of the message + """ + + time : int + """ + The time (unix timestamp) the message was received/ sent. + """ + + message : AgentMessage + """ + The message received/ sent. + """ + + processed : bool + """ + Did the management process the message, i.e., did the tasks necessary for this message (mostly only relevant for received messages). """ \ No newline at end of file