الذاكرة
ذاكرة دلالية طويلة المدى لوكلاء الذكاء الاصطناعي باستخدام adk-memory.
نظرة عامة
يوفر نظام الذاكرة تخزينًا دائمًا وقابلاً للبحث لمحادثات الوكلاء. على عكس حالة الجلسة (التي تكون مؤقتة)، تستمر الذاكرة عبر الجلسات وتمكن الوكلاء من استدعاء السياق ذي الصلة من التفاعلات السابقة.
التثبيت
[dependencies]
adk-memory = "2.0.0"
المفاهيم الأساسية
MemoryEntry
سجل ذاكرة واحد يحتوي على المحتوى والمؤلف والطابع الزمني:
use adk_memory::MemoryEntry;
use adk_core::Content;
use chrono::Utc;
let entry = MemoryEntry {
content: Content::new("user").with_text("I prefer dark mode"),
author: "user".to_string(),
timestamp: Utc::now(),
};
MemoryService Trait
السمة الأساسية لخلفيات الذاكرة:
#[async_trait]
pub trait MemoryService: Send + Sync {
/// Store session memories for a user
async fn add_session(
&self,
app_name: &str,
user_id: &str,
session_id: &str,
entries: Vec<MemoryEntry>,
) -> Result<()>;
/// Search memories by query
async fn search(&self, req: SearchRequest) -> Result<SearchResponse>;
}
SearchRequest
معلمات الاستعلام للبحث في الذاكرة:
use adk_memory::SearchRequest;
let request = SearchRequest {
query: "user preferences".to_string(),
user_id: "user-123".to_string(),
app_name: "my_app".to_string(),
limit: None,
min_score: None,
project_id: None, // None = global only, Some("id") = global + project
};
InMemoryMemoryService
تطبيق بسيط في الذاكرة للتطوير والاختبار:
use adk_memory::{InMemoryMemoryService, MemoryService, MemoryEntry, SearchRequest};
use adk_core::Content;
use chrono::Utc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let memory = InMemoryMemoryService::new();
// Store memories from a session
let entries = vec![
MemoryEntry {
content: Content::new("user").with_text("I like Rust programming"),
author: "user".to_string(),
timestamp: Utc::now(),
},
MemoryEntry {
content: Content::new("assistant").with_text("Rust is great for systems programming"),
author: "assistant".to_string(),
timestamp: Utc::now(),
},
];
memory.add_session("my_app", "user-123", "session-1", entries).await?;
// Search memories
let request = SearchRequest {
query: "Rust".to_string(),
user_id: "user-123".to_string(),
app_name: "my_app".to_string(),
limit: None,
min_score: None,
project_id: None,
};
let response = memory.search(request).await?;
println!("Found {} memories", response.memories.len());
Ok(())
}
عزل الذاكرة
يتم عزل الذكريات بواسطة:
- app_name: التطبيقات المختلفة لها مساحات ذاكرة منفصلة
- user_id: ذكريات كل مستخدم خاصة
- project_id (اختياري): يمكن تحديد نطاق الإدخالات لمشروع داخل مستخدم
// User A's memories
memory.add_session("app", "user-a", "sess-1", entries_a).await?;
// User B's memories (separate)
memory.add_session("app", "user-b", "sess-1", entries_b).await?;
// Search only returns user-a's memories
let request = SearchRequest {
query: "topic".to_string(),
user_id: "user-a".to_string(),
app_name: "app".to_string(),
limit: None,
min_score: None,
project_id: None, // None = global entries only
};
الذاكرة ذات النطاق المحدد للمشروع
يمكن تحديد نطاق الذكريات لمشروع داخل مستخدم. يصبح مفتاح العزل (app_name, user_id, project_id?):
- الإدخالات العامة (
project_id = None): مرئية في جميع سياقات المشروع وفي عمليات البحث العالمية فقط. - إدخالات المشروع (
project_id = Some(id)): مرئية فقط عند البحث داخل هذا المشروع المحدد. - بحث المشروع (
project_id = Some(id)): يعيد الإدخالات العامة + إدخالات لهذا المشروع. - البحث العام (
project_id = None): يعيد الإدخالات العامة فقط.
تخزين الإدخالات ذات النطاق المحدد للمشروع
use adk_memory::{InMemoryMemoryService, MemoryService, MemoryEntry};
use adk_core::Content;
use chrono::Utc;
let service = InMemoryMemoryService::new();
let entry = MemoryEntry {
content: Content::new("user").with_text("Project uses microservices"),
author: "user".to_string(),
timestamp: Utc::now(),
};
// Global entry (no project scope)
service.add_session("app", "user-1", "sess-1", vec![entry.clone()]).await?;
// Project-scoped entry
service.add_session_to_project("app", "user-1", "sess-2", "my-project", vec![entry.clone()]).await?;
// Single entry to a project
service.add_entry_to_project("app", "user-1", "my-project", entry).await?;
البحث بنطاق المشروع
use adk_memory::SearchRequest;
// Global-only search — returns only global entries
let global = service.search(SearchRequest {
query: "microservices".into(),
user_id: "user-1".into(),
app_name: "app".into(),
limit: None,
min_score: None,
project_id: None,
}).await?;
// Project search — returns global + project entries
let project = service.search(SearchRequest {
query: "microservices".into(),
user_id: "user-1".into(),
app_name: "app".into(),
limit: None,
min_score: None,
project_id: Some("my-project".into()),
}).await?;
حذف بنطاق المشروع
// Delete entries matching a query within a project only
service.delete_entries_in_project("app", "user-1", "my-project", "microservices").await?;
// Delete ALL entries for a project
service.delete_project("app", "user-1", "my-project").await?;
// Global delete — only removes global entries, project entries are unaffected
service.delete_entries("app", "user-1", "microservices").await?;
// GDPR delete_user — removes everything (global + all projects)
service.delete_user("app", "user-1").await?;
MemoryServiceAdapter بنطاق المشروع
يربط MemoryServiceAdapter بين MemoryService و adk_core::Memory. استخدم with_project_id() لتحديد نطاق جميع العمليات:
use adk_memory::{InMemoryMemoryService, MemoryServiceAdapter};
use adk_core::Memory;
use std::sync::Arc;
let service = Arc::new(InMemoryMemoryService::new());
// Adapter without project — operates on global entries
let global_adapter = MemoryServiceAdapter::new(service.clone(), "app", "user-1");
// Adapter with project — all search/add/delete operations scoped to the project
let project_adapter = MemoryServiceAdapter::new(service.clone(), "app", "user-1")
.with_project_id("my-project");
// Core Memory trait also supports ad-hoc project access
global_adapter.search_in_project("query", "other-project").await?;
global_adapter.add_to_project(entry, "other-project").await?;
التحقق من معرف المشروع
يتم التحقق من معرفات المشروع في جميع عمليات الكتابة:
- يجب ألا تكون فارغة
- يجب ألا تتجاوز 256 حرفًا
use adk_memory::validate_project_id;
validate_project_id("my-project")?; // Ok
validate_project_id("")?; // Err: must not be empty
validate_project_id(&"x".repeat(257))?; // Err: exceeds 256 chars
مصفوفة دلالات البحث
SearchRequest.project_id | إرجاع الإدخالات العامة | إرجاع إدخالات المشروع |
|---|---|---|
None | ✅ مطابقة الاستعلام | ❌ لا شيء |
Some("A") | ✅ مطابقة الاستعلام | ✅ إدخالات المشروع "A" فقط المطابقة للاستعلام |
مصفوفة دلالات الحذف
| العملية | النطاق |
|---|---|
delete_entries (لا يوجد مشروع) | الإدخالات العامة المطابقة للاستعلام فقط |
delete_entries_in_project("A") | إدخالات المشروع "A" المطابقة للاستعلام فقط |
delete_project("A") | جميع إدخالات المشروع "A" |
delete_user | جميع الإدخالات (العامة + جميع المشاريع) |
سلوك البحث
تستخدم InMemoryMemoryService المطابقة القائمة على الكلمات:
- يتم تقسيم الاستعلام إلى كلمات (أحرف صغيرة)
- يتم تقسيم محتوى كل ذاكرة
- يتم إرجاع الذكريات التي تحتوي على أي كلمات متطابقة
// Query: "rust programming"
// Matches memories containing "rust" OR "programming"
واجهة خلفية ذاكرة مخصصة
قم بتطبيق MemoryService للتخزين المخصص (مثل قاعدة بيانات المتجهات):
use adk_memory::{MemoryService, MemoryEntry, SearchRequest, SearchResponse};
use adk_core::Result;
use async_trait::async_trait;
pub struct VectorMemoryService {
// Your vector DB client
}
#[async_trait]
impl MemoryService for VectorMemoryService {
async fn add_session(
&self,
app_name: &str,
user_id: &str,
session_id: &str,
entries: Vec<MemoryEntry>,
) -> Result<()> {
// 1. Generate embeddings for each entry
// 2. Store in vector database with metadata
Ok(())
}
async fn search(&self, req: SearchRequest) -> Result<SearchResponse> {
// 1. Generate embedding for query
// 2. Perform similarity search
// 3. Return top-k results
Ok(SearchResponse { memories: vec![] })
}
}
التكامل مع Agents
تتكامل الذاكرة مع LlmAgentBuilder:
use adk_agent::LlmAgentBuilder;
use adk_memory::InMemoryMemoryService;
use std::sync::Arc;
let memory = Arc::new(InMemoryMemoryService::new());
let agent = LlmAgentBuilder::new("assistant")
.model(model)
.instruction("You are a helpful assistant with memory.")
.memory(memory)
.build()?;
عندما يتم تكوين الذاكرة:
- قبل كل دور، يتم البحث عن الذكريات ذات الصلة
- يتم حقن الذكريات المتطابقة في السياق
- بعد كل جلسة، يتم تخزين المحادثة كذكريات
البنية
┌─────────────────────────────────────────────────────────────┐
│ Agent Request │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Memory Search │
│ │
│ SearchRequest { query, user_id, app_name, project_id } │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ MemoryService │ │
│ │ ┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ │ │
│ │ │InMemory │ │ SQLite │ │Postgres│ │ Redis │ │ │
│ │ │(dev) │ │ │ │pgvector│ │ │ │ │
│ │ └─────────┘ └──────────┘ └────────┘ └──────────┘ │ │
│ │ ┌─────────┐ ┌──────────┐ │ │
│ │ │MongoDB │ │ Neo4j │ │ │
│ │ └─────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ SearchResponse { memories: Vec<MemoryEntry> } │
│ (filtered by project scope) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Context Injection │
│ │
│ Relevant memories added to agent context │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Agent Execution │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Memory Storage │
│ │
│ Session conversation stored for future recall │
│ (global or project-scoped) │
└─────────────────────────────────────────────────────────────┘
أفضل الممارسات
| الممارسة | الوصف |
|---|---|
| استخدام قاعدة بيانات المتجهات في الإنتاج | InMemory مخصص للتطوير/الاختبار فقط |
| التحديد حسب المستخدم | قم دائمًا بتضمين user_id للخصوصية |
| تحديد النتائج | حدد الذكريات المعادة لتجنب تجاوز سعة السياق |
| تنظيف الذكريات القديمة | نفّذ TTL أو أرشفة للبيانات القديمة |
| التضمين بشكل استراتيجي | خزّن الملخصات، وليس المحادثات الخام |
مقارنة مع Sessions
| الميزة | حالة الجلسة | الذاكرة |
|---|---|---|
| الاستمرارية | مدة الجلسة | دائم |
| النطاق | جلسة واحدة | عبر الجلسات |
| البحث | البحث بالقيمة المفتاحية | البحث الدلالي |
| حالة الاستخدام | السياق الحالي | الاستدعاء طويل الأمد |
السابق: ← Guardrails | التالي: Studio →