Skip to content
This repository was archived by the owner on Feb 24, 2026. It is now read-only.

Commit a685fd5

Browse files
committed
feat: rank and filter discovery by curated manifest relevance
1 parent 94b059b commit a685fd5

File tree

2 files changed

+248
-6
lines changed

2 files changed

+248
-6
lines changed

src/agenticflow_cli/main.py

Lines changed: 154 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import sys
1010
import re
11+
from functools import lru_cache
1112
from time import perf_counter
1213
from pathlib import Path
1314
from typing import Any, Callable, Mapping
@@ -49,6 +50,9 @@
4950
CODE_SEARCH_SCHEMA_VERSION = "agenticflow.code.search.v1"
5051
CODE_EXECUTE_SCHEMA_VERSION = "agenticflow.code.execute.v1"
5152
CLI_CONFIG_DIR_ENV_VAR = "AGENTICFLOW_CLI_DIR"
53+
CURATED_MANIFEST_PATH = Path(__file__).resolve().parent / "public_ops_manifest.json"
54+
SUPPORT_SCOPE_EXECUTED = "supported-executed"
55+
SUPPORT_SCOPE_BLOCKED = "supported-blocked-policy"
5256

5357

5458
def _add_common_call_flags(parser: argparse.ArgumentParser) -> None:
@@ -643,18 +647,81 @@ def _catalog_operation_item(operation: Any) -> dict[str, Any]:
643647
}
644648

645649

650+
@lru_cache(maxsize=1)
651+
def _manifest_scope_by_operation_id() -> dict[str, str]:
652+
try:
653+
raw = json.loads(CURATED_MANIFEST_PATH.read_text(encoding="utf-8"))
654+
except Exception:
655+
return {}
656+
if not isinstance(raw, list):
657+
return {}
658+
659+
scopes: dict[str, str] = {}
660+
for item in raw:
661+
if not isinstance(item, Mapping):
662+
continue
663+
operation_id = item.get("operation_id")
664+
support_scope = item.get("support_scope")
665+
if isinstance(operation_id, str) and operation_id and isinstance(support_scope, str):
666+
scopes[operation_id] = support_scope
667+
return scopes
668+
669+
670+
def _should_use_curated_manifest(spec_file: Path, public_only: bool) -> bool:
671+
if not public_only:
672+
return False
673+
try:
674+
return spec_file.resolve() == default_spec_path().resolve()
675+
except Exception:
676+
return False
677+
678+
679+
def _apply_curated_manifest_filter(
680+
operations: list[Any],
681+
*,
682+
public_only: bool,
683+
spec_file: Path,
684+
) -> list[Any]:
685+
if not _should_use_curated_manifest(spec_file, public_only):
686+
return operations
687+
688+
manifest_scope = _manifest_scope_by_operation_id()
689+
if not manifest_scope:
690+
return operations
691+
allowed_operation_ids = set(manifest_scope)
692+
return [
693+
operation
694+
for operation in operations
695+
if getattr(operation, "operation_id", None) in allowed_operation_ids
696+
]
697+
698+
646699
def _catalog_records(
647-
registry: OperationRegistry, public_only: bool
700+
registry: OperationRegistry,
701+
public_only: bool,
702+
spec_file: Path,
648703
) -> list[dict[str, Any]]:
649704
operations = _list_operations(registry, public_only=public_only, tag=None)
705+
operations = _apply_curated_manifest_filter(
706+
operations,
707+
public_only=public_only,
708+
spec_file=spec_file,
709+
)
650710
records = [_catalog_operation_item(operation) for operation in operations]
651711
return sorted(records, key=lambda item: (item["path"], item["method"], item["operation_id"]))
652712

653713

