注意
- 仅在一对一聊天中支持流式处理代理消息。
- Teams 每次仅支持每个聊天一次一个并发流式处理响应。
- 流式处理已在 Web、桌面和移动设备上正式发布。
您可以流式传输代理消息,在生成完整响应的同时将代理的响应作为小更新传递给用户,从而增强用户体验。 通常,代理需要很长时间才能在不更新用户界面的情况下生成响应,从而导致体验不太吸引人。
当用户观察到代理实时处理他们的请求时,可以增加他们的满意度和信任度。 这种感知到的响应能力和透明度增强了用户参与度并减少了与代理的对话放弃。
Stream 消息用户体验
流式处理代理消息具有两种类型的更新:
信息性更新:信息性更新在聊天底部显示为蓝色进度栏。 在生成响应时,它会通知用户代理正在进行的操作。
信息性消息不得超过 1 KB 或 1000 个字符。
响应流:响应流显示为键入指示器。 在生成完整响应时,它会将代理对用户的响应显示为小更新。
停止按钮:通过该
按钮,用户可以通过提前停止流式处理响应来控制流式处理响应。 默认情况下,它在流媒体期间可用,允许用户优化提示或发送新提示。 了解停止流式处理按钮的工作原理有助于设计更有效、用户友好的对话界面。流式处理内容:在流式处理时,代理消息必须包含之前的流式处理内容。
例如:这是可接受的流响应示例。
A 棕色
一只棕色的狐狸
一只棕色狐狸越过围栏非示例:这是将返回错误的流式处理响应示例。
A 棕色
你好有关错误的详细信息,请参阅 错误代码。
使用 Teams SDK 实现流式传输
用于 Stream.Update 在开始消息流之前编写信息性更新。
Stream.Update 可以使用不同的更新文本多次调用。
用于 Stream.Emit 将内容块写入流。 Teams 收到区块后,它们将呈现到消息中。 第一次调用 Stream.Emit后,将不再显示信息性更新, Stream.Update 并且不会起作用。
app.OnMessage(async (context, cancellationToken) =>
{
context.Stream.Update("Testing");
await Task.Delay(1000);
context.Stream.Emit("hello");
context.Stream.Emit(", ");
context.Stream.Emit("world!");
});
用于 stream.update 在开始消息流之前编写信息性更新。
stream.update 可以使用不同的更新文本多次调用。
用于 stream.emit 将内容块写入流。 Teams 收到区块后,它们将呈现到消息中。 第一次调用 stream.emit后,将不再显示信息性更新, stream.update 并且不会起作用。
app.on('message', async ({ activity, stream }) => {
stream.update("Thinking...");
await new Promise(resolve => setTimeout(resolve, 1000))
stream.emit('hello');
stream.emit(', ');
stream.emit('world!');
// result message: "hello, world!"
});
用于 stream.update 在开始消息流之前编写信息性更新。
stream.update 可以使用不同的更新文本多次调用。
用于 stream.emit 将内容块写入流。 Teams 收到区块后,它们将呈现到消息中。 第一次调用 stream.emit后,将不再显示信息性更新, stream.update 并且不会起作用。
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
ctx.stream.update("Stream starting...")
await asyncio.sleep(1)
# Stream messages with delays using ctx.stream.emit
for message in STREAM_MESSAGES:
# Add some randomness to timing
await asyncio.sleep(random())
ctx.stream.emit(message)
有关使用扩展 Markdown 格式化流式消息的指南(包括支持的功能和语法),请参阅 格式化代理消息。
通过 REST API Stream 消息
代理消息可以通过 REST API 流式传输。 流式处理消息支持富文本和引文。 附件、AI 标签、反馈按钮和敏感度标签仅适用于最终流式处理邮件。 有关详细信息,请参阅包含 AI 生成内容的附件和代理消息。
当代理通过 REST API 调用流式处理时,请确保仅在收到初始 API 调用的成功响应后才调用下一个流式处理 API。 如果您的代理使用 SDK,请验证您是否从发送活动方法收到空响应对象,以确认上一个调用已成功传输。
当代理调用流式处理 API 太快时,可能会遇到问题,流式处理体验可能会中断。 我们建议代理一次流式传输一条消息,以确保它以一致的速度调用流式处理 API。 否则,请求可能会受到限制。 将模型中的令牌缓冲 1.5 到 2 秒,以确保流式处理过程顺利进行。
以下是流式处理代理消息的属性:
| 属性 | 必需 | 说明 |
|---|---|---|
type |
✔️ | 支持的值为 typingmessage或 。
• typing:在流式传输邮件时使用。
• message:用于最终流式传输的消息。 |
text |
✔️ | 要流式传输的消息的内容。 |
entities.type |
✔️ | 必须是 streamInfo |
entities.streamId |
✔️ |
streamId 从初始流式处理请求开始流 式处理。 |
entities.streamType |
流式处理更新的类型。 支持的值为 informative、 或 streamingfinal。 默认值为 streaming。
final 仅在最终消息中使用。 |
|
entities.streamSequence |
✔️ | 每个请求的增量整数。 |
注意
下面是用于 streamSequence REST API 的要求:
- 第一个必须是数字“1”。
- 除最终) 外 (的后续数字必须是单调递增整数 (,例如 1-2-3>>) 。
- 对于最终消息,
streamSequence不得设置。
要在代理中启用流式处理,请执行以下步骤:
开始流式处理
代理可以发送信息性消息或流式消息作为其初始通信。 响应包括 streamId,这对于执行后续调用非常重要。
代理可以在处理用户的请求时发送多个信息更新,例如扫描 文档、 汇总内容和 找到相关工作项。 可以在代理生成对用户的最终响应之前发送这些更新。
//Ex: An agent sends the first request with content & the content is informative loading message.
POST /conversations/<conversationId>/activities HTTP/1.1
{
"type": "typing",
"serviceurl": "https://smba.trafficmanager.net/amer/",
"channelId": "msteams",
"from": {
"id": "<botId>",
"name": "<BotName>"
},
"conversation": {
"conversationType": "personal",
"id": "<conversationId>"
},
"recipient": {
"id": "<recipientId>",
"name": "<recipientName>",
"aadObjectId": "<recipient aad objecID>"
},
"locale": "en-US",
"text": "Searching through documents...", //(required) first informative loading message.
"entities":[
{
"type": "streaminfo",
"streamType": "informative", // informative or streaming; default= streaming.
"streamSequence": 1 // (required) incremental integer; must be present for start and continue streaming request, but must not be set for final streaming request.
}
],
}
201 created { "id": "a-0000l" } // return stream id
下图是开始流式传输的示例:
继续流式传输
使用从初始请求收到的信息发送 streamId 信息性消息或流式消息。 您可以 从信息更新开始 ,然后在最终响应准备就绪时 切换到响应流 。
从信息更新开始
代理生成响应时,向用户发送信息性更新,例如扫描 文档、汇 总内容和 找到相关工作项。 确保仅在座席收到上一次呼叫的成功响应后才进行后续呼叫。
// Ex: An agent sends the second request with content & the content is informative loading message.
POST /conversations/<conversationId>/activities HTTP/1.1
{
"type": "typing",
"serviceurl": "https://smba.trafficmanager.net/amer/",
"channelId": "msteams",
"from": {
"id": "<botId>",
"name": "<BotName>"
},
"conversation": {
"conversationType": "personal",
"id" : "<conversationId>"
},
"recipient": {
"id": "<recipientId>",
"name": "<recipientName>",
"aadObjectId": "<recipient aad objecID>"
},
"locale": "en -US",
"text": "Searching through emails...", // (required) second informative loading message.
"entities":[
{
"type": "streaminfo",
"streamId": "a-0000l", // // (required) must be present for any subsequent request after the first chunk.
"streamType": "informative", // informative or streaming; default= streaming.
"streamSequence": 2 // (required) incremental integer; must be present for start and continue streaming request, but must not be set for final streaming request.
}
],
}
202 0K { }
下图是提供信息性更新的代理示例:
切换到响应流
代理准备好为用户生成最终消息后,从提供信息性更新切换到提供响应流。 对于每个响应流更新,消息内容应是最终消息的最新版本。 这意味着代理应合并大型语言模型 (LLM) 生成的任何新令牌。 将这些令牌追加到上一个消息版本,然后将其发送给用户。
限制为每秒 1 个请求。 您必须确保代理在此限制内发送请求。 代理可以根据需要以较慢的速率发送请求。
// Ex: An agent sends the third request with content & the content is actual streaming content.
POST /conversations/<conversationId>/activities HTTP/1.1
{
"type": "typing",
"serviceurl" : "https://smba.trafficmanager.net/amer/ ",
"channelId": "msteams",
"from": {
"id": "<botId>",
"name": "<BotName>"
},
"conversation": {
"conversationType": "personal",
"id" : "<conversationId>"
},
"recipient": {
"id" : "<recipientId>",
"name": "<recipientName>",
"aadObjectId": "<recipient aad objecID>"
},
"locale": "en-US" ,
"text": "A brown fox", // (required) first streaming content.
"entities":[
{
"type": "streaminfo",
"streamId": "a-0000l", // // (required) must be present for any subsequent request after the first chunk.
"streamType": "streaming", // informative or streaming; default= streaming.
"streamSequence": 3 // (required) incremental integer; must be present for start and continue streaming request, but must not be set for final streaming request.
}
],
}
202 0K{ }
// Ex: An agent sends the fourth request with content & the content is actual streaming content.
POST /conversations/<conversationId>/activities HTTP/1.1
{
"type": "typing",
"serviceurl" : "https://smba.trafficmanager.net/amer/ ",
"channelId": "msteams",
"from": {
"id": "<botId>",
"name": "<BotName>"
},
"conversation": {
"conversationType": "personal",
"id" : "<conversationId>"
},
"recipient": {
"id" : "<recipientId>",
"name": "<recipientName>",
"aadObjectId": "<recipient aad objecID>"
},
"locale": "en-US" ,
"text": "A brown fox jumped over the fence", // (required) first streaming content.
"entities":[
{
"type": "streaminfo",
"streamId": "a-0000l", // // (required) must be present for any subsequent request after the first chunk.
"streamType": "streaming", // informative or streaming; default= streaming.
"streamSequence": 4 // (required) incremental integer; must be present for start and continue streaming request, but must not be set for final streaming request.
}
],
}
202 0K{ }
下图是代理以区块形式提供更新的示例:
最终流式传输
代理完成生成消息后,将结束流信号与最终消息一起发送。 对于最终消息, type 活动 message为 。 在这里,代理设置允许用于常规消息活动但 final 唯一允许 streamType值的任何字段。
// Ex: An agent sends the second request with content && the content is informative loading message.
POST /conversations/<conversationId>/activities HTTP/1.1
{
"type": "message",
"serviceurl" : "https://smba.trafficmanager.net/amer/ ",
"channelId": "msteams",
"from": {
"id": "<botId>",
"name": "<BotName>"
},
"conversation": {
"conversationType": "personal",
"id" : "<conversationId>"
},
"recipient": {
"id" : "recipientId>",
"name": "<recipientName>",
"aadObjectId": "<recipient aad objecID>"
},
"locale": "en-US",
"text": "A brown fox jumped over the fence.", // (required) first streaming content.
"entities":[
{
"type": "streaminfo",
"streamId": "a-0000l", // // (required) must be present for any subsequent request after the first chunk.
"streamType": "final", // (required) final is only allowed for the last message of the streaming.
}
],
}
202 0K{ }
下图是代理最终响应的示例:
停止流式处理代理响应
该按钮允许用户控制流式处理响应。 在流式传输期间,默认情况下“ 停止” 按钮可用,允许用户提前停止响应。 用户可以中断消息流并优化提示或发送新提示。 它增强了与代理的对话管理,以获得更好的用户体验。
用户停止消息生成后:
代理在对话中将停止的回复视为不完整或丢弃。
代理无法更改已流式传输的内容。
如果代理继续对用户停止的消息进行流式传输,则会生成以下错误:
错误详细信息 说明 Http 状态代码 403 错误代码 ContentStreamNotAllowed错误消息 用户已取消内容流。 说明 流式处理已被用户停止。
响应代码
以下是成功代码和错误代码:
成功代码
| Http 状态代码 | 返回值 | 说明 |
|---|---|---|
201 |
streamId,这与activityId{"id":"1728640934763"} |
代理在发送初始流式处理请求后返回此值。
对于任何后续的流式处理请求,是必需的 streamId 。 |
202 |
{} |
任何后续流式处理请求的成功代码。 |
错误代码
| Http 状态代码 | 错误代码 | 错误消息 | 说明 |
|---|---|---|---|
202 |
ContentStreamSequenceOrderPreConditionFailed |
PreCondition failed exception when processing streaming activity. |
很少有流式处理请求可能会按顺序到达并被丢弃。 以无序方式接收请求时,将使用最新的流式处理请求(由 streamSequence确定)。 确保按顺序发送每个请求。 |
400 |
BadRequest |
根据不同的场景,你可能会遇到各种错误消息,例如 Start streaming activities should include text |
传入的有效负载不遵守或不包含必要的值。 |
403 |
ContentStreamNotAllowed |
Content stream is not allowed |
用户或代理不允许使用流式处理 API 功能。 |
403 |
ContentStreamNotAllowed |
Content stream is not allowed on an already completed streamed message |
代理无法连续流式传输已流式传输并完成的消息。 |
403 |
ContentStreamNotAllowed |
Content stream finished due to exceeded streaming time. |
代理未能在两分钟的严格时间限制内完成流式传输过程。 |
403 |
ContentStreamNotAllowed |
Message size too large |
代理发送的消息超过当前 消息大小 限制。 |
403 |
ContentStreamNotAllowed |
Content stream was canceled by user |
流式处理已被用户停止。 |
403 |
ContentStreamNotAllowed |
Request streamed content should contain the previously streamed content |
流消息的传入内容不包含已流式传输的内容。 |
429 |
不适用 | API calls quota exceeded |
代理流式传输的消息数已超出配额。 |
代码示例
| 示例名称 | Description | Node.js | C# | Python |
|---|---|---|---|---|
| Teams 流式处理代理示例 | 此示例应用可用于在 Teams 中使用 Azure Open AI 和 Bot Framework v4 进行个人范围的流式处理方案。 | 不适用 | View | 不适用 |
| 对话流代理 | 这是使用 Teams SDK 的对话流式处理代理。 | View | View | View |