主导航

遗留 API

ChatKit 中的操作

在聊天中通过用户交互触发后端操作。

操作是 ChatKit SDK 前端触发流式响应(无需用户提交消息)的一种方式。它们还可用于触发 ChatKit SDK 之外的副作用。

触发操作

响应用户与组件(widgets)的交互

可以通过将 ActionConfig 附加到任何支持它的组件节点来触发操作。例如,您可以响应按钮的点击事件。当用户点击该按钮时,操作将发送到您的服务器,您可以在服务器上更新组件、运行推理、流式传输新的会话项等。

1
2
3
4
5
6
7
Button(
    label="Example",
    onClickAction=ActionConfig(
      type="example",
      payload={"id": 123},
    )
)

操作也可以由您的前端通过 sendAction() 以命令式方式发送。当您需要 ChatKit 响应 ChatKit 之外发生的交互时,这非常有用;它也可用于在需要同时响应客户端和服务器(下文将详细介绍)时链接多个操作。

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

处理操作

在服务器端

默认情况下,操作会发送到您的服务器。您可以通过在 ChatKitServer 上实现 action 方法来处理服务器端的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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="item_123",
                  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 e in self.generate(context, thread):
              yield e

注意:与任何客户端/服务器交互一样,操作及其有效负载(payload)均由客户端发送,应被视为不可信数据。

在客户端

有时您希望在客户端集成中处理操作。为此,您需要通过在 ActionConfig 中添加 handler="client" 来指定该操作应发送到您的客户端操作处理程序。

1
2
3
4
5
6
7
8
Button(
    label="Example",
    onClickAction=ActionConfig(
      type="example",
      payload={"id": 123},
      handler="client"
    )
)

然后,当触发该操作时,它将被传递给您在实例化 ChatKit 时提供的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function handleWidgetAction(action: {type: string, Record<string, unknown>}) {
  if (action.type === "example") {
    const res = await doSomething(action)

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

chatKit.setOptions({
  // other options...
  widgets: { onAction: handleWidgetAction }
})

强类型操作

默认情况下,ActionActionConfig 不是强类型的。不过,我们确实在 Action 上提供了一个 create 辅助方法,可以轻松地从一组强类型操作中生成 ActionConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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.model_validate(action)

# Usage in a widget
# Action provides a create helper which makes it easy to generate
# ActionConfigs from strongly typed actions.
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)

使用组件和操作创建自定义表单

当接收用户输入的组件节点被挂载在 Form 内时,来自这些字段的值将被包含在源自该 Form 内的所有操作的 payload 中。

表单值在 payload 中以其 name 为键,例如:

  • Select(name="title")action.payload.title
  • Select(name="todo.title")action.payload.todo.title
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Form(
	direction="col",
	validation="native"
  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", type="submit")
  ]
)

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"):
          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']

	        # ...

验证

Form 使用基础的原生表单验证;它会在配置了 requiredpattern 的字段上强制执行这些规则,并在表单包含任何无效字段时阻止提交。

我们未来可能会添加具有更好用户体验、更具表达力的验证、自定义错误显示等功能的验证模式。在此之前,组件并不是处理复杂且验证逻辑繁琐的表单的最佳媒介。如果您有此需求,更好的模式是使用客户端操作处理来触发模态框(modal),在模态框中显示自定义表单,然后通过 sendAction 将结果传回 ChatKit。

Card 视为 Form

您可以将 asForm=True 传递给 Card,它将表现得像一个 Form,运行验证并将收集到的字段传递给 Card 的 confirm 操作。

有效负载键冲突

如果与有效负载中其他现有的预定义键发生命名冲突,表单值将被忽略。这通常是一个错误,因此当我们检测到这种情况时,将触发一个 error 事件。

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

使用 ActionConfig.loadingBehavior 来控制操作如何触发组件中的不同加载状态。

1
2
3
4
5
6
7
Button(
    label="This make take a while...",
    onClickAction=ActionConfig(
      type="long_running_action_that_should_block_other_ui_interactions",
      loadingBehavior="container"
    )
)
行为
auto操作将根据其使用方式进行调整。(默认值
self操作会在绑定了该操作的组件节点上触发加载状态。
container操作会在整个组件容器上触发加载状态。这会导致组件稍微淡出并变得不可交互。
none无加载状态

使用 auto 行为

通常,我们建议使用默认的 autoauto 会根据操作绑定的位置触发加载状态,例如:

  • Button.onClickActionself
  • Select.onChangeActionnone
  • Card.confirm.actioncontainer
© . This website operates independently and is not affiliated with or endorsed by OpenAI, Inc. All brand names, logos, and trademarks are the property of their respective owners.