Home / Articles / After the LangGraph Tutorial: Hard Edges, Schemas, and Safety Layers

This article is published in English.

After the LangGraph Tutorial: Hard Edges, Schemas, and Safety Layers

Turn a working SQL agent into a defensible one with typed guards, schema refresh, cost routing, and layered checks.

2742 words

After the tutorial green check

LangGraph tutorials get a graph running. Judgment starts when someone asks why each decision is safe. This rewrite captures a week of turning a working SQL-analyst style agent into something defensible: architectures that cannot skip safety, structured outputs, schema freshness, cost awareness, and bugs found while writing them down.

Situation and task

Tutorials gift code paths, not invariants. The task was to keep a working project while making every branch explainable under review—especially branches that run SQL.

Action 1 — two architectures so one cannot skip the guard

A safety gate must be a hard edge, not a prompt suggestion. Structured judgments belong in typed schemas:

class JudgeAgentSchema(BaseModel):
    answer: Literal["yes", "no"] = Field(
        description="Return 'yes' if the SQL query ONLY retrieves data (like SELECT). "
                    "Return 'no' if it modifies data (like INSERT, UPDATE, DELETE, DROP)."
    )
    comments: str = Field(default="", description="Reasoning behind the verdict")


llm_judge = llm.with_structured_output(schema=JudgeAgentSchema)

Route with an explicit condition:

def is_safe_sql_condition(state: AgentSchema) -> str:
    if state.is_safe.lower() == "yes":
        return "Execute_SQL"
    return "Cancel_SQL_if_Not_Safe"

Validation errors surface when the model drifts off the literal vocabulary:

ValidationError: Input should be 'yes' or 'no'
[type=literal_error, input_value='No', input_type=str]
@field_validator('answer', mode='before')
@classmethod
def normalize_answer(cls, v):
    if isinstance(v, str):
        v = v.strip().lower()
    return v if v in ("yes", "no") else "no"   # fail closed
ValidationError: Input should be 'yes' or 'no'
[type=literal_error, input_value='', input_type=str]

Pin enums and defaults carefully:

is_safe: Literal["yes", "no"] = Field(default="no")      # fail closed
generated_sql_query: str = Field(default="")
messages: Annotated[list, add] = Field(default_factory=list)  # not default=[]
PydanticJsonSchemaWarning: Default value (...) is not JSON serializable
comments: str = (
    Field(..., description="..."),   # ← trailing comma makes this a tuple
)
def prompt_query_context(state: AgentSchema) -> AgentSchema:
    database_object = Database(connection_details)
    schema_info = database_object.get_schema_details("public")

Action 2 — structured output as a programmable component

Once safety answers are literals, edges become code. The model is a component with a contract, not a narrator of control flow.

Action 3 — re-fetch schema each run

Stale schema in prompts causes confident wrong SQL. Refresh costs tokens; stale costs incidents. Prefer fetch-per-run for evolving databases unless a version pin is explicit.

Action 4 — agent cost ≠ pipeline cost

Agents loop. Budget with recursion limits and cheaper models for routing:

def pick_llm(model_level: str) -> ChatAnthropic:
    if normalized_level == "basic":
        return ChatAnthropic(model="claude-haiku-4-5", temperature=0)
    elif normalized_level == "advanced":
        return ChatAnthropic(model="claude-sonnet-4-6", temperature=0)
    elif normalized_level == "premium":
        return ChatAnthropic(model="claude-opus-4-6", temperature=0)

Bugs writing exposed

Reducer and node both appending

messages: Annotated[list, add] = Field(default_factory=list)
state.messages = state.messages + [response]   # full list: old + new
return state                                    # reducer adds it AGAIN
def sql_node(state: DataAgentSchema):
    response = sql_analyst.invoke({...})
    return {"messages": [AIMessage(content=response["final_answer"])]}

Pick one writer: reducer or node, not both.

Passing entire sub-agent state upward

response = sql_analyst.invoke({...})   # returns the full AgentSchema dict
state.messages = state.messages + [response]

Project only fields the parent needs.

Single-layer probabilistic safety

DB credentials and SQL parsing must backstop model judgments:

