2. Advanced services#

This tutorial demonstrates various configuration options for services.

For more information, see API ref.

[1]:
# installing dependencies
%pip install -q chatsky
Note: you may need to restart the kernel to use updated packages.
[2]:
import logging
import sys
from importlib import reload

from chatsky import Context, Pipeline, BaseProcessing
from chatsky.core.service import Service
from chatsky.utils.testing.common import (
    check_happy_path,
    is_interactive_mode,
)

from chatsky.utils.testing.toy_script import TOY_SCRIPT_KWARGS, HAPPY_PATH

reload(logging)
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="")
logger = logging.getLogger(__name__)

Intro#

In the previous tutorial we used a function as a Service. Under the hood a function is converted to a Service object with the function as its handler argument.

The Service model has other arguments that modify its execution.

Service Arguments#

  • handler - Function or BaseProcessing.

  • before_handler - a list of functions that run before the service. You can read more about the handlers in this tutorial.

  • after_handler - a list of functions that run after the service. You can read more about the handlers in this tutorial.

  • timeout - service timeout.

  • concurrent - whether this service can run concurrently, see tutorial 3.

  • start_condition - service start condition, see tutorial 4.

  • name - name of the service, see tutorial 4.

Service subclassing#

Services can also be defined as subclasses of Service, allowing access to all the fields described above via self.

To do this, derive your class from Service, then implement an async call method which will now replace the handler (see the PreProcess example below).

Tip

When defining a service as a subclass of Service, you can also change default parameters such as timeout or start_condition.

Code explanation#

In this example, pipeline contains three services, defined in three different ways.

The first is defined as a Service with a function handler.

The second derives from the Service class.

The third is defined as a Service with a processing handler.

[3]:
async def function_handler(ctx: Context):
    logger.info(
        "function_handler running:\n"
        "timeout of this service cannot be determined"
    )


class ServiceSubclass(Service):
    async def call(self, ctx: Context):
        logger.info(
            f"{self.name or self.computed_name} running:\n"
            f"timeout: {self.timeout}"
        )

    timeout: float = 1.0
    # this overrides the default `None` timeout,
    # but can still be overridden in class instances


class ProcessingService(BaseProcessing):
    async def call(self, ctx: Context) -> None:
        try:
            logger.info(self.timeout)
        except AttributeError:
            # this is BaseProcessing not Service so there's no `timeout` field
            logger.info(
                "ProcessingService running:\n"
                "timeout of this service cannot be determined"
            )


pipeline = Pipeline(
    **TOY_SCRIPT_KWARGS,
    pre_services=[
        Service(
            handler=function_handler,
            timeout=0.5,
        ),
        ServiceSubclass(name="ServiceSubclassWithCustomName", timeout=100),
        ServiceSubclass(),
        Service(handler=ProcessingService(), timeout=4),
    ],
)
[4]:
if __name__ == "__main__":
    check_happy_path(pipeline, HAPPY_PATH[:1], printout=True)
    if is_interactive_mode():
        pipeline.run()
USER: text='Hi'
function_handler running:
timeout of this service cannot be determined
ServiceSubclassWithCustomName running:
timeout: 100.0
ServiceSubclass running:
timeout: 1.0
ProcessingService running:
timeout of this service cannot be determined
BOT : text='Hi, how are you?'