mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -74,6 +74,25 @@ const POLYMARKET_TRADING_DATA: Option<CapabilityPrivacy> = Some(CapabilityPrivac
|
||||
destinations: &["Polymarket CLOB API"],
|
||||
});
|
||||
|
||||
// "Test Connection" on the Embeddings settings panel routes a small probe
|
||||
// payload to *whichever provider the user has selected* — not just the
|
||||
// managed cloud default. `DERIVED_TO_BACKEND` only enumerates the managed
|
||||
// path (OpenHuman backend / Neocortex), which under-reports the actual
|
||||
// privacy surface when the user has switched to OpenAI / Cohere / a
|
||||
// self-hosted endpoint. The catalog needs to list every reachable
|
||||
// destination so the Privacy surface can render the full set instead of
|
||||
// implying probes always stay on the managed path.
|
||||
const EMBEDDING_PROBE_TO_CONFIGURED_PROVIDER: Option<CapabilityPrivacy> = Some(CapabilityPrivacy {
|
||||
leaves_device: true,
|
||||
data_kind: PrivacyDataKind::Derived,
|
||||
destinations: &[
|
||||
"OpenHuman backend / TinyHumans Neocortex (managed cloud default)",
|
||||
"OpenAI API (api.openai.com)",
|
||||
"Cohere API (api.cohere.com)",
|
||||
"User-configured OpenAI-compatible endpoint (custom:<url>)",
|
||||
],
|
||||
});
|
||||
|
||||
const CAPABILITIES: &[Capability] = &[
|
||||
Capability {
|
||||
id: "conversation.create",
|
||||
@@ -294,6 +313,50 @@ const CAPABILITIES: &[Capability] = &[
|
||||
status: CapabilityStatus::Beta,
|
||||
privacy: LOCAL_RAW,
|
||||
},
|
||||
Capability {
|
||||
id: "intelligence.embedding_provider_config",
|
||||
name: "Configure Embedding Provider",
|
||||
domain: "embeddings",
|
||||
category: CapabilityCategory::Intelligence,
|
||||
description:
|
||||
"Pick which embedding provider drives semantic search across your memory: \
|
||||
managed cloud (default, Voyage-backed via api.tinyhumans.ai), OpenAI, \
|
||||
Cohere, local Ollama, or a custom OpenAI-compatible endpoint. API keys \
|
||||
are stored encrypted via the local keyring under `embeddings:<slug>`; \
|
||||
model name and embedding dimensions are tunable per provider. The \
|
||||
legacy `inference_embed` RPC is aliased to `embeddings_embed` so \
|
||||
existing callers continue to work.",
|
||||
how_to: "Settings > AI > Embeddings",
|
||||
status: CapabilityStatus::Beta,
|
||||
// Privacy depends on the selected provider — see
|
||||
// `intelligence.embedding_provider_test` for the per-provider data
|
||||
// destinations. The configuration surface itself only writes to the
|
||||
// local keyring and config, so leaving this `None` (treat-as-unknown)
|
||||
// would under-report; we annotate the credential side here and the
|
||||
// network side on the test action.
|
||||
privacy: LOCAL_CREDENTIALS,
|
||||
},
|
||||
Capability {
|
||||
id: "intelligence.embedding_provider_test",
|
||||
name: "Test Embedding Provider",
|
||||
domain: "embeddings",
|
||||
category: CapabilityCategory::Intelligence,
|
||||
description:
|
||||
"Verify a configured embedding provider before committing it to \
|
||||
memory ingestion. Sends a small one-shot embed request and reports \
|
||||
the model, dimensions, and any auth/error surface so a \
|
||||
misconfigured key doesn't get discovered halfway through a 50k \
|
||||
chunk backfill.",
|
||||
how_to: "Settings > AI > Embeddings > Test Connection",
|
||||
// The probe payload routes to whichever provider the user has
|
||||
// selected — managed cloud (default), OpenAI, Cohere, or a custom
|
||||
// OpenAI-compatible endpoint. Using `DERIVED_TO_BACKEND` here would
|
||||
// under-report by only listing the managed path; the dedicated
|
||||
// constant enumerates every reachable destination so the Privacy
|
||||
// surface renders the full set.
|
||||
status: CapabilityStatus::Beta,
|
||||
privacy: EMBEDDING_PROBE_TO_CONFIGURED_PROVIDER,
|
||||
},
|
||||
Capability {
|
||||
id: "intelligence.mcp_server",
|
||||
name: "MCP Server",
|
||||
|
||||
@@ -103,6 +103,8 @@ fn catalog_includes_additional_user_facing_surfaces() {
|
||||
"intelligence.mcp_server",
|
||||
"intelligence.searxng_search",
|
||||
"intelligence.tool_registry",
|
||||
"intelligence.embedding_provider_config",
|
||||
"intelligence.embedding_provider_test",
|
||||
"conversation.subagent_mascots",
|
||||
] {
|
||||
assert!(
|
||||
@@ -111,3 +113,113 @@ fn catalog_includes_additional_user_facing_surfaces() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The two embeddings entries surface a Settings-side configuration panel.
|
||||
/// They share the same domain (`embeddings`) but are listed under the
|
||||
/// Intelligence umbrella so they sit next to memory_tree_retrieval / mcp_server
|
||||
/// in the in-app feature catalog. Pinning the relationships here defends
|
||||
/// against an inadvertent recategorisation that would split them across the
|
||||
/// UI's tab grouping.
|
||||
#[test]
|
||||
fn embedding_provider_capabilities_share_domain_and_category() {
|
||||
let config = lookup("intelligence.embedding_provider_config")
|
||||
.expect("embedding_provider_config registered");
|
||||
let test =
|
||||
lookup("intelligence.embedding_provider_test").expect("embedding_provider_test registered");
|
||||
|
||||
assert_eq!(config.domain, "embeddings");
|
||||
assert_eq!(test.domain, "embeddings");
|
||||
assert_eq!(
|
||||
config.category, test.category,
|
||||
"both embedding capabilities must land in the same UI category"
|
||||
);
|
||||
|
||||
// The Settings panel they describe is the same one — make sure the
|
||||
// `how_to` strings point at it, not at an out-of-date breadcrumb.
|
||||
assert!(
|
||||
config.how_to.contains("Settings") && config.how_to.contains("Embeddings"),
|
||||
"config how_to must mention Settings > … > Embeddings, got: {}",
|
||||
config.how_to
|
||||
);
|
||||
assert!(
|
||||
test.how_to.contains("Settings") && test.how_to.contains("Embeddings"),
|
||||
"test how_to must mention Settings > … > Embeddings, got: {}",
|
||||
test.how_to
|
||||
);
|
||||
}
|
||||
|
||||
/// Privacy annotations must split cleanly: the config side touches only the
|
||||
/// local keyring (LOCAL_CREDENTIALS — leaves_device=false), the test side
|
||||
/// fires a probe at the configured provider (leaves_device=true). Without
|
||||
/// this split, a single `None` privacy flag would force the UI to treat the
|
||||
/// embeddings panel as "unknown" and the Privacy surface would under-report
|
||||
/// where data goes when the test button gets clicked.
|
||||
#[test]
|
||||
fn embedding_provider_capabilities_split_privacy_correctly() {
|
||||
let config = lookup("intelligence.embedding_provider_config")
|
||||
.expect("embedding_provider_config registered");
|
||||
let test =
|
||||
lookup("intelligence.embedding_provider_test").expect("embedding_provider_test registered");
|
||||
|
||||
let config_privacy = config
|
||||
.privacy
|
||||
.expect("config capability has privacy annotation");
|
||||
assert!(
|
||||
!config_privacy.leaves_device,
|
||||
"configuration writes only to local keyring; nothing should leave the device"
|
||||
);
|
||||
|
||||
let test_privacy = test
|
||||
.privacy
|
||||
.expect("test capability has privacy annotation");
|
||||
assert!(
|
||||
test_privacy.leaves_device,
|
||||
"test fires a probe at the configured provider — must report as leaves_device"
|
||||
);
|
||||
}
|
||||
|
||||
/// The Test Connection probe can hit any of the configured providers, not
|
||||
/// just the managed cloud default. Pinning the destinations list defends
|
||||
/// the Privacy surface against silently shrinking back to a single
|
||||
/// destination — that's the exact under-reporting failure flagged in #2656
|
||||
/// review (CodeRabbit + @graycyrus both pointed at the same line).
|
||||
#[test]
|
||||
fn embedding_provider_test_destinations_cover_all_providers() {
|
||||
let cap =
|
||||
lookup("intelligence.embedding_provider_test").expect("embedding_provider_test registered");
|
||||
let privacy = cap.privacy.expect("test capability has privacy annotation");
|
||||
|
||||
// Joining the destinations into a single haystack so the assertions
|
||||
// tolerate cosmetic punctuation changes (parens, suffixes) but still
|
||||
// catch a destination genuinely going missing.
|
||||
let haystack = privacy.destinations.join(" | ").to_lowercase();
|
||||
|
||||
for needle in ["openhuman", "openai", "cohere"] {
|
||||
assert!(
|
||||
haystack.contains(needle),
|
||||
"test probe destinations must list `{needle}` — without it the \
|
||||
Privacy surface under-reports when that provider is selected. \
|
||||
Current destinations: {:?}",
|
||||
privacy.destinations
|
||||
);
|
||||
}
|
||||
// The "custom OpenAI-compatible" path is a real provider option in
|
||||
// #2583 — listed as `custom:<url>` in `create_embedding_provider`.
|
||||
assert!(
|
||||
haystack.contains("custom") || haystack.contains("user-configured"),
|
||||
"test probe destinations must acknowledge user-configured custom \
|
||||
endpoints. Current destinations: {:?}",
|
||||
privacy.destinations
|
||||
);
|
||||
|
||||
// Belt-and-braces: at least 4 distinct destinations (managed +
|
||||
// openai + cohere + custom). A drop below this means someone
|
||||
// collapsed entries.
|
||||
assert!(
|
||||
privacy.destinations.len() >= 4,
|
||||
"expected ≥4 destinations covering managed + openai + cohere + custom, \
|
||||
got {}: {:?}",
|
||||
privacy.destinations.len(),
|
||||
privacy.destinations
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user