diff --git a/trustgraph-flow/trustgraph/gateway/flow_endpoint.py b/trustgraph-flow/trustgraph/gateway/flow_endpoint.py new file mode 100644 index 00000000..8c69af76 --- /dev/null +++ b/trustgraph-flow/trustgraph/gateway/flow_endpoint.py @@ -0,0 +1,75 @@ + +import asyncio +from aiohttp import web +import uuid +import logging + +logger = logging.getLogger("flow-endpoint") +logger.setLevel(logging.INFO) + +class FlowEndpoint: + + def __init__(self, endpoint_path, auth, requestors): + + self.path = endpoint_path + + self.auth = auth + self.operation = "service" + + self.requestors = requestors + + async def start(self): + pass + + def add_routes(self, app): + + pass + app.add_routes([ + web.post(self.path, self.handle), + ]) + + async def handle(self, request): + + print(request.path, "...") + + flow_id = request.match_info['flow'] + kind = request.match_info['kind'] + k = (flow_id, kind) + + if k not in self.requestors: + raise web.HTTPBadRequest() + + requestor = self.requestors[k] + + try: + ht = request.headers["Authorization"] + tokens = ht.split(" ", 2) + if tokens[0] != "Bearer": + return web.HTTPUnauthorized() + token = tokens[1] + except: + token = "" + + if not self.auth.permitted(token, self.operation): + return web.HTTPUnauthorized() + + try: + + data = await request.json() + + print(data) + + async def responder(x, fin): + print(x) + + resp = await requestor.process(data, responder) + + return web.json_response(resp) + + except Exception as e: + logging.error(f"Exception: {e}") + + return web.json_response( + { "error": str(e) } + ) + diff --git a/trustgraph-flow/trustgraph/gateway/requestor.py b/trustgraph-flow/trustgraph/gateway/requestor.py index 63395203..ec70812b 100644 --- a/trustgraph-flow/trustgraph/gateway/requestor.py +++ b/trustgraph-flow/trustgraph/gateway/requestor.py @@ -38,6 +38,13 @@ class ServiceRequestor: await self.pub.start() await self.sub.start() + async def stop(self): + + print("STOPPING") + + await self.pub.stop() + await self.sub.stop() + def to_request(self, request): raise RuntimeError("Not defined") diff --git a/trustgraph-flow/trustgraph/gateway/service.py b/trustgraph-flow/trustgraph/gateway/service.py index d7df3240..975b6968 100755 --- a/trustgraph-flow/trustgraph/gateway/service.py +++ b/trustgraph-flow/trustgraph/gateway/service.py @@ -17,6 +17,8 @@ from aiohttp import web import logging import os import base64 +import uuid +import json import pulsar from prometheus_client import start_http_server @@ -26,7 +28,9 @@ from .. log_level import LogLevel from . serialize import to_subgraph from . running import Running -#from . text_completion import TextCompletionRequestor +from .. schema import ConfigPush, config_push_queue + +from . text_completion import TextCompletionRequestor #from . prompt import PromptRequestor #from . graph_rag import GraphRagRequestor #from . document_rag import DocumentRagRequestor @@ -52,7 +56,10 @@ from . mux import MuxEndpoint from . metrics import MetricsEndpoint from . endpoint import ServiceEndpoint +from . flow_endpoint import FlowEndpoint from . auth import Authenticator +from .. base import Subscriber +from .. base import Consumer logger = logging.getLogger("api") logger.setLevel(logging.INFO) @@ -68,11 +75,6 @@ class Api: def __init__(self, **config): - self.app = web.Application( - middlewares=[], - client_max_size=256 * 1024 * 1024 - ) - self.port = int(config.get("port", default_port)) self.timeout = int(config.get("timeout", default_timeout)) self.pulsar_host = config.get("pulsar_host", default_pulsar_host) @@ -217,6 +219,11 @@ class Api: endpoint_path = "/api/v1/flow", auth=self.auth, requestor = self.services["flow"], ), + FlowEndpoint( + endpoint_path = "/api/v1/flow/{flow}/{kind}", + auth=self.auth, + requestors = self.services, + ), # ServiceEndpoint( # endpoint_path = "/api/v1/encyclopedia", auth=self.auth, # requestor = self.services["encyclopedia"], @@ -273,10 +280,102 @@ class Api: ), ] - for ep in self.endpoints: - ep.add_routes(self.app) + self.flows = {} + +# self.services = {} + + async def on_config(self, msg, proc, flow): + + try: + + v = msg.value() + + print(f"Config version", v.version) + + if "flows" in v.config: + + flows = v.config["flows"] + + wanted = list(flows.keys()) + current = list(self.flows.keys()) + + for k in wanted: + if k not in current: + self.flows[k] = json.loads(flows[k]) + await self.start_flow(k, self.flows[k]) + + for k in current: + if k not in wanted: + await self.stop_flow(k, self.flows[k]) + del self.flows[k] + + except Exception as e: + print(f"Exception: {e}", flush=True) + + async def start_flow(self, id, flow): + print("Start flow", id) + intf = flow["interfaces"] + + if "text-completion" in intf: + k = (id, "text-completion") + if k in self.services: + await self.services[k].stop() + del self.services[k] + + self.services[k] = TextCompletionRequestor( + pulsar_client=self.pulsar_client, timeout=self.timeout, + request_queue = intf["text-completion"]["request"], + response_queue = intf["text-completion"]["response"], + consumer = f"api-gateway-{id}-text-completion-request", + subscriber = f"api-gateway-{id}-text-completion-request", + auth = self.auth, + ) + await self.services[k].start() + + async def stop_flow(self, id, flow): + print("Stop flow", id) + intf = flow["interfaces"] + + if "text-completion" in intf: + k = (id, "text-completion") + if k in self.services: + await self.services[k].stop() + del self.services[k] + + async def config_loader(self): + + async with asyncio.TaskGroup() as tg: + + id = str(uuid.uuid4()) + + self.config_cons = Consumer( + taskgroup = tg, + flow = None, + client = self.pulsar_client, + subscriber = f"gateway-{id}", + topic = config_push_queue, + schema = ConfigPush, + handler = self.on_config, + start_of_messages = True, + ) + + await self.config_cons.start() + + print("Waiting...") + + print("Config consumer done. :/") async def app_factory(self): + + self.app = web.Application( + middlewares=[], + client_max_size=256 * 1024 * 1024 + ) + + asyncio.create_task(self.config_loader()) + + for ep in self.endpoints: + ep.add_routes(self.app) for ep in self.endpoints: await ep.start() diff --git a/trustgraph-flow/trustgraph/gateway/text_completion.py b/trustgraph-flow/trustgraph/gateway/text_completion.py index ec84e5d6..3c6d1c38 100644 --- a/trustgraph-flow/trustgraph/gateway/text_completion.py +++ b/trustgraph-flow/trustgraph/gateway/text_completion.py @@ -1,20 +1,23 @@ from .. schema import TextCompletionRequest, TextCompletionResponse -from .. schema import text_completion_request_queue -from .. schema import text_completion_response_queue from . endpoint import ServiceEndpoint from . requestor import ServiceRequestor class TextCompletionRequestor(ServiceRequestor): - def __init__(self, pulsar_client, timeout, auth): + def __init__( + self, pulsar_client, request_queue, response_queue, timeout, auth, + consumer, subscriber, + ): super(TextCompletionRequestor, self).__init__( pulsar_client=pulsar_client, - request_queue=text_completion_request_queue, - response_queue=text_completion_response_queue, + request_queue=request_queue, + response_queue=response_queue, request_schema=TextCompletionRequest, response_schema=TextCompletionResponse, + subscription = subscriber, + consumer_name = consumer, timeout=timeout, )