前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9258
  • AI评委也会上当:评测模型审查作弊代码的深度测试
  • 生产级LLM系统的状态架构设计要点
  • LLM输出JSON解析的正确做法
  • 2026年MCP Server实战入门:从零构建可用的工具服务器
  • Meta Muse Mac应用安全漏洞:零日与文件系统泄露
  • GitHub Actions添加cancel-in-progress:节省30%构建分钟数
  • MCP工具描述即指令:信任边界的安全教训
  • 7种数据格式Token消耗对比:JSON缩进比CSV多花3倍
  • LLM推理工程:KV-Cache瓶颈与PagedAttention
  • GitHub Copilot 代码审查支持 API 调用,默认改为 Balanced 级别
  • Cloudflare 开源 Clef 决策模型:39ms 完成 AI Agent 决策,无需人工介入
  • jev-ultrafast:把浏览器操作变成多选题
  • AI Agent库的兼容性不止语义版本号
  • OpenAI悄然发布GPT-6官方使用指南
  • 远程MCP长时任务:Agent实际收到什么
  • AI Agent合规审计链路工程观察
  • Go+DiskANN+MCP 构建 AI Agent 时序记忆引擎实战
  • Copilot 桌面控制能力上线:权限模型与安全护栏详解
  • Node 拒绝 package 导入?一 CLI 告诉你根因
  • K8s"单体"经验对AI Agent架构的启示
  • GPT-6模型家族选型指南发布
  • AWS Quick+ MCP模式实现大规模合规审查
  • Bedrock AgentCore为Claude Desktop添加安全网页搜索
  • SageMaker多轮RL微调搜索Agent实战
  • Asta 快速报告生成模型 AstaBrief 开源
  • GitHub:AI时代开发者需加强的三项技能
  • 状态机设计:如何让数据字段影响状态流转
  • 收据、副本、SHA:三种验证方式的本质区别
  • 心跳驱动:无存储的 Agent 健康状态追踪
  • LMCP:让 AI 编程工具直接操控 Mac 原生应用的 MCP 服务器
  • Anthropic开放Claude Code模组自定义功能
  • Meta Muse Agent登陆智能眼镜:云端Linux虚拟机运行,可代打电话/购物
  • 已加载 32 / 9258
8.0
热点
AI SCORE
技术实践2026-10-03 04:00

生产级LLM系统的状态架构设计要点

dev.to · AI#LLM架构#RAG#生产系统
Editor brief · 编辑速览

Jev框架提出用Dot-and-Index路径和上下文隔离解决生产环境LLM的上下文腐烂、提示注入等问题,构建可靠的子100ms决策循环。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

如果你正在构建生产级 AI 系统,你很可能遇到了一堵令人沮丧的墙。你的提示词经过精心设计,检索增强生成(RAG)管道运行正常,但面对复杂的、多租户数据载荷时,模型仍然会产生幻觉、偏离主题,或给出严重校准不足的回答。

问题不在于你的提示词。问题在于你的状态架构。

在早期的教程中,向 AI 模型传入单个客户消息或简短的交易字符串可以无缝工作。但生产系统处理的不是单句话,而是包含订单、用户、历史消息、策略、功能开关和潜在工作流状态的大规模 JSON 投影。

如果你把状态当作一个被动的"输入载荷"而不是一个严格的、可遍历的 API 契约,你就是在让应用程序面临上下文腐化、提示词注入和统计稀释的风险。

理论基础:状态即 API 契约

要使用 Jev 构建可靠的、亚 100 毫秒决策循环,你需要理解 System One 架构的基本不对称性:状态是共享的,但问题是隔离的。

当你向 Jev 提交请求时,你传递一个单一的状态对象以及一组独立的问题。每个问题并行地针对相同的状态进行评估。没有一个问题能看到另一个问题的答案。状态是信息流动的唯一通道。

正因为如此,状态不是一个中性的容器。它是每一种判断行走的领域。

DOM 和文件系统类比

把状态对象想象成前端开发中的文档对象模型(DOM)。你不会通过将页面的原始文本倾倒到一个黑箱中来与网页交互;你通过使用 CSS 选择器的结构化、可寻址树来与它交互。

Jev 状态的工作原理完全相同。当你写一个针对 ticket.messages[0].text 的问题时,你是在写一个选择器。你在通过命名键和数字索引遍历来描述一个结构化对象的绝对路径。

类似地,想象一个文件系统路径如 /var/log/nginx/access.log。层次结构编码了语义和访问模型。像 order.charges[0].status 这样的路径会迫使模型的注意力聚焦。它告诉读者——在任何一字节的推理发生之前——这个查询是关于一笔扣款,是列表中的第一笔扣款,我们问的是它的状态,而不是时间戳或金额。

路径特异性与推理成本成反比。路径越完整地指定其目标,模型需要猜测的就越少,其概率分布就越集中在正确答案周围。

