feat(memory): config embedders (LOT C2) + suggestion contextuelle (LOT C3) + contexte projet partagé
- LOT C2 (§14.5.3) : use cases de configuration des embedders déclaratifs (List/Save/Delete + DescribeEmbedderEngines : modèles ONNX recommandés, environnement local détecté, stratégies compilées). UI EmbedderSettings. - LOT C3 (§14.5.5) : suggestion contextuelle best-effort à l'activation quand la mémoire dépasse le budget de recall sans embedder configuré (event EmbedderSuggested, anti-spam 1×/session, « ne plus demander »). - Contexte projet partagé .ideai/CONTEXT.md (model-agnostic) injecté à tous les agents/profils au lancement, avant la persona. UI ProjectContextPanel. Tests : backend workspace vert (0 échec) ; frontend 306/306. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -71,7 +71,8 @@ async fn onnx_unknown_model_is_unsupported() {
|
||||
// *before* any `try_new`/download — so this is safe without a network. We point
|
||||
// the cache at a path that is never created to prove no download is attempted.
|
||||
let cache = std::path::Path::new("/idea-onnx-cache-that-never-exists");
|
||||
let embedder = OnnxEmbedder::from_profile(&onnx_profile(Some("definitely-unknown"), 384), cache);
|
||||
let embedder =
|
||||
OnnxEmbedder::from_profile(&onnx_profile(Some("definitely-unknown"), 384), cache);
|
||||
|
||||
let err = embedder
|
||||
.embed(&["x".to_string()])
|
||||
@ -113,7 +114,8 @@ async fn onnx_construction_is_cheap() {
|
||||
// and an unknown one (construction never fails for either).
|
||||
let cache = std::path::Path::new("/idea-onnx-cache-that-never-exists-3");
|
||||
|
||||
let known = OnnxEmbedder::from_profile(&onnx_profile(Some("multilingual-e5-small"), 384), cache);
|
||||
let known =
|
||||
OnnxEmbedder::from_profile(&onnx_profile(Some("multilingual-e5-small"), 384), cache);
|
||||
assert_eq!(known.id(), "test-onnx");
|
||||
assert_eq!(known.dimension(), 384);
|
||||
|
||||
@ -139,14 +141,16 @@ async fn embedder_from_profile_localonnx_is_real_engine_not_unsupported_stub() {
|
||||
// whereas the StubEmbedder would return Unsupported even for an empty batch.
|
||||
let cache = std::path::Path::new("/idea-onnx-cache-that-never-exists-4");
|
||||
let known = onnx_profile(Some("multilingual-e5-small"), 384);
|
||||
let embedder =
|
||||
embedder_from_profile(&known, cache).expect("localOnnx must yield an embedder");
|
||||
let embedder = embedder_from_profile(&known, cache).expect("localOnnx must yield an embedder");
|
||||
assert_eq!(embedder.dimension(), 384);
|
||||
let out = embedder
|
||||
.embed(&[])
|
||||
.await
|
||||
.expect("real engine short-circuits empty input to Ok(vec![])");
|
||||
assert!(out.is_empty(), "empty batch ⇒ empty result on the real engine");
|
||||
assert!(
|
||||
out.is_empty(),
|
||||
"empty batch ⇒ empty result on the real engine"
|
||||
);
|
||||
|
||||
// And an *unknown* model still surfaces Unsupported (deferred resolution), so the
|
||||
// mapping is the real engine in both cases (the stub would also say Unsupported,
|
||||
@ -187,11 +191,17 @@ async fn onnx_embeds_e5_small_real_model() {
|
||||
for v in &out {
|
||||
assert_eq!(v.len(), 384, "e5-small produces 384-dim vectors");
|
||||
let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
|
||||
assert!((norm - 1.0).abs() < 1e-2, "fastembed L2-normalises; norm ≈ 1, got {norm}");
|
||||
assert!(
|
||||
(norm - 1.0).abs() < 1e-2,
|
||||
"fastembed L2-normalises; norm ≈ 1, got {norm}"
|
||||
);
|
||||
}
|
||||
|
||||
// Deterministic across calls (model already cached after the first call).
|
||||
let again = embedder.embed(&texts).await.expect("second embedding must succeed");
|
||||
let again = embedder
|
||||
.embed(&texts)
|
||||
.await
|
||||
.expect("second embedding must succeed");
|
||||
assert_eq!(out, again, "embedding must be deterministic across calls");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user