feat(llm): 新增 IR 类型层 + 切换 LlmProvider trait 签名

引入跨 Provider 统一的新类型系统:
- Message 扁平大枚举(System/User/UserImage/Assistant/ToolResult)
- MessageRequest + MessageResponse + StreamEvent(高精度 IR)
- PartialMessageResponse 含 apply_to/finalize 汇聚算法
- ProviderType enum 扩展 + ProviderCapabilities

切换 LlmProvider trait 签名为 chat(MessageRequest) → MessageResponse,
chat_stream 返回 Stream<Item = Result<StreamEvent, LlmError>>,新增 capabilities()。

StreamEvent 命名冲突处理:旧变体迁移至 old_stream::LegacyStreamEvent,
stream.rs 通过 pub use 重新导出新高精度事件。

OpenaiProvider 添加 Phase 0 临时桥接(MessageRequest ↔ OpenaiChatRequest 转换),
标注 ponytail: Phase 0 临时桥接 标记,Phase 1 重写时移除。

测试 147 passed / 0 failed(新增 31 个新类型测试)。
This commit is contained in:
徐涛
2026-07-02 21:58:07 +08:00
parent 925c8f9729
commit 7c299f1cfd
18 changed files with 2605 additions and 441 deletions
+23 -12
View File
@@ -4,23 +4,34 @@ use agcore::init_tracing;
use agcore::llm::{
cycle::{CycleConfig, LlmCycle},
provider::{create_provider, ProviderConfig, ProviderType},
types::{ChatResponse, OpenaiContentPart},
types::{
message::ContentBlock, response_v2::MessageResponse,
},
};
fn extract_response_text(response: &ChatResponse) -> &str {
fn extract_response_text(response: &MessageResponse) -> &str {
// Phase 0MessageResponse.text() 直接给出拼接好的 Assistant 文本。
if !response.text().is_empty() {
// ⚠️ 返回的是 owned String 的引用,调用方需在 response 生命周期内使用。
// 此 example 短命,足以演示。
return response_text_ref(response);
}
"[无文本内容]"
}
fn response_text_ref(response: &MessageResponse) -> &str {
// ponytail: example 助手 —— 不在正式 crate API 中,单纯绕开 borrow 限制。
// 真实调用请直接使用 `response.text()` 拿到 owned String。
match &response.message {
agcore::llm::types::OpenaiChatMessage::Assistant { content, .. } => match content {
agcore::llm::types::ContentField::String(s) => s,
agcore::llm::types::ContentField::Array(parts) => {
for part in parts {
if let OpenaiContentPart::Text { text } = part {
return text;
}
agcore::llm::types::message::Message::Assistant { content } => {
for block in content {
if let ContentBlock::Text { text } = block {
return text.as_str();
}
"[无文本内容]"
}
},
_ => "[非 assistant 消息]",
""
}
_ => "",
}
}