Skip to content

Spec-Driven Design

An agent that calls a tool built by writing whatever shape of data seemed reasonable at the time works fine right up until someone else on your team builds a second tool the same way, with a slightly different shape. Contract-first design, defining the interface before the implementation, is how you keep an agent's tools honest as a system grows past the one you can hold in your head.

Owl mascot

What you'll learn

  • The real cost of JSON/YAML parsing at scale (the "serialization tax")
  • Pydantic v2's Rust core and why contract-first design pays off
  • OpenAPI vs. JSON Schema vs. Protobuf vs. GraphQL, compared head-to-head

How it works: what a contract actually buys you

In high-performance agentic systems, the transition from Unstructured LLM Output to Structured System State is not free. Every validation layer adds a physical latency cost to your execution pipeline.

The Serialization Cost (YAML vs. JSON)

  • YAML: Human-readable and preferred for "Agent Context." However, YAML parsing in Python (via PyYAML) is significantly slower than JSON parsing due to its complex specification and lack of a native C/Rust-optimized parser in the standard library.
  • JSON: The "Machine Standard." When an agent executes a tool, we prefer JSON for the final payload to minimize In-Transit Latency and take advantage of rapid simdjson or ujson implementations.

Schema Parsing Latency

When an agent reads a 5,000-line OpenAPI YAML file, it consumes tokens and increases Prompt Processing Latency.

  • The Bottleneck: As your API suite grows, "In-Context Specs" can become a primary bottleneck. Production architectures use Spec Indexing (treating API endpoints like RAG documents) or Dynamic Schema Pruning to only show the agent the endpoints relevant to the current task.

Options & when to use each

IDL StandardOptimizationAgent CompatibilitySerialization OverheadPrimary Production Bottleneck
OpenAPI 3.0Ecosystem ReachHighest (Native Support)Moderate (YAML/JSON)Large spec files bloat context windows.
JSON SchemaData ValidationHigh (Structured Output)Low (Direct JSON)Lacks "Action" semantics (Paths/Methods).
ProtobufNetwork LatencyLow (Needs JSON Proxy)Minimal (Binary)Agents cannot natively "read" binary specs.
GraphQLData DensityModerate (Requires Tools)High (Introspection)High LLM hallucination rate on complex schemas.

Build it

Pattern: Pydantic v2 (The Rust Core)

In Lab 2, we utilize Pydantic v2. Unlike standard Python libraries, Pydantic v2 is built on pydantic-core, a validation engine written in Rust.

  1. Compiled Logic: Schema validation logic is compiled into machine code rather than interpreted Python bytecode.
  2. Zero-Copy Coercion: It safely converts types (e.g., str ➡️ datetime) with minimal memory allocation.
  3. Rationale: This ensures that even if an LLM provides a slightly malformed string, your agent's "Safe Boundary" can correct it or reject it in <1ms.

Pattern: Contract-First Injection

In Lab 3, we feed the Raw YAML to Gemini.

  • Rationale: By providing the entire OpenAPI spec, we leverage Gemini's ability to reason over long-context documentation. This eliminates the need for manual "Action Mappings" and allows the agent to self-discover new features as soon as the YAML file is updated.

What goes wrong

Failure ModeError/Log SignatureRoot CauseCode-Level Mitigation
Schema DriftHTTP 422 Unprocessable EntityCode changed but the YAML spec was not updated.Implement CI/CD checks that generate YAML from Pydantic.
Ambiguous OperationOperationId not foundThe LLM "guessed" a method name that doesn't exist.Use strict Enum mapping for operationId in the prompt.
Validation RecursionRecursionError: maximum depthRecursive Pydantic models (e.g. A ➡️ B ➡️ A) without depth limits.Use ForwardRef and explicit max_length constraints.
Payload BloatPrompt length exceededSpec file is too large for the model's active window.Implement "Spec Pruning" based on task classification.

Confirm it worked

When executing the labs in your Linux environment, monitor these telemetry signals:

  1. Validation Trace: Intentionally pass an invalid string to your BookMeetingTool.
    • Observation: Watch the console for pydantic_core._pydantic_core.ValidationError. Note the highly specific error map detailing exactly which field failed and why.
  2. YAML vs. JSON Tokens: Run the agent with a YAML spec, then run it with a JSON-minified version of the same spec.
    • Observation: Check the Gemini token usage logs. You will see that YAML consumes significantly more tokens due to whitespace and structural markers.
  3. Context Loading: Use time python spec_agent.py.
    • Observation: Note the "Real Time" vs "User Time." Most of the delay is "Network Wait" (IO-bound), the same network overhead covered in Calling AI APIs Reliably.

Next Step: proceed to Deploying Your Agent to get this running reliably in production.