OpenAI ChatKit 动作

2026-11-06 16:45 更新

动作(action)让ChatKit SDK前端在用户没有提交消息时,也能触发流式响应,还可以触发ChatKit之外的副作用。

触发动作

响应用户与组件的交互

给支持动作的组件节点附加ActionConfig即可触发。例如为按钮绑定点击动作,用户点击后,动作被送到服务器,可更新组件、运行推理或流式返回新的对话线程项目。

来源示例 1(python):

## [中文] 示例1:响应用户与组件的交互。
## [中文] 创建标签为“示例”的按钮,点击发送type=example、载荷id=123的动作,默认交由服务器处理。
button = Button(
    label="Example",
    onClickAction=ActionConfig(
        type="example",
        payload={"id": 123},
    ),
)

前端也可直接调用sendAction()。它适合响应ChatKit之外的界面交互;需要客户端和服务端共同处理时,也可用来串联动作。

来源示例 2(javascript):

// [中文] 示例2:响应用户与组件的交互。
// [中文] 前端直接发送example动作,无需用户输入消息;chatKit实例须由集成代码提供。
await chatKit.sendAction({
  type: "example",
  payload: { id: 123 },
});

处理动作

服务端

默认将动作发送给服务器,通过实现ChatKitServer.action处理:

来源示例 3(python):

## [中文] 示例3:服务端。
## [中文] 服务器按动作类型读取id并执行应用逻辑,写入隐藏上下文“用户做了一项操作”,随后生成并流式返回事件。
## [中文] 动作与载荷不可信,需验证授权及字段;do_thing、generate、存储和导入均由应用提供。
## [中文] 线程项目ID由store.generate_item_id生成,避免复用固定示意ID;仍须提供持久化store和上下文。
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

动作及其载荷由客户端发来,必须视为不可信数据。

客户端

要在客户端集成中处理动作,在ActionConfig中设置handler="client":

来源示例 4(python):

## [中文] 示例4:客户端。
## [中文] 将按钮动作标记handler=client,使其交给客户端回调;“示例”是按钮标签。
button = Button(
    label="Example",
    onClickAction=ActionConfig(type="example", payload={"id": 123}, handler="client"),
)

触发时,动作会被传给初始化ChatKit时提供的回调:

来源示例 5(javascript):

// [中文] 示例5:客户端。
// [中文] 客户端处理example后,将结果作为example_complete动作送回服务器;widgets.onAction绑定处理器。
// [中文] doSomething与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 },
});

强类型动作

Action与ActionConfig默认不是强类型,但Action.create辅助方法可从一组强类型动作生成ActionConfig:

来源示例 6(python):

## [中文] 示例6:强类型动作。
## [中文] 用Literal与Pydantic区分example及other载荷,通过TypeAdapter验证,再用Action.create构造动作配置。
## [中文] 处理成功后产生文字“动作完成”;原片段依赖相关导入与应用逻辑,验证失败应由应用补错误处理。
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.")],
                )
            )

用组件与动作创建自定义表单

接收用户输入的节点放入Form后,这些字段值会加入表单内部触发的所有动作的payload。

表单字段按name组织载荷:Select(name="title")对应action.payload.title,Select(name="todo.title")对应action.payload.todo.title。

来源示例 7(python):

## [中文] 示例7:用组件与动作创建自定义表单。
## [中文] 编辑待办表单包含标题、描述和“保存”按钮;标题为必填,提交动作带待办ID和收集到的字段。
## [中文] 服务器按update_todo更新并输出“待办已更新”;todo、update_todo和认证、字段校验仍由应用提供。
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.")],
                )
            )

验证

Form使用基础原生表单验证,对已配置的required与pattern执行检查;存在无效字段时阻止提交。

未来可能加入体验更好、表达更丰富或可定制错误显示的验证模式。目前,复杂且验证条件繁琐的表单不适合仅靠组件实现。可改用客户端动作打开弹窗,在其中显示自定义表单,再用sendAction把结果送回ChatKit。

将Card用作Form

向Card传入asForm=True,它会按Form工作,执行验证,并将收集到的字段传给Card的confirm动作。

载荷键冲突

如果表单字段名称与载荷已有的预定义键冲突,表单值会被忽略。这通常是一个错误,检测到时会发出error事件。

控制组件加载状态下的交互

使用ActionConfig.loadingBehavior控制动作在组件中触发的加载状态:

来源示例 8(python):

## [中文] 示例8:控制组件加载状态下的交互。
## [中文] 按钮文字是“这可能需要一段时间”;loadingBehavior=container使整个组件容器进入加载状态,而非只影响按钮。
button = Button(
    label="This may take a while...",
    onClickAction=ActionConfig(
        type="long_running_action_that_should_block_other_ui_interactions",
        loadingBehavior="container",
    ),
)

值 行为
auto 根据使用位置调整,默认值
self 在动作绑定的组件节点上显示加载状态
container 整个组件容器进入加载状态,略微淡出并不可交互
none 不显示加载状态

使用auto行为

通常建议使用默认的auto,它依据绑定位置决定状态,例如:

  • Button.onClickAction使用self。
  • Select.onChangeAction使用none。
  • Card.confirm.action使用container。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号