Skip to main content

Best Practices and Rules

Follow these guidelines to build reliable, maintainable Conversational Graph agents. These patterns are drawn from real-world usage and help you avoid common pitfalls.

Use the Right Tool for Each Job

Conversational Graph provides several ways to communicate and control flow. Choosing the right one for each situation ensures your graph behaves predictably.

NeedUseWhy
Deterministic speechctx.say("message")Exact words via TTS
Data collectioncollect(prompt=...)Internal extractor extracts from user input
Advance to nodeRoute("target")Explicit flow control
Retry current nodeInterrupt(say)Re-ask with new message
Announce (no extraction)ctx.ask("instruction")LLM rephrases

Prompt Audience

Understanding who reads your text - the LLM or the user - is critical to writing effective prompts and messages.

  • collect(prompt=...) and ctx.ask() are LLM-facing - write as instructions to the model.
  • Interrupt(say) and ctx.say() are user-facing - write as direct speech.
tip

Interrupt() holds the graph and retries the same node. ctx.say() just streams and moves ahead.

Positive Guards

Write guards that check for specific known values rather than negating unknown ones. This makes your branching logic clearer and less error-prone.

# Good
if state.employment_status == "unemployed":
return Route("reject_unemployed", update={"income": 0})

# Avoid
if not state.employment_status or state.employment_status != "employed":
...

Skip-Ahead Guards

If a value has already been collected (e.g., from a previous session via checkpointing), skip the collection node and route to the next step. This avoids asking the user for information they've already provided.

async def collect_name(state, ctx):
if state.name:
return Route("collect_email") # already have it, skip
...

Explicit Constraints

Include constraints in both collect() prompts (LLM-facing) and Interrupt messages (user-facing). This ensures the LLM knows what to extract and the user knows what's expected if they need to retry.

Keep Nodes Small

One node = one task. Don't combine collection, validation, and routing in the same node. Smaller nodes are easier to test, reuse, and debug.

LLM Requirements

Keep in mind these rules about how the LLM interacts with the graph engine.

  • extractor.collect() requires an LLM in the pipeline. Use ctx.say() for TTS-only flows.
  • The LLM does not control routing - it only responds and extracts.

Limits

These are the built-in safeguards that prevent runaway or misconfigured graphs.

  • Recursion: Node depth capped at 10 (GraphRecursionError).
  • Retries: Per-node Interrupt count capped at max_retries (default 10).
  • Validation: Invalid extractions silently dropped - field stays None.
  • Replay/Resume from END: No-op - target a checkpoint with outgoing transitions.

Got a Question? Ask us on discord