Skip to main content

OpenAPI Search-To-Plan Tutorial

This tutorial walks through the first production-grade workflow for a large OpenAPI catalog:

  1. inspect the source
  2. build a collection artifact
  3. check readiness
  4. retrieve candidates with evidence
  5. select the final target
  6. synthesize a plan

The flow is deterministic until the optional LLM intent step. It does not call the downstream API, store credentials, write to a database, or depend on an XGEN-specific adapter.

What You Build​

The tutorial creates a collection.json artifact that contains:

Artifact SectionWhy It Matters
toolsnormalized OpenAPI operations as ToolSchema records
graphstructural, contract, semantic, manual, and trace-ready edges
semantic_summaryaction/resource/module/result-shape coverage
edge_quality_summaryedge evidence distribution
readiness_reportdeterministic issue codes and recommendations
source_snapshot_manifestsource hashes and operation counts

After the artifact is saved, you load it as a ToolGraph and run retrieval, selection, and plan synthesis against the loaded graph.

Before You Start​

Install the OpenAPI extra:

pip install "graph-tool-call[openapi]"

Use a local file while developing:

export OPENAPI_SOURCE=./openapi.json

For private Swagger UI URLs, enable private host access only inside a trusted environment:

export OPENAPI_SOURCE=https://internal.example.com/swagger-ui/index.html

Private source access is a build-time concern. Runtime auth tokens for executing API calls should stay in your adapter.

1. Inspect The Source​

Start with deterministic readiness diagnostics. This catches missing schemas, generic request bodies, response envelopes, auth requirements, duplicate operation ids, and weak graph connectivity before you tune retrieval.

graph-tool-call inspect-openapi "$OPENAPI_SOURCE" --json

Treat blocked as a stop sign for execute. A warning status can still be usable for search and plan cases when the issue evidence is understood.

2. Build The Collection Artifact​

Build a storage-ready artifact when the catalog will be used by a product UI, Quality Lab, Planflow adapter, or repeated regression tests.

graph-tool-call build-openapi-collection "$OPENAPI_SOURCE" \
-o collection.json \
--context-field tenantId,siteNo \
--paging-field page,size \
--search-filter-field keyword,searchText

The field-name options are generic hints. Pass product or customer naming conventions from the adapter. Do not hardcode them into graph-tool-call.

3. Check The Build Summary​

Read the artifact before sending anything to the LLM.

import json
from pathlib import Path

artifact = json.loads(Path("collection.json").read_text(encoding="utf-8"))

print(artifact["metadata"]["tool_count"])
print(artifact["semantic_summary"])
print(artifact["edge_quality_summary"])
print(artifact["readiness_report"]["summary"])

Healthy large catalogs should show high action/resource/module coverage, useful contract coverage, and a small number of strong edge types. A catalog can be searchable before every response schema is perfect, but plan and execute gates should stay stricter.

4. Retrieve With Evidence​

Load the saved artifact as a ToolGraph. This keeps the public retrieval API working against the same persisted shape your adapter stores.

from graph_tool_call import ToolGraph
from graph_tool_call.graphify import retrieve_graphify

graph = ToolGraph.load("collection.json")

query = "find refund-ready orders"
retrieval = retrieve_graphify(
graph,
query,
top_k=8,
include_evidence=True,
)

for row in retrieval["results"]:
print(row["name"], row["score"])
print(row["score_breakdown"])

Inspect these fields first:

FieldUse It For
score_breakdown.seedwhether the tool was a direct text/semantic hit
score_breakdown.graph_expansionwhether graph traversal pulled it in
score_breakdown.action_matchwhether query action matched canonical_action
score_breakdown.resource_matchwhether query resource matched primary_resource
semantic_evidenceselector-ready semantic matches
edge_evidencegraph edge that expanded or supported the row
stats.token_budget_usedrough context budget for downstream prompts

Do not tune from rank alone. A high rank with weak evidence should be treated differently from a high rank with action, resource, shape, and contract support.

5. Select The Final Target​

Retrieval returns candidates. Target selection decides which tool should become the final goal. If an LLM has already proposed a target, pass it as llm_target; otherwise the selector can still rank the candidate set.

from graph_tool_call.graphify import select_target_candidate

selection = select_target_candidate(
query=query,
candidates=[row["name"] for row in retrieval["results"]],
tools=graph.tools,
retrieval_results=retrieval["results"],
llm_target=None,
)

print(selection["selected_target"])
print(selection["confidence"])
print(selection["reason_codes"])
print(selection["candidate_evidence"][:3])

The default policy is conservative. It overrides an LLM target only when strong deterministic evidence has enough margin. Otherwise it records ambiguity and keeps the uncertainty visible.

6. Synthesize A Plan​

Plan synthesis consumes a plain graph payload with graph and tools keys. Build that payload from the loaded ToolGraph.

from graph_tool_call.plan import PathSynthesizer, PlanSynthesisError

graph_payload = {
"graph": graph.graph.to_dict(),
"tools": {
name: tool.to_dict()
for name, tool in graph.tools.items()
},
}

try:
plan = PathSynthesizer(
graph_payload,
context_defaults={"siteNo": "1001"},
).synthesize(
target=selection["selected_target"],
goal=query,
entities={"keyword": "refund-ready"},
)
except PlanSynthesisError as exc:
print(exc.to_dict())
else:
print(plan.id)
print([step.tool for step in plan.steps])
print(plan.metadata["synthesis"])

If the plan needs a user-provided field, inspect plan.metadata["user_input_slots"]. If synthesis fails, use exc.to_dict() instead of parsing the exception message.

7. Add A Quality Case​

Once the flow works manually, turn it into a regression case.

{
"id": "orders-refund-ready-search-001",
"mode": "plan",
"query": "find refund-ready orders",
"expected_target": "getRefundableOrders",
"expected_top_k": 8,
"provided_entities": {
"keyword": "refund-ready",
"siteNo": "1001"
},
"assertions": [
{"type": "top_k_contains", "tool": "getRefundableOrders", "k": 8},
{"type": "selected_target", "equals": "getRefundableOrders"},
{"type": "plan_contains_tool", "tool": "getRefundableOrders"}
]
}

Use execute mode only after auth readiness and mutation safety are configured by the adapter.

Troubleshooting​

SymptomLikely CauseNext Check
Correct tool missing from Top-Kweak semantic metadata or missing operation textsemantic_summary, indexed action/resource fields
Correct tool in Top-K but not selectedweak selector margin or sibling ambiguitytarget_selector.rank_signals
Plan asks for too many user inputscontract fields were not produced by prior toolsapi_contract.produces, selected_producers
unknown_targetselector target is not in the loaded graphuse graph.tools.keys() to verify names
auth_required appears in readinesscollection needs adapter authconfigure product auth profile before execute
Search looks good but execute failsruntime adapter issue, not retrievalinspect auth_readiness and runner events

Validation Commands​

Run these before turning the tutorial into a product workflow:

graph-tool-call inspect-openapi "$OPENAPI_SOURCE" --json
graph-tool-call build-openapi-collection "$OPENAPI_SOURCE" -o collection.json
poetry run pytest tests/test_graphify_contract_025.py tests/test_plan_synthesizer.py -q

Before publishing a quality claim, use the validation and benchmark gates rather than a single manual query.