For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegación principal
Theme

Acciones en ChatKit

Activa acciones en el servidor cuando el usuario interactúa con tu chat.

Las acciones permiten que el frontend del SDK de ChatKit inicie una respuesta en streaming sin que el usuario tenga que enviar un mensaje. También pueden producir efectos secundarios fuera del SDK de ChatKit.

Activar acciones

Cuando el usuario interactúa con los widgets

Para activar una acción, asocia un ActionConfig a cualquier nodo de widget que lo admita. Por ejemplo, puedes responder a eventos de clic en botones. Cuando el usuario haga clic en el botón del siguiente ejemplo, la acción se enviará a tu servidor, donde podrás actualizar el widget, ejecutar una inferencia o transmitir nuevos elementos del hilo, entre otras cosas.

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

También puedes enviar acciones de forma imperativa desde tu frontend con sendAction(). Esta opción suele ser especialmente útil cuando necesitas que ChatKit responda a una interacción que ocurre fuera de ChatKit. También puedes usarla para encadenar acciones cuando necesitas responder tanto en el cliente como en el servidor, como se explica más adelante.

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

Manejar acciones

En el servidor

De forma predeterminada, las acciones se envían a tu servidor. Para manejarlas allí, implementa el método action en 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

Trata las acciones y sus cargas útiles como datos no confiables, ya que el cliente las envía a tu servidor.

En el cliente

A veces querrás manejar las acciones en tu integración del lado del cliente. Para indicar que una acción debe enviarse al controlador de acciones del cliente, agrega handler="client" al ActionConfig.

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

Cuando se active la acción, se pasará como argumento a la función de callback que proporciones al crear la instancia 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 },
});

Acciones con tipado fuerte

De forma predeterminada, Action y ActionConfig no tienen tipado fuerte. Sin embargo, el método auxiliar create de Action genera instancias de ActionConfig a partir de un conjunto de acciones con tipado fuerte.

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.")],
                )
            )

Crear formularios personalizados con widgets y acciones

Cuando se montan nodos de widget que aceptan datos del usuario dentro de un Form, los valores de esos campos se incluyen en el payload de todas las acciones que se originan dentro del Form.

En el payload, el valor de cada campo del formulario se guarda bajo la clave que indica su name. Por ejemplo:

  • 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.")],
                )
            )

Validación

Form usa la validación nativa básica de formularios. Aplica las reglas required y pattern a los campos que las tengan configuradas y bloquea el envío si algún campo no es válido.

En el futuro, es posible que agreguemos modos de validación que ofrezcan una mejor experiencia de usuario, permitan definir reglas más expresivas o personalizar la forma de mostrar los errores, entre otras mejoras. Por ahora, los widgets no son la mejor opción para formularios complejos con reglas de validación difíciles de implementar. Si necesitas un formulario así, conviene manejar la acción en el cliente para abrir una ventana modal, mostrar allí un formulario personalizado y devolver el resultado a ChatKit con sendAction.

Usar Card como Form

Puedes pasar asForm=True a Card para que se comporte como un Form. Así, la tarjeta ejecuta la validación y pasa los valores de los campos a su acción confirm.

Conflictos entre claves de la carga útil

Si el nombre de un campo coincide con una clave predefinida que ya existe en la carga útil, se ignora el valor de ese campo del formulario. Esta coincidencia probablemente se deba a un error, por lo que emitiremos un evento error si la detectamos.

Controlar cómo las acciones activan los estados de carga de los widgets

Usa ActionConfig.loadingBehavior para controlar cómo las acciones activan los distintos estados de carga de un widget.

button = Button(
    label="This may take a while...",
    onClickAction=ActionConfig(
        type="long_running_action_that_should_block_other_ui_interactions",
        loadingBehavior="container",
    ),
)
Valor Comportamiento
auto La acción se adapta al modo en que se usa. (predeterminado)
self La acción activa el estado de carga en el nodo del widget al que está asociada.
container La acción activa el estado de carga en todo el contenedor del widget. El widget se vuelve ligeramente más transparente y deja de responder a las interacciones.
none Sin estado de carga

Usar el comportamiento auto

En general, recomendamos usar auto, el valor predeterminado. Con auto, el estado de carga depende de dónde esté asociada la acción. Por ejemplo:

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