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.