Capstone: a real end-to-end ChatGPT workflow
Let's build one realistic system end to end: a customer-support assistant for a small SaaS company that answers from a knowledge base, pulls live order and account data, escalates hard tickets to a human, and emails a weekly health report to the founder every Monday. You already know the pieces individually. This lesson is about wiring them together and, more importantly, **choosing *which* piece to use where**.
The scenario and the constraints
"SupportBot" must do four things:
- Answer product questions from internal docs.
- Look up a customer's live subscription and recent tickets.
- Hand off to a human when confidence is low or the customer is angry.
- Produce a weekly report on ticket volume, resolution time, and top issues.
Three constraints drive every decision: the team is two people, the docs change weekly, and customer data lives in a Postgres database and Zendesk. Keep these in mind, because they push us toward different surfaces for different jobs.
Decision 1: custom GPT or the APIAPIApplication Programming Interface: a standardised interface that lets applications communicate and exchange data without knowing each other's internal workings.View full definition →?
This is the central architecture fork, so resolve it first.
A Custom GPT (configured at chatgpt.com, distributed via the GPT Store or a private link) is the right home for the *internal-facing* assistant. Your two support agents open it inside ChatGPT, paste a customer email, and get a drafted reply grounded in your docs. Zero hosting, zero auth code, and it inherits Connectors and Actions natively.
The API (the Responses API) is the right home for anything *customer-facing* or *automated*: the widget on your website, the nightly report job. You need programmatic control, your own auth, logging, and the ability to run without a human in the loop.
So the answer is both, split by audience:
- Agent-facing drafting tool → Custom GPT.
- Website widget + scheduled report → API.
Don't try to force one surface to do everything. The Custom GPT is your fast path; the API is your durable path.
Decision 2: How docs get into the answer
The docs change weekly, so grounding strategy matters.
For the Custom GPT, use Connectors to link the live Google Drive folder where the team keeps docs. The GPT searches that source at query time, so a Monday edit is reflected Monday. This beats uploading static files to the GPT's knowledge, which would go stale and require re-uploading. See help.openai.com for the current Connectors catalog and admin controls.
For the API-backed widget, you own retrieval. Either run your own vector search and pass results into the prompt, or use file search as a hosted tool on the Responses API. For a two-person team, hosted file search is the pragmatic choice: less infrastructure to babysit. A weekly cron job re-syncs the docs into the file store.
Decision 3: Live data via Actions vs. function calling
The assistant needs getCustomer(email) and getRecentTickets(customerId). Same capability, two implementations depending on surface.
In the Custom GPT, you expose these as GPT Actions: an OpenAPI spec pointing at your internal API, with auth configured in the GPT builder. The model decides when to call them.
In the API widget, the same operations become function calling with structured outputs, so the arguments arrive as schemaschemaA schema is the formal blueprint that defines how data is structured, named, typed, and related within a database, file, or message.View full definition →-valid JSON every time. Here is the tool definition for the Responses API:
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"name": "get_customer",
"description": "Look up a customer's subscription and account status by email.",
"parameters": {
"type": "object",
"properties": {
"email": {"type": "string", "description": "Customer email address"}
},
"required": ["email"],
"additionalProperties": False
},
"strict": True
}]
response = client.responses.create(
model="gpt-4.1",
input="What plan is taylor@acme.com on, and is their account in good standing?",
tools=tools
)
print(response.output)strict: True is the part worth internalizing: it guarantees the model's arguments conform to your schema, which means your backend code never has to defensively parse a malformed email field. Read the function calling guide for the full loop, including how you send the tool result back for the model's final answer.
The key insight: the *same OpenAPI operations* back both your GPT Actions and your function-calling tools. Write the API once, expose it twice.
Decision 4: Escalation and the human-in-the-loop boundary
Never let a support bot guarantee a refund or close a ticket on its own. Build an explicit escalation tool, escalate_to_human(reason, urgency), and instruct the model to call it when the customer is upset, asks for money, or the docs don't cover the question.
Make escalation a *first-class tool*, not a vibe. When called, your code drops the conversation into a Zendesk queue and tells the customer a human is on it. This turns "the bot got it wrong" into "the bot knew its limits," which is the difference between a useful assistant and a liability.
Decision 5: Where the model orchestrates vs. where your code does
When you have several tools and multi-step logic (look up customer → check tickets → decide to answer or escalate), you have a choice:
- Let the model orchestrate via the Responses API tool loop. Simple, good for linear flows.
- Use the [Agents SDK](https://platform.openai.com/docs/guides/agents-sdk) when you want typed agents, handoffs between specialized agents (a "billing agent" vs. a "technical agent"), guardrailsguardrailsRules and controls that keep an AI system inside safe, legal and on-brand boundaries, blocking outputs and actions that cross the line.View full definition →, and tracing. For SupportBot's escalation-and-handoff pattern, the Agents SDK earns its keep because handoffs are a built-in primitive.
If you're prototyping interactively, the ChatGPT agent (the browsing, clicking, multi-step agent inside ChatGPT) is great for one-off investigations ("go find every open ticket mentioning the new billing bug"), but it's not where you run production traffic. Keep that distinction sharp: ChatGPT agent for exploration, Agents SDK for the deployed system.
Decision 6: The weekly report
This is where Advanced Data Analysis (Code Interpreter) shines. The report needs real computation: median resolution time, week-over-week ticket deltas, a chart of top issue categories.
You have two clean options:
Option A: Scheduled task inside ChatGPT. ChatGPT supports scheduled tasks that run a prompt on a recurring basis. Create a task in your Custom GPT: "Every Monday at 8am, pull last week's tickets via the Zendesk connector, compute resolution stats, and produce a summary with a chart." Lowest effort, no code to host.
Option B: API job on your own cron. A small script queries Postgres, hands the rows to the model with the Code Interpreter tool to crunch numbers and render a chart, then emails the result. More control, version-controlled, testable. Choose this when the report feeds something downstream or the founder needs it in a specific format every time.
For a two-person team, start with Option A. Graduate to Option B when the report becomes load-bearing.
Building a Customer Support Agent with the OpenAI Agents SDK
Knowledge check
1. In the SupportBot design, why is a Custom GPT chosen for the agent-facing drafting tool rather than the API?
2. The lesson concludes the answer to 'Custom GPT or API?' is 'both.' What principle drives that split?
3. Given that the docs change weekly, why does the lesson prefer linking a live Google Drive folder via Connectors over uploading static files to the GPT's knowledge?
4. Select ALL tasks in the SupportBot scenario that are best served by the API rather than the Custom GPT.
Select all the correct answers.
5. Select ALL constraints the lesson says should drive SupportBot's architecture decisions.
Select all the correct answers.
Putting the surfaces together
Here is the full picture, surface by surface:
| Job | Surface | Why |
|---|---|---|
| Agents drafting replies | Custom GPT + Connectors + Actions | Fast, no hosting, live docs |
| Website chat widget | Responses API + file search + function calling | Programmatic, owns auth and logging |
| Multi-agent routing & escalation | Agents SDK | Handoffs and guardrails are built in |
| Weekly report | Scheduled task (start) → API + Code Interpreter (scale) | Real computation on a schedule |
Notice the through-line: one backend API serves the lookups, exposed as Actions to the GPT and as function tools to the API. One doc source (the Drive folder) feeds both the Connector and the file-search sync. You are not building four systems; you are building two integration points (your API, your docs) and pointing multiple surfaces at them.
Memory, Projects, and instructions
A few ChatGPT features quietly improve the agent-facing tool:
- Put SupportBot's tone rules, escalation policy, and "always cite the doc" rule in the Custom GPT's instructions, not in every prompt.
- Group the team's support work in a Project so conversations, files, and custom instructions stay scoped together, separate from their other ChatGPT use.
- Leave memory off for the support Custom GPT. You do not want it carrying assumptions between unrelated customers. Memory is great for a personal assistant, risky for a shared support tool.
A note on Canvas and Codex
When you're *writing* the report templates or the email copy, Canvas is the better editing surface than chat: you iterate on a document in place. And the actual implementation work (the Responses API script, the OpenAPI spec, the cron job) is exactly what Codex is for. Use it to scaffold the function-calling loop and the Postgres query, then review every line before it touches customer data.
Failure modes to design for
Before you ship, pre-decide what happens when things break:
- The lookup API is down. Your function should return a clear error object, and the model's instructions should say "if a lookup fails, escalate rather than guess."
- The model invents a policy. Constrain it: answer only from retrieved docs, and if retrieval returns nothing relevant, say so and escalate.
- Rate limits or cost spikes. Log tokentokenA token is the basic unit of text that language models process, often a word fragment, whole word, or punctuation mark rather than a single character.View full definition → usage per conversation. The website widget is your uncapped surface, so cap conversation length and add a fallback to human handoff.
Design these on paper first. They are cheaper to decide now than to debug in production.
Key Takeaways
- Split by audience, not by feature. Custom GPT for your internal team (fast, no hosting); the Responses API for customer-facing and automated flows (control, logging, auth).
- Build the backend once, expose it twice. The same OpenAPI operations power your GPT Actions and your function-calling tools. Same idea for docs: one source feeds both Connectors and file search.
- Make escalation a first-class tool with
strictstructured outputs, and instruct the model to escalate on failure or low confidence instead of guessing. - Start on the low-effort surface, graduate when load-bearing. Use a ChatGPT scheduled task for the weekly report first; move to an API + Code Interpreter cron job only when it feeds something downstream.
- Reach for the Agents SDK when you need handoffs, guardrails, and tracing; keep the ChatGPT agent for exploration, not production traffic.
What to do, from this lesson
These actions are compiled in the role's Playbook.
- Let the model own only generation; gather, validate, and deliver in code