For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegação principal
Theme

Ações no ChatKit

Dispare ações no servidor quando o usuário interagir com o chat.

Com as ações, o frontend do SDK do ChatKit pode iniciar uma resposta em streaming sem que o usuário envie uma mensagem. As ações também podem produzir efeitos colaterais fora do SDK do ChatKit.

Como disparar ações

Em resposta a interações do usuário com widgets

Para disparar uma ação, associe um ActionConfig a qualquer node de widget que aceite essa configuração. Por exemplo, você pode responder a eventos de clique em botões. Quando o usuário clica no botão, o ChatKit envia a ação ao seu servidor, onde você pode atualizar o widget, executar inferência ou transmitir novos itens da conversa em streaming, entre outras operações.

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

O frontend também pode enviar ações diretamente com sendAction(). Isso costuma ser mais útil quando o ChatKit precisa responder a uma interação que ocorre fora dele. Você também pode usar esse método para encadear ações quando precisar responder tanto no cliente quanto no servidor, como descrito a seguir.

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

Como tratar ações

No servidor

Por padrão, o ChatKit envia as ações ao seu servidor. Para tratá-las no servidor, implemente o método action em 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

Trate as ações e seus payloads como dados não confiáveis, pois quem os envia ao servidor é o cliente.

No cliente

Em alguns casos, você pode querer tratar as ações na integração do lado do cliente. Para que o ChatKit envie a ação ao manipulador de ações do cliente, adicione handler="client" ao ActionConfig.

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

Quando a ação for disparada, o ChatKit a encaminhará ao callback que você fornecer ao instanciá-lo.

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 },
});

Ações com tipagem forte

Por padrão, Action e ActionConfig não têm tipagem forte. No entanto, o método auxiliar create de Action gera configurações do tipo ActionConfig a partir de um conjunto de ações com tipagem forte.

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

Use widgets e ações para criar formulários personalizados

Quando você monta nodes de widget que recebem dados do usuário dentro de um Form, os valores desses campos são incluídos no payload de todas as ações originadas nesse Form.

No payload, o name de cada campo define a chave usada para acessar seu valor. Veja os exemplos abaixo.

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

Validação

O Form usa a validação nativa básica de formulários. Ele aplica as regras required e pattern aos campos em que elas estão configuradas e impede o envio se algum campo for inválido.

No futuro, poderemos adicionar modos de validação que melhorem a experiência do usuário e permitam definir regras mais expressivas e personalizar a exibição de erros, entre outros recursos. Por enquanto, widgets são pouco adequados para formulários complexos com regras de validação difíceis de implementar. Nesse caso, uma abordagem melhor é tratar a ação no cliente para abrir uma janela modal, exibir nela um formulário personalizado e enviar o resultado de volta ao ChatKit com sendAction.

Como usar Card como Form

Você pode passar asForm=True para Card para que ele se comporte como um Form, execute a validação e encaminhe os valores coletados nos campos à ação confirm do Card.

Conflitos entre chaves do payload

Se o nome de um campo coincidir com o de uma chave predefinida já presente no payload, o ChatKit ignorará o valor desse campo do formulário. Como isso provavelmente indica um bug, o ChatKit emite um evento error ao detectar o conflito.

Controle os estados de carregamento dos widgets durante as interações

Use ActionConfig.loadingBehavior para controlar como as ações ativam diferentes estados de carregamento em um widget.

button = Button(
    label="This may take a while...",
    onClickAction=ActionConfig(
        type="long_running_action_that_should_block_other_ui_interactions",
        loadingBehavior="container",
    ),
)
Valor Comportamento
auto O comportamento da ação se adapta à forma como ela é usada. (padrão)
self A ação ativa o estado de carregamento no node do widget ao qual ela está associada.
container A ação ativa o estado de carregamento em todo o contêiner do widget. O widget fica ligeiramente mais transparente e deixa de responder a interações.
none Nenhum estado de carregamento

Como usar o comportamento auto

Em geral, recomendamos usar auto, que é o padrão. Com auto, o estado de carregamento depende de onde você associa a ação, como mostram os exemplos abaixo.

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