Agent should work
This commit is contained in:
@ -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
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
- `./docker-agent/`
|
||||
- `./ums/agent/`
|
||||
- `./build-agent.sh`
|
||||
|
||||
|
||||
## Development
|
||||
|
50
build-agent.sh
Executable file
50
build-agent.sh
Executable file
@ -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;
|
@ -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"]
|
16
docker-agent/requirements-frozen.txt
Normal file
16
docker-agent/requirements-frozen.txt
Normal file
@ -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:
|
||||
# ...
|
15
docker-agent/requirements.txt
Normal file
15
docker-agent/requirements.txt
Normal file
@ -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
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
@ -7,3 +7,12 @@
|
||||
#
|
||||
# source code released under the terms of GNU Public License Version 3
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
|
||||
from ums.agent.agent import (
|
||||
AgentCapability,
|
||||
BasicAgent,
|
||||
ExtractAgent,
|
||||
ExtractAudioAgent, ExtractImageAgent, ExtractTextAgent,
|
||||
SolveAgent,
|
||||
GatekeeperAgent
|
||||
)
|
243
ums/agent/agent.py
Normal file
243
ums/agent/agent.py
Normal file
@ -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`.
|
||||
"""
|
65
ums/agent/main.py
Normal file
65
ums/agent/main.py
Normal file
@ -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
|
95
ums/agent/process.py
Normal file
95
ums/agent/process.py
Normal file
@ -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
|
9
ums/example/__init__.py
Normal file
9
ums/example/__init__.py
Normal file
@ -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
|
@ -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(
|
71
ums/example/example.py
Normal file
71
ums/example/example.py
Normal file
@ -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
|
||||
]
|
@ -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
|
||||
|
@ -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
|
@ -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')
|
||||
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
|
@ -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
|
||||
|
1
vars.sh
1
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"
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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){
|
||||
|
@ -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
|
||||
-#}
|
||||
<div class="modal" tabindex="-1" id="message_sent">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
Reference in New Issue
Block a user