Strona główna / Artykuły

Tworzenie agentów AI w Rust – część 8

Część 8 budowania agentów AI w Rust: kolejna warstwa agenta, z uporządkowanymi polami na kod, notatkami o przywracaniu funkcjonalności oraz listą kontrolną do jego uruchomienia.

Wydawca: REACTAPP.TOP4 min czytania883 słów

Źródła: 🦀 Building AI Agents in Rust - part 8

Niech to służy jako wersja przeznaczona dla operatorów, zawierająca zasady przedstawione w „Budowanie agentów AI w Rust – część 8”: wyraźne etapy, uporządkowane sekcje kodu oraz notatki dotyczące przywracania stanu po przeniesieniu obowiązków.

---
name: rust-typing
description: User prefers terse explanations, no hedging.
type: user
---

The user has been writing Rust for years and finds verbose explanations
annoying. Frame technical answers as a peer-to-peer conversation, not as
teaching from a tutorial.
pub async fn read_entrypoint(&self, max_bytes: usize) -> Result<Option<String>, MemoryError> {
    let path = self.dir.join("MEMORY.md");
    match fs::read_to_string(&path).await {
        Ok(text) => {
            let trimmed = text.trim();
            if trimmed.len() <= max_bytes {
                Ok(Some(trimmed.to_string()))
            } else {
                let cut = trimmed[..max_bytes]
                    .rsplit_once('\n')
                    .map(|(a, _)| a)
                    .unwrap_or(&trimmed[..max_bytes]);
                Ok(Some(format!(
                    "{cut}\n\n[memory entrypoint truncated: file exceeds {max_bytes} bytes]"
                )))
            }
        }
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
        Err(e) => Err(MemoryError::Io(e.to_string())),
    }
}
pub async fn select_relevant_memories<F, Fut>(
    query: &str,
    mem: &FileMemory,
    limit: usize,
    selector: F,
) -> Result<Vec<MemoryEntry>, MemoryError>
where
    F: FnOnce(&str, String) -> Fut,
    Fut: std::future::Future<Output = Result<Vec<String>, MemoryError>>,
{
    let headers = mem.list().await?;
    if headers.is_empty() {
        return Ok(Vec::new());
    }
    let manifest = format_manifest(&headers);
    let picked = selector(query, manifest).await?;
    let mut entries = Vec::with_capacity(limit.min(picked.len()));
    for name in picked.into_iter().take(limit) {
        if let Ok(entry) = mem.load(&name).await {
            entries.push(entry);
        }
    }
    Ok(entries)
}
#[async_trait]
pub trait VectorStore: Send + Sync + 'static {
    async fn upsert(&self, id: &str, embedding: Vec<f32>, payload: Value)
        -> Result<(), MemoryError>;

    async fn search(&self, embedding: &[f32], limit: usize)
        -> Result<Vec<VectorHit>, MemoryError>;

    async fn delete(&self, id: &str) -> Result<(), MemoryError>;
}
#[async_trait]
pub trait Embedder: Send + Sync + 'static {
    async fn embed(&self, text: &str) -> Result<Vec<f32>, MemoryError>;
}
1. embed the user's query → query_vec
2. store.search(&query_vec, 5) → top-5 hits
3. for each hit, return the payload text
let dense_hits = vec_store.search(&q_vec, 20).await?;
let sparse_hits = bm25_index.search(&query, 20).await?;
let fused = rrf(dense_hits, sparse_hits);
let final_hits = reranker.rerank(&query, fused, 5).await?;
fn tool_defs() -> Vec<Tool> {
    vec![
        Tool {
            name: "remember".into(),
            description: "Save a memory for future conversations. Provide a short kebab-case \
                          `name`, a one-line `description`, an optional `type`, and the full `body`.",
            input_schema: serde_json::json!({ /* ... */ }),
        },
        Tool {
            name: "recall".into(),
            description: "Search the memory directory by free-form query. Returns the most \
                          relevant memories (up to 5), routed by a small selector model.",
            input_schema: serde_json::json!({ /* ... */ }),
        },
    ]
}
$ ANTHROPIC_API_KEY=... cargo run -- "remember that I prefer Rust and terse answers"
[memory] /Users/me/.eugene/memory
I've saved that. I'll keep replies short and assume you'd rather see Rust than Python.
$ ANTHROPIC_API_KEY=... cargo run -- "what do you know about me?"
[memory] /Users/me/.eugene/memory
You prefer Rust over Python and want terse answers, no hedging.

Lista kontrolna operacyjna

Etap 0 dotyczący wzmocnienia bezpieczeństwa działa najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą przywracania stanu przed rozszerzaniem zakresu. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces.

Szczegół wzmocnienia bezpieczeństwa 0/836: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę, opierając się na ustalonej serii pytań, a nie na osobistych obserwacjach.

Dla pierwszego etapu ulepszeń związanych z wzmocnieniem bezpieczeństwa należy określić dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie wykonać ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu systemu. Należy rejestrować czas trwania operacji oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy proces przechodzi z środowiska demonstracyjnego do współdzielonych środowisk.

Szczegóły ulepszenia nr 1/836: należy zmierzyć czas wykonywania operacji, klasę błędów oraz zużycie tokenów dla tego etapu, a następnie zdecydować o utrzymaniu zmiany na podstawie ustalonego zestawu kryteriów, a nie jedynie informacji anegdotycznych.

Dla etapu 0 notatki dotyczącej wzmocnienia bezpieczeństwa należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, nie musząc czytać całej struktury.

Szczegóły wzmocnienia bezpieczeństwa 0/855: należy zmierzyć czas wykonywania, klasę błędów oraz zużycie tokenów dla tej notatki, a następnie zdecydować o utrzymaniu zmiany na podstawie ustalonego zestawu pytań, a nie opisów przypadków.

Gdy przechodzisz przez pierwszy etap notatki dotyczącej wzmocnienia bezpieczeństwa, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces.

Szczegół 1/855 dotyczący wzmocnienia bezpieczeństwa: zmierz czas wykonywania, klasę błędu oraz zużycie tokenów dla tej notatki, a następnie zdecyduj, czy zachować zmianę na podstawie ustalonego zestawu pytań, a nie jedynie anegdot.