📌 新手提示:本文结尾提供了一键部署脚本(Windows / macOS / Linux 通用),自动装好 Python 环境、配好 API Key、生成一个能直接跑的 AI 聊天网页应用。

前言

你已经学会了调 Claude API(本站《Claude API 开发实战》),但都是"黑乎乎的终端输出"——能不能把自己写的 AI 变成一个带聊天框、带按钮、别人点开就能用的网页?

Streamlit 就是干这个的:一个开源的 Python 库,纯 Python 写网页——没有 HTML、没有 CSS、没有前后端分离,几十行代码就能出一个漂亮的 Web 工具。

它的口号很诚实:“The fastest way to build and share data apps”(构建和分享数据应用的最快方式)。对 AI 应用来说尤其合适:你有 API 逻辑,Streamlit 帮你把外壳(输入框/聊天气泡/按钮/状态)全包了。

本文基于官方教程(https://docs.streamlit.io/develop/tutorials/llms/build-conversational-apps,海外站点国内访问需科学上网)编写,最终产出:一个 ChatGPT 式的聊天网页应用,可以在浏览器里和 AI 连续对话。

一、安装与首跑(30 秒)

pip install streamlit

写一个 app.py

import streamlit as st

st.title("我的第一个 AI 应用")
name = st.text_input("你叫什么?")
if st.button("打招呼"):
    st.write(f"你好,{name}!")

运行:

streamlit run app.py

浏览器自动打开 http://localhost:8501——你已经有一个网页应用了。

power point:改代码保存,页面自动刷新(Streamlit 的"热重载")。根本不用重启服务。

二、5 个核心 API(掌握这些就能写 90% 的页面)

API干什么一句话理解
st.title / st.write标题 / 任意内容往页面打印东西
st.text_input单行文本输入框让用户输入
st.button按钮点击触发逻辑
st.chat_message聊天气泡(user/assistant)像微信那样两边对话
st.chat_input聊天输入框(底部)聊天气泡式输入

再加一个进阶必知st.session_state——Streamlit 每次交互都会把整个脚本重跑一遍(这叫 rerun),普通变量会被清空。想"记住"东西(比如对话历史、计数器),就放进 st.session_state(一个像字典一样的东西,跨重跑存活):

if "count" not in st.session_state:
    st.session_state.count = 0
if st.button("+1"):
    st.session_state.count += 1
st.write(f"计数:{st.session_state.count}")

三、核心代码:ChatGPT 式聊天应用(官方原版)

这是官方教程的完整代码(约 50 行),实现"流式打字机聊天":

from openai import OpenAI
import streamlit as st

st.title("ChatGPT-like clone")

client = OpenAI(api_key=st.secrets["OPENAI_API_KEY"])

if "openai_model" not in st.session_state:
    st.session_state["openai_model"] = "gpt-3.5-turbo"

if "messages" not in st.session_state:
    st.session_state.messages = []

# 每次重跑时回放历史消息
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# 接收用户输入(同时判空并赋值)
if prompt := st.chat_input("What is up?"):
    # 追加并显示用户消息
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    # 流式生成助手回复(打字机效果)
    with st.chat_message("assistant"):
        stream = client.chat.completions.create(
            model=st.session_state["openai_model"],
            messages=[
                {"role": m["role"], "content": m["content"]}
                for m in st.session_state.messages
            ],
            stream=True,
        )
        response = st.write_stream(stream)
    st.session_state.messages.append({"role": "assistant", "content": response})

逐段理解

  1. 开头初始化:OpenAI 客户端 + 模型名 + 空的 messages 列表(对话历史就存在 st.session_state
  2. 每次页面刷新(rerun),把历史消息用 st.chat_message 全部回放出来——这就是为什么刷新后聊天记录还在
  3. st.chat_input 拿到用户输入后:追加历史 → 显示 user 气泡 → 带完整历史调用 API(stream=True 流式)→ st.write_stream 边生成边显示(打字机效果)→ 追加 assistant 进历史

原理一句话:ChatGPT 的"上下文"= 每次把全部历史重发给模型。Streamlit 只是把历史存起来、渲染出来。

四、API Key 放哪?两种安全姿势

方式 A:secrets 文件(官方推荐)

项目目录建 .streamlit/secrets.toml

OPENAI_API_KEY = "你的key"

代码里 st.secrets["OPENAI_API_KEY"] 读取。这个文件不要提交到 GitHub(记得加进 .gitignore)。

方式 B:环境变量

export OPENAI_API_KEY="你的key"
streamlit run app.py

代码里 os.environ["OPENAI_API_KEY"] 读取。

五、一键脚本帮你跑通(重点)

文末脚本完成三件事:装环境、写 .streamlit/secrets.toml、生成上面完整代码——运行后打开 http://localhost:8501 就能和 AI 对话。

唯一要你决定的:用谁的 Key。脚本默认配置云间 API 中转站(OpenAI 兼容格式,所以上面的 OpenAI() 代码原样可用,只需拼上中转站地址):注册即取 Key,一把 Key 同时调 GPT、Claude、DeepSeek、GLM 等 90+ 模型(按 token 计费、Claude 系列约官方 0.4~0.6 倍起、国内直连免翻墙)——切换"对话模型"时,改 15 行那个 model 变量就行。输入 N 也可用自己的任意 OpenAI 兼容 Key(DeepSeek 官方等都行)。

六、常见问题速查

问题解决
streamlit run app.py 报 No module named openaipip install openai streamlit 两个都要装
页面一直转圈Key 无效或网络不通:先跑通本文第一部分的 curl 直测,再看 secrets 文件
聊天记录刷新就没了确认用了 st.session_state(消息列表存里面)
端口 8501 被占streamlit run app.py --server.port 8502
想部署到公网(给朋友用)Streamlit Community Cloud 免费托管,或用你自己的服务器

尾声

你现在的技能栈已经闭环了:调 API(Claude/DeepSeek 篇)→ 写逻辑(提示词工程篇)→ 套壳上线(本篇)。200 行代码级别的 Streamlit 应用,足以支撑个人工具、团队内部工具,甚至小型产品的 MVP(最小可行产品)。

下一步三选一:给应用加输入(st.text_area 多行文本、st.file_uploader 上传文件);接知识库(配合本站《LangChain RAG 实战》的检索逻辑);或者部署上线分享给朋友。

动手吧——跑一次文末脚本,10 分钟内你就能把 AI 聊天应用发给身边人用了。别忘了 Key 用云间 API(https://cloudzone-api.cyou/)注册的话,对比换模型会非常省心。


附:一键部署脚本

不方便下载的同学可以直接复制下方完整源码,新建文本文档粘贴后改后缀为 .sh(macOS/Linux/WSL)或 .ps1(Windows)运行。也可以下载现成文件:

macOS / Linux / WSL(.sh)

#!/usr/bin/env bash
set -u
# ============================================================
#  Streamlit LLM 应用 一键脚本(macOS / Linux / WSL)
#  公开源码,欢迎审查 —— 不放心可先复制给 AI 判断
#  默认配置:云间 API 中转站(https://cloudzone-api.cyou/)
#  生成:.streamlit/secrets.toml + chat_app.py(ChatGPT 式聊天应用)
# ============================================================

GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
info()  { echo -e "${GREEN}[INFO]${NC} $1"; }
warn()  { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
step()  { echo -e "${CYAN}[STEP]${NC} $1"; }

CLOUDZONE_URL="https://cloudzone-api.cyou"
CLOUDZONE_API_BASE="https://cloudzone-api.cyou/v1"

detect_network() {
  step "检测网络环境(国内 / 国外)..."
  if curl -fsI --max-time 5 "https://claude.ai" >/dev/null 2>&1; then
    info "可直连 claude.ai,判定为海外网络"
    echo "overseas"
  else
    warn "无法直连 claude.ai,判定为国内网络环境(将自动切换国内镜像)"
    echo "domestic"
  fi
}

check_python() {
  step "检查 Python 3..."
  if command -v python3 >/dev/null 2>&1 && python3 --version >/dev/null 2>&1; then
    PY="python3"
    info "找到 python3:$(python3 --version 2>&1)"
    return 0
  fi
  error "未找到 python3。请先安装:https://www.python.org/downloads/ (Windows 建议使用 WSL 后执行本脚本)"
  return 1
}

setup_env() {
  local net="$1"
  step "创建虚拟环境并安装 streamlit + openai(约 1-2 分钟)..."
  mkdir -p streamlit-app && cd streamlit-app || return 1
  python3 -m venv .venv 2>/dev/null || { error "创建虚拟环境失败,手动执行:python3 -m venv .venv"; return 1; }
  # shellcheck disable=SC1091
  source .venv/bin/activate || { warn "激活虚拟环境失败,继续使用系统环境"; }

  step "安装依赖..."
  local pypi="https://pypi.org/simple"
  if [ "$net" = "domestic" ]; then
    pypi="https://pypi.tuna.tsinghua.edu.cn/simple"
  fi

  if pip install streamlit openai -i "$pypi" --timeout 90 >/dev/null 2>&1; then
    info "依赖安装成功"
    return 0
  fi
  warn "主源失败,回退备用镜像(阿里云 PyPI)..."
  if pip install streamlit openai -i "https://mirrors.aliyun.com/pypi/simple" --timeout 90 >/dev/null 2>&1; then
    info "备用镜像安装成功"
    return 0
  fi
  error "依赖安装失败。请手动执行:pip install streamlit openai -i https://pypi.tuna.tsinghua.edu.cn/simple"
  return 1
}

configure_api() {
  step "配置 API Key..."
  echo ""
  echo "聊天应用需要一个 OpenAI 兼容的 Key。默认推荐云间 API 中转站:"
  echo "  · OpenAI 兼容格式,代码无需改动,模型随便换"
  echo "  · 国内直连免翻墙,一把 Key 调 GPT / Claude / DeepSeek / GLM 等 90+ 模型"
  echo ""
  echo "是否现在跳转注册并获取 API Key?"
  echo "  Y - 立即跳转至 ${CLOUDZONE_URL}"
  echo "  N - 我已有 Key(OpenAI / DeepSeek 官方或第三方),自行输入"
  read -r -p "请选择 [Y/N]: " choice

  local API_KEY=""
  local BASE_URL="$CLOUDZONE_API_BASE"
  if [ "${choice:-N}" = "Y" ] || [ "${choice:-N}" = "y" ]; then
    info "正在打开浏览器..."
    if command -v xdg-open >/dev/null 2>&1; then
      xdg-open "$CLOUDZONE_URL" >/dev/null 2>&1
    elif command -v open >/dev/null 2>&1; then
      open "$CLOUDZONE_URL" >/dev/null 2>&1
    else
      warn "无法自动打开浏览器,请手动访问:$CLOUDZONE_URL"
    fi
    echo "注册后在控制台复制 Key(以 sk- 开头)。"
    read -r -p "粘贴你的 API Key: " API_KEY
    read -r -p "API 地址(直接回车用默认 ${CLOUDZONE_API_BASE}): " input_base
    [ -n "${input_base:-}" ] && BASE_URL="$input_base"
  else
    read -r -s -p "请输入你的 API Key (sk-...): " API_KEY
    echo
    echo "OpenAI 官方 Key 则地址填 https://api.openai.com/v1;DeepSeek 官方填 https://api.deepseek.com。"
    read -r -p "API 地址(直接回车用默认 ${CLOUDZONE_API_BASE}): " input_base
    [ -n "${input_base:-}" ] && BASE_URL="$input_base"
  fi

  if [ -z "${API_KEY:-}" ]; then
    error "未输入 API Key,跳过配置。可稍后手动编辑 .streamlit/secrets.toml。"
    return 1
  fi

  mkdir -p .streamlit
  cat > .streamlit/secrets.toml <<TOMEOF
# Streamlit secrets —— 请勿提交到 GitHub(含密钥)
OPENAI_API_KEY = "$API_KEY"
OPENAI_BASE_URL = "$BASE_URL"
TOMEOF

  info "已写入 .streamlit/secrets.toml(密钥文件,已自动加入 .gitignore 说明)"
  warn "注意:该文件含密钥,别提交到公开仓库!"

  if [ -f ".gitignore" ] && ! grep -q "secrets.toml" .gitignore; then
    echo ".streamlit/secrets.toml" >> .gitignore
  elif [ ! -f ".gitignore" ]; then
    echo ".streamlit/secrets.toml" > .gitignore
  fi
  return 0
}

write_app() {
  step "生成 chat_app.py(ChatGPT 式聊天应用)..."
  cat > chat_app.py <<'PYEOF'
import os
from openai import OpenAI
import streamlit as st

st.title("🤖 我的 AI 聊天室")

client = OpenAI(
    api_key=st.secrets.get("OPENAI_API_KEY", os.environ.get("OPENAI_API_KEY")),
    base_url=st.secrets.get("OPENAI_BASE_URL", os.environ.get("OPENAI_BASE_URL")),
)

if "model" not in st.session_state:
    st.session_state.model = "gpt-5.5"   # 换成支持模型名,如 deepseek-v4-pro / claude-sonnet-5

if "messages" not in st.session_state:
    st.session_state.messages = []

for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

if prompt := st.chat_input("说点什么…"):
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    with st.chat_message("assistant"):
        stream = client.chat.completions.create(
            model=st.session_state.model,
            messages=[
                {"role": m["role"], "content": m["content"]}
                for m in st.session_state.messages
            ],
            stream=True,
        )
        response = st.write_stream(stream)
    st.session_state.messages.append({"role": "assistant", "content": response})
PYEOF
  info "已生成 streamlit-app/chat_app.py"
}

main() {
  echo "============================================"
  echo "  Streamlit LLM 应用 一键脚本"
  echo "  适用于 macOS / Linux / WSL"
  echo "  默认接入:云间 API 中转站"
  echo "============================================"
  echo ""

  local NET
  NET=$(detect_network)

  if ! check_python; then
    error "缺少 Python 3,脚本终止。请先安装 Python 后重跑。"
    exit 1
  fi

  if ! setup_env "$NET"; then
    error "环境准备失败,脚本终止。请按上方提示手动处理。"
    exit 1
  fi

  configure_api || warn "Key 未配置,应用仍会生成,运行时需自行补全 secrets"

  write_app

  echo ""
  echo "============================================"
  info "全部完成!启动你的 AI 聊天网页:"
  echo "    cd streamlit-app && source .venv/bin/activate && streamlit run chat_app.py"
  echo "浏览器访问 http://localhost:8501"
  echo "换模型:改 chat_app.py 里 model 那一行"
  echo "============================================"
}

main "$@"

Windows(.ps1)

Windows 运行 .ps1 若提示执行策略限制:打开 PowerShell 输入 Set-ExecutionPolicy -Scope Process Bypass 后回车,再运行脚本。-Scope Process 仅对当前窗口生效,关掉即恢复,不会改动系统全局策略(系统默认限制来自安全设计,不要为了省事永久 Bypass)。

# ============================================================
#  Streamlit LLM 应用 一键脚本(Windows PowerShell)
#  公开源码,欢迎审查 -- 不放心可先复制给 AI 判断
#  默认配置:云间 API 中转站(https://cloudzone-api.cyou/)
#  生成:.streamlit/secrets.toml + chat_app.py(ChatGPT 式聊天应用)
# ============================================================
$ErrorActionPreference = "Stop"

function Write-Info  { Write-Host "[INFO] $args" -ForegroundColor Green }
function Write-Warn  { Write-Host "[WARN] $args" -ForegroundColor Yellow }
function Write-Err   { Write-Host "[ERROR] $args" -ForegroundColor Red }
function Write-Step  { Write-Host "[STEP] $args" -ForegroundColor Cyan }

$CLOUDZONE_URL = "https://cloudzone-api.cyou"
$CLOUDZONE_API_BASE = "https://cloudzone-api.cyou/v1"

function Detect-Network {
    Write-Step "检测网络环境(国内 / 国外)..."
    try {
        $null = Invoke-WebRequest -Uri "https://claude.ai" -Method Head -TimeoutSec 5 -UseBasicParsing
        Write-Info "可直连 claude.ai,判定为海外网络"
        return "overseas"
    } catch {
        Write-Warn "无法直连 claude.ai,判定为国内网络环境(将自动切换国内镜像)"
        return "domestic"
    }
}

function Find-Python {
    Write-Step "检查 Python 3..."
    $python = $null
    foreach ($candidate in @("python", "py")) {
        try {
            $out = & $candidate --version 2>&1
            if ($LASTEXITCODE -eq 0) { $python = $candidate; break }
        } catch { }
    }
    if ($null -eq $python) {
        Write-Err "未找到 Python。请先安装:https://www.python.org/downloads/ (安装时勾选 Add Python to PATH)"
        return $null
    }
    Write-Info "找到 Python:$(& $python --version 2>&1)"
    return $python
}

function Setup-Env {
    param([string]$Net)
    Write-Step "创建虚拟环境并安装 streamlit + openai(约 1-2 分钟)..."
    New-Item -ItemType Directory -Force -Path "streamlit-app" | Out-Null
    Set-Location "streamlit-app"

    & $script:PY -m venv .venv
    if ($LASTEXITCODE -ne 0) {
        Write-Err "创建虚拟环境失败,手动执行:$script:PY -m venv .venv"
        return $false
    }
    $script:PY = Join-Path (Get-Location) ".venv\Scripts\python.exe"

    Write-Step "安装依赖..."
    $pypi = if ($Net -eq "domestic") { "https://pypi.tuna.tsinghua.edu.cn/simple" } else { "https://pypi.org/simple" }

    & $script:PY -m pip install streamlit openai -i $pypi --timeout 90 2>$null
    if ($LASTEXITCODE -eq 0) {
        Write-Info "依赖安装成功"
        return $true
    }
    Write-Warn "主源失败,回退备用镜像(阿里云 PyPI)..."
    & $script:PY -m pip install streamlit openai -i "https://mirrors.aliyun.com/pypi/simple" --timeout 90 2>$null
    if ($LASTEXITCODE -eq 0) {
        Write-Info "备用镜像安装成功"
        return $true
    }
    Write-Err "依赖安装失败。请手动执行:$script:PY -m pip install streamlit openai -i https://pypi.tuna.tsinghua.edu.cn/simple"
    return $false
}

function Configure-Api {
    Write-Step "配置 API Key..."
    Write-Host ""
    Write-Host "聊天应用需要一个 OpenAI 兼容的 Key。默认推荐云间 API 中转站:"
    Write-Host "  · OpenAI 兼容格式,代码无需改动,模型随便换"
    Write-Host "  · 国内直连免翻墙,一把 Key 调 GPT / Claude / DeepSeek / GLM 等 90+ 模型"
    Write-Host ""
    Write-Host "是否现在跳转注册并获取 API Key?"
    Write-Host "  Y - 立即跳转至 $CLOUDZONE_URL"
    Write-Host "  N - 我已有 Key(OpenAI / DeepSeek 官方或第三方),自行输入"
    $choice = Read-Host "请选择 [Y/N]"

    $apiKey = ""
    $baseUrl = $CLOUDZONE_API_BASE
    if ($choice -eq "Y" -or $choice -eq "y") {
        Write-Info "正在打开浏览器..."
        try {
            Start-Process "$CLOUDZONE_URL"
        } catch {
            Write-Warn "无法自动打开浏览器,请手动访问:$CLOUDZONE_URL"
        }
        Write-Host "注册后在控制台复制 Key(以 sk- 开头)。"
    } else {
        Write-Host "OpenAI 官方 Key 则地址填 https://api.openai.com/v1;DeepSeek 官方填 https://api.deepseek.com。"
    }

    $sec = Read-Host "粘贴你的 API Key(输入时不显示字符)" -AsSecureString
    $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)
    $apiKey = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
    [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)

    if ([string]::IsNullOrWhiteSpace($apiKey)) {
        Write-Err "未输入 API Key,跳过配置。可稍后手动编辑 .streamlit/secrets.toml。"
        return $false
    }

    $inputBase = Read-Host "API 地址(直接回车用默认 ${baseUrl})"
    if (-not [string]::IsNullOrWhiteSpace($inputBase)) { $baseUrl = $inputBase }

    New-Item -ItemType Directory -Force -Path ".streamlit" | Out-Null
    @"
# Streamlit secrets -- 请勿提交到 GitHub(含密钥)
OPENAI_API_KEY = "$apiKey"
OPENAI_BASE_URL = "$baseUrl"
"@ | Set-Content -Path ".streamlit\secrets.toml" -Encoding UTF8
    Write-Info "已写入 .streamlit/secrets.toml"
    Write-Warn "注意:该文件含密钥,别提交到公开仓库!"
    return $true
}

function Write-App {
    Write-Step "生成 chat_app.py(ChatGPT 式聊天应用)..."
    @'
import os
from openai import OpenAI
import streamlit as st

st.title("🤖 我的 AI 聊天室")

client = OpenAI(
    api_key=st.secrets.get("OPENAI_API_KEY", os.environ.get("OPENAI_API_KEY")),
    base_url=st.secrets.get("OPENAI_BASE_URL", os.environ.get("OPENAI_BASE_URL")),
)

if "model" not in st.session_state:
    st.session_state.model = "gpt-5.5"   # 换成支持模型名,如 deepseek-v4-pro / claude-sonnet-5

if "messages" not in st.session_state:
    st.session_state.messages = []

for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

if prompt := st.chat_input("说点什么…"):
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    with st.chat_message("assistant"):
        stream = client.chat.completions.create(
            model=st.session_state.model,
            messages=[
                {"role": m["role"], "content": m["content"]}
                for m in st.session_state.messages
            ],
            stream=True,
        )
        response = st.write_stream(stream)
    st.session_state.messages.append({"role": "assistant", "content": response})
'@ | Set-Content -Path "chat_app.py" -Encoding UTF8
    Write-Info "已生成 streamlit-app/chat_app.py"
}

Write-Host "============================================"
Write-Host "  Streamlit LLM 应用 一键脚本"
Write-Host "  适用于 Windows(PowerShell)"
Write-Host "  默认接入:云间 API 中转站"
Write-Host "============================================"
Write-Host ""

$net = Detect-Network

$script:PY = Find-Python
if ($null -eq $script:PY) {
    Write-Err "缺少 Python,脚本终止。请先安装 Python 后重跑。"
    exit 1
}

if (-not (Setup-Env -Net $net)) {
    Write-Err "环境准备失败,脚本终止。请按上方提示手动处理。"
    exit 1
}

if (-not (Configure-Api)) {
    Write-Warn "Key 未配置,应用仍会生成,运行时需自行补全 secrets"
}

Write-App

Write-Host ""
Write-Host "============================================"
Write-Info "全部完成!启动你的 AI 聊天网页:"
Write-Host "    cd streamlit-app; .\.venv\Scripts\streamlit run chat_app.py"
Write-Host "浏览器访问 http://localhost:8501"
Write-Host "换模型:改 chat_app.py 里 model 那一行"
Write-Host "============================================"