rasa-sdk
Python SDK for building the custom action server that connects a Rasa chatbot to your own APIs and business logic.
Repository Health
Technical Analysis
Rasa SDK is the official Python toolkit for implementing custom actions that Rasa’s dialogue engine calls when a conversation needs to reach outside built-in NLU and rules — hitting an internal API, updating slots, or querying a database. It ships as a standalone action server: a Sanic-based HTTP webhook by default, with a full gRPC server as an alternative transport, so custom business logic and credentials run in a separate process from the core Rasa model.
Built around Action and FormValidationAction base classes, a Tracker for reading conversation state, and a CollectingDispatcher for returning responses, the SDK gives custom-action authors the same primitives whether the transport is a single HTTP request or a persistent gRPC stream. It also bundles OpenTelemetry-based tracing, a pluggy-based plugin system for extending the Sanic app, and a small knowledge-base module for FAQ-style lookups.
What You Get
- A standalone action server (
rasa_sdk.endpoint.run) exposing/webhook,/actions, and/healthover Sanic, callable by any Rasa assistant over the network - An alternative gRPC server (
rasa_sdk.grpc_server) for teams that want a persistent, typed connection instead of unary HTTP webhooks ActionandFormValidationActionbase classes plus aTrackerobject for reading slots, intents, and conversation history inside custom logicCollectingDispatcherwith both a batched API (utter_message) and a streaming API (stream_start/stream_chunk/stream_end) for incremental responses- Built-in OpenTelemetry tracing hooks and a pluggy-based plugin system for extending the Sanic app at startup
Common Use Cases
- Calling an internal REST API or database from within a Rasa conversation to fetch account or order data before responding
- Validating and normalizing form slots (e.g. an email format or a zip code lookup) before a Rasa form proceeds
- Running custom business logic — pricing calculations, entitlement checks, escalation rules — that shouldn’t live inside the NLU model
- Streaming partial responses (e.g. from an LLM call made inside a custom action) back to the end user as they’re generated
- Answering FAQ-style queries via the bundled knowledge-base module against a small stored graph of facts
Under The Hood
Architecture
The SDK follows a layered, transport-agnostic design: rasa_sdk.endpoint.create_app builds a Sanic application exposing /webhook, /actions, and /health, delegating request handling to executor.ActionExecutor, which discovers Action subclasses via pkgutil/importlib scanning of user action packages and dispatches to their async run() methods with a Tracker (conversation state) and CollectingDispatcher (response collection) injected as arguments. A parallel grpc_server.py implements the same dispatch contract over gRPC using generated protobuf stubs, so the executor’s core dispatch logic is decoupled from transport. Cross-cutting concerns — OpenTelemetry tracing (tracing/) and a pluggy-based plugin system (plugin.py) — are layered on top of both transports rather than baked into ActionExecutor, and forms.py builds a FormValidationAction abstraction on top of the base Action class for structured slot validation. Because actions are loaded dynamically at runtime rather than compiled against a pinned SDK version, changes to the core Action/Tracker contract would ripple into every user-authored action package across all Rasa deployments.
Tech Stack
The project targets Python >=3.10,<3.15 and is built with Poetry. The HTTP action server runs on Sanic ~25.12 with Sanic-Cors for CORS handling and multidict for header manipulation; the gRPC alternative depends on grpcio ~1.80 and grpcio-health-checking, with protobuf ~5.29 for wire messages. Distributed tracing runs on the full opentelemetry-sdk/opentelemetry-exporter-otlp/opentelemetry-api ~1.33 stack; pydantic ~2.13 validates request/response payloads; pluggy powers the plugin-hook system; coloredlogs, ruamel.yaml, websockets, and typing-extensions round out smaller utility dependencies. No database dependency is bundled — the knowledge_base module ships its own lightweight in-memory/file-backed storage rather than an ORM. Deployment targets Docker, via an official rasa/rasa-sdk image built from the repo’s own Dockerfile, fronting a Rasa assistant over the network.
Code Quality
The project carries an extensive test suite spanning thousands of lines across tests/, mirroring nearly every module — test_executor.py, test_forms.py, test_grpc_server.py, test_interfaces.py, test_tracker.py — plus dedicated knowledge_base/ and tracing/ test subdirectories, run via pytest with pytest-asyncio’s auto mode and coverage reporting. Type checking is enforced via mypy, and the codebase is comprehensively type-hinted throughout interfaces.py, executor.py, and events.py. Style is enforced via ruff (lint and format, with docstring rules selected), and CI runs the full test/lint/typecheck matrix alongside separate gRPC standalone and Docker-based integration test jobs. Error handling is explicit and typed — custom exceptions such as ActionExecutionRejection, ActionNotFoundException, and ActionMissingDomainException are raised deliberately and mapped to specific HTTP status codes in endpoint.py rather than being swallowed.
API Design
Custom actions are authored by subclassing Action and implementing name() and async run(dispatcher, tracker, domain) — a minimal, consistent contract that stays identical whether the action server is invoked via HTTP webhook or gRPC. FormValidationAction further reduces boilerplate for multi-turn slot collection by letting authors define one validate_<slot_name> method per slot instead of hand-rolling a state machine. The CollectingDispatcher is the standout ergonomic choice: it exposes the same utter_message-style API regardless of transport, while its stream_start/stream_chunk/stream_end API lets an action stream partial responses (for example, from an LLM call) without the author needing to branch on which transport invoked them — that complexity is absorbed by the executor instead. Getting started requires minimal boilerplate: a bare action package with one __init__.py and a single Action subclass is runnable via rasa_sdk.endpoint immediately.