mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Steven Enamakel
parent
c09b118cbe
commit
801a5bbf4f
@@ -157,6 +157,13 @@ pub(crate) struct LazyToolkitResolver {
|
||||
pub(crate) actions: Vec<crate::openhuman::context::prompt::ConnectedIntegrationTool>,
|
||||
}
|
||||
|
||||
/// Minimum normalized-slug length before the prefix/superstring tier in
|
||||
/// [`LazyToolkitResolver::find_action`] engages (#3152). Below this, a stray
|
||||
/// short slug (`notion`, `gmail`) would prefix-match too many actions; the
|
||||
/// uniqueness check would reject it anyway, but the length gate makes the
|
||||
/// intent explicit and skips needless scans.
|
||||
const TIER4_MIN_SLUG_LEN: usize = 8;
|
||||
|
||||
impl LazyToolkitResolver {
|
||||
pub(super) fn resolve(&self, name: &str) -> Option<Box<dyn crate::openhuman::tools::Tool>> {
|
||||
let action = self.find_action(name)?;
|
||||
@@ -222,6 +229,38 @@ impl LazyToolkitResolver {
|
||||
norm = %norm,
|
||||
"[subagent_runner] ambiguous normalized-slug match — multiple actions resolve to the same slug; not resolving"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Tier 4: unique prefix/superstring match (#3152). Models
|
||||
// routinely emit a TRUNCATED action slug — `NOTION_SEARCH_NOTION`
|
||||
// for the catalogued `NOTION_SEARCH_NOTION_PAGE` — or, less often,
|
||||
// a suffixed one. Accept only when exactly one action's normalized
|
||||
// slug extends the request (or vice-versa). Gated on a non-trivial
|
||||
// request length so a short or hallucinated slug can't fan out
|
||||
// across many actions, and strictly unique so a near-miss WRITE
|
||||
// can never silently dispatch to the wrong action (data-integrity:
|
||||
// a mis-resolved create/update would touch the wrong resource).
|
||||
if norm.len() >= TIER4_MIN_SLUG_LEN {
|
||||
let mut prefix_matches = self.actions.iter().filter(|a| {
|
||||
let cand = normalize_slug(&a.name);
|
||||
!cand.is_empty() && (cand.starts_with(&norm) || norm.starts_with(&cand))
|
||||
});
|
||||
if let Some(action) = prefix_matches.next() {
|
||||
if prefix_matches.next().is_none() {
|
||||
tracing::info!(
|
||||
requested = %name,
|
||||
matched = %action.name,
|
||||
"[subagent_runner] resolved tool by unique prefix/superstring match"
|
||||
);
|
||||
return Some(action);
|
||||
}
|
||||
tracing::warn!(
|
||||
requested = %name,
|
||||
norm = %norm,
|
||||
"[subagent_runner] ambiguous prefix/superstring match — multiple actions share the slug prefix; not resolving"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
@@ -1473,3 +1473,66 @@ fn nested_subagent_dispatch_runs_on_a_constrained_worker_stack() {
|
||||
outcome.output
|
||||
);
|
||||
}
|
||||
|
||||
// ── Repro: issue #3152 — near-miss write slug fails to resolve ──────
|
||||
//
|
||||
// The model emits `NOTION_SEARCH_NOTION` (drops the `_PAGE` suffix). The
|
||||
// real action `NOTION_SEARCH_NOTION_PAGE` is the unique superstring, yet
|
||||
// find_action's three tiers (exact / case-insensitive / normalized) all
|
||||
// miss → None → lazy registration never fires → allowlist gate blocks the
|
||||
// write. Asserts DESIRED post-fix behaviour → RED until the unique
|
||||
// prefix/superstring resolution tier lands. Must stay conservative: a
|
||||
// fabricated slug with no unique match must still resolve to None (covered
|
||||
// by `lazy_resolver_tolerates_near_miss_slugs`).
|
||||
#[test]
|
||||
fn repro_3152_near_miss_write_slug_resolves_uniquely() {
|
||||
use crate::openhuman::context::prompt::ConnectedIntegrationTool;
|
||||
let mk = |name: &str| ConnectedIntegrationTool {
|
||||
name: name.into(),
|
||||
description: "d".into(),
|
||||
parameters: None,
|
||||
};
|
||||
let resolver = LazyToolkitResolver {
|
||||
config: std::sync::Arc::new(crate::openhuman::config::Config::default()),
|
||||
actions: vec![
|
||||
mk("NOTION_SEARCH_NOTION_PAGE"),
|
||||
mk("NOTION_CREATE_NOTION_PAGE"),
|
||||
mk("NOTION_FETCH_DATA"),
|
||||
],
|
||||
};
|
||||
let resolved = resolver
|
||||
.resolve("NOTION_SEARCH_NOTION")
|
||||
.expect("#3152: near-miss write slug must resolve to its unique superstring");
|
||||
assert_eq!(resolved.name(), "NOTION_SEARCH_NOTION_PAGE");
|
||||
}
|
||||
|
||||
// ── Guard: #3152 prefix tier must stay strictly unique ──────────────
|
||||
//
|
||||
// When a truncated slug prefix-matches MORE than one catalogued action,
|
||||
// the resolver must refuse rather than guess — a mis-dispatched write
|
||||
// could create/update the wrong resource (data-integrity). Also asserts
|
||||
// the length gate: a too-short request never fans out.
|
||||
#[test]
|
||||
fn prefix_tier_refuses_ambiguous_and_short_slugs() {
|
||||
use crate::openhuman::context::prompt::ConnectedIntegrationTool;
|
||||
let mk = |name: &str| ConnectedIntegrationTool {
|
||||
name: name.into(),
|
||||
description: "d".into(),
|
||||
parameters: None,
|
||||
};
|
||||
let resolver = LazyToolkitResolver {
|
||||
config: std::sync::Arc::new(crate::openhuman::config::Config::default()),
|
||||
actions: vec![
|
||||
mk("NOTION_SEARCH_NOTION_PAGE"),
|
||||
mk("NOTION_SEARCH_NOTION_DATABASE"),
|
||||
mk("NOTION_CREATE_NOTION_PAGE"),
|
||||
],
|
||||
};
|
||||
// `NOTION_SEARCH_NOTION` is a prefix of TWO actions → ambiguous → None.
|
||||
assert!(
|
||||
resolver.resolve("NOTION_SEARCH_NOTION").is_none(),
|
||||
"#3152: ambiguous prefix must not silently dispatch to a guess"
|
||||
);
|
||||
// Short slug below the length gate never engages the prefix tier.
|
||||
assert!(resolver.resolve("NOTION").is_none());
|
||||
}
|
||||
|
||||
@@ -332,3 +332,26 @@ fn real_data_full_funnel_report() {
|
||||
);
|
||||
assert!(total_out < total_in / 3, "overall reduction should be >66%");
|
||||
}
|
||||
|
||||
// ── Repro: issue #3152 — Composio write action unreachable ──────────
|
||||
//
|
||||
// `integrations_agent` asked to CREATE a Notion page. Notion is a
|
||||
// HEAVY_SCHEMA toolkit → production top_k = 12. The verb gate + score cull
|
||||
// advertise only read-leaning actions, so `NOTION_CREATE_NOTION_PAGE` never
|
||||
// reaches the model. Asserts DESIRED post-fix behaviour → RED until the
|
||||
// write-reservation fix lands.
|
||||
#[test]
|
||||
fn repro_3152_create_page_reachable_in_top_k() {
|
||||
let actions = load_real_toolkit("notion");
|
||||
let hits = filter_actions_by_prompt(
|
||||
"create a notion page with the meeting notes and give me the link",
|
||||
&actions,
|
||||
12,
|
||||
);
|
||||
assert_in_top(
|
||||
&actions,
|
||||
&hits,
|
||||
"NOTION_CREATE_NOTION_PAGE",
|
||||
"#3152 notion create-page",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user