这里演示的概念和代码直接来自我的电子书 Jev: The Definitive Guide to System One AI。另请查看 9 卷合集:TypeScript AI & Agentic Engineer Masterclass

浅层状态与模式设计原则

设计状态模式时,必须平衡三个相互竞争的优先级:路径清晰度、更新局部性和上下文隔离。

保持路径浅层:像 workflow.stages[2].steps[5].outputs.results[0].data.amount 这样深层嵌套的结构会迫使模型维持过多的认知参考框架。这增加了遍历成本,降低了置信度分数,并扩大了概率分布。目标是最多三到四层深度。

为不可变更新而设计:在实时应用中,状态持续变化。如果你在原地变更状态对象,diff 变得不可能,迫使你不必要地重新运行昂贵的查询。通过使用结构共享实现不可变更新,只有状态树的修改分支被替换。这使状态 diff 变得微不足道,并让你的缓存层决定哪些问题族实际需要重新评估。

强制严格隔离:Jev 容易受到提示词注入和对抗性内容的影响,如果恶意文本与可信的系统逻辑混合在同一个命名空间中。隔离必须在模型运行之前、在状态模式层面强制执行。在良好作用域的状态中,未被引用的内容充当中性的背景上下文,而不是污染活跃目标。

生产级 Jev 系统解剖

让我们看一个完整的端到端 TypeScript 程序,它将这些理论基础付诸实践。这个账单分诊示例处理嵌套的 JSON 状态,在单个并行请求中评估三种原始类型(noul、choice 和 score),并完全在代码中组合最终的业务逻辑。

// src/lib/triage.ts
import { TypeSafeClient, choice, noul, score } from "@typesafe-ai/sdk";

/**
 * One per-process client. Reads `TYPESAFE_API_KEY` from the environment
 * and defaults to `jev-latest`. Reuse this object across requests so
 * the connection pool and retry policy stay warm.
 */
const client = new TypeSafeClient();

/**
 * The `state` is the single blob of evidence the model reads to answer
 * every question. Paths *inside* this object are the addressing surface
 * that the questions point at, using backticked dot-and-index
 * expressions such as `ticket.messages[0].text`.
 */
const state = {
  ticket: {
    id: "T-1042",
    messages: [
      {
        from: "customer",
        text: "I was charged twice for order A-104. Please refund the duplicate.",
      },
      {
        from: "support",
        text: "We are checking the charges.",
      },
    ],
  },
  order: {
    id: "A-104",
    charges: [
      { amount_usd: 49, status: "captured" },
      { amount_usd: 49, status: "captured" },
    ],
  },
  refund_policy: "Duplicate charges are eligible for a refund.",
};

/**
 * Every question is defined statically. The keys (`refund_requested`, 
 * `department`, etc.) are what the answers are keyed by in the response.
 */
const questions = {
  refund_requested: noul(
    "Does `ticket.messages[0].text` request a refund?",
  ),
  duplicate_charge: noul(
    "Do `order.charges[0].amount_usd` and `order.charges[1].amount_usd` match?",
  ),
  policy_supports_refund: noul(
    "Given `refund_policy`, does it support the request in `ticket.messages[0].text`?",
  ),
  department: choice("Which team should handle `ticket.messages[0].text`?", {
    billing: "Payment or subscription issues",
    technical: "Bugs or integration problems",
    sales: "Pricing or account questions",
  }),
  frustration: score(
    "How frustrated is the customer in `ticket.messages[0].text`?",
    [
      "Calm and factual",
      "Frustrated but civil",
      "Very angry, strong language",
    ],
  ),
};

/**
 * Orchestrates one triage pass in a single systemOne call.
 */
export async function triage(): Promise<void> {
  const result = await client.systemOne({ state, questions });

const refund = result.answers.refund_requested.noul;
  const duplicate = result.answers.duplicate_charge.noul;
  const policy = result.answers.policy_supports_refund.noul;
  const department = result.answers.department.choice;
  const frustration = result.answers.frustration.score;

// Compose independent judgments into a deterministic business rule in code.
  const shouldAutoRefund =
    refund >= 0.8 && duplicate >= 0.8 && policy >= 0.8;

console.log({
    department,
    frustration,
    shouldAutoRefund,
    probabilities: result.answers.department.probabilities,
    confidence: result.answers.department.confidence,
  });
}

triage().catch(console.error);

逐行解析程序

  1. 客户端生命周期

在模块级别构造一次 TypeSafeClient() 而不是在处理函数内部构造是至关重要的。重用客户端实例可以保持你的 TLS 设置、重试书签和 keep-alive 连接池处于热状态,防止热请求路径中的延迟峰值。

  1. 反引号点号与索引路径

