98dfe6c1ed
Phase 5 三个 Step 全部落地: Step 5.2 — Ollama Provider - 新增 OllamaProvider newtype 包装(默认 localhost:11434/v1,零 API key) - ProviderType 新增 Ollama 变体与 FromStr 解析 Step 5.3 — #[non_exhaustive] 前置标记 - ProviderType / StopReason / FinishReason / EvictionPolicy 加 #[non_exhaustive] - 编译期兼容护栏,避免下游 silent break Step 5.1 — ProviderConfig 扩展 - 加 timeout_secs / max_retries 字段、Default、from_env(prefix) - create_provider 各分支通过 pub(crate) from_parts 一次性构造并注入 timeout (同时避开 Anthropic 的 default_headers 与双重 client 构造) - map_reqwest_error 改为方法读取 self.timeout_secs(移除硬编码 120s) - AnthropicProvider::with_timeout 同值短路,with_client 标 #[deprecated] - DeepSeek / Qwen 加公开 with_client,new_with_client 走代理 - 7 个新测试:5 个 from_env 单元测试 + 3 个 timeout 传导 wiremock (OpenAI Chat / DeepSeek / Anthropic) - Cargo.toml 加 temp-env dev-dep
87 lines
2.8 KiB
Rust
87 lines
2.8 KiB
Rust
use std::env;
|
||
|
||
use agcore::init_tracing;
|
||
use agcore::llm::{
|
||
cycle::{CycleConfig, LlmCycle},
|
||
provider::{ProviderConfig, ProviderType, create_provider},
|
||
types::{message::ContentBlock, message::Message, response_v2::MessageResponse},
|
||
};
|
||
|
||
fn extract_response_text(response: &MessageResponse) -> &str {
|
||
// Phase 0:MessageResponse.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::message::Message::Assistant { content } => {
|
||
for block in content {
|
||
if let ContentBlock::Text { text } = block {
|
||
return text.as_str();
|
||
}
|
||
}
|
||
""
|
||
}
|
||
_ => "",
|
||
}
|
||
}
|
||
|
||
#[tokio::main]
|
||
async fn main() {
|
||
dotenvy::dotenv().ok();
|
||
init_tracing();
|
||
|
||
let api_key = env::var("OPENAI_API_KEY").expect("未设置 OPENAI_API_KEY 环境变量");
|
||
let base_url = env::var("OPENAI_BASE_URL").expect("未设置 OPENAI_BASE_URL 环境变量");
|
||
let model = env::var("OPENAI_MODEL").expect("未设置 OPENAI_MODEL 环境变量");
|
||
|
||
let provider_type = env::var("PROVIDER")
|
||
.unwrap_or_else(|_| "openai".into())
|
||
.parse::<ProviderType>()
|
||
.expect("无效的 PROVIDER 值");
|
||
|
||
let config = ProviderConfig {
|
||
base_url,
|
||
api_key,
|
||
model: model.clone(),
|
||
timeout_secs: 30,
|
||
max_retries: 3,
|
||
};
|
||
|
||
let provider = create_provider(provider_type, config).expect("创建 Provider 失败");
|
||
|
||
let cycle_config = CycleConfig {
|
||
model,
|
||
max_tokens: Some(65536),
|
||
temperature: Some(1.3),
|
||
..CycleConfig::default()
|
||
};
|
||
|
||
let mut cycle = LlmCycle::new(provider, cycle_config).with_messages(vec![Message::system(
|
||
"你是一个简洁的助手,对于任何问题都是用一句话回答。",
|
||
)]);
|
||
|
||
println!("发送请求...");
|
||
|
||
match cycle.submit("介绍一下你自己吧。".to_string(), vec![]).await {
|
||
Ok(response) => {
|
||
println!("LLM 回复:{}", extract_response_text(&response));
|
||
println!(
|
||
"Token 用量:{} 输入, {} 输出",
|
||
response.usage.prompt_tokens, response.usage.completion_tokens
|
||
);
|
||
}
|
||
Err(e) => {
|
||
eprintln!("请求失败:{e}");
|
||
std::process::exit(1);
|
||
}
|
||
}
|
||
}
|