Skip to content

Commit e170b6f

Browse files
authored
Merge pull request #68 from VibeEngineeringAI/fix/pr62-16-resolve-dataset-token-match
PR #62 follow-up 16: match resolve_dataset names on token boundaries
2 parents 3c62307 + f550901 commit e170b6f

2 files changed

Lines changed: 55 additions & 6 deletions

File tree

‎docs/tool-api.md‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,13 @@ datasets.
174174
returns `not_found` if its telemetry type does not match.
175175
- Otherwise it scores the visible datasets that match `telemetryType` by name
176176
against `query`, ignoring case: 1000 if the name equals the query, 750 if the
177-
query contains the name, or 500 if the name contains the query, plus 25 for
178-
each query term longer than two characters that the name contains, plus 15
179-
when `telemetryType` is given. It resolves only a unique top score of at least
180-
500. In every other case it returns `ambiguous` with up to `limit` scored
181-
`candidates`, or `not_found` if no visible dataset matches `telemetryType`.
177+
query contains the name as a whole word (`-` and `_` count as part of a name,
178+
so `api` is not in `rapid` or `api-gateway`), or 500 if the name contains the
179+
query, plus 25 for each query term longer than two characters that the name
180+
contains, plus 15 when `telemetryType` is given. It resolves only a unique top
181+
score of at least 500. In every other case it returns `ambiguous` with up to
182+
`limit` scored `candidates`, or `not_found` if no visible dataset matches
183+
`telemetryType`.
182184

183185
It never falls back to an arbitrary dataset.
184186

‎src/tool_executor.rs‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,24 @@ fn dataset_matches_telemetry_type(dataset: &Value, expected: Option<&str>) -> bo
344344
})
345345
}
346346

347+
/// Whether `name` occurs in `query` as a whole token. Dataset names may contain
348+
/// `-` and `_`, so those count as part of a name rather than as boundaries.
349+
fn query_mentions_dataset_name(query: &str, name: &str) -> bool {
350+
let is_name_character =
351+
|character: char| character.is_ascii_alphanumeric() || character == '_' || character == '-';
352+
!name.is_empty()
353+
&& query.match_indices(name).any(|(start, matched)| {
354+
!query[..start]
355+
.chars()
356+
.next_back()
357+
.is_some_and(is_name_character)
358+
&& !query[start + matched.len()..]
359+
.chars()
360+
.next()
361+
.is_some_and(is_name_character)
362+
})
363+
}
364+
347365
pub fn resolve_dataset_candidates(payload: Value, arguments: &Value) -> Value {
348366
let query = arguments
349367
.get("query")
@@ -470,7 +488,7 @@ pub fn resolve_dataset_candidates(payload: Value, arguments: &Value) -> Value {
470488
if !query.is_empty() && name == query {
471489
score += 1_000;
472490
reasons.push("exact dataset name");
473-
} else if !query.is_empty() && query.contains(&name) {
491+
} else if !query.is_empty() && query_mentions_dataset_name(&query, &name) {
474492
score += 750;
475493
reasons.push("dataset name explicitly mentioned in the question");
476494
} else if !query.is_empty() && name.contains(&query) {
@@ -2229,6 +2247,35 @@ mod tests {
22292247
assert_eq!(sole_mismatch["status"], "not_found");
22302248
}
22312249

2250+
#[actix_web::test]
2251+
async fn shared_dataset_resolution_requires_whole_name_mentions() {
2252+
let datasets = json!([{ "name": "api" }, { "name": "web" }]);
2253+
let embedded = resolve_dataset_candidates(
2254+
datasets.clone(),
2255+
&json!({ "query": "show rapid failures" }),
2256+
);
2257+
assert_ne!(embedded["status"], "resolved");
2258+
2259+
let mentioned =
2260+
resolve_dataset_candidates(datasets.clone(), &json!({ "query": "show api errors" }));
2261+
assert_eq!(mentioned["status"], "resolved");
2262+
assert_eq!(mentioned["selectedDatasets"], json!(["api"]));
2263+
2264+
let punctuated =
2265+
resolve_dataset_candidates(datasets, &json!({ "query": "errors in (api)?" }));
2266+
assert_eq!(punctuated["selectedDatasets"], json!(["api"]));
2267+
2268+
let hyphenated = json!([{ "name": "otel-metrics" }, { "name": "otel-traces" }]);
2269+
let longer_name = resolve_dataset_candidates(
2270+
hyphenated.clone(),
2271+
&json!({ "query": "show otel-metrics-v2 errors" }),
2272+
);
2273+
assert_ne!(longer_name["status"], "resolved");
2274+
let underscored =
2275+
resolve_dataset_candidates(hyphenated, &json!({ "query": "show my_otel-metrics" }));
2276+
assert_ne!(underscored["status"], "resolved");
2277+
}
2278+
22322279
#[actix_web::test]
22332280
async fn shared_dataset_resolution_prefers_exact_case_selection() {
22342281
let datasets = json!([{ "name": "Logs" }, { "name": "logs" }, { "name": "Traces" }]);

0 commit comments

Comments
 (0)