Skip to main content

zeph_memory/graph/
strategy_classifier.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! LLM-based retrieval strategy classifier for hybrid graph recall.
5//!
6//! [`classify_retrieval_strategy`] sends a one-shot prompt to the configured LLM
7//! provider and returns the name of the selected strategy. On any error the
8//! function silently returns `"synapse"` — it never propagates LLM failures to the caller.
9
10use zeph_llm::any::AnyProvider;
11use zeph_llm::provider::{LlmProvider as _, Message, Role};
12
13const CLASSIFY_PROMPT: &str = r#"Classify this query into one retrieval strategy. Reply with exactly one word.
14
15Strategies:
16- astar: factual lookups ("who is X", "what does X do", "find X")
17- watercircles: exploratory ("tell me about X", "what relates to X", "overview of X")
18- beam_search: multi-hop reasoning ("how does X connect to Y", "path from X to Z")
19- synapse: default/unclear
20
21Query: "#;
22
23/// Classify a query's intent to select the best graph retrieval strategy.
24///
25/// Returns one of: `"astar"`, `"watercircles"`, `"beam_search"`, or `"synapse"`.
26/// On any LLM error or unrecognised response, returns `"synapse"` as a safe fallback.
27/// This function never propagates errors.
28///
29/// # Examples
30///
31/// ```no_run
32/// # use zeph_memory::graph::strategy_classifier::classify_retrieval_strategy;
33/// # use zeph_llm::any::AnyProvider;
34/// # use zeph_llm::mock::MockProvider;
35/// # async fn demo() {
36/// let provider = AnyProvider::Mock(MockProvider::default());
37/// let strategy = classify_retrieval_strategy(&provider, "who is Alice?").await;
38/// assert!(["astar", "watercircles", "beam_search", "synapse"].contains(&strategy.as_str()));
39/// # }
40/// ```
41pub async fn classify_retrieval_strategy(provider: &AnyProvider, query: &str) -> String {
42    let _span = tracing::info_span!("memory.graph.classify_strategy").entered();
43
44    let prompt = format!("{CLASSIFY_PROMPT}{query}");
45    let messages = [Message {
46        role: Role::User,
47        content: prompt,
48        ..Default::default()
49    }];
50
51    let response = match provider.chat(&messages).await {
52        Ok(r) => r,
53        Err(e) => {
54            tracing::warn!(
55                error = %e,
56                "strategy classifier: LLM error, falling back to synapse"
57            );
58            return "synapse".to_owned();
59        }
60    };
61
62    let word = response.trim().to_lowercase();
63    match word.as_str() {
64        "astar" | "watercircles" | "beam_search" | "synapse" => word,
65        _ => "synapse".to_owned(),
66    }
67}