First Pipeline Tutorial
Creating Your First Pipeline
Section titled “Creating Your First Pipeline”This guide walks through building a working ticket-classification pipeline on top of the latest Open Ticket AI configuration schema. You will learn how plugin modules are loaded, how services are registered by identifier, and how the orchestrator is composed entirely from nested PipeConfig definitions.
Configuration building blocks
Section titled “Configuration building blocks”All configuration lives under the top-level open_ticket_ai key. Every object underneath is validated by Pydantic models before the orchestrator starts, so matching the schema is essential.
Plugin modules (open_ticket_ai.plugins)
Section titled “Plugin modules (open_ticket_ai.plugins)”Plugins are enabled by module (entry point) name. List each plugin you want to load as a string. At minimum you need the core otai-base plugin to access the built-in runners, orchestrators, and utility pipes.
Service registry (open_ticket_ai.services)
Section titled “Service registry (open_ticket_ai.services)”Services are defined in a dictionary keyed by their identifier. Each service describes the implementation to instantiate via use, optional constructor dependencies via injects, and configuration data via params. Services become injectable dependencies for pipes and other services. Open Ticket AI expects exactly one TemplateRenderer service; the example below registers the default Jinja renderer.
Orchestrator (open_ticket_ai.orchestrator)
Section titled “Orchestrator (open_ticket_ai.orchestrator)”The orchestrator itself is a PipeConfig. You choose an orchestrator implementation via use (for example base:SimpleSequentialOrchestrator) and configure it via params. In the sequential orchestrator, params.steps is a list of nested PipeConfig objects. Each step can be any pipe—including runners such as SimpleSequentialRunner, other orchestrators, or concrete business logic pipes.
The following minimal configuration highlights the structure and validates against the current schema:
open_ticket_ai: api_version: '1' plugins: - otai-base services: default_renderer: use: 'base:JinjaRenderer' orchestrator: id: orchestrator use: 'base:SimpleSequentialOrchestrator' params: steps: []Build a minimal ticket-routing pipeline
Section titled “Build a minimal ticket-routing pipeline”We will extend the skeleton above into a runnable pipeline that fetches tickets from OTOBO/Znuny, classifies the queue using a Hugging Face model, updates the ticket, and stores a note. Each section explains how the configuration maps to concrete classes in the codebase.
Step 1: Declare plugin modules and template renderer
Section titled “Step 1: Declare plugin modules and template renderer”Add the plugins that provide the injectables we need:
otai-base– core orchestrators (SimpleSequentialOrchestrator), runners (SimpleSequentialRunner), pipes (CompositePipe,FetchTicketsPipe,UpdateTicketPipe,AddNotePipe,ExpressionPipe), triggers (IntervalTrigger), and the defaultJinjaRenderer.otai-otobo-znuny– ticket system integration service (OTOBOZnunyTicketSystemService).otai-hf-local– on-device Hugging Face classification service (HFClassificationService).
Keep the default_renderer service registered so template rendering is available before any other service instantiates.
Step 2: Connect external services
Section titled “Step 2: Connect external services”Define services keyed by ID:
default_rendererinstantiatesbase:JinjaRenderer, satisfying the requirement that exactly oneTemplateRendererexists.otobo_znunypoints atotobo-znuny:OTOBOZnunyTicketSystemServiceand passes connection credentials insideparams.hf_classifierresolves tohf-local:HFClassificationServicewith an optional API token.
These IDs (for example otobo_znuny and hf_classifier) will be referenced from pipe injects sections later.
Step 3: Configure the orchestrator and runner
Section titled “Step 3: Configure the orchestrator and runner”Use base:SimpleSequentialOrchestrator for a continuously running event loop. Its params.orchestrator_sleep controls the idle time between cycles. Inside params.steps, add a single SimpleSequentialRunner. This runner expects two nested PipeConfig definitions under params:
on– a trigger pipe; we usebase:IntervalTriggerwith atimedeltainterval (PT60Sruns once per minute).run– the pipeline to execute when the trigger succeeds. Here we referencebase:CompositePipe, which processes its ownparams.stepssequentially.
Step 4: Define the composite pipeline steps
Section titled “Step 4: Define the composite pipeline steps”Inside the composite pipe, each element in params.steps is another PipeConfig that maps 1:1 to a pipe class:
fetch_open_tickets(base:FetchTicketsPipe) injects the ticket system service and loads incoming tickets viaticket_search_criteria.ensure_tickets_found(base:ExpressionPipe) callsfail()if no tickets were fetched, stopping the runner gracefully.queue_classifier(base:ClassificationPipe) injects the Hugging Face service and classifies the ticket text.update_queue(base:UpdateTicketPipe) writes the new queue selection back to OTOBO/Znuny.add_classification_note(base:AddNotePipe) records an audit trail of the automated classification.
All parameters reference earlier results using helper functions exposed by the Jinja renderer (get_pipe_result, fail, get_env, etc.).
Complete configuration
Section titled “Complete configuration”Save the following validated configuration as config.yml in your working directory. It combines all steps described above and matches the current schema exactly.
open_ticket_ai: api_version: '1' plugins: - otai-base - otai-otobo-znuny - otai-hf-local services: default_renderer: use: 'base:JinjaRenderer' otobo_znuny: use: 'otobo-znuny:OTOBOZnunyTicketSystemService' params: base_url: 'https://helpdesk.example.com/otobo/nph-genericinterface.pl' username: 'open_ticket_ai' password: "{{ get_env('OTOBO_API_TOKEN') }}" hf_classifier: use: 'hf-local:HFClassificationService' params: api_token: "{{ get_env('HF_TOKEN') }}" orchestrator: id: ticket-automation use: 'base:SimpleSequentialOrchestrator' params: orchestrator_sleep: 'PT60S' steps: - id: ticket-routing-runner use: 'base:SimpleSequentialRunner' params: on: id: every-minute use: 'base:IntervalTrigger' params: interval: 'PT60S' run: id: ticket-routing use: 'base:CompositePipe' params: steps: - id: fetch_open_tickets use: 'base:FetchTicketsPipe' injects: ticket_system: 'otobo_znuny' params: ticket_search_criteria: queue: name: 'OpenTicketAI::Incoming' limit: 25 - id: ensure_tickets_found use: 'base:ExpressionPipe' params: expression: "{{ fail('No open tickets found') if (get_pipe_result('fetch_open_tickets','fetched_tickets') | length) == 0 else 'tickets ready' }}" - id: queue_classifier use: 'base:ClassificationPipe' injects: classification_service: 'hf_classifier' params: text: "{{ get_pipe_result('fetch_open_tickets','fetched_tickets')[0]['subject'] }} {{ get_pipe_result('fetch_open_tickets','fetched_tickets')[0]['body'] }}" model_name: 'softoft/otai-queue-de-bert-v1' - id: update_queue use: 'base:UpdateTicketPipe' injects: ticket_system: 'otobo_znuny' params: ticket_id: "{{ get_pipe_result('fetch_open_tickets','fetched_tickets')[0]['id'] }}" updated_ticket: queue: name: "{{ get_pipe_result('queue_classifier','label') }}" - id: add_classification_note use: 'base:AddNotePipe' injects: ticket_system: 'otobo_znuny' params: ticket_id: "{{ get_pipe_result('fetch_open_tickets','fetched_tickets')[0]['id'] }}" note: subject: 'Queue classification' body: | Auto-classified queue: {{ get_pipe_result('queue_classifier','label') }} Confidence: {{ (get_pipe_result('queue_classifier','confidence') * 100) | round(1) }}%Running and verifying the pipeline
Section titled “Running and verifying the pipeline”-
Install the required packages (core plus the plugins listed above).
-
Provide credentials through environment variables referenced in the configuration (for example
OTOBO_API_TOKEN,HF_TOKEN). -
Place the configuration file at
config.ymlin your working directory soAppConfigpicks it up automatically. -
Start the orchestrator:
Terminal window uv run python -m open_ticket_ai.main
AppConfig validates the YAML against the schema on startup. If a section is missing or a parameter name is wrong, the application exits with a descriptive validation error before any tickets are processed. Once running, the orchestrator executes the SimpleSequentialRunner every minute, applies the nested CompositePipe steps, and logs progress through the configured services.