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/// ```
41#[tracing::instrument(skip_all, name = "memory.graph.classify_strategy")]
42pub async fn classify_retrieval_strategy(provider: &AnyProvider, query: &str) -> String {
43    let prompt = format!("{CLASSIFY_PROMPT}{query}");
44    let messages = [Message {
45        role: Role::User,
46        content: prompt,
47        ..Default::default()
48    }];
49
50    let response = match provider.chat(&messages).await {
51        Ok(r) => r,
52        Err(e) => {
53            tracing::warn!(
54                error = %e,
55                "strategy classifier: LLM error, falling back to synapse"
56            );
57            return "synapse".to_owned();
58        }
59    };
60
61    let word = response.trim().to_lowercase();
62    match word.as_str() {
63        "astar" | "watercircles" | "beam_search" | "synapse" => word,
64        _ => "synapse".to_owned(),
65    }
66}