Search⌘ K
AI Features

Chaining Calls

Understand how to build multi-stage AI workflows by chaining calls where one validated output feeds the next. Explore key failure modes such as low-confidence results stopping chains and schema-valid but inaccurate data causing amplified errors. Learn to design prompts that separate extraction and drafting tasks, validate chaining outputs, and consider the impact on cost and latency. This lesson prepares you to implement reliable multi-call AI features in backend services.

Every AI call in this course so far has stood alone, one prompt, one validated result, one action taken. A lot of real backend tasks need more than one call working together, where the second call's input is the first call's validated output. This lesson builds exactly that, an extraction call that reads a raw support ticket, followed by a drafting call that writes a reply based on what was extracted, and it looks closely at what specifically goes wrong once two calls depend on each other instead of standing alone.

Already configured setup code

The block below defines the schema stage one produces, and two functions standing in for two separate model calls, each with a mock response by default so this file runs with no API key. None of this is new in spirit, it is the same schema and mock pattern from every earlier lesson, just applied to a two stage task instead of a single call.

from enum import Enum
from pydantic import BaseModel
class ProductArea(str, Enum):
CHECKOUT = "checkout"
ACCOUNT = "account"
SEARCH = "search"
OTHER = "other"
class Confidence(str, Enum):
HIGH = "high"
LOW = "low"
class ExtractedComplaint(BaseModel):
product_area: ProductArea
issue_summary: str
confidence: Confidence
def build_extraction_prompt(ticket_text: str) -> str:
return (
"Extract the product area and a short issue summary from the "
"ticket below. Respond with a JSON object in exactly this shape, "
"and nothing else:\n\n"
"{\n"
' "product_area": one of "checkout", "account", "search", "other",\n'
' "issue_summary": a string, one short sentence,\n'
' "confidence": one of "high", "low"\n'
"}\n\n"
'Set confidence to low if the ticket is vague about which part of '
'the product is actually affected.\n\n'
f'Ticket: "{ticket_text}"'
)
def build_reply_prompt(extraction: ExtractedComplaint) -> str:
return (
"Draft a short, empathetic customer support reply, two to three "
f"sentences, about an issue in the {extraction.product_area.value} "
f"area of the product. The issue is: {extraction.issue_summary}"
)
# USE_MOCK is True by default, so this file runs immediately with no
# API key. Set it to False and fill in a real provider block inside
# each function below to use an actual model for that stage.
USE_MOCK = True
def call_llm_extract(prompt: str) -> str:
if USE_MOCK:
return (
'{"product_area": "checkout", "issue_summary": '
'"Payment fails intermittently at the final step.", '
'"confidence": "high"}'
)
# from openai import OpenAI
# client = OpenAI(api_key="your-openai-api-key")
# response = client.chat.completions.create(
# model="your-chosen-openai-model",
# messages=[{"role": "user", "content": prompt}],
# )
# return response.choices[0].message.content
raise NotImplementedError("Fill in a real provider block, or set USE_MOCK back to True.")
def call_llm_draft(prompt: str) -> str:
if USE_MOCK:
return (
"Thanks for flagging this, and sorry for the trouble. We can see "
"payments are failing intermittently at the final checkout step, "
"and our team is looking into it right now."
)
# from openai import OpenAI
# client = OpenAI(api_key="your-openai-api-key")
# response = client.chat.completions.create(
# model="your-chosen-openai-model",
# messages=[{"role": "user", "content": prompt}],
# )
# return response.choices[0].message.content
raise NotImplementedError("Fill in a real provider block, or set USE_MOCK back to True.")

Notice there are two separate mock functions here, call_llm_extract and call_llm_draft, rather than one shared call_llm. That split matters for this lesson specifically, since chaining means two genuinely different calls happening one after another, and keeping them as separate functions makes that separation visible in the code itself, not just in the prompt text.

Building the chain

This is what the lesson is actually about. Stage one reads a raw ticket and pulls out a structured description of the complaint. Stage two takes that structured description, not the original ticket text, and drafts a reply. This split mirrors a real workflow: ...