第12课

AI Web应用搭建

把 AI 做成一个网页:用户输入,后端调用,页面显示。

本课项目:AI故事生成网页

学习重点:前端、后端、API Key 安全、页面渲染。工具重点:Web App + Responses API。

OpenAI AI编程第12课封面
AI 工具地图

OpenAI 项目工具栈

页面里加入 OpenAI、Node.js、Python 和 JSON 图标,帮助学生把 AI 能力、后端调用、脚本实验和结构化输出放在同一条学习路线里理解。

学习路线

阶段入口与周课入口

先用阶段卡片看清大方向,再用周课卡片进入具体项目。每节课都保留理论、例子、Node.js、Python、练习和自测,学生可以直接按卡片推进。

OpenAI 项目地图

阶段入口

第01阶段:AI应用基础与 OpenAI 入门

先看懂 AI 应用的工作流程,再完成第一次 API 调用,最后学会写清楚提示词。

进入学习

第02阶段:让 AI 输出程序能读懂的结果

AI 不只要会说话,还要按固定格式交答案,这样程序才能稳定处理。

进入学习

第03阶段:Function Calling:让 AI 调用函数

AI 负责理解语言,函数负责精确计算;两者配合,结果更可靠。

进入学习

第04阶段:多模态应用:文字 + 图片

把图片也交给 AI 看,再让它用清楚的文字或 JSON 结果回答。

进入学习

第05阶段:内置工具:Web Search 与 File Search

需要最新消息时查网页;需要班级资料时查文件。先找资料,再回答。

进入学习

第06阶段:前后端 AI 应用开发

把 AI 能力做成网页:前端收集输入,后端保护密钥,页面展示结果。

进入学习

第07阶段:AI Agent、安全与成本

Agent 会按步骤完成任务,但你要给它工具、边界、检查规则和预算意识。

进入学习

第08阶段:毕业项目发布会

把前面学过的提示词、JSON、函数、检索、网页和安全设计合在一个作品里。

进入学习

周课入口

第12课:AI Web应用搭建

把 AI 做成一个网页:用户输入,后端调用,页面显示。

进入学习

第13课:流式输出:让结果一点点出现

长回答不用等全部生成完,可以像打字一样逐步显示。

进入学习

12.1 今天你要完成什么

前端负责界面,后端负责安全调用 OpenAI。API Key 必须放在后端,不能暴露给浏览器。

本课闯关:完成“AI故事生成网页”,并用 3 组输入测试它。

12.2 核心理论

理论不是背概念,而是帮你判断项目为什么这样设计。下面这些规则会在代码里反复出现。

12.3 课堂讲解

这一课的项目是“AI故事生成网页”,重点是“前端、后端、API Key 安全、页面渲染”。你可以把它当成一个小实验:先给它一个清楚输入,再观察代码里哪些地方用到了 Web App + Responses API。课堂里我们不会一上来就追求复杂功能,而是先把最小版本做出来。最小版本跑通以后,你再改输入、改提示词、改输出格式,变化就会看得很清楚。

这几课把能力做成网页。网页项目会让你看到真实用户体验:按钮、等待、错误提示、结果展示,一个都不能少。

本课有一条很实用的学习线索:先问“用户到底给了什么”,再问“程序希望拿到什么”。比如你可以试这些输入:角色:小猫;地点:太空站;难题:迷路;科目:数学;目标:提高计算速度;角色:机器人;地点:图书馆;难题:停电。这些输入故意有简单的,也有容易出问题的。正常输入能帮你确认功能;短输入、空输入、奇怪输入能帮你发现系统边界。

写代码时建议你分三轮。第一轮只跑通官方调用,不加自己的想法;第二轮把输入换成自己的例子,看看结果是否还合理;第三轮才开始改结构,比如增加字段、加错误提示、做网页交互。这个顺序有点慢,但很稳。真正浪费时间的不是慢,而是一下子改太多,最后不知道错在哪里。