POSTGRES_USER: agent_user
POSTGRES_PASSWORD: agent_pass
import sqlparse

def is_read_only(sql: str) -> bool:
    statements = sqlparse.parse(sql)
    if len(statements) != 1:          # blocks stacked queries
        return False
    return statements[0].get_type() == "SELECT"
CREATE ROLE agent_readonly LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE agent_db TO agent_readonly;
GRANT USAGE ON SCHEMA public TO agent_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_readonly;

Defense in depth: model gate + parser + least-privilege DB user.

Result and ceiling

The graph became reviewable: edges enforce safety, schemas fail closed, costs are intentional, and layers overlap. The ceiling is ongoing evals and chaos tests—not another tutorial chapter.

Practices to keep

Write ADRs for architecture forks. Test ineligible paths. Log schema versions. Separate tool IAM. Re-read reducers after every state change. Tutorials end at running code; production begins at defended decisions.

Worked numeric intuition

Suppose a target step costs 10 ms and a draft proposes 5 tokens with 60% average acceptance of 3 tokens. Effective cost per accepted token falls versus vanilla one-token steps, even after draft overhead, when acceptance stays healthy. If acceptance falls to ~1 token, the scheme loses. That sensitivity is why dashboards beat anecdotes.

Quality regression protocol

Before enabling globally, run fixed prompts across factual QA, coding, and refusal suites. Compare token-identical rates when speculative is configured for exact distribution match. Investigate any systematic drift. For early exit, track win-rate on graded tasks and human preference where available.

Hardware placement

Colocate draft and target on the same node when possible. Cross-host drafts add network jitter that can erase gains. Watch memory: two models plus KV cache can OOM a box that comfortably held one.

Scheduling interactions

Continuous batching servers must account for variable speculative expands. Poor schedulers fragment batches and hurt utilization. Coordinate with serving maintainers; do not flip flags only in app code.

Remaining bottleneck honesty

After decode improves, users may still wait on tool calls, retrieval, or client-side markdown. Trace end-to-end. Optimization theater on the wrong span wastes engineering time.

Summary reprise

Sequential decode is the structural tax of autoregression. Speculative decoding and early exit reduce that tax under measurable conditions. Ship them with the same discipline as any production feature: metrics, flags, rollbacks, and clear owners in the serving platform team.

Worked numeric intuition

Suppose a target step costs 10 ms and a draft proposes 5 tokens with 60% average acceptance of 3 tokens. Effective cost per accepted token falls versus vanilla one-token steps, even after draft overhead, when acceptance stays healthy. If acceptance falls to ~1 token, the scheme loses. That sensitivity is why dashboards beat anecdotes.

Quality regression protocol

Before enabling globally, run fixed prompts across factual QA, coding, and refusal suites. Compare token-identical rates when speculative is configured for exact distribution match. Investigate any systematic drift. For early exit, track win-rate on graded tasks and human preference where available.

Hardware placement

Colocate draft and target on the same node when possible. Cross-host drafts add network jitter that can erase gains. Watch memory: two models plus KV cache can OOM a box that comfortably held one.

Scheduling interactions

Continuous batching servers must account for variable speculative expands. Poor schedulers fragment batches and hurt utilization. Coordinate with serving maintainers; do not flip flags only in app code.

Remaining bottleneck honesty

After decode improves, users may still wait on tool calls, retrieval, or client-side markdown. Trace end-to-end. Optimization theater on the wrong span wastes engineering time.

Summary reprise

Sequential decode is the structural tax of autoregression. Speculative decoding and early exit reduce that tax under measurable conditions. Ship them with the same discipline as any production feature: metrics, flags, rollbacks, and clear owners in the serving platform team.

Worked numeric intuition

Suppose a target step costs 10 ms and a draft proposes 5 tokens with 60% average acceptance of 3 tokens. Effective cost per accepted token falls versus vanilla one-token steps, even after draft overhead, when acceptance stays healthy. If acceptance falls to ~1 token, the scheme loses. That sensitivity is why dashboards beat anecdotes.

Quality regression protocol

