Галоўная / Артыкулы

AI-агенты на Rust, частка 8

Частка 8 серыі пра AI-агентаў на Rust: наступны пласт агента з упарадкаванымі фрагментамі кода, нататкамі пра аднаўленне і эксплуатацыйным чэклістам.

Выдавец: REACTAPP.TOP4 хв чытання883 слоў

Крыніцы: 🦀 Building AI Agents in Rust - part 8

Існавайце гэта як перапрацоўаны варыянт ідэй з “Building AI Agents in Rust - part 8” для аператараў: чыстыя этапы, арганізаваныя блакі коду і прыметкі з восстанавлення, якія застаюцца пасля перадачы.

---
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.

Чэрніцкі ліст аператыўных дзеянняў

Этап 0 з прыметкамі паўнэйшага захавання работае лепей, калі яго спрыяваць як вімерную плошчу. Запісаўце адну ідэальную транскрыпцыю, адзін кейс неудачы і прыметкі з вярнення да пачатковага стану прычым расшырэнню масштаба. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісьці крок не выйшаў, неудача должна вказваць на адну конкрэтную адпаведальнасць, а не на заплутаны працэс.

Дзялянка паўнэйшага захавання 0/836: звярніце увагу на час выканання, класыя ошыбакі і вітрату токенав для гэтай прыметкі, а пасля рашыце, чы хацяце застаўці змяну, спыраючыся на фіксаваны набор пытанняў, а не на індывідуальныя спостарожэння.

Для першага пасэгу з ударожвання неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крытэрыя завершэння пры зміне коду. Аператары должны магчыма ўвайсці крок з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Запісвайце час выканання і вартасьць токена або запыту паляглі разам з функцыйнальнымі рэзультатамі. Відразліва візуабельнасьць вартасцей запобегае неспакоўным рахункам, калі працэс пераходзіць з дэмаверсіі ў спяльныя асяроддзя.

Дзеянне ударожвання 1/836: змерыце час выканання, класію паканаў і выкарыстаны токен для гэтага пасэгу, а потым вынікніце, чы хацяце застаўіць зміны, адпаведна фіксаванаму набору пытанняў, а не індывідуальным спостараванням.

Для этапы 0 пры практыкаванні змецчэння неабяжна прадзерабатаваць параметры вхідных дадзеных, адпраўніка крока і крытэрыя завершэння пры змены коду. Аперацыйныя працавнікі павінны магчымае перазапускаць крок з вядомай точкі контролю, не падозрэўаючы пра схованы стан. Конфігурацыю трэба захаваць праз аддзел коду прыкладнення. Файлы сераўіса, хранальнікі секрэтных дадзеных і флагі функцыйяў павінны знаходзіцца ў адном месцы, якое працавнікі можуць пераглядаць, не чытаючы весь код.

Дзялейны пункт змецчэння 0/855: вы мераваеце час выканання, класію адказоў і колькасць выкорыстоўваных токеноў для гэтага пункту, а потым выявляеце, чы хацеце застаўіць змену, спынюючыся на апранаванай сэтцы пытанняў, а не на індывідуальных спостараваннях.

Калі працюеце над першым этапам зміцнення, спачатку запісайце умовы кантракта: неабяжлівыя даны, сігнал успеху і тое, што выходзіць пад частковыя неудачы. Такі спіс контролю дапамагае залішацца чыстым пад будучыя зміны коду. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выходзіць, неудача должна паказваць на адну конкрэтную відпаведальнасць, а не на заплутаны ланцюг задач.

Дзеянне зміцнення 1/855: вымерыце час выканання, класію памылак і колькасць выкарыстоўваных токенав для гэтага пункту, а потым выберыце, чы робіць зміны на адной основе фіксаванага набору пытанняў, а не на адной лячбе.