1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Summary / AI capability for `TerraphimService` (LLM-backed description
//! enhancement and document summarisation). Split from lib.rs as part of the
//! Gitea #1910 god-file decomposition; behaviour unchanged. Methods remain on
//! `TerraphimService`, so the public API is identical.
use terraphim_config::Role;
use terraphim_types::Document;
use super::{Result, ServiceError, TerraphimService};
impl TerraphimService {
/// Enhance document descriptions with AI-generated summaries using OpenRouter
///
/// This method uses the OpenRouter service to generate intelligent summaries
/// of document content, replacing basic text excerpts with AI-powered descriptions.
#[allow(dead_code)] // Used in 7+ places but compiler can't see due to async/feature boundaries
async fn enhance_descriptions_with_ai(
&self,
mut documents: Vec<Document>,
role: &Role,
) -> Result<Vec<Document>> {
use crate::llm::{SummarizeOptions, build_llm_from_role};
eprintln!("🤖 Attempting to build LLM client for role: {}", role.name);
let llm = match build_llm_from_role(role) {
Some(client) => {
eprintln!("✅ LLM client successfully created: {}", client.name());
client
}
None => {
eprintln!("❌ No LLM client available for role: {}", role.name);
return Ok(documents);
}
};
log::info!(
"Enhancing {} document descriptions with LLM provider: {}",
documents.len(),
llm.name()
);
let mut enhanced_count = 0;
let mut error_count = 0;
for document in &mut documents {
if self.should_generate_ai_summary(document) {
let summary_length = 250;
match llm
.summarize(
&document.body,
SummarizeOptions {
max_length: summary_length,
},
)
.await
{
Ok(ai_summary) => {
log::debug!(
"Generated AI summary for '{}': {} characters",
document.title,
ai_summary.len()
);
document.description = Some(ai_summary);
enhanced_count += 1;
}
Err(e) => {
log::warn!(
"Failed to generate AI summary for '{}': {}",
document.title,
e
);
error_count += 1;
}
}
}
}
log::info!(
"LLM enhancement complete: {} enhanced, {} errors, {} skipped",
enhanced_count,
error_count,
documents.len() - enhanced_count - error_count
);
Ok(documents)
}
/// Determine if a document should receive an AI-generated summary
///
/// This helper method checks various criteria to decide whether a document
/// would benefit from AI summarization.
#[allow(dead_code)] // Used by enhance_descriptions_with_ai, compiler can't see due to async boundaries
fn should_generate_ai_summary(&self, document: &Document) -> bool {
// Don't enhance if the document body is too short to summarize meaningfully
if document.body.trim().len() < 200 {
return false;
}
// Don't enhance if we already have a high-quality description
if let Some(ref description) = document.description {
// If the description is substantial and doesn't look like a simple excerpt, keep it
if description.len() > 100 && !description.ends_with("...") {
return false;
}
}
// Don't enhance very large documents (cost control)
if document.body.len() > 8000 {
return false;
}
// Good candidates for AI summarization
true
}
/// Get the role for the given search query
/// Generate a summary for a document using OpenRouter
///
/// This method takes a document and generates an AI-powered summary using the OpenRouter service.
/// The summary is generated based on the document's content and can be customized with different
/// models and length constraints.
///
/// # Arguments
///
/// * `document` - The document to summarize
/// * `api_key` - The OpenRouter API key
/// * `model` - The model to use for summarization (e.g., "openai/gpt-3.5-turbo")
/// * `max_length` - Maximum length of the summary in characters
///
/// # Returns
///
/// Returns a `Result<String>` containing the generated summary or an error if summarization fails.
#[cfg(feature = "openrouter")]
pub async fn generate_document_summary(
&self,
document: &Document,
api_key: &str,
model: &str,
max_length: usize,
) -> Result<String> {
use crate::openrouter::OpenRouterService;
log::debug!(
"Generating summary for document '{}' using model '{}'",
document.id,
model
);
// Create the OpenRouter service
let openrouter_service =
OpenRouterService::new(api_key, model).map_err(ServiceError::OpenRouter)?;
// Use the document body for summarization
let content = &document.body;
if content.trim().is_empty() {
return Err(ServiceError::Config(
"Document body is empty, cannot generate summary".to_string(),
));
}
// Generate the summary
let summary = openrouter_service
.generate_summary(content, max_length)
.await
.map_err(ServiceError::OpenRouter)?;
log::info!(
"Generated {}-character summary for document '{}' using model '{}'",
summary.len(),
document.id,
model
);
Ok(summary)
}
/// Generate a summary for a document using OpenRouter (stub when feature is disabled)
#[cfg(not(feature = "openrouter"))]
pub async fn generate_document_summary(
&self,
_document: &Document,
_api_key: &str,
_model: &str,
_max_length: usize,
) -> Result<String> {
Err(ServiceError::Config(
"OpenRouter feature not enabled during compilation".to_string(),
))
}
}