654714
def _catalog_operations(
655-
registry: OperationRegistry, public_only: bool
715+
registry: OperationRegistry,
716+
public_only: bool,
717+
spec_file: Path,
656718
) -> list[Any]:
657719
operations = _list_operations(registry, public_only=public_only, tag=None)
720+
operations = _apply_curated_manifest_filter(
721+
operations,
722+
public_only=public_only,
723+
spec_file=spec_file,
724+
)
658725
return sorted(
659726
operations,
660727
key=lambda item: (item.path, item.method, item.operation_id),
@@ -711,6 +778,7 @@ def _rank_catalog_operations(
711778
task: str,
712779
max_cost: float | None = None,
713780
max_latency_ms: float | None = None,
781+
manifest_scope_by_operation_id: Mapping[str, str] | None = None,
714782
) -> list[dict[str, Any]]:
715783
task_terms = _tokenize_catalog_text(task)
716784
if not task_terms:
@@ -736,13 +804,64 @@ def _rank_catalog_operations(
736804
if max_latency_ms is not None and latency > max_latency_ms:
737805
continue
738806

739-
score = round((relevance * 10) - cost - (latency / 200), 3)
807+
support_scope = None
808+
if manifest_scope_by_operation_id is not None:
809+
support_scope = manifest_scope_by_operation_id.get(
810+
operation_record["operation_id"]
811+
)
812+
scope_bonus = 0.0
813+
if support_scope == SUPPORT_SCOPE_EXECUTED:
814+
scope_bonus = 4.0
815+
elif support_scope == SUPPORT_SCOPE_BLOCKED:
816+
scope_bonus = 2.0
817+
818+
dependency_bonus = 0.0
819+
builder_terms = {
820+
"build",
821+
"builder",
822+
"create",
823+
"workflow",
824+
"workflows",
825+
"agent",
826+
"agents",
827+
"workforce",
828+
"dependencies",
829+
"dependency",
830+
}
831+
if task_terms.intersection(builder_terms):
832+
dependency_tokens = {
833+
"node",
834+
"nodes",
835+
"connection",
836+
"connections",
837+
"provider",
838+
"providers",
839+
"template",
840+
"templates",
841+
"validate",
842+
"schema",
843+
}
844+
dependency_bonus = float(
845+
min(3, len(operation_tokens.intersection(dependency_tokens)))
846+
)
847+
848+
score = round(
849+
(relevance * 10)
850+
- cost
851+
- (latency / 200)
852+
+ scope_bonus
853+
+ dependency_bonus,
854+
3,
855+
)
740856
ranked.append(
741857
{
742858
**operation_record,
743859
"relevance": relevance,
744860
"cost": cost,
745861
"estimated_latency_ms": latency,
862+
"support_scope": support_scope,
863+
"scope_bonus": scope_bonus,
864+
"dependency_bonus": dependency_bonus,
746865
"score": score,
747866
}
748867
)
@@ -1406,6 +1525,11 @@ def _invoke_sdk_operation(
14061525
def _run_ops_command(args: argparse.Namespace, registry: OperationRegistry) -> int:
14071526
if args.ops_command == "list":
14081527
operations = _list_operations(registry, args.public_only, args.tag)
1528+
operations = _apply_curated_manifest_filter(
1529+
operations,
1530+
public_only=args.public_only,
1531+
spec_file=args.spec_file,
1532+
)
14091533
for operation in operations:
14101534
operation_id = getattr(operation, "operation_id", "")
14111535
method = getattr(operation, "method", "")
@@ -1456,7 +1580,11 @@ def _run_catalog_command(
14561580
args: argparse.Namespace, registry: OperationRegistry
14571581
) -> int:
14581582
if args.catalog_command == "export":
1459-
items = _catalog_records(registry, public_only=args.public_only)
1583+
items = _catalog_records(
1584+
registry,
1585+
public_only=args.public_only,
1586+
spec_file=args.spec_file,
1587+
)
14601588
if args.json:
14611589
payload = {
14621590
"schema_version": CATALOG_EXPORT_SCHEMA_VERSION,
@@ -1474,12 +1602,22 @@ def _run_catalog_command(
14741602
return 0
14751603

14761604
if args.catalog_command == "rank":
1477-
operations = _catalog_operations(registry, public_only=args.public_only)
1605+
operations = _catalog_operations(
1606+
registry,
1607+
public_only=args.public_only,
1608+
spec_file=args.spec_file,
1609+
)
1610+
manifest_scope = (
1611+
_manifest_scope_by_operation_id()
1612+
if _should_use_curated_manifest(args.spec_file, args.public_only)
1613+
else None
1614+
)
14781615
ranked = _rank_catalog_operations(
14791616
operations,
14801617
task=args.task,
14811618
max_cost=args.max_cost,
14821619
max_latency_ms=args.max_latency_ms,
1620+
manifest_scope_by_operation_id=manifest_scope,
14831621
)
14841622
if args.json:
14851623
payload = {
@@ -2191,11 +2329,21 @@ def _run_code_search_command(
21912329
registry: OperationRegistry,
21922330
sdk_client: AgenticFlowSDK,
21932331
) -> int:
2332+
manifest_scope = (
2333+
_manifest_scope_by_operation_id()
2334+
if _should_use_curated_manifest(args.spec_file, args.public_only)
2335+
else None
2336+
)
21942337
ranked = _rank_catalog_operations(
2195-
_catalog_operations(registry, public_only=args.public_only),
2338+
_catalog_operations(
2339+
registry,
2340+
public_only=args.public_only,
2341+
spec_file=args.spec_file,
2342+
),
21962343
task=args.task,
21972344
max_cost=args.max_cost,
21982345
max_latency_ms=args.max_latency_ms,
2346+
manifest_scope_by_operation_id=manifest_scope,
21992347
)
22002348

22012349
if args.limit is not None and args.limit > 0:

tests/unit/test_main.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,32 @@ def _write_catalog_spec(path: Path) -> None:
148148
)
149149

150150

151+
def _write_rank_scope_spec(path: Path) -> None:
152+
path.write_text(
153+
json.dumps(
154+
{
155+
"openapi": "3.1.0",
156+
"paths": {
157+
"/v1/public/a": {
158+
"get": {
159+
"operationId": "op_a",
160+
"tags": ["public", "workflow", "nodes"],
161+
"responses": {"200": {"description": "ok"}},
162+
},
163+
},
164+
"/v1/public/b": {
165+
"get": {
166+
"operationId": "op_b",
167+
"tags": ["public", "workflow", "nodes"],
168+
"responses": {"200": {"description": "ok"}},
169+
},
170+
},
171+
},
172+
}
173+
)
174+
)
175+
176+
151177
def _snapshot_operation_ids() -> set[str]:
152178
registry = OperationRegistry.from_spec(load_openapi_spec(default_spec_path()))
153179
return {op.operation_id for op in registry.list_operations(public_only=False)}
@@ -247,6 +273,32 @@ def test_ops_list_public_only_outputs_only_public_operations(capsys, tmp_path: P
247273
assert "admin_items" not in out
248274

249275

276+
def test_ops_list_public_only_applies_curated_manifest_filter_when_enabled(
277+
capsys,
278+
tmp_path: Path,
279+
monkeypatch,
280+
) -> None:
281+
spec_file = tmp_path / "openapi.json"
282+
_write_rank_scope_spec(spec_file)
283+
monkeypatch.setattr(
284+
main_module,
285+
"_should_use_curated_manifest",
286+
lambda *_args, **_kwargs: True,
287+
)
288+
monkeypatch.setattr(
289+
main_module,
290+
"_manifest_scope_by_operation_id",
291+
lambda: {"op_a": "supported-executed"},
292+
)
293+
294+
rc = run_cli(["--spec-file", str(spec_file), "ops", "list", "--public-only"])
295+
out = capsys.readouterr().out
296+
297+
assert rc == 0
298+
assert "op_a" in out
299+
assert "op_b" not in out
300+
301+
250302
def test_call_dry_run_by_operation_id(capsys, tmp_path: Path) -> None:
251303
spec_file = tmp_path / "openapi.json"
252304
_write_spec(spec_file)
@@ -728,6 +780,48 @@ def test_catalog_rank_json_applies_relevance_heuristic(
728780
assert payload["heuristic"]["formula"] == "score = relevance*10 - cost - latency/200"
729781

730782

783+
def test_catalog_rank_prefers_executed_manifest_scope_for_builder_tasks(
784+
capsys,
785+
tmp_path: Path,
786+
monkeypatch,
787+
) -> None:
788+
spec_file = tmp_path / "openapi.json"
789+
_write_rank_scope_spec(spec_file)
790+
monkeypatch.setattr(
791+
main_module,
792+
"_should_use_curated_manifest",
793+
lambda *_args, **_kwargs: True,
794+
)
795+
monkeypatch.setattr(
796+
main_module,
797+
"_manifest_scope_by_operation_id",
798+
lambda: {
799+
"op_a": "supported-executed",
800+
"op_b": "supported-blocked-policy",
801+
},
802+
)
803+
804+
rc = run_cli(
805+
[
806+
"--spec-file",
807+
str(spec_file),
808+
"catalog",
809+
"rank",
810+
"--public-only",
811+
"--task",
812+
"build workflow",
813+
"--json",
814+
],
815+
)
816+
payload = json.loads(capsys.readouterr().out)
817+
ranked = payload["items"]
818+
819+
assert rc == 0
820+
assert ranked[0]["operation_id"] == "op_a"
821+
assert ranked[0]["support_scope"] == "supported-executed"
822+
assert ranked[0]["scope_bonus"] > ranked[1]["scope_bonus"]
823+
824+
731825
def test_workflow_run_routes_to_expected_operation(capsys, monkeypatch) -> None:
732826
captured: dict[str, object] = {}
733827

0 commit comments

Comments
 (0)