Skip to content

Commit 4e89381

Browse files
committed
⚡️ Improve token optimization efficiency
Enhance the token optimization process across commit service and token optimizer modules. - Integrate `TokenOptimizer` in commit service to handle token limits effectively when generating AI prompts. - Add logging to track token usage and optimization stages, ensuring clarity and easier debugging. - Update configuration handling to account for token limits in provider configs and ensure efficient resource usage. - Modifications include refining and truncating strings based on available token budget, prioritizing diffs, commits, and file contents accordingly. This spins token budgeting with efficiency, making the AI prompt generation future-proof and scalable.
1 parent b4a45bc commit 4e89381

File tree

4 files changed

+111
-10
lines changed

4 files changed

+111
-10
lines changed

src/commit/service.rs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use crate::config::Config;
88
use crate::context::{CommitContext, GeneratedMessage};
99
use crate::git::{CommitResult, GitRepo};
1010
use crate::llm;
11-
use crate::llm_providers::LLMProviderType;
11+
use crate::llm_providers::{get_provider_metadata, LLMProviderType};
12+
use crate::log_debug;
13+
use crate::token_optimizer::TokenOptimizer;
1214

1315
/// Service for handling Git commit operations with AI assistance
1416
pub struct IrisCommitService {
@@ -93,16 +95,68 @@ impl IrisCommitService {
9395
config_clone.instruction_preset = preset.to_string();
9496
config_clone.instructions = instructions.to_string();
9597

96-
let context = self.get_git_info().await?;
98+
let mut context = self.get_git_info().await?;
9799

100+
// Get the token limit from the provider config
101+
let token_limit = config_clone
102+
.providers
103+
.get(&self.provider_type.to_string())
104+
.and_then(|p| p.token_limit)
105+
.unwrap_or_else(|| get_provider_metadata(&self.provider_type).default_token_limit);
106+
107+
// Create system prompt first to know its token count
98108
let system_prompt = create_system_prompt(&config_clone)?;
109+
110+
// Create a token optimizer to count tokens
111+
let optimizer = TokenOptimizer::new(token_limit);
112+
let system_tokens = optimizer.count_tokens(&system_prompt);
113+
114+
log_debug!("Token limit: {}", token_limit);
115+
log_debug!("System prompt tokens: {}", system_tokens);
116+
117+
// Reserve tokens for system prompt and some buffer for formatting
118+
let context_token_limit = token_limit.saturating_sub(system_tokens + 1000); // 1000 token buffer for safety
119+
log_debug!("Available tokens for context: {}", context_token_limit);
120+
121+
// Count tokens before optimization
122+
let user_prompt_before = create_user_prompt(&context);
123+
let total_tokens_before = system_tokens + optimizer.count_tokens(&user_prompt_before);
124+
log_debug!("Total tokens before optimization: {}", total_tokens_before);
125+
126+
// Optimize the context with remaining token budget
127+
context.optimize(context_token_limit);
128+
99129
let user_prompt = create_user_prompt(&context);
130+
let user_tokens = optimizer.count_tokens(&user_prompt);
131+
let total_tokens = system_tokens + user_tokens;
132+
133+
log_debug!("User prompt tokens after optimization: {}", user_tokens);
134+
log_debug!("Total tokens after optimization: {}", total_tokens);
135+
136+
// If we're still over the limit, truncate the user prompt directly
137+
let final_user_prompt = if total_tokens > token_limit {
138+
log_debug!(
139+
"Total tokens {} still exceeds limit {}, truncating user prompt",
140+
total_tokens,
141+
token_limit
142+
);
143+
let max_user_tokens = token_limit.saturating_sub(system_tokens + 100); // 100 token safety buffer
144+
optimizer.truncate_string(&user_prompt, max_user_tokens)
145+
} else {
146+
user_prompt
147+
};
148+
149+
let final_tokens = system_tokens + optimizer.count_tokens(&final_user_prompt);
150+
log_debug!(
151+
"Final total tokens after potential truncation: {}",
152+
final_tokens
153+
);
100154

101155
let mut generated_message = llm::get_refined_message::<GeneratedMessage>(
102156
&config_clone,
103157
&self.provider_type,
104158
&system_prompt,
105-
&user_prompt,
159+
&final_user_prompt,
106160
)
107161
.await?;
108162

src/config.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,10 +247,17 @@ impl ProviderConfig {
247247

248248
/// Convert to `LLMProviderConfig`
249249
pub fn to_llm_provider_config(&self) -> LLMProviderConfig {
250+
let mut additional_params = self.additional_params.clone();
251+
252+
// Add token limit to additional params if set
253+
if let Some(limit) = self.token_limit {
254+
additional_params.insert("token_limit".to_string(), limit.to_string());
255+
}
256+
250257
LLMProviderConfig {
251258
api_key: self.api_key.clone(),
252259
model: self.model.clone(),
253-
additional_params: self.additional_params.clone(),
260+
additional_params,
254261
}
255262
}
256263
}

src/git.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ impl GitRepo {
291291
if Self::is_binary_diff(&diff_string) {
292292
Ok("[Binary file changed]".to_string())
293293
} else {
294+
log_debug!("Generated diff for {} ({} bytes)", path, diff_string.len());
294295
Ok(diff_string)
295296
}
296297
}

src/token_optimizer.rs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::context::CommitContext;
2+
use crate::log_debug;
23
use tiktoken_rs::cl100k_base;
34

45
pub struct TokenOptimizer {
@@ -17,19 +18,32 @@ impl TokenOptimizer {
1718

1819
pub fn optimize_context(&self, context: &mut CommitContext) {
1920
let mut remaining_tokens = self.max_tokens;
21+
let mut total_tokens = 0;
2022

2123
// Step 1: Allocate tokens for the diffs (highest priority)
2224
for file in &mut context.staged_files {
2325
let diff_tokens = self.count_tokens(&file.diff);
24-
if diff_tokens > remaining_tokens {
26+
if total_tokens + diff_tokens > self.max_tokens {
27+
log_debug!(
28+
"Truncating diff for {} from {} tokens to {} tokens",
29+
file.path,
30+
diff_tokens,
31+
remaining_tokens
32+
);
2533
file.diff = self.truncate_string(&file.diff, remaining_tokens);
34+
total_tokens += remaining_tokens;
2635
remaining_tokens = 0;
2736
} else {
28-
remaining_tokens = remaining_tokens.saturating_sub(diff_tokens);
37+
total_tokens += diff_tokens;
38+
remaining_tokens = self.max_tokens.saturating_sub(total_tokens);
2939
}
3040

3141
if remaining_tokens == 0 {
3242
// If we exhaust the tokens in step 1, clear commits and contents
43+
log_debug!(
44+
"Token budget exhausted after diffs (total: {}), clearing commits and contents",
45+
total_tokens
46+
);
3347
Self::clear_commits_and_contents(context);
3448
return;
3549
}
@@ -38,15 +52,26 @@ impl TokenOptimizer {
3852
// Step 2: Allocate remaining tokens for recent commits (medium priority)
3953
for commit in &mut context.recent_commits {
4054
let commit_tokens = self.count_tokens(&commit.message);
41-
if commit_tokens > remaining_tokens {
55+
if total_tokens + commit_tokens > self.max_tokens {
56+
log_debug!(
57+
"Truncating commit message from {} tokens to {} tokens",
58+
commit_tokens,
59+
remaining_tokens
60+
);
4261
commit.message = self.truncate_string(&commit.message, remaining_tokens);
62+
total_tokens += remaining_tokens;
4363
remaining_tokens = 0;
4464
} else {
45-
remaining_tokens = remaining_tokens.saturating_sub(commit_tokens);
65+
total_tokens += commit_tokens;
66+
remaining_tokens = self.max_tokens.saturating_sub(total_tokens);
4667
}
4768

4869
if remaining_tokens == 0 {
4970
// If we exhaust the tokens in step 2, clear contents
71+
log_debug!(
72+
"Token budget exhausted after commits (total: {}), clearing contents",
73+
total_tokens
74+
);
5075
Self::clear_contents(context);
5176
return;
5277
}
@@ -56,18 +81,32 @@ impl TokenOptimizer {
5681
for file in &mut context.staged_files {
5782
if let Some(content) = &mut file.content {
5883
let content_tokens = self.count_tokens(content);
59-
if content_tokens > remaining_tokens {
84+
if total_tokens + content_tokens > self.max_tokens {
85+
log_debug!(
86+
"Truncating file content for {} from {} tokens to {} tokens",
87+
file.path,
88+
content_tokens,
89+
remaining_tokens
90+
);
6091
*content = self.truncate_string(content, remaining_tokens);
92+
total_tokens += remaining_tokens;
6193
remaining_tokens = 0;
6294
} else {
63-
remaining_tokens = remaining_tokens.saturating_sub(content_tokens);
95+
total_tokens += content_tokens;
96+
remaining_tokens = self.max_tokens.saturating_sub(total_tokens);
6497
}
6598

6699
if remaining_tokens == 0 {
100+
log_debug!(
101+
"Token budget exhausted after file contents (total: {})",
102+
total_tokens
103+
);
67104
return; // Exit early if we've exhausted the token budget
68105
}
69106
}
70107
}
108+
109+
log_debug!("Final token count after optimization: {}", total_tokens);
71110
}
72111

73112
// Truncate a string to fit within the specified token limit

0 commit comments

Comments
 (0)