DEV Community

Cover image for "You Got This Error Last Week" — Building an AI That Remembers Your Past Errors
hiyoyo
hiyoyo

Posted on

"You Got This Error Last Week" — Building an AI That Remembers Your Past Errors

If this is useful, a ❤️ helps others find it.

All tests run on an 8-year-old MacBook Air.

The same error appears twice. Most AI tools diagnose it twice — two API calls, same answer.

HiyokoHelper remembers. When the same error appears again, it responds instantly from cache: "💡 先日も同じケースが発生し、〇〇で解決しました"

Here's how the history cache works.


The data structure

#[derive(Serialize, Deserialize, Clone)]
pub struct HistoryEntry {
    pub error_hash: String,
    pub error_preview: String,
    pub diagnosis: String,
    pub resolved: bool,
    pub created_at: u64,
    pub hit_count: u32,
}
Enter fullscreen mode Exit fullscreen mode

Stored in history.json via tauri-plugin-store. Local only, never leaves the machine.


Normalizing before hashing

Same error, different timestamps → same hash:

pub fn normalize_error(text: &str) -> String {
    let mut result = text.to_string();

    let timestamp_re = Regex::new(r"\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}").unwrap();
    result = timestamp_re.replace_all(&result, "[TIMESTAMP]").to_string();

    let line_re = Regex::new(r"line \d+").unwrap();
    result = line_re.replace_all(&result, "line [N]").to_string();

    let pid_re = Regex::new(r"\bpid[: ]\d+").unwrap();
    result = pid_re.replace_all(&result, "pid [PID]").to_string();

    result.split_whitespace().collect::>().join(" ")
}
Enter fullscreen mode Exit fullscreen mode

The lookup flow

pub async fn diagnose_with_history(
    input: &str,
    api_key: &str,
    history: &mut HistoryCache,
) -> DiagnosisResult {
    let hash = error_hash(input);

    if let Some(entry) = history.get(&hash) {
        entry.hit_count += 1;
        let msg = if entry.resolved {
            format!("💡 先日も同じエラーが発生し、解決済みです。\n\n{}", entry.diagnosis)
        } else {
            format!("⚠️ このエラーは以前も発生しています({}回目)。\n\n{}", entry.hit_count, entry.diagnosis)
        };
        return DiagnosisResult::FromHistory { diagnosis: entry.diagnosis.clone(), message: msg };
    }

    let diagnosis = call_gemini(input, api_key).await?;
    history.insert(hash, HistoryEntry {
        error_hash: hash.clone(),
        error_preview: input.chars().take(100).collect(),
        diagnosis: diagnosis.clone(),
        resolved: false,
        created_at: unix_now(),
        hit_count: 1,
    });

    DiagnosisResult::Fresh { diagnosis }
}
Enter fullscreen mode Exit fullscreen mode

The "resolved" button

pub fn mark_resolved(history: &mut HistoryCache, hash: &str) {
    if let Some(entry) = history.get_mut(hash) {
        entry.resolved = true;
    }
    history.save();
}
Enter fullscreen mode Exit fullscreen mode

Next time: "You had this issue and resolved it. Here's what worked."


Cache eviction

Unresolved entries older than 30 days evicted. Resolved entries kept forever.

pub fn evict_old_entries(history: &mut HistoryCache) {
    let cutoff = unix_now() - (30 * 24 * 60 * 60);
    history.entries.retain(|_, entry| {
        entry.created_at > cutoff || entry.resolved
    });
}
Enter fullscreen mode Exit fullscreen mode

HiyokoHelper (OSS) → github.com/hiyoyok/HiyokoHelper
X → @hiyoyok

Top comments (0)