注意问题指令如何用反引号包裹路径:`ticket.messages[0].text`。这不是单纯的格式;而是对模型的形式指令。它指示 Jev 将该标记视为结构坐标而非通用文本。省略反引号会导致路径解析不一致,以及当周围文本恰好匹配模式中的关键字时出现脆弱行为。

  1. 原始类型的威力(noul、choice、score)

noul:返回一个介于 0 和 1 之间的校准浮点数,表示封闭的二元概率。

choice:返回一个与你标准键匹配的类型化联合类型,以及完整的概率分布和置信度指标。这允许你检查候选答案并将模糊情况路由到人工审核队列。

score:评估有序的标准列表并返回概率加权的平均分数,捕捉细微差别和梯度,而不是强制严格的二元类别。

  1. 代码驱动的业务规则

注意决策逻辑的位置:完全在 TypeScript 代码中(refund >= 0.8 && duplicate >= 0.8 && policy >= 0.8)。模型的工作仅限于在精确坐标上评估归一化证据。你的代码负责将这些校准后的输出应用于业务阈值。

进阶应用:Next.js 中的请求级隔离

当扩展到处理数万个请求的多租户架构时,简单的 JSON 载荷已经不够。你需要物理检索隔离、路径渲染工具以及请求级状态管理。

下面是一个高级架构,利用 Node 的 AsyncLocalStorage 来保证并发租户操作永远不会交叉污染,同时整合了 Pinecone 向量搜索与 Jev 状态投影的服务器动作。

File 1 — 状态形状与选择器(lib/jev/state-shape.ts)

export type PathSeg = string | number;
export type StatePath = readonly PathSeg[];

export function renderPath(path: StatePath): string {
  return path.reduce(
    (acc, seg) =>
      typeof seg === "number"
        ? acc[{acc}[acc[{acc}[{seg}] : acc ? acc.{acc}.acc. {seg} : seg),
    "",
  );
}

export const cite = (path: StatePath): string => \${renderPath(path)}``;

export function getAt(root: unknown, path: StatePath): unknown {
  let node: unknown = root;
  for (const seg of path) {
    if (node === null || node === undefined) return undefined;
    node = (node as Record)[seg];
  }
  return node;
}

export function setAt<T>(root: T, path: StatePath, value: unknown): T {
  if (path.length === 0) return value as T;
  const [head, ...rest] = path;
  if (Array.isArray(root)) {
    const copy = root.slice();
    copy[head as number] = setAt(copy[head as number], rest, value);
    return copy as unknown as T;
  }
  const base = (root ?? {}) as Record<string, unknown>;
  return { ...base, [head]: setAt(base[head], rest, value) } as T;
}

export interface Candidate {
  rank: number;
  id: string;
  similarity: number;
  ageDays: number;
  sourceType: "policy" | "contract" | "kb" | "forum";
  title: string;
  passage: string;
}

export interface ShapedState {
  fastPath: {
    entitlements: {
      plan: "free" | "pro" | "enterprise";
      allowsAutoReply: boolean;
    };
    routing: {
      autoReplyFloor: number;
      escalateBelow: number;
      injectionCeiling: number;
    };
  };
  task: {
    question: string;
    locale: string;
    channel: "email" | "chat" | "api";
  };
  evidence: {
    index: string;
    namespace: string;
    topK: number;
    candidates: Candidate[];
  };
  derived: {
    candidateCount: number;
    maxSimilarity: number;
    totalPassageChars: number;
    hasPolicySource: boolean;
  };
}

export function project(state: ShapedState): Record<string, unknown> {
  return {
    task: state.task,
    evidence: state.evidence,
    derived: state.derived,
  };
}

File 2 — 请求级隔离(lib/jev/scope.ts)

import { AsyncLocalStorage } from "node:async_hooks";
import { setAt, getAt, type ShapedState, type StatePath } from "./state-shape";

export const vectorNamespace = (tenantId: string): string => `tenant:${tenantId}`;

export interface ScopeHandle {
  readonly tenantId: string;
  readonly requestId: string;
  readonly state: ShapedState;
  commit(path: StatePath, value: unknown): void;
  at<T>(path: StatePath): T;
}

const storage = new AsyncLocalStorage<string, ScopeHandle>();

export function withScope<T>(
  init: {
    tenantId: string;
    requestId: string;
    fastPath: ShapedState["fastPath"];
  },
  fn: (scope: ScopeHandle) => Promise<T>,
): Promise<T> {
  let state: ShapedState = {
    fastPath: init.fastPath,
    task: { question: "", locale: "en", channel: "api" },
    evidence: { index: "", namespace: vectorNamespace(init.tenantId), topK: 0, candidates: [] },
    derived: { candidateCount: 0, maxSimilarity: 0, totalPassageChars: 0, hasPolicySource: false },
  } as ShapedState;

  const handle: ScopeHandle = {
    tenantId: init.tenantId,
    requestId: init.requestId,
    get state() { return state; },
    commit(path, value) {
      state = setAt(state, path, value);
    },
    at<T>(path: StatePath) {
      return getAt(state, path) as T;
    },
  };

  return storage.run(handle, () => fn(handle));
}

export function currentScope(): ScopeHandle {
  const scope = storage.getStore();
  if (!scope) throw new Error("No Jev scope open: call withScope() at the request boundary.");
  return scope;
}

File 3 — 服务器动作(app/actions/answer-from-evidence.ts)

"use server";

import { Pinecone } from "@pinecone-database/pinecone";
import { choice, noul, score, RateLimitError, TypeSafeClient } from "@typesafe-ai/sdk";
import { passagePath, project, type Candidate } from "@/lib/jev/state-shape";
import { vectorNamespace, withScope } from "@/lib/jev/scope";

const JEV_MODEL = "jev-1.13.0";
const TOP_K = 12;
const INJECTION_SCAN_K = 6;

const jev = new TypeSafeClient({
  apiKey: process.env.TYPESAFE_API_KEY!,
  defaultModel: JEV_MODEL,
  timeout: 4_000,
});

const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY! });
const index = pinecone.index(process.env.PINECONE_INDEX!);

