Implementation
The implementation of a service primarily consists of defining the corresponding version and a (asynchronous) handler function that processes incoming messages and returns an appropriate result.
simple implementation
Section titled “simple implementation”import { createService } from '@requence/service'
createService('1.0.0', async (context) => { // process incomming message, return result return "someResult"})from requence.service import Service, ContextHelper
def message_handler(context: ContextHelper): # process incomming message, return result "someResult"
Service("1.0.0", message_handler)accessing incoming message data
Section titled “accessing incoming message data”The handler function takes a single argument: the service context. Through the context, you can access the incoming data (among other options).
import { createService } from '@requence/service'
createService('1.0.0', async (context) => { const myInput = context.getData().myInput return "someResult"})from requence.service import Service, ContextHelper
def message_handler(context: ContextHelper): my_input = context.get_data()["myInput"] "someResult"
Service("1.0.0", message_handler)accessing service configuration
Section titled “accessing service configuration”the service context also includes the service configuration. This is usefull for informations like api keys.
import { createService } from '@requence/service'
createService('1.0.0', async (context) => { const { openAiApiKey } = context.getConfiguration() return "someResult"})from requence.service import Service, ContextHelper
def message_handler(context: ContextHelper): open_ai_api_key = context.get_configuration()["openAiApiKey"] "someResult"
Service("1.0.0", message_handler)returning data to different outputs
Section titled “returning data to different outputs”By default, the return value of the handler function is always sent to the standard output. If the service has been configured with multiple outputs, the result can also be sent to a different output.
import { createService } from '@requence/service'
createService('1.0.0', async (context) => { // this will send the result to a named output... context.toOutput("myOtherOutput", "someOtherResult") // ... and this will additionally send the result to the default output return "someResult"})from requence.service import Service, ContextHelper
def message_handler(context: ContextHelper): # this will send the result to a named output... context.to_output("myOtherOutput", "someOtherResult") # ... and this will additionally send the result to the default output return "someResult"
Service("1.0.0", message_handler)accessing task input and meta data
Section titled “accessing task input and meta data”import { createService } from '@requence/service'
createService('1.0.0', async (context) => { const taskInput = context.getInput() const taskMeta = context.getMeta() return "someResult"})from requence.service import Service, ContextHelper
def message_handler(context: ContextHelper): task_input = context.get_input() task_meta = context.get_meta() "someResult"
Service("1.0.0", message_handler)persisting temporary state
Section titled “persisting temporary state”During the execution of a task, the service may be called multiple times. The service can store data on a state object, which remains valid for the duration of the task.
This is especially useful when there are multiple instances of the service and it cannot be guaranteed that the same instance will be called again on subsequent invocations.
import { createService } from '@requence/service'
createService('1.0.0', async (context) => { const invocationCount = context.state.invocations ?? 0 context.state.invocations = invocationCount + 1 return "someResult"})from requence.service import Service, ContextHelper
def message_handler(context: ContextHelper): invocation_count = context.state.get("invocations", 0) context.state["invocations"] = invocation_count + 1 "someResult"
Service("1.0.0", message_handler)logging info to requence backend
Section titled “logging info to requence backend”The service has the ability to send debug information to the Requence backend. This information is visible in the task overview.
import { createService } from '@requence/service'
createService('1.0.0', async (context) => { context.debug.info("this is just an info…") context.debug.log({ logMessage: '… this will log an object…'}) context.debug.warn("… this is a warning…") context.debug.error("… and finally this is an error") return "someResult"})from requence.service import Service, ContextHelper
def message_handler(context: ContextHelper): context.debug.info("this is just an info…") context.debug.log({ logMessage: '… this will log an object…'}) context.debug.warn("… this is a warning…") context.debug.error("… and finally this is an error") "someResult"
Service("1.0.0", message_handler)gracefully abort execution
Section titled “gracefully abort execution”In some situations, a service may not be able to process a request (e.g., when a required resource like a database is unavailable). Instead of throwing an exception, there are special functions available to either abort the execution or retry it at a later time.
import { createService } from '@requence/service'
createService('1.0.0', async (context) => { // This aborts the execution. If there is no “on fail” path defined in the corresponding task template, the task will fail. context.abort("optional reason")
// Alternatively, you can retry the execution after an optional delay. context.retry(3000) // ms
// the return value will never be reached in this case return "someResult"})from requence.service import Service, ContextHelper
def message_handler(context: ContextHelper): # This aborts the execution. If there is no “on fail” path defined in the corresponding task template, the task will fail. context.abort("optional reason")
# Alternatively, you can retry the execution after an optional delay. context.retry(3000)
# the return value will never be reached in this case "someResult"
Service("1.0.0", message_handler)deferring execution
Section titled “deferring execution”Although the handler function is asynchronous, there may be circumstances that significantly delay processing (e.g., waiting for user interaction). In such cases, processing can be deferred and resumed at a later time.
import { createService } from '@requence/service'
const userInteractions = new Map<string, string>()
// save a reference to the serviceconst service = createService('1.0.0', async (context) => { const userInteractionKey = createUserInteraction(context.getData().question) // generate a defer key for later use const deferKey = context.defer("waiting for user interaction (optional reason)") userInteractions.set(userInteractionKey, deferKey) // no return necessary})
// some time later// retrieve persisted defer keyconst persistedDeferKey = userInteractions.get(completeUserInteractionKey)
service.act(persistedDeferKey, api => { api.send('this is a deferred result')})from requence.service import Service, ContextHelper, ActorApi
user_interactions: dict[str, str] = {}
def message_handler(context: ContextHelper): user_interaction_key = create_user_interaction(context.get_data().question) # generate a defer key for later use defer_key = context.defer("waiting for user interaction (optional reason)") user_interactions[user_interaction_key] = defer_key # no return necessary
# save a reference to the serviceservice = Service("1.0.0", message_handler)
persisted_defer_key = user_interactions.get(complete_user_interaction_key)
def actor(api: ActorApi): api.send("this is a deferred result")
service.act(persisted_defer_key, actor)