理论部分

当我们把“调用模型”的代码写成脚本后,会很快遇到一个问题:脚本能跑,但离“应用”还差很远。原因是脚本天然是单机、单用户、一次性运行的形态,而应用需要:

  • 可复用:前端、其他服务、甚至不同终端都能调用
  • 可观测:能知道服务是否健康、耗时多少、报错是什么
  • 可交付:一个 URL 或一个页面,让人可以直接用

因此,最常见的工程形态是“前后端分离”:

  • 后端服务(API):统一封装模型调用,负责鉴权/限流/日志/错误处理等(本篇先做最小版本)
  • 前端应用(UI):提供交互体验,负责展示与用户输入

这一篇我们用 FastAPI 写一个最小对话服务,然后用 Streamlit 写一个最小聊天界面,把端到端链路跑通。


实践部分

本案例做什么

我们会做两件事:

  1. 启动 FastAPI 后端服务:提供 /health/chat 接口
  2. 启动 Streamlit 前端:把用户输入发给后端 /chat,展示模型回复

主要代码 1:FastAPI 后端

脚本:src/5.2_backend_api.py

import uvicorn
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import time
import os
from dotenv import load_dotenv
from openai import OpenAI

# 加载环境变量
load_dotenv()

# 初始化 OpenAI 客户端 (智谱 AI)
client = OpenAI(
    api_key=os.getenv("ZHIPUAI_API_KEY"),
    base_url=os.getenv("ZHIPUAI_BASE_URL")
)

app = FastAPI(
    title="LLM Chat API Service",
    description="基于 FastAPI 的大模型对话服务 (Direct API)",
    version="1.0.0"
)

# === 定义请求体模型 ===
class ChatRequest(BaseModel):
    query: str
    model_name: str = "glm-4-flash" # 默认模型

class ChatResponse(BaseModel):
    answer: str
    processing_time: float

# === API 路由 ===

@app.get("/")
def read_root():
    return {"message": "Welcome to LLM Chat API Service! Visit /docs for Swagger UI."}

@app.get("/health")
def health_check():
    return {"status": "healthy", "timestamp": time.time()}

@app.post("/chat", response_model=ChatResponse)
def chat_endpoint(request: ChatRequest):
    start_time = time.time()
    
    try:
        print(f"收到请求: {request.query} (Model: {request.model_name})")
        
        # 直接调用大模型 API
        response = client.chat.completions.create(
            model=request.model_name,
            messages=[
                {"role": "system", "content": "你是一个乐于助人的 AI 助手。"},
                {"role": "user", "content": request.query}
            ],
            temperature=0.7
        )
        
        answer = response.choices[0].message.content
        process_time = time.time() - start_time
        
        return ChatResponse(
            answer=answer,
            processing_time=process_time
        )
        
    except Exception as e:
        print(f"Error: {e}")
        raise HTTPException(status_code=500, detail=str(e))

if __name__ == "__main__":
    print("🚀 正在启动 FastAPI 后端服务 (Direct LLM)...")
    print("文档地址: http://127.0.0.1:8002/docs")
    uvicorn.run(app, host="127.0.0.1", port=8002)

主要代码 2:Streamlit 前端

脚本:src/5.2_frontend_ui.py

import streamlit as st
import requests
import time

# === 配置 ===
BACKEND_URL = "http://127.0.0.1:8002/chat"
st.set_page_config(page_title="AI 聊天助手", page_icon="🤖")

# === 侧边栏 ===
with st.sidebar:
    st.header("⚙️ 设置")
    model_name = st.selectbox("选择模型", ["glm-4-flash", "glm-4-air", "glm-4-plus"])
    st.info("这是一个基于 FastAPI + Streamlit 的前后端分离演示应用。")
    st.markdown("---")
    st.markdown("**状态**: 🟢 后端服务需先启动")

# === 主界面 ===
st.title("🤖 智能对话助手 (API 版)")
st.caption("🚀 由 Streamlit 和 FastAPI 驱动,直接调用大模型")

# === 初始化会话状态 (History) ===
if "messages" not in st.session_state:
    st.session_state.messages = [
        {"role": "assistant", "content": "你好!我是你的 AI 助手,有什么可以帮你的吗?"}
    ]

# === 显示历史消息 ===
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

# === 处理用户输入 ===
if prompt := st.chat_input("请输入你的问题..."):
    # 1. 显示用户消息
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    # 2. 调用后端 API 获取回答
    with st.chat_message("assistant"):
        message_placeholder = st.empty()
        full_response = ""
        
        try:
            with st.spinner("思考中..."):
                # 发送请求到后端
                payload = {"query": prompt, "model_name": model_name}
                response = requests.post(BACKEND_URL, json=payload)
                
                if response.status_code == 200:
                    data = response.json()
                    answer = data.get("answer", "")
                    
                    # 模拟打字机效果
                    for chunk in answer:
                        full_response += chunk
                        message_placeholder.markdown(full_response + "▌")
                    message_placeholder.markdown(full_response)
                    
                else:
                    st.error(f"后端报错: {response.status_code} - {response.text}")
                    full_response = "抱歉,服务暂时不可用。"

        except requests.exceptions.ConnectionError:
            st.error("❌ 无法连接到后端服务。请确保 `src/5.2_backend_api.py` 正在运行。")
            full_response = "连接失败"
        
        # 3. 保存助手回复到历史
        st.session_state.messages.append({"role": "assistant", "content": full_response})

运行方式

第一步,启动后端(保持运行):

python3 src/5.2_backend_api.py

第二步,验证后端健康(可选):

curl http://127.0.0.1:8002/health

第三步,启动前端(另开一个终端):

streamlit run src/5.2_frontend_ui.py

运行结果示例

后端启动后会看到类似输出,并提示文档地址:

🚀 正在启动 FastAPI 后端服务 (Direct LLM)...
文档地址: http://127.0.0.1:8002/docs
INFO:     Uvicorn running on http://127.0.0.1:8002 (Press CTRL+C to quit)

访问健康检查接口会返回类似 JSON:

{"status":"healthy","timestamp":1730000000.0}

前端启动后会输出 Streamlit 的访问地址(本地一般为 8501 端口),在页面里输入问题即可得到模型回复。


总结

这一篇我们把脚本升级为可交互的应用形态:用 FastAPI 把模型调用封装为统一接口,用 Streamlit 做一个轻量前端界面,并跑通了端到端链路。

作为整个教程的收尾,我们也把 12 篇内容串起来回顾一遍。我们从最基础的环境与第一次调用开始,逐步搭起了一套完整的 LLM 应用能力栈:

  • 第 2~4 篇:跑通调用与多轮对话,理解消息列表与会话状态
  • 第 5~6 篇:掌握提示词工程与结构化输出,让结果更稳定、更可用
  • 第 7~8 篇:理解并落地 RAG,从“能检索”升级到“能基于资料回答”
  • 第 9~10 篇:跑通工具调用与 ReAct Agent,让模型具备“多步行动”能力
  • 第 11 篇:理解微调的定位与流程,知道何时需要以及产物是什么
  • 第 12 篇:完成服务化与前后端串联,把能力封装成一个可交付的应用形态

到这里,我们已经具备了从 0 到 1 开发大模型应用的完整路径:既能用 Prompt/RAG/Agent 解决效果问题,也能用 API + UI 的方式把能力交付出去。接下来如果要继续增强,就可以围绕“效果、性能、成本、安全、评测、观测”把这个应用逐步工程化。

更多推荐