For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navigation principale
Theme

Les actions dans ChatKit

Déclenchez des actions sur le serveur lorsque les utilisateurs interagissent avec votre interface de discussion.

Les actions permettent au frontend du SDK ChatKit de déclencher une réponse en streaming sans que l’utilisateur ait à envoyer de message. Elles peuvent aussi déclencher des effets de bord en dehors du SDK ChatKit.

Déclenchement des actions

Lors d’une interaction avec un widget

Pour déclencher une action, associez un ActionConfig à un nœud de widget qui le prend en charge. Vous pouvez, par exemple, réagir aux clics sur un bouton. Lorsque l’utilisateur clique sur le bouton de l’exemple ci-dessous, ChatKit envoie l’action à votre serveur. Celui-ci peut alors mettre à jour le widget, lancer une inférence ou encore diffuser de nouveaux éléments du fil de discussion en streaming.

button = Button(
    label="Example",
    onClickAction=ActionConfig(
        type="example",
        payload={"id": 123},
    ),
)

Votre frontend peut aussi envoyer une action en appelant directement sendAction(). Cet appel est particulièrement utile lorsque ChatKit doit réagir à une interaction qui a lieu en dehors de ChatKit. Il permet également d’enchaîner des actions lorsqu’une interaction nécessite un traitement à la fois côté client et côté serveur, comme expliqué plus bas.

await chatKit.sendAction({
  type: "example",
  payload: { id: 123 },
});

Traitement des actions

Côté serveur

Par défaut, ChatKit envoie les actions à votre serveur. Pour les traiter côté serveur, implémentez la méthode action de ChatKitServer.

class MyChatKitServer(ChatKitServer[RequestContext]):
    async def action(
        self,
        thread: ThreadMetadata,
        action: Action[str, Any],
        sender: WidgetItem | None,
        context: RequestContext,
    ) -> AsyncIterator[Event]:
        if action.type == "example":
            await do_thing(action.payload["id"])

            # Often you'll want to add a HiddenContextItem so the model
            # can see that the user did something.
            await self.store.add_thread_item(
                thread.id,
                HiddenContextItem(
                    id=self.store.generate_item_id("message", thread, context),
                    thread_id=thread.id,
                    created_at=datetime.now(),
                    content="<USER_ACTION>The user did a thing</USER_ACTION>",
                ),
                context,
            )

            # Then you might want to run inference to stream a response
            # back to the user.
            async for event in self.generate(context, thread):
                yield event

Traitez les actions et leurs charges utiles comme des données non fiables, car c’est le client qui les envoie à votre serveur.

Côté client

Dans certains cas, vous pouvez avoir besoin de traiter les actions dans votre intégration côté client. Pour que ChatKit transmette une action à votre gestionnaire côté client, ajoutez handler="client" à l’objet ActionConfig.

button = Button(
    label="Example",
    onClickAction=ActionConfig(type="example", payload={"id": 123}, handler="client"),
)

Lorsque l’action se déclenche, ChatKit la transmet à la fonction de rappel que vous avez fournie lors de l’instanciation de ChatKit.

async function handleWidgetAction(action) {
  if (action.type === "example") {
    const res = await doSomething(action);

    // You can fire off actions to your server from here as well.
    // For example, stream new thread items or update a widget.
    await chatKit.sendAction({
      type: "example_complete",
      payload: res,
    });
  }
}

chatKit.setOptions({
  // Other options...
  widgets: { onAction: handleWidgetAction },
});

Actions fortement typées

Par défaut, Action et ActionConfig ne sont pas fortement typés. La méthode utilitaire create de Action permet toutefois de générer des objets ActionConfig à partir d’un ensemble d’actions fortement typées.

class ExamplePayload(BaseModel):
    id: int


ExampleAction = Action[Literal["example"], ExamplePayload]
OtherAction = Action[Literal["other"], None]

AppAction = Annotated[
    ExampleAction | OtherAction,
    Field(discriminator="type"),
]

ActionAdapter: TypeAdapter[AppAction] = TypeAdapter(AppAction)


def parse_app_action(action: Action[str, Any]) -> AppAction:
    return ActionAdapter.validate_python(action)


# Usage in a widget
# Action provides a create helper which makes it easy to generate
# ActionConfigs from strongly typed actions.
button = Button(
    label="Example",
    onClickAction=ExampleAction.create(ExamplePayload(id=123)),
)