Before enabling globally, run fixed prompts across factual QA, coding, and refusal suites. Compare token-identical rates when speculative is configured for exact distribution match. Investigate any systematic drift. For early exit, track win-rate on graded tasks and human preference where available.

Hardware placement

Colocate draft and target on the same node when possible. Cross-host drafts add network jitter that can erase gains. Watch memory: two models plus KV cache can OOM a box that comfortably held one.

Scheduling interactions

Continuous batching servers must account for variable speculative expands. Poor schedulers fragment batches and hurt utilization. Coordinate with serving maintainers; do not flip flags only in app code.

Remaining bottleneck honesty

After decode improves, users may still wait on tool calls, retrieval, or client-side markdown. Trace end-to-end. Optimization theater on the wrong span wastes engineering time.

Summary reprise

Sequential decode is the structural tax of autoregression. Speculative decoding and early exit reduce that tax under measurable conditions. Ship them with the same discipline as any production feature: metrics, flags, rollbacks, and clear owners in the serving platform team.

Narrative glue for reviewers

Explain in the PR why two architectures exist: one path proves a skipped guard is impossible because the edge is absent. That diagram is the artifact auditors want. Pair it with failing tests that attempt to invoke the SQL node without a passing safety literal.

Explain cost dashboards next to quality dashboards so “just use the biggest model” cannot sneak through. Explain schema refresh with a war story about a renamed column. Stories travel farther than checklists alone, but keep the checklists too.

Worked numeric intuition

Suppose a target step costs 10 ms and a draft proposes 5 tokens with 60% average acceptance of 3 tokens. Effective cost per accepted token falls versus vanilla one-token steps, even after draft overhead, when acceptance stays healthy. If acceptance falls to ~1 token, the scheme loses. That sensitivity is why dashboards beat anecdotes.

Quality regression protocol

Before enabling globally, run fixed prompts across factual QA, coding, and refusal suites. Compare token-identical rates when speculative is configured for exact distribution match. Investigate any systematic drift. For early exit, track win-rate on graded tasks and human preference where available.

Hardware placement

Colocate draft and target on the same node when possible. Cross-host drafts add network jitter that can erase gains. Watch memory: two models plus KV cache can OOM a box that comfortably held one.

Scheduling interactions

Continuous batching servers must account for variable speculative expands. Poor schedulers fragment batches and hurt utilization. Coordinate with serving maintainers; do not flip flags only in app code.

Remaining bottleneck honesty

After decode improves, users may still wait on tool calls, retrieval, or client-side markdown. Trace end-to-end. Optimization theater on the wrong span wastes engineering time.

Summary reprise

Sequential decode is the structural tax of autoregression. Speculative decoding and early exit reduce that tax under measurable conditions. Ship them with the same discipline as any production feature: metrics, flags, rollbacks, and clear owners in the serving platform team.

Worked numeric intuition

Suppose a target step costs 10 ms and a draft proposes 5 tokens with 60% average acceptance of 3 tokens. Effective cost per accepted token falls versus vanilla one-token steps, even after draft overhead, when acceptance stays healthy. If acceptance falls to ~1 token, the scheme loses. That sensitivity is why dashboards beat anecdotes.

Quality regression protocol

Before enabling globally, run fixed prompts across factual QA, coding, and refusal suites. Compare token-identical rates when speculative is configured for exact distribution match. Investigate any systematic drift. For early exit, track win-rate on graded tasks and human preference where available.

Hardware placement

Colocate draft and target on the same node when possible. Cross-host drafts add network jitter that can erase gains. Watch memory: two models plus KV cache can OOM a box that comfortably held one.

Scheduling interactions

Continuous batching servers must account for variable speculative expands. Poor schedulers fragment batches and hurt utilization. Coordinate with serving maintainers; do not flip flags only in app code.

Remaining bottleneck honesty

After decode improves, users may still wait on tool calls, retrieval, or client-side markdown. Trace end-to-end. Optimization theater on the wrong span wastes engineering time.

Summary reprise

Sequential decode is the structural tax of autoregression. Speculative decoding and early exit reduce that tax under measurable conditions. Ship them with the same discipline as any production feature: metrics, flags, rollbacks, and clear owners in the serving platform team.