export async function answerFromEvidence(input: {
  tenantId: string;
  requestId: string;
  question: string;
  locale: string;
  channel: "email" | "chat" | "api";
  plan: "free" | "pro" | "enterprise";
}) {
  return withScope(
    {
      tenantId: input.tenantId,
      requestId: input.requestId,
      fastPath: {
        entitlements: {
          plan: input.plan,
          allowsAutoReply: input.plan !== "free",
        },
        routing: {
          autoReplyFloor: 0.85,
          escalateBelow: 0.55,
          injectionCeiling: 0.7,
        },
      },
    },
    async (scope) => {
      // 1. 在租户的物理命名空间中检索
      const vector = [0.1]; // Mock embedding vector
      const { matches } = await index
        .namespace(vectorNamespace(scope.tenantId))
        .query({ vector, topK: TOP_K, includeMetadata: true });

      // 2. 塑形并预计算标量,使 Jev 从不做算术运算
      const now = Date.now();
      const candidates: Candidate[] = matches.map((m, i) => ({
        rank: i + 1,
        id: String(m.id),
        similarity: Number((m.score ?? 0).toFixed(4)),
        ageDays: 2,
        sourceType: "kb",
        title: "Sample Doc",
        passage: String(m.metadata?.text ?? "").slice(0, 1_200),
      }));

      scope.commit(["task"], { question: input.question, locale: input.locale, channel: input.channel });
      scope.commit(["evidence"], { index: "idx", namespace: vectorNamespace(scope.tenantId), topK: TOP_K, candidates });

      // 3. 构建针对精确路径的问题
      const core = {
        best_passage: choice(
          `Which passage in \`evidence.candidates\` states the answer to \`task.question\`?`,
          { ...Object.fromEntries(candidates.map((c) => [c.id, null])), none: "No matching passage." }
        ),
        evidence_sufficient: noul(
          `Do the passages in \`evidence.candidates\` contain everything needed to answer \`task.question\`?`
        ),
      };

      const questions = { ...core };

      // 4. 执行单一并行调用
      const result = await jev.systemOne({
        state: project(scope.state),
        questions,
        model: JEV_MODEL,
      });

      return {
        success: true,
        answers: result.answers,
      };
    },
  );
}

生产环境中应避免的常见陷阱

忘记反引号:写路径时如 ticket.messages[0].text 不加反引号,会迫使模型猜测你是在写代码还是自然语言,导致置信度大幅下降。始终用反引号包裹路径。

在循环中提问或请求:永远不要在单独的问题或不同票据的循环内调用 systemOne。将问题批量放入单一状态载荷,使用 Promise.all() 展开票据请求。

将数学运算委托给模型:Jev 是一个推理引擎,不是计算器。不要让模型计数数组项、比较日期或求账单总额。在 TypeScript 代码中预计算这些值,作为标量字段传入状态。

租户隔离失败:依赖查询过滤字符串({ tenantId: currentTenant })会使数据库暴露于人为错误。使用物理存储命名空间(如 Pinecone 命名空间)和 AsyncLocalStorage 在基础设施层面强制执行隔离。

状态塑形本质上是 API 设计。当你将 Jev 状态对象视为结构化、可寻址的契约而非混乱的字符串转储时,一切都改变了。

通过保持路径扁平、在代码中预计算标量、强制请求级隔离,以及将问题指向精确坐标路径,你将 LLM 输出从概率赌博转化为可靠的高性能工程原语。正确构建你的地形,模型每次都会给出清晰、校准的答案。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
AI评委也会上当:评测模型审查作弊代码的深度测试
下一篇
LLM输出JSON解析的正确做法