首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >AI 全栈:当不确定性进入系统核心,每一层都要重写契约

AI 全栈:当不确定性进入系统核心,每一层都要重写契约

原创
作者头像
用户12687280
修改于 2026-11-02 15:20:44
修改于 2026-11-02 15:20:44
650
举报

传统全栈的工程前提是确定性:输入确定,逻辑确定,输出确定,故障点集中在数据库、网络和代码缺陷上,排查路径清晰。AI 全栈的底层假设被推翻了——同一个输入,模型两次调用可能给出不同结果;推理延迟随 Token 长度和负载剧烈波动;幻觉、格式非法、工具调用循环成为新的故障类别。这意味着,把一个大模型 API 塞进原有的前端/后端结构里,得到的不是“AI 全栈”,而是一个把不确定性偷偷放进了确定性管道的脆弱系统。真正的 AI 全栈,是从前端流式渲染、后端流式代理、AI 编排层到网关治理层,每一层都围绕“不确定性”重新设计契约。

前端:从“渲染字符串”到“编排流式部件”

传统前端拿到的是已经完成的 JSON,渲染是一个同步动作。AI 前端拿到的是一个尚未完成的、逐 token 到达的流。Vercel AI SDK 6 的 useChat 把这个流封装成一个有限状态机:submitted | streaming | ready | error,同时把消息模型从“一个 content: string”翻转为 parts: MessagePart[]——一条消息内部可能同时包含文本、工具调用、推理过程和附件。这个变化的影响远超 API 换名:你的渲染单元从“一条消息”变成了“一条消息里的一个 part”,旧的 message.content 拼接写法在 v6 下会直接失效。

代码语言:javascript
复制
// app/chat/ChatPanel.tsx
'use client';
import { useChat } from 'ai/react';

export function ChatPanel() {
  const { messages, sendMessage, status, stop, error } = useChat({
    api: '/api/chat',
    maxSteps: 5,                 // Agent 多步工具调用的上限
    onError: (e) => reportToSentry(e),
  });
  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          {m.parts.map((p, i) => {
            if (p.type === 'text')     return <Markdown key={i} text={p.text} />;
            if (p.type === 'tool-call') return <ToolCall key={i} part={p} />;
            if (p.type === 'reasoning') return <ThinkCollapsible key={i} text={p.text} />;
            return null;             // 未来新增 part 类型不会让页面崩溃
          })}
        </div>
      ))}
      <Composer onSend={sendMessage} disabled={status === 'streaming'} />
      {status === 'streaming' && <button onClick={stop}>停止</button>}
      {error && <ErrorBanner onRetry={() => location.reload()} />}
    </div>
  );
}

default return null 不是偷懒,而是为 SDK 未来新增 audio、image-generation 等 part 类型留出的演进空间。前端不再是“等结果”的被动渲染层,而是一个对流式中间状态做分支编排的运行时。

后端:不是“调用模型”,是“把流正确转发出去”

AI 全栈的后端最容易犯的错误,是把它写成同步调用:等模型生成完毕,再一次性返回。这在前端表现为“白屏十几秒然后突然出现全部文字”,用户体验等同于没有流式。正确的后端职责是流式代理 + 协议适配。如果 AI 编排层用 FastAPI + LangGraph,SSE 是最直接的承载方式:

代码语言:javascript
复制
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from langgraph.graph import StateGraph

app = FastAPI()

async def event_stream(graph, inputs: dict, config: dict):
    async for chunk in graph.astream(inputs, config, stream_mode="messages"):
        token = chunk[0].content
        if token:
            yield f"data: {json.dumps({'type': 'text', 'text': token})}\n\n"
    yield "data: [DONE]\n\n"

@app.post("/api/chat")
async def chat(req: ChatRequest):
    graph = build_graph()          # LangGraph StateGraph
    return StreamingResponse(
        event_stream(graph, {"messages": req.messages}, {"configurable": {"thread_id": req.thread_id}}),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )

X-Accel-Buffering: no 是上线时最容易被漏掉的一行——Nginx 默认会缓冲 SSE 流,导致前端看到的仍然是“批量到达”而不是逐 token 到达。thread_id 交给 LangGraph 的 checkpointer 做会话持久化,多轮对话的状态不在前端内存里,而在编排层可恢复。FastAPI 本身不生成 token,它只负责把 AI 层的流无损、不缓冲地递到 HTTP 边界上。

网关层:AI 全栈真正的“新中间件”

前端和后端把流跑通,只是 Demo 的门槛。生产环境真正拉开差距的是网关层:模型路由与故障切换、Token 感知的限流、语义缓存、成本可观测。这一层在传统全栈里不存在,因为传统 API 不按 Token 计费、没有“相似请求复用回答”的可能。以下是一个同时做语义缓存和双桶限流的最小网关骨架:

代码语言:javascript
复制
class AIGateway:
    def __init__(self, similarity_threshold=0.92, max_rpm=60, max_tpm=100_000):
        self.cache = SemanticCache(threshold=similarity_threshold, ttl=1800)
        self.limiter = DualTokenBucket(max_rpm=max_rpm, max_tpm=max_tpm)

    async def dispatch(self, org_id: str, query: str, est_tokens: int) -> dict:
        if (cached := await self.cache.lookup(query)) is not None:
            return {"source": "cache", "answer": cached}
        allowed = await self.limiter.acquire(org_id, est_tokens)
        if not allowed:
            return {"source": "rejected", "reason": "rate_limited"}
        result = await call_llm(query)          # 真正的模型调用
        await self.cache.store(query, result["answer"])
        await record_usage(org_id, result["usage"])
        return {"source": "llm", **result}

语义缓存的关键在阈值:0.92 意味着只有语义上几乎等价的查询才命中,否则“帮我改一下这段代码”和“帮我重写这段代码”会被错误地当成同一个请求。限流必须是双桶——只限请求数不限制 Token,一个超长 prompt 就能绕过 RPM 把 TPM 打爆;只限 Token 不限制请求数,高频短请求的调度开销同样会拖垮服务。这两项加上模型故障时的 stale 降级,才是 AI 网关区别于普通 API Gateway 的实质。

AI 全栈的“全”,不在于前端用了 Next.js、后端用了 FastAPI、中间挂了 LangChain——这些是选型清单,不是架构。真正的全栈贯通,是前端能消费流式的中间状态,后端能无损转发流,AI 层能在不确定的输出上建立可恢复的编排,网关层能在成本和稳定性失控之前把请求拦下来。每一层都在处理同一个东西:传统全栈可以推到系统边界之外的不确定性,在 AI 全栈里必须被每一层显式接住。飞书学习:xcnysqigiht4.feishu.cn/wiki/Sfy0wBIwmiaN3vkN3NTcdqarnIB?from=from_copylink

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 前端:从“渲染字符串”到“编排流式部件”
  • 后端:不是“调用模型”,是“把流正确转发出去”
  • 网关层:AI 全栈真正的“新中间件”
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档