Files
agcore/examples/streaming_events_demo.rs
T
徐涛 932a06f512 refactor(core): 完成 v0.3.2 Cargo features 拆分基础设施
- 定义 16 个 features + 4 个快捷组合,default = ["full"] 保持向后兼容
- 12 个重型依赖 optional 化(tokio/reqwest/rusqlite 等)
- 全模块 #[cfg(feature)] 门控注入(llm/tools/memory/agent/engine)
- 将 LlmProvider trait 及关联类型移出 provider 模块归属 llm(ADR-1)
- 为 session.rs bundle() 方法添加 engine feature 门控
- 更新 12 个内部文件 + 4 个示例文件的 import 路径
- 向后兼容:provider.rs 保留 pub use 重导出老路径
2026-07-19 07:58:04 +08:00

116 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! streaming_events_demo —— LLM 流式响应事件流消费(含错误路径)。
//!
//! 演示:
//! 1. `MockProvider::chat_stream` 输出标准 `StreamEvent` 流(离线可跑)
//! 2. `LlmCycle::submit_stream` 消费流
//! 3. match 各类 `StreamEvent`MessageStart / ContentBlockStart / TextDelta /
//! ContentBlockEnd / CostUpdate / MessageComplete
//! 4. 实时累计文本与解析事件计数
//! 5. 从 `MessageComplete` 拿到完整 `MessageResponse`
//! 6. **错误路径**:队列耗尽时 `chat_stream` 返回 `Err``submit_stream` 同样
//! 返回 `Err`(错误不进流,直接 fail-fast
//!
//! 运行:`cargo run --example streaming_events_demo`
use std::sync::Arc;
use agcore::llm::cycle::{CycleConfig, LlmCycle};
use agcore::llm::mock::MockProvider;
use agcore::llm::LlmProvider;
use agcore::llm::types::Usage;
use agcore::llm::types::message::{ContentBlock, Message};
use agcore::llm::types::response_v2::{MessageResponse, StopReason, StreamEvent};
use futures_util::StreamExt;
/// 构造预设的纯文本响应。
fn text_response(id: &str, text: &str) -> MessageResponse {
MessageResponse {
id: id.to_string(),
model: "mock".into(),
message: Message::Assistant {
content: vec![ContentBlock::Text { text: text.into() }],
},
usage: Usage::from_input_output(3, text.chars().count() as u32),
stop_reason: StopReason::Stop,
extra: Default::default(),
}
}
/// 消费一个流直到终止事件,并打印事件轨迹。
async fn drain_stream(stream: &mut (impl futures_util::Stream<Item = StreamEvent> + Unpin)) {
while let Some(event) = stream.next().await {
match event {
StreamEvent::MessageStart { id, model } => {
println!("[MessageStart] id={id}, model={model}");
}
StreamEvent::ContentBlockStart { index, .. } => {
println!("[ContentBlockStart] index={index}");
}
StreamEvent::TextDelta { text } => {
print!("[TextDelta] {text}");
use std::io::Write;
std::io::stdout().flush().ok();
}
StreamEvent::ContentBlockEnd { index } => {
println!("\n[ContentBlockEnd] index={index}");
}
StreamEvent::CostUpdate { usage } => {
let p = usage.prompt_tokens.unwrap_or(0);
let c = usage.completion_tokens.unwrap_or(0);
println!("[CostUpdate] prompt={p}, completion={c}");
}
StreamEvent::MessageComplete { full_response } => {
println!(
"[MessageComplete] stop_reason={:?}",
full_response.stop_reason
);
break;
}
other => {
// 未匹配的事件变体(ThinkingDelta / RefusalDelta / ToolCall* 等)
// 在 MockProvider 当前实现下不可达;保留打印以便未来 Provider 扩展时易调试。
eprintln!("[unhandled event] {other:?}");
}
}
}
}
#[tokio::main]
async fn main() {
// 1. 准备 Mock provider(仅含一轮文本响应,第二次调用会触发错误)
let provider = Arc::new(MockProvider::new(vec![text_response(
"resp-001",
"今天天气晴朗,适合户外活动。",
)]));
let dyn_provider: Arc<dyn LlmProvider> = provider.clone();
// ===== 阶段 1:正常流式响应 =====
println!("=== 阶段 1:正常流式响应 ===");
let mut cycle = LlmCycle::new_with_arc(dyn_provider.clone(), CycleConfig::default());
let mut stream = cycle
.submit_stream("讲个笑话".to_string(), vec![])
.await
.expect("阶段 1 submit_stream 应成功");
drain_stream(&mut stream).await;
// ===== 阶段 2:错误路径(队列耗尽)=====
// 队列中已无响应 → `chat_stream` 返回 `Err(LlmError::Other)` →
// `submit_stream` 用 `?` 立即传播(错误不进流,fail-fast)。
// 上层 Agent 通过 `match` 或 `?` 处理 `AgentError::Llm(_)`。
println!("\n=== 阶段 2:错误路径(队列耗尽)===");
let mut cycle = LlmCycle::new_with_arc(dyn_provider, CycleConfig::default());
let result = cycle.submit_stream("第二次提问".to_string(), vec![]).await;
match result {
Ok(_) => panic!("阶段 2 必须失败(队列耗尽)"),
Err(e) => {
eprintln!("[expected error] {e}");
assert!(
e.to_string().contains("预设响应已用完"),
"应包含 MockProvider 的队列耗尽提示"
);
}
}
println!("\n✓ streaming_events_demo 完成");
}