← Library AI AI-Powered

AIClassifyMultiLabelOp

Classifies text into zero or more categories from a fixed set — returns a subset of the configured labels.

Inputs

Field Type Description
Input *string Text to classify

Outputs

Field Type Description
Result []string Matched labels (CSV-encoded slice)

Reasoning is captured by WithReasoningLog — no graph output needed.

Params

Key Type Default Description
categories string required Comma-separated label set (minimum 2 labels)
max_retries string "3" Number of retries on parse error

Typical Use Cases

  • Support ticket categorization into multiple issue types
  • Content tagging with overlapping topic labels
  • Multi-topic labeling of articles or messages
  • Routing a request to multiple teams simultaneously

Zero matches are valid. When no category applies, the op returns an empty slice rather than an error. Use IfEmptySliceStringOp downstream to detect and handle this case.

Retry on parse error. If the model response cannot be parsed as a valid subset of the configured categories, the op retries up to max_retries times before returning an error. This makes it resilient to occasional malformed model output.

Complete Runnable Example

Classifies a support ticket into multiple categories simultaneously, returning all matching labels.

main.go
package main

import (
	"context"
	"encoding/json"
	"log"
	"os"
	"time"

	"github.com/akennis/sparsi-go/library"
	_ "github.com/akennis/dagor/operator/builtin"

	"github.com/panjf2000/ants/v2"
	"github.com/akennis/dagor"
	"github.com/akennis/dagor/graph"
)

func main() {
	ticket := "My payment failed and now I can't access my account. The export feature is also broken."

	library.RegisterConst("ticket_val", ticket)

	g, err := graph.NewBuilder("ticket_classifier").
		Vertex("ticket_src").Op("ticket_val").
		Output("Result", "ticket_wire").

		Vertex("classify").Op("AIClassifyMultiLabelOp").
		Params(map[string]string{
			"categories":  "billing,technical,account,feature-request,abuse",
			"max_retries": "3",
		}).
		Input("Input", "ticket_wire").
		Output("Result", "matched_labels").

		Build()
	if err != nil {
		log.Fatalf("build: %v", err)
	}

	pool, err := ants.NewPool(4)
	if err != nil {
		log.Fatalf("pool: %v", err)
	}
	defer pool.Release()

	eng, err := dagor.NewEngine(g, pool)
	if err != nil {
		log.Fatalf("engine: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	if err := eng.Run(ctx); err != nil {
		log.Fatalf("run: %v", err)
	}

	out := map[string]any{}
	if raw, ok := eng.GetOutput("matched_labels"); ok {
		out["labels"] = *raw.(*[]string)
	}

	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	enc.Encode(out)
}