Створення AI-агентів на Rust – частина 8
Частина 8 книги про створення AI-агентів на Rust: наступний рівень агента з впорядкованими слотами для коду, примітками щодо відновлення та чек-листом для його роботи.
Використовуйте цей документ як оновлену версію ідей з «Створення AI-агентів на Rust – частина 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 щодо зпрочнення: виміряйте час виконання, клас помилки та кількість витрачених токенів для цього етапу, а потім вирішіть, чи залишити зміни, ґрунтуючись на фіксованому наборі критеріїв, а не на індивідуальних спостереженнях.