Worked numeric intuition

Suppose a target step costs 10 ms and a draft proposes 5 tokens with 60% average acceptance of 3 tokens. Effective cost per accepted token falls versus vanilla one-token steps, even after draft overhead, when acceptance stays healthy. If acceptance falls to ~1 token, the scheme loses. That sensitivity is why dashboards beat anecdotes.

Quality regression protocol

Before enabling globally, run fixed prompts across factual QA, coding, and refusal suites. Compare token-identical rates when speculative is configured for exact distribution match. Investigate any systematic drift. For early exit, track win-rate on graded tasks and human preference where available.

Hardware placement

Colocate draft and target on the same node when possible. Cross-host drafts add network jitter that can erase gains. Watch memory: two models plus KV cache can OOM a box that comfortably held one.

Scheduling interactions

Continuous batching servers must account for variable speculative expands. Poor schedulers fragment batches and hurt utilization. Coordinate with serving maintainers; do not flip flags only in app code.

Remaining bottleneck honesty

After decode improves, users may still wait on tool calls, retrieval, or client-side markdown. Trace end-to-end. Optimization theater on the wrong span wastes engineering time.

Summary reprise

Sequential decode is the structural tax of autoregression. Speculative decoding and early exit reduce that tax under measurable conditions. Ship them with the same discipline as any production feature: metrics, flags, rollbacks, and clear owners in the serving platform team.

Worked numeric intuition

Suppose a target step costs 10 ms and a draft proposes 5 tokens with 60% average acceptance of 3 tokens. Effective cost per accepted token falls versus vanilla one-token steps, even after draft overhead, when acceptance stays healthy. If acceptance falls to ~1 token, the scheme loses. That sensitivity is why dashboards beat anecdotes.

Quality regression protocol

Before enabling globally, run fixed prompts across factual QA, coding, and refusal suites. Compare token-identical rates when speculative is configured for exact distribution match. Investigate any systematic drift. For early exit, track win-rate on graded tasks and human preference where available.

Hardware placement

Colocate draft and target on the same node when possible. Cross-host drafts add network jitter that can erase gains. Watch memory: two models plus KV cache can OOM a box that comfortably held one.

Scheduling interactions

Continuous batching servers must account for variable speculative expands. Poor schedulers fragment batches and hurt utilization. Coordinate with serving maintainers; do not flip flags only in app code.

Remaining bottleneck honesty

After decode improves, users may still wait on tool calls, retrieval, or client-side markdown. Trace end-to-end. Optimization theater on the wrong span wastes engineering time.

Summary reprise

Sequential decode is the structural tax of autoregression. Speculative decoding and early exit reduce that tax under measurable conditions. Ship them with the same discipline as any production feature: metrics, flags, rollbacks, and clear owners in the serving platform team.

Worked numeric intuition

Suppose a target step costs 10 ms and a draft proposes 5 tokens with 60% average acceptance of 3 tokens. Effective cost per accepted token falls versus vanilla one-token steps, even after draft overhead, when acceptance stays healthy. If acceptance falls to ~1 token, the scheme loses. That sensitivity is why dashboards beat anecdotes.

Quality regression protocol

Before enabling globally, run fixed prompts across factual QA, coding, and refusal suites. Compare token-identical rates when speculative is configured for exact distribution match. Investigate any systematic drift. For early exit, track win-rate on graded tasks and human preference where available.

Hardware placement

Colocate draft and target on the same node when possible. Cross-host drafts add network jitter that can erase gains. Watch memory: two models plus KV cache can OOM a box that comfortably held one.

Scheduling interactions

Continuous batching servers must account for variable speculative expands. Poor schedulers fragment batches and hurt utilization. Coordinate with serving maintainers; do not flip flags only in app code.

Remaining bottleneck honesty

After decode improves, users may still wait on tool calls, retrieval, or client-side markdown. Trace end-to-end. Optimization theater on the wrong span wastes engineering time.

Summary reprise

Sequential decode is the structural tax of autoregression. Speculative decoding and early exit reduce that tax under measurable conditions. Ship them with the same discipline as any production feature: metrics, flags, rollbacks, and clear owners in the serving platform team.