vectorless/summarizer/
llm.rs1use async_openai::{
10 types::chat::{ChatCompletionRequestSystemMessage, ChatCompletionRequestUserMessage, CreateChatCompletionRequestArgs},
11 Client,
12 config::OpenAIConfig,
13 error::OpenAIError,
14};
15use thiserror::Error;
16use crate::config::SummaryConfig;
17
18#[derive(Debug, Error)]
20pub enum LlmError {
21 #[error("API error: {0}")]
23 Api(String),
24
25 #[error("Request error: {0}")]
27 Request(String),
28
29 #[error("Configuration error: {0}")]
31 Config(String),
32}
33
34impl From<OpenAIError> for LlmError {
35 fn from(e: OpenAIError) -> Self {
36 LlmError::Api(e.to_string())
37 }
38}
39
40fn get_api_key_from_env() -> Option<String> {
44 std::env::var("OPENAI_API_KEY").ok()
45 .or_else(|| std::env::var("ANTHROPIC_API_KEY").ok())
46 .or_else(|| std::env::var("AZURE_OPENAI_API_KEY").ok())
47}
48
49fn get_api_base_from_env() -> Option<String> {
51 std::env::var("OPENAI_API_BASE").ok()
52 .or_else(|| std::env::var("OPENAI_BASE_URL").ok())
53 .or_else(|| std::env::var("AZURE_OPENAI_ENDPOINT").ok())
54}
55
56pub async fn summarize(
61 config: &SummaryConfig,
62 text: &str,
63) -> Result<String, LlmError> {
64 let api_key = config.api_key.as_ref()
66 .cloned()
67 .or_else(get_api_key_from_env)
68 .ok_or_else(|| LlmError::Config(
69 "No API key found. Set OPENAI_API_KEY environment variable or configure in SummaryConfig.".to_string()
70 ))?;
71
72 let api_base = if config.endpoint.is_empty() || config.endpoint == "https://api.openai.com/v1" {
74 get_api_base_from_env().unwrap_or_else(|| config.endpoint.clone())
75 } else {
76 config.endpoint.clone()
77 };
78
79 let openai_config = OpenAIConfig::new()
80 .with_api_key(api_key)
81 .with_api_base(&api_base);
82 let client = Client::with_config(openai_config);
83
84 let truncated = if text.len() > 8000 { &text[..8000] } else { text };
85
86 let request = CreateChatCompletionRequestArgs::default()
87 .model(&config.model)
88 .messages(
89 [
90 ChatCompletionRequestSystemMessage::from(
92 "You are a helpful assistant that summarizes text concisely and accurately. \
93 Summarize the user's text in 2-3 sentences. Be specific and factual. \
94 Do not add anything not in the original text."
95 ).into(),
96 ChatCompletionRequestUserMessage::from(truncated).into(),
98 ]
99 )
100 .temperature(1.0)
101 .build()
102 .map_err(|e| LlmError::Request(e.to_string()))?;
103
104 let response = client.chat().create(request).await?;
105 let content = response
106 .choices
107 .first()
108 .map(|choice| choice.message.content.clone())
109 .unwrap_or_default();
110
111 content.ok_or_else(|| LlmError::Api("LLM returned no content".to_string()))
112}