前端不能保存 API Key。浏览器里的代码别人能看到,密钥一旦放进去,就像把钥匙贴在门上。

理论部分要和代码一起看。比如“输入契约”不是一个漂亮词,它在代码里就是长度检查、必填字段、表单校验;“输出契约”也不是空话,它在代码里就是 JSON Schema、固定字段或页面渲染规则。你每写一行检查代码,都是在告诉系统:什么结果可以接受,什么结果需要退回去重新处理。

课堂里可以把同桌当成第一个用户。你把项目跑给同桌看,让对方换一个输入,观察系统会不会乱。很多问题都是别人随手一试才出现的,比如输入太短、问题太模糊、连续点击按钮、图片看不清。能处理这些小麻烦,作品就会从“我电脑上能跑”变成“别人也能用”。

最后做复盘时,不要只写“我学会了调用 API”。可以写得更具体:我学会了怎样限制输入,怎样让输出固定,怎样判断结果不可靠,怎样把报错变成用户看得懂的提示。这样的复盘有用,因为下一课你真的会再次用到它。

课堂讨论题

12.4 先看例子

先把例子看懂,再动手写代码。你不需要一次记住所有概念,先能说清楚“输入是什么、输出是什么、程序要检查什么”。

表单字段

角色:小猫
地点:太空站
难题:飞船迷路

后端返回

{
  "title": "太空站里的小猫船长",
  "story": "小猫发现星图倒过来了……"
}

安全例子

前端只发送故事需求;后端读取 OPENAI_API_KEY 并调用 API;前端永远看不到密钥。

你可以替换成这些输入

12.5 完整代码实现

下面同时给出 Node.js 和 Python 两套完整最小实现。先任选一种原样跑通,再改输入、改提示词、改输出格式。

Node.js 运行方式

  1. 新建文件 lesson-12.mjs,把下面完整代码放进去。
  2. 在终端运行:npm init -y
  3. 安装依赖:npm install openai express
  4. 设置环境变量 OPENAI_API_KEY。Windows PowerShell 示例:$env:OPENAI_API_KEY="你的密钥"
  5. 运行:node lesson-12.mjs
  6. 运行后打开浏览器访问 http://localhost:3000。

Node.js 完整代码

import express from "express";
import OpenAI from "openai";

const app = express();
const client = new OpenAI();
const MODEL = process.env.OPENAI_MODEL || "gpt-5.5";

app.use(express.json());

app.get("/", (req, res) => {
  res.send(`
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <title>AI故事生成网页</title>
  <style>
    body { font-family: sans-serif; max-width: 760px; margin: 40px auto; line-height: 1.7; }
    input, button, textarea { width: 100%; padding: 10px; margin: 6px 0 14px; }
    button { cursor: pointer; }
    #result { white-space: pre-wrap; background: #f4f7ff; padding: 16px; border-radius: 8px; }
  </style>
</head>
<body>
  <h1>AI故事生成网页</h1>
  <input id="hero" placeholder="角色,例如:小猫">
  <input id="place" placeholder="地点,例如:太空站">
  <input id="problem" placeholder="难题,例如:飞船迷路">
  <button id="btn">生成故事</button>
  <div id="result"></div>
  <script>
    const btn = document.querySelector("#btn");
    const result = document.querySelector("#result");
    btn.onclick = async () => {
      btn.disabled = true;
      result.textContent = "生成中...";
      const body = {
        hero: document.querySelector("#hero").value,
        place: document.querySelector("#place").value,
        problem: document.querySelector("#problem").value
      };
      const res = await fetch("/api/story", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body)
      });
      const data = await res.json();
      result.textContent = data.story || data.error;
      btn.disabled = false;
    };
  </script>
</body>
</html>`);
});

