Agent should work
This commit is contained in:
@ -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
|
||||
# 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
|
||||
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')
|
||||
TEMPLATE_PATH = os.path.join(BASE_PATH, 'plattform', 'web', 'templates')
|
||||
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
|
||||
|
Reference in New Issue
Block a user