Chaining Calls
Explore how to chain multiple AI model calls in backend workflows by validating outputs, managing dependencies, and handling new failure modes. Understand the importance of confidence checks between stages and the impact of chaining on cost and latency. Learn to design stages that separate extraction from drafting to improve response accuracy and reliability.
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 Enumfrom pydantic import BaseModelclass ProductArea(str, Enum):CHECKOUT = "checkout"ACCOUNT = "account"SEARCH = "search"OTHER = "other"class Confidence(str, Enum):HIGH = "high"LOW = "low"class ExtractedComplaint(BaseModel):product_area: ProductAreaissue_summary: strconfidence: Confidencedef 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 = Truedef 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.contentraise 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.contentraise 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: ...