app.post("/api/story", async (req, res) => {
  const { hero, place, problem } = req.body;

  if (!hero || !place || !problem) {
    return res.status(400).json({ error: "请填写角色、地点和难题。" });
  }

  const userInput = `角色:${hero}\n地点:${place}\n难题:${problem}`;
  if (userInput.length > 500) {
    return res.status(400).json({ error: "输入太长,请缩短后再试。" });
  }

  const response = await client.responses.create({
    model: MODEL,
    input: `请给 10-14 岁学生写一个 200 字以内的故事。\n${userInput}`,
  });

  res.json({ story: response.output_text });
});

app.listen(3000, () => {
  console.log("打开 http://localhost:3000");
});

Python 运行方式

  1. 新建文件 lesson-12.py,把下面完整代码放进去。
  2. 建议新建虚拟环境后再安装依赖。
  3. 安装依赖:pip install openai flask
  4. 设置环境变量 OPENAI_API_KEY。Windows PowerShell 示例:$env:OPENAI_API_KEY="你的密钥"
  5. 运行:python lesson-12.py
  6. 运行后打开浏览器访问 http://localhost:3000。

Python 完整代码

from flask import Flask, jsonify, request
from openai import OpenAI
import os

app = Flask(__name__)
client = OpenAI()
MODEL = os.getenv("OPENAI_MODEL", "gpt-5.5")


@app.get("/")
def index():
    return """
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <title>AI故事生成网页</title>
  <style>
    body { font-family: sans-serif; max-width: 760px; margin: 40px auto; line-height: 1.7; }
    input, button { width: 100%; padding: 10px; margin: 6px 0 14px; }
    button { cursor: pointer; }
    #result { white-space: pre-wrap; background: #f4f7ff; padding: 16px; border-radius: 8px; }
  </style>
</head>
<body>
  <h1>AI故事生成网页</h1>
  <input id="hero" placeholder="角色,例如:小猫">
  <input id="place" placeholder="地点,例如:太空站">
  <input id="problem" placeholder="难题,例如:飞船迷路">
  <button id="btn">生成故事</button>
  <div id="result"></div>
  <script>
    const btn = document.querySelector("#btn");
    const result = document.querySelector("#result");
    btn.onclick = async () => {
      btn.disabled = true;
      result.textContent = "生成中...";
      const body = {
        hero: document.querySelector("#hero").value,
        place: document.querySelector("#place").value,
        problem: document.querySelector("#problem").value
      };
      const res = await fetch("/api/story", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body)
      });
      const data = await res.json();
      result.textContent = data.story || data.error;
      btn.disabled = false;
    };
  </script>
</body>
</html>
"""


@app.post("/api/story")
def story():
    data = request.get_json(force=True)
    hero = data.get("hero", "")
    place = data.get("place", "")
    problem = data.get("problem", "")

    if not hero or not place or not problem:
        return jsonify({"error": "请填写角色、地点和难题。"}), 400

    user_input = f"角色:{hero}\n地点:{place}\n难题:{problem}"
    if len(user_input) > 500:
        return jsonify({"error": "输入太长,请缩短后再试。"}), 400

    response = client.responses.create(
        model=MODEL,
        input=f"请给 10-14 岁学生写一个 200 字以内的故事。\n{user_input}",
    )
    return jsonify({"story": response.output_text})


if __name__ == "__main__":
    app.run(port=3000, debug=True)

12.6 跟着做

  1. 写一个 HTML 表单。
  2. 点击按钮后把输入发给后端接口。
  3. 后端检查输入是否为空或太长。
  4. 后端调用 OpenAI 并返回结果。
  5. 前端把标题和故事显示出来。

12.7 常见错误

排查顺序:先看输入,再看提示词,再看输出格式,最后看程序逻辑。

12.8 课后练习

  1. 把故事生成网页改成“学习计划生成网页”。
  2. 表单包含科目、目标、每天可用时间。
  3. 结果显示今日任务、三天计划、提醒。

12.9 自测清单