# usage in action handler
class MyChatKitServer(ChatKitServer[RequestContext]):
    async def action(
        self,
        thread: ThreadMetadata,
        action: Action[str, Any],
        sender: WidgetItem | None,
        context: RequestContext,
    ) -> AsyncIterator[Event]:
        # add custom error handling if needed
        app_action = parse_app_action(action)
        if app_action.type == "example":
            await do_thing(app_action.payload.id)
            yield ThreadItemDoneEvent(
                item=AssistantMessageItem(
                    id=self.store.generate_item_id("message", thread, context),
                    thread_id=thread.id,
                    created_at=datetime.now(),
                    content=[AssistantMessageContent(text="Action complete.")],
                )
            )

Création de formulaires personnalisés avec des widgets et des actions

Lorsque vous montez des nœuds de widget qui acceptent une saisie utilisateur dans un Form, les valeurs de ces champs sont incluses dans le payload de chaque action déclenchée depuis ce Form.

Dans le payload, la clé de chaque valeur correspond à l’attribut name du champ. Par exemple :

  • Select(name="title") → action.payload.title
  • Select(name="todo.title") → action.payload.todo.title
form = Form(
    direction="col",
    onSubmitAction=ActionConfig(
        type="update_todo",
        payload={"id": todo.id},
    ),
    children=[
        Title(value="Edit Todo"),
        Text(value="Title", color="secondary", size="sm"),
        Text(
            value=todo.title,
            editable=EditableProps(name="title", required=True),
        ),
        Text(value="Description", color="secondary", size="sm"),
        Text(
            value=todo.description,
            editable=EditableProps(name="description"),
        ),
        Button(label="Save", submit=True),
    ],
)


class MyChatKitServer(ChatKitServer[RequestContext]):
    async def action(
        self,
        thread: ThreadMetadata,
        action: Action[str, Any],
        sender: WidgetItem | None,
        context: RequestContext,
    ) -> AsyncIterator[Event]:
        if action.type == "update_todo":
            todo_id = action.payload["id"]
            # Any action that originates from within the Form will
            # include title and description.
            title = action.payload["title"]
            description = action.payload["description"]

            await update_todo(todo_id, title, description)
            yield ThreadItemDoneEvent(
                item=AssistantMessageItem(
                    id=self.store.generate_item_id("message", thread, context),
                    thread_id=thread.id,
                    created_at=datetime.now(),
                    content=[AssistantMessageContent(text="Todo updated.")],
                )
            )

Validation

Form utilise la validation native de base des formulaires. Il applique les contraintes required et pattern aux champs pour lesquels elles sont définies et bloque l’envoi dès qu’un champ est invalide.

À l’avenir, nous pourrions proposer de nouveaux modes de validation pour améliorer l’expérience utilisateur, permettre de définir des règles plus fines ou encore personnaliser l’affichage des erreurs. Pour le moment, les widgets se prêtent mal aux formulaires complexes dont les règles de validation sont délicates à mettre en œuvre. Dans ce cas, utilisez un gestionnaire d’actions côté client pour ouvrir une fenêtre modale, affichez-y un formulaire personnalisé, puis renvoyez le résultat à ChatKit avec sendAction.

Utilisation de Card comme Form

Si vous passez asForm=True à Card, la carte se comporte comme un Form : elle valide les champs et transmet les valeurs recueillies à son action confirm.

Conflits entre clés de la charge utile

Si le nom d’un champ entre en conflit avec une clé prédéfinie déjà présente dans la charge utile, ChatKit ignore la valeur du champ. Comme ce conflit signale probablement un bug, ChatKit émet un événement error lorsqu’il le détecte.

Gestion des états de chargement dans les widgets

Utilisez ActionConfig.loadingBehavior pour définir comment une action déclenche un état de chargement dans un widget.

button = Button(
    label="This may take a while...",
    onClickAction=ActionConfig(
        type="long_running_action_that_should_block_other_ui_interactions",
        loadingBehavior="container",
    ),
)
Valeur Comportement
auto L’action s’adapte à son contexte d’utilisation. (par défaut)
self L’action place le nœud de widget auquel elle est associée en état de chargement.
container L’action place tout le conteneur du widget en état de chargement. Le widget devient légèrement transparent et ne réagit plus aux interactions.
none Aucun état de chargement

Comportement de auto

En règle générale, nous recommandons auto, la valeur par défaut. Avec auto, l’état de chargement dépend de l’endroit où l’action est associée. Par exemple :

  • Button.onClickAction → self
  • Select.onChangeAction → none
  • Card.confirm.action → container