Praxis Cloud 开发者文档

2026-10-22

概览

直接运行智能体,或使用你需要的基础能力

智能体服务封装模型调用、工具执行等能力,通过智能体 API 完成业务任务。需要直接控制模型、沙箱或搜索时,也可以独立使用对应服务。

开始使用

01

选择服务

运行智能体,或直接调用模型、沙箱与搜索。

02

创建云 Key

在 Praxis Cloud 控制台创建 API Key,了解鉴权方式与权限范围。

03

完成第一次调用

打开所选服务的快速开始,完成一个请求并查看结果。

选择你需要的服务

每项服务都有独立的快速开始、使用指南与 API。


智能体

定义职责、提供资料并运行任务。由智能体服务管理底层能力调用。

快速开始

创建并运行第一个智能体

核心概念

智能体与运行

会话、记忆与协作

使用指南

配置职责与能力

提供文件与资料

使用工具与组织协作

接入自己的应用

技术参考

限制与错误处理

智能体 API


模型

通过统一接口调用模型,生成文本、处理多模态输入。


沙箱

在隔离环境中执行代码、操作文件和使用浏览器。

快速开始

创建沙箱并执行一个任务

核心概念

沙箱与生命周期

执行环境与资源

使用指南

执行命令 · 管理文件

使用浏览器与网络

回收环境与处理失败

技术参考

限制与错误处理

沙箱 API


搜索

查询所需信息,获取搜索结果与来源。

快速开始

完成第一次搜索

核心概念

搜索范围与查询

结果、排序与来源

使用指南

设置搜索条件

读取与筛选结果

在应用中使用搜索

技术参考

限制与错误处理

搜索 API

管理你的云服务

身份与权限

管理云 Key 与授权范围

保护服务访问凭证

用量与费用

计量口径、用量查询

账单与费用管理

配额与限制

查询服务配额

了解请求与资源限制

观测与故障排查

请求标识、日志与排错

服务异常的定位路径

API

智能体 / 模型 / 沙箱 / 搜索 —— 查阅请求参数、响应结构、错误码与代码示例。需要验证调用时,在当前页面展开调试面板,配置并发送请求。

浏览所有 API →

第一个请求

不想写代码?直接看接入方式,把 Praxis 接进 Claude Code、Cursor、Cherry Studio 等客户端。

前提

一个 Praxis 账号。新注册账号自带 $1 免费额度,跑完本页绰绰有余,不需要先充值。

1. 创建 API Key

在 控制台 → API Keys 创建一个 Key。名称必填,有效期默认 90 天,创建后立即复制保存。

2. 发出第一个请求

只需要一个地址:https://api.deepraxis.ai。用你已有的 SDK,把 base_url 指过来即可。

示范模型 deepseek-flash:便宜、快、中英文都好。

curl https://api.deepraxis.ai/openai/v1/chat/completions \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "deepseek-flash", "messages": [{"role": "user", "content": "你好"}]}'

用你熟悉的语言也行:

Python

from openai import OpenAI

client = OpenAI(
    base_url="https://api.deepraxis.ai/openai/v1",
    api_key="sk-praxis-你的Key",
)

completion = client.chat.completions.create(
    model="deepseek-flash",
    messages=[{"role": "user", "content": "你好"}],
)
print(completion.choices[0].message.content)

Node

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.deepraxis.ai/openai/v1",
  apiKey: process.env.PRAXIS_API_KEY,
});

const res = await client.chat.completions.create({
  model: "deepseek-flash",
  messages: [{ role: "user", content: "你好" }],
});
console.log(res.choices[0].message.content);

3. 读懂响应

响应的 choices[0].message.content 是回复文本。usage 里有 token 数——注意 cost 字段直接给出本次调用的费用。

然后打开 控制台 → 用量与费用:你刚才这一次调用已经记在那里了。

下一步

选择服务

Praxis Cloud 提供多个服务域。你的场景决定用哪个——很多场景只需要一个。

模型(Model)

你要做什么:调用大语言模型(对话、写作、翻译、代码、分析)。

怎么用:通过 OpenAI 或 Anthropic 兼容 API 直接调用。把现有代码的 base_url 指向 Praxis 即可。

适合:已有应用要加 AI 能力、写 SDK 代码、接入第三方工具(Cursor、Cherry Studio 等)。

→ 模型快速开始 · 模型目录

智能体(Agent)

你要做什么:让 AI 在云端持续工作——有上下文、有记忆、能调用工具。

怎么用:通过 Agent API 创建会话、发送事件、获取回复。智能体可以记住之前的对话、执行多步任务。

适合:构建 AI 助手、客服机器人、有状态的工作流。

→ 智能体快速开始

沙箱(Sandbox)

你要做什么:在安全隔离的环境中运行代码。

怎么用:通过 Sandbox API 创建沙箱实例,上传代码并执行,获取结果。

适合:代码执行、自动化测试、数据处理管道。

→ 沙箱快速开始

搜索(Search)

你要做什么:给 AI 提供网络搜索能力。

怎么用:通过 Search API 执行搜索,返回结构化结果。

适合:构建有实时信息能力的 AI 应用。

→ 搜索快速开始

怎么选

你的场景推荐
只需要调模型(对话/生成)模型
需要 AI 有记忆和工具智能体
需要安全执行代码沙箱
需要搜索网络信息搜索
组合使用全部可以组合——智能体可以调用模型和搜索,沙箱可以运行任何代码

不确定的话:从模型开始——最简单、最直接,3 分钟就能跑通。

核心概念

理解这几个概念,使用 Praxis 会更顺手。

一个 Key,所有服务

Praxis Cloud 的所有服务(模型、智能体、沙箱、搜索)共用同一个 API Key。你不需要为不同服务创建不同的 Key——一个 Key 就够了。

兼容协议

Praxis 提供两种协议入口,你已有的代码不需要换库:

  • OpenAI 兼容(https://api.deepraxis.ai/openai/v1)——OpenAI SDK 改 base_url 即用
  • Anthropic 兼容(https://api.deepraxis.ai/anthropic)——Anthropic SDK 改 base_url 即用

两种协议背后的模型池是同一个——你可以在 OpenAI 协议下调 Claude 模型,也可以在 Anthropic 协议下调 DeepSeek 模型。

模型与路由

Praxis 聚合多家供应商的模型,统一以一个 API 提供。每个模型 ID 背后是具体的供应商路由——你不需要关心用的是哪家的服务,只需要选对模型 ID。

两种模型 ID:

  • 滚动入口(如 deepseek-flash)——始终指向最新一代,升级时无需改代码
  • 版本快照(如 deepseek-v4-flash)——固定在某一个版本,适合需要可复现性的场景

按次计费

每次 API 调用按实际 token 用量计费。输入和输出 token 分别计价,推理模型的思维链 token 也计费。不调用不收费——没有月费、没有最低消费。

API 响应中的 usage.cost 字段直接返回本次调用的费用,你可以在应用里实时追踪成本。

想深入某个服务?

智能体

让智能体回答产品问题、查询业务数据或完成工具任务。你配置模型、指令与工具,Praxis 管理会话执行和事件记录;你的应用决定谁可以使用、哪些业务操作可以执行。

适用对象与结果

给已有产品接入助手时,可以使用现成对话界面,把用户身份和当前页面带入会话。需要完全自定义交互、后台任务或非网页客户端时,使用 API 创建会话并读取事件。业务数据通过你授权的工具取得,订单权限仍由业务服务器核验。

如果只需要一次模型调用,或希望自己编写模型与工具循环,从模型服务开始。智能体服务适合希望复用配置、持续对话并追踪任务过程的应用。

对象如何关联

对象负责什么与其他对象的关系
智能体名称、指令、模型、工具保存配置形成版本;一个智能体可以创建多个会话
会话一段独立对话及其执行状态创建时固定智能体版本,后续配置修改不会替换该快照
环境会话运行所需的机器配置API 可省略环境选择使用轻型机器;需要沙箱时选择环境
事件用户消息、智能体回复、工具调用和状态变化属于会话;发送成功表示输入已接收,回复要从事件中读取
终端用户使用你产品的人嵌入令牌把会话访问绑定到该用户和智能体

服务智能体面向你的客户,每个会话使用独立环境;个人智能体绑定持久电脑。本文的接入指南围绕服务智能体展开,详细关系见智能体与会话。公开智能体 ID 形如 agent_…,应直接使用创建或查询响应中的值。

选择交付形态

形态你负责从哪里开始
API服务端鉴权、消息和事件处理、自有界面运行第一个智能体
PraxisKit在宿主网页挂载组件,接好身份与业务交互嵌入到网页
iframe提供签发端点并加载托管界面一行嵌入
Hosted验证用户身份,签发短期链接并跳转打开托管对话

React 是使用 Kit 的一种方式。公开资料问答可使用匿名访客接入;需要账户数据时,由宿主后端验证登录身份再签发令牌。完整比较见选择接入方式。

按任务继续

下一步:API 参考 · 文档问答配方 · 限制与错误

运行第一个智能体

用 API 建立一段服务智能体对话,并从事件中验证真实回复。本页只在服务端运行,不需要网页组件或嵌入策略。

准备 Bun 或支持原生 fetch 的 Node.js、Develop 组织的 API Key,以及该组织可用的 deepseek-flash 模型和余额。在服务端进程设置 PRAXIS_API_KEY;密钥创建见API Key。如果你的 Key 属于其他环境,先统一 API 地址、组织和模型,不能混用环境。

1. 创建智能体

新建 first-agent.mjs,从下面的代码开始。后续三段 JavaScript 按顺序追加到同一个文件,不要分别执行。助手只回答一个算术问题,便于判断首次调用是否完成;业务工具在接通后再添加。

POST /agent/v1/agents 使用名称、类型、模型和指令创建配置。tools: [] 不声明额外工具。请求辅助函数为每次调用检查状态并设置超时,失败时只输出状态和请求标识,不打印 Key。

import assert from "node:assert/strict";
import { setTimeout as delay } from "node:timers/promises";

const { PRAXIS_API_KEY } = process.env;
assert(PRAXIS_API_KEY, "Set PRAXIS_API_KEY in the server process");
const base = "https://api-develop.deepraxis.com/agent/v1";
async function request(path, body) {
  const response = await fetch(`${base}${path}`, {
    method: body === undefined ? "GET" : "POST",
    headers: {
      Authorization: `Bearer ${PRAXIS_API_KEY}`,
      "Content-Type": "application/json",
    },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
    signal: AbortSignal.timeout(30_000),
  });
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}; request=${response.headers.get("X-Request-Id") ?? response.headers.get("request-id")}`);
  }
  return response.json();
}
const agent = await request("/agents", {
  name: "First support agent", kind: "service", model: "deepseek-flash",
  system: "Answer the user's question briefly. Do not use tools for this exercise.",
  tools: [],
});
assert(agent.id.startsWith("agent_"));
console.log({ agent: agent.id, version: agent.version });

预期结果: 创建响应为 HTTP 200,包含公开 id 和 version。运行脚本时会打印这两个值,后续直接复用,不手写猜测 ID。

2. 建立会话

追加以下代码。POST /agent/v1/sessions 把刚创建的版本固定在会话中。省略 environment_id 使用轻型机器;这一步不需要先创建沙箱。示例显式设置 100 美分的会话预算,不把预算金额当作预计费用。

const session = await request("/sessions", {
  agent: { type: "agent", id: agent.id, version: agent.version },
  title: "First API conversation",
  budget: { type: "limit", max_list_cost: { amount: "100", currency: "USD" } },
});
assert(session.id);
console.log({ session: session.id });

预期结果: HTTP 201,响应包含会话 id。此时还没有发送用户问题;即使状态为 idle,也不代表已经得到回复。预算决定后续请求能否继续,不保证生成特定答案。

3. 发送第一条消息

追加以下代码,调用 POST /agent/v1/sessions/{session_id}/events。events 是输入数组;这里发送一条 user.message,其中只有一个非空文本块。

const accepted = await request(`/sessions/${session.id}/events`, {
  events: [{ type: "user.message", content: [{ type: "text", text: "What is 2 + 2?" }] }],
});
assert(accepted.data.some(event => event.type === "user.message"));

预期结果: HTTP 200,data 中出现已接收的用户事件。这是接收确认;不要把它当作智能体答案,也不要因为暂时没有答案就再次发送同一条消息。

4. 读取并验证回复

追加以下代码,然后执行 node first-agent.mjs 或 bun first-agent.mjs。它用 GET /agent/v1/sessions/{session_id}/events 分页读取事件,先找到 agent.message,再找到其后的 session.status_idle。初始空闲状态不会被误判为成功。

let completed = false;
const deadline = Date.now() + 120_000;
while (Date.now() < deadline) {
  const events = [];
  let page;
  do {
    const query = new URLSearchParams({ order: "asc", limit: "100" });
    if (page) query.set("page", page);
    const result = await request(`/sessions/${session.id}/events?${query}`);
    events.push(...result.data);
    page = result.next_page;
  } while (page && Date.now() < deadline);
  const reply = events.findIndex(event => event.type === "agent.message");
  const idle = events.findIndex((event, i) => i > reply && event.type === "session.status_idle");
  if (reply >= 0 && idle > reply) {
    const text = events.filter(event => event.type === "agent.message")
      .flatMap(event => event.content ?? []).filter(block => block.type === "text")
      .map(block => block.text).join("\n");
    assert(text.trim(), "Expected a nonempty reply");
    console.log({ reply: text, completed: true });
    completed = true;
    break;
  }
  await delay(1500);
}
assert(completed, `No completed reply before timeout; inspect session ${session.id}`);

完成判定: 进程正常退出,输出非空 reply 和 completed: true,答案应表达 4。脚本的 120 秒是本例等待期限,不是服务运行上限。只通过文本非空断言还不足以验证业务答案;上线前为实际问题准备预期结果。

失败时检查

症状下一步
HTTP 401检查服务端 Key 是否存在、有效,是否对应当前环境
HTTP 400核对字段与模型;错误详情见限制与错误
HTTP 404使用创建响应里的 ID,确认组织与所选版本一致
HTTP 429 或临时上游失败按响应重试提示退避;先查询会话,避免重新创建资源或重复发消息
超时仍无完成回复保留会话 ID,读取事件检查工具等待、预算或运行错误;不要仅依据创建响应的状态判断

重新运行整份脚本会新建智能体和会话。排错时优先查询已输出的 ID。测试记录可在确认不再需要后按 API 参考处理,不要把仍在运行的会话当作可直接删除的记录。

下一步

继续配置智能体、了解智能体与会话,或用 PraxisKit 接入网页。

交给你的代理:

按 /docs/developers/agent/quickstart 在服务端运行首次对话。
需要我提供 Develop 组织 API Key,并确认 deepseek-flash 可用。
完成标准:收到非空 agent.message,其后出现 session.status_idle。
不要把 Key 放进浏览器或日志;记录智能体 ID、版本和会话 ID。

智能体与会话

智能体是一份可复用的配置,会话是使用这份配置开展的一段独立对话。先区分这两个对象,才能判断修改指令会影响谁、消息应该发到哪里,以及如何验证任务结束。

配置与运行对象

对象包含什么创建与使用
智能体模型、指令、工具、MCP 配置及版本创建后取得 agent_… ID,供多个会话复用
会话固定版本、消息与工具事件、运行状态以智能体 ID 或带版本的引用创建;后续向该会话发送输入
环境运行会话的机器配置为需要沙箱的会话选择环境;普通 API 起步可省略
事件用户输入、回复、工具执行与状态变化写入输入事件,读取历史或订阅事件流
vault可供访问工具服务的凭据与配置指令分开管理,不能用提示词代替凭据控制

服务智能体为每段会话使用独立运行环境。个人智能体绑定持久电脑,适合保留工作目录的任务;两种类型的选择在创建时确定。API 起步使用服务智能体,不需要先准备个人电脑。

版本与会话生命周期

保存智能体配置会创建新版本。创建会话时,agent 可以是 ID 字符串,表示取当时最新版本;也可以是 { type: "agent", id, version },明确选择版本。会话固定这份配置,之后修改智能体不会把已有会话换到新版本。

会话可以处于 idle、running、rescheduling 或 terminated 状态。idle 表示当前没有运行中的任务,可以继续发送消息;terminated 会话不能继续运行。归档是另一项状态:记录仍可查询,但不能继续发送消息。保留记录与停止接收输入是不同需求,清理前先判断需要哪一种。

发送 user.message 得到成功响应,只说明输入被接收。读取 agent.message 才能取得回复;观察其后的 session.status_idle 才能判断这一轮已经回到空闲。需要用户批准工具时,应处理待确认事件,不能将等待确认误判为接口无响应。

环境与身份边界

API 创建会话省略 environment_id,且未选择电脑时,使用轻型机器;选择环境时使用对应沙箱配置。环境配置在会话创建时固定。会话的文件和工具能力取决于配置,不应仅凭“环境”这个名字推断支持任意运行时。

组织 API Key 留在服务端。面向终端用户的网页使用嵌入令牌,由宿主后端从已验证登录态确定 external_user_id,再为指定智能体签发。令牌限定智能体、版本及终端用户的访问范围;前端自己填写用户 ID 不能建立可信身份。

公开资料问答可使用可公开 key 获取匿名访客令牌。同一浏览器存储延续匿名身份;这不等于登录账户,也不授予访问订单或其他私有资料的权限。用户切换时销毁旧组件并重新取得凭证,不能只清空消息列表。

工具确认表达用户是否同意本次操作。你的订单或业务服务仍须验证调用凭据和当前用户的资源权限;页面上下文、模型参数和“允许执行”按钮都不能代替这一步。指令和上下文也不适合保存秘密。

选择下一步

第一次验证 API,用运行第一个智能体。要修改配置或选择嵌入版本,读配置智能体;创建与更新请求的字段以 API 参考为准。

接入客户产品时,先选交付形态,再按嵌入令牌与用户接好身份。出现执行问题时,从查看会话与用量定位记录,再按限制与错误处理。

选择接入方式

选择对话出现的位置,再决定由谁编写界面。PraxisKit、iframe 和 Hosted 提供对话界面;API 适合自己实现界面的应用。

比较接入方式

方式用户在哪里对话你需要实现外观入口开始接入
Kit 组件你的网页内宿主签发端点、组件初始化宿主代码的 setOptions嵌入到网页
iframe网页内的独立框架宿主签发端点、一行加载脚本控制台保存,脚本可覆盖布局一行嵌入
HostedPraxis 托管的独立对话页宿主验证身份、签发链接并跳转与 iframe 共用保存的外观;全页显示打开托管对话
API你自己的网页、App 或服务会话、事件处理与所需界面由你的应用决定运行第一个智能体

按任务选择

需要在网页里调整组件、控制打开时机或连接页面交互时,选择 Kit。Kit 是 Web Component,也能在 React 应用里挂载;你在宿主代码中设置界面选项。

只想插入现成对话界面、把外观交给控制台维护时,选择 iframe。这里的“一行”指加载脚本,登录用户的后端签发端点仍需实现。复制一个裸 iframe 地址不能代替身份验证。

希望用户点按钮进入独立页面时,选择 Hosted。宿主后端为当前用户生成短期链接,浏览器立即跳转;不需要在宿主页安装组件。每次进入都重新取得链接,不能把用户的链接作为公共入口分享。

已有自己的聊天界面,或要从非网页应用接入时,选择 API。产品快速开始先带你从服务端跑通一次会话;向终端用户开放时,再按令牌指南隔离每个人的访问权限。

准备身份与配置

登录用户接入需要一个服务智能体、保存的嵌入策略,以及能够验证宿主登录态的后端。API Key 留在后端;浏览器只取得当前终端用户的嵌入令牌。更换界面形态不应改变你为同一个用户分配的稳定标识。

公开问答站不要求登录时,可选 Kit 的匿名访客接入:使用可公开 key,由 Kit 获取匿名令牌。匿名身份不能代替你的账户登录,也不能据此开放订单等账户数据,见嵌入令牌与用户。

嵌入策略和持久化外观目前只能在控制台配置。Kit 的实例外观则由宿主代码设置。先选一种形态跑通真实问答,再增加上下文、工具或主题。

下一步

嵌入到网页

在你的网页中加入 PraxisKit,让登录用户与服务智能体对话。本页用本地订单助手跑通后端签发、前端挂载和身份隔离;其他形态见选择接入方式。

准备 Bun、一个可用的服务智能体及运行环境,以及同组织的 API Key。示例连接 Develop,API Key 仅存为后端环境变量 PRAXIS_API_KEY。浏览器登录宿主后,由宿主核验身份、向 Praxis 签发令牌,再把令牌交给 Kit。

1. 准备智能体

在 Develop 控制台进入服务智能体,选择已有智能体。在接入与配置 → 配置 → 指令保存指令;在配置 → 安全选择运行环境,打开允许 localhost,点保存安全设置。本页宿主页为 http://localhost:4242。请使用专用测试智能体,来源白名单暂留空,仅启用 localhost;令牌继承非空 HTTPS 来源列表时,本地请求会被拒绝。上线到 HTTPS 网站时,添加该网站的来源。

嵌入策略目前只能在控制台配置。复制公开智能体 ID(agent_…)备用。页面显示“已保存,不产生新版本”后,继续实际签发;保存提示本身不能证明对话可用。

2. 创建签发端点

新建目录,把代码保存为 server.ts。这是仅绑定本机的演示服务器,用 HTTP Basic 验证两个测试账户。DEMO_PASSWORD 是你自行设置的测试密码,与 Praxis API Key 无关。服务器从验证成功的账户取用户 ID,不读取浏览器提交的用户 ID。

import { timingSafeEqual } from "node:crypto";

const apiKey = process.env.PRAXIS_API_KEY;
const agentId = process.env.PRAXIS_AGENT_ID;
const password = process.env.DEMO_PASSWORD;
if (!apiKey || !agentId || !password) throw new Error("Set the three server variables");
const origin = "http://localhost:4242";
const noStore = { "Cache-Control": "no-store" };
function authenticate(request: Request): string | undefined {
  const supplied = Buffer.from(request.headers.get("Authorization") ?? "");
  for (const id of ["customer_123", "customer_456"]) {
    const expected = Buffer.from("Basic " + Buffer.from(`${id}:${password}`).toString("base64"));
    if (supplied.length === expected.length && timingSafeEqual(supplied, expected)) return id;
  }
}
Bun.serve({
  hostname: "127.0.0.1", port: 4242,
  async fetch(request) {
    const url = new URL(request.url);
    if (url.origin !== origin) return new Response("Use localhost:4242", { status: 403 });
    const userId = authenticate(request);
    if (!userId) return new Response("Sign in", {
      status: 401, headers: { ...noStore, "WWW-Authenticate": 'Basic realm="Local demo"' },
    });
    if (request.method === "GET" && url.pathname === "/") {
      return new Response(Bun.file("index.html"), { headers: noStore });
    }
    if (request.method !== "POST" || url.pathname !== "/api/praxis-token") {
      return new Response("Not found", { status: 404 });
    }
    if (request.headers.get("Origin") !== origin) {
      return new Response("Origin rejected", { status: 403 });
    }
    try {
      const response = await fetch("https://api-develop.deepraxis.com/agent/v1/embed_tokens", {
        method: "POST",
        headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
        body: JSON.stringify({ agent: agentId, external_user_id: userId }),
        signal: AbortSignal.timeout(30_000),
      });
      if (!response.ok) return new Response("Token issuance failed", {
        status: response.status, headers: noStore,
      });
      const data = await response.json();
      if (typeof data.token !== "string" || typeof data.expires_at !== "string") {
        throw new Error("Invalid token response");
      }
      return Response.json({ token: data.token, expires_at: data.expires_at }, { headers: noStore });
    } catch {
      return new Response("Token service unavailable", { status: 502, headers: noStore });
    }
  },
});
console.log(origin);

将三个环境变量放入服务端进程;PRAXIS_AGENT_ID 替换为实际 ID。启动前还需在下一步保存 index.html。

export PRAXIS_AGENT_ID="agent_REPLACE_ME"
# Set PRAXIS_API_KEY and DEMO_PASSWORD in this server process, then run:
bun server.ts

期望:终端输出本地网址。未认证请求被宿主拒绝;已认证且同源的请求才会调用 Praxis。Praxis 签发成功返回 HTTP 201;宿主端点只返回 token、expires_at,并禁止缓存。

接入已有应用时,将 authenticate 替换为应用的会话验证,把路由接在登录与 CSRF 保护之后,保留每次签发的身份核验。不要将本地测试账户机制用于线上登录。有效期和刷新见嵌入令牌与用户。

3. 挂载组件

选择一种前端实现。API 地址、CDN 和智能体必须属于同一环境;以下固定使用 Develop 的 Kit 0.2.8。

保存为同目录的 index.html,然后运行上一步启动命令。

<!doctype html>
<html lang="zh">
<meta charset="utf-8">
<title>Order assistant</title>
<script type="module" src="https://kit-develop.praxisusercontent.com/v0.2.8/praxis-kit.js" integrity="sha384-vey+VZZcFIWJq9Fmv/nmLxspKtXRiL8XiDOfg2g2QFvwZi5MA+tkiQvPBtoMS8eU" crossorigin="anonymous"></script>
<praxis-agent style="display:block;height:600px;width:100%"></praxis-agent>
<p id="status" role="status"></p>
<script type="module">
  await customElements.whenDefined("praxis-agent");
  const agent = document.querySelector("praxis-agent");
  agent.addEventListener("praxis.error", () => {
    document.querySelector("#status").textContent = "Chat unavailable. Check your sign-in and configuration.";
  });
  agent.setOptions({
    api: {
      url: "https://api-develop.deepraxis.com",
      getToken: async () => {
        const response = await fetch("/api/praxis-token", {
          method: "POST", credentials: "same-origin", cache: "no-store",
        });
        if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
        const data = await response.json();
        if (typeof data.token !== "string" || !data.token) throw new Error("Missing token");
        return data.token;
      },
    },
    agent: { name: "Order assistant" },
  });
</script>
</html>

期望:页面出现高度为 600 像素的对话区。api.url 指定 API 地址,getToken 返回令牌字符串,agent.name 是界面名称。等待元素注册后再调用 setOptions。praxis.ready 只表示组件挂载;必须收到真实回复才能证明接入成功。

4. 验证接入

  1. 打开 http://localhost:4242,用 customer_123 和所设测试密码登录。发送“你好,请介绍你能帮助处理的订单问题”,确认收到回复。
  2. 在网络面板检查宿主令牌请求成功,响应没有 API Key;会话请求使用嵌入令牌。不要把令牌复制到日志或截图。
  3. 用独立浏览器配置或无痕窗口登录 customer_456,期望看不到第一个账户的会话。不能用两个同登录态的标签页代替此检查。
  4. 不带凭据请求本地签发端点,验证宿主返回 HTTP 401。已有应用还应验证退出登录后不能再签发。
curl --silent --output /dev/null --write-out "%{http_code}\n" \
  -X POST http://localhost:4242/api/praxis-token \
  -H "Origin: http://localhost:4242"

失败时检查

以下是接口定义的失败分支,按网络面板中的实际响应检查:

  • 签发 HTTP 400:确认已保存嵌入策略,运行环境仍可用。
  • 签发 HTTP 404:确认 API Key 与服务智能体属于同组织,ID 已替换正确。
  • 对话返回 EMBED_ORIGIN_NOT_ALLOWED(HTTP 403):核对实际页面来源;本地应启用 localhost,上线应保存 HTTPS 来源。

下一步

一行嵌入

在网页中插入一行加载脚本,显示 Praxis 托管的 iframe 对话界面。外观在控制台保存,用户留在你的网页中对话;“一行”只指前端安装,后端仍需为登录用户签发令牌。

准备 HTTPS 宿主

准备一个可修改页面和后端的 HTTPS 网站、Bun、同组织的 API Key,以及有短号的服务智能体。以下连接 Develop。用于演示的 HTTPS 入口应转发到本机 127.0.0.1:4242,并保留原始 Host 请求头;证书和域名由你的宿主提供。

在 Develop 控制台打开服务智能体 → 你的智能体 → 接入与配置 → 配置 → 安全,选择运行环境,在来源白名单添加实际 HTTPS 来源,例如 https://support.example.com,点保存安全设置。来源不包含路径。嵌入策略目前只能在控制台配置。

仅打开允许 localhost不能授权 iframe 嵌入;本页需要已保存的 HTTPS 来源。若只做本机 HTTP 调试,先使用 Kit 快速开始。

记录两个标识:签发请求使用公开 ID agent_…;脚本的 data-agent 使用控制台顶部可复制的短号,格式为 5–8 位字母或数字。不要把完整 ID 填进短号位置。

提供签发端点

把以下代码保存为 server.ts,放在 HTTPS 入口后。示例使用两个受密码保护的测试账户;上线时将 authenticate 换成宿主登录会话验证,并保留 CSRF 保护。HOST_ORIGIN 填实际 HTTPS 来源,不带末尾斜线。所有环境变量只配置在后端。

import { timingSafeEqual } from "node:crypto";

const apiKey = process.env.PRAXIS_API_KEY;
const agentId = process.env.PRAXIS_AGENT_ID;
const password = process.env.DEMO_PASSWORD;
if (!apiKey || !agentId || !password) throw new Error("Set the three server variables");
const origin = process.env.HOST_ORIGIN;
if (!origin || new URL(origin).protocol !== "https:") throw new Error("Set HOST_ORIGIN to your HTTPS origin");
const noStore = { "Cache-Control": "no-store" };
function authenticate(request: Request): string | undefined {
  const supplied = Buffer.from(request.headers.get("Authorization") ?? "");
  for (const id of ["customer_123", "customer_456"]) {
    const expected = Buffer.from("Basic " + Buffer.from(`${id}:${password}`).toString("base64"));
    if (supplied.length === expected.length && timingSafeEqual(supplied, expected)) return id;
  }
}
Bun.serve({
  hostname: "127.0.0.1", port: 4242,
  async fetch(request) {
    const url = new URL(request.url);
    if (request.headers.get("Host") !== new URL(origin).host) return new Response("Host rejected", { status: 403 });
    const userId = authenticate(request);
    if (!userId) return new Response("Sign in", {
      status: 401, headers: { ...noStore, "WWW-Authenticate": 'Basic realm="Local demo"' },
    });
    if (request.method === "GET" && url.pathname === "/") {
      return new Response(Bun.file("index.html"), { headers: noStore });
    }
    if (request.method !== "POST" || url.pathname !== "/api/praxis-token") {
      return new Response("Not found", { status: 404 });
    }
    if (request.headers.get("Origin") !== origin) {
      return new Response("Origin rejected", { status: 403 });
    }
    try {
      const response = await fetch("https://api-develop.deepraxis.com/agent/v1/embed_tokens", {
        method: "POST",
        headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
        body: JSON.stringify({ agent: agentId, external_user_id: userId }),
        signal: AbortSignal.timeout(30_000),
      });
      if (!response.ok) return new Response("Token issuance failed", {
        status: response.status, headers: noStore,
      });
      const data = await response.json();
      if (typeof data.token !== "string" || typeof data.expires_at !== "string") {
        throw new Error("Invalid token response");
      }
      return Response.json({ token: data.token, expires_at: data.expires_at }, { headers: noStore });
    } catch {
      return new Response("Token service unavailable", { status: 502, headers: noStore });
    }
  },
});
console.log(origin);

在下一节保存 index.html 后启动。DEMO_PASSWORD 是你自己设置的测试账户密码,不是 API Key;浏览器用 customer_123 或 customer_456 登录。

export HOST_ORIGIN="https://support.example.com"
export PRAXIS_AGENT_ID="agent_REPLACE_ME"
# Set PRAXIS_API_KEY and DEMO_PASSWORD in the server process.
bun server.ts

加载器向 data-token-endpoint 发 POST 请求,并携带浏览器凭据。端点必须从已验证登录态取得用户 ID,每次签发都重新核验。Praxis 返回 HTTP 201 后,宿主向加载器返回 token 和 expires_at。不要在 HTML 中写入 API Key,也不要让令牌进入页面 URL。

插入加载脚本

保存为 index.html。把 ABC123 替换为实际短号;若已有宿主页,只需将其中的脚本标签放到需要出现对话的位置。

<!doctype html>
<html lang="zh">
<meta charset="utf-8">
<title>Order assistant</title>
<body>
  <h1>Order support</h1>
  <script src="https://kit-develop.praxisusercontent.com/v1/loader.js" data-agent="ABC123" data-token-endpoint="/api/praxis-token" async></script>
</body>
</html>

加载器自动创建 iframe 并完成令牌传递。直接打开或粘贴一个 iframe 地址不会自动获得宿主登录用户的身份。

脚本属性用途
data-agent必填,智能体短号
data-token-endpoint必填,宿主签发端点;示例与页面同源
data-layout可选,inline、popup、sidebar 或 modal
data-launcher可选,bubble、none 或已有按钮的 CSS 选择器

未指定布局和启动按钮时使用控制台保存的外观;还没有保存布局时,加载器采用 popup。显式设置脚本属性会覆盖对应选项。如果使用 inline,脚本所在位置就是插入位置,给外层容器预留足够高度。

保存外观

在接入与配置 → 接入方式与外观 → iframe调整名称、欢迎语和布局,点保存。期望显示“已保存,嵌入页刷新后生效”;刷新你的宿主页验证。持久化外观目前只能在控制台配置。

如果选择 data-launcher="none",浮层没有默认打开按钮。为已有按钮设置 CSS 选择器,或在宿主代码中控制打开,示例如下。按钮需在加载器执行前存在。

<button id="open-support" type="button">Open support</button>
<script src="https://kit-develop.praxisusercontent.com/v1/loader.js" data-agent="ABC123" data-token-endpoint="/api/praxis-token" data-layout="popup" data-launcher="#open-support" async></script>

这是上一节脚本的替代写法,同一智能体在页面中只保留一份加载脚本。

验证与排错

打开 HTTPS 宿主页并登录,确认对话界面或启动按钮出现;发送问题并收到回复。再用独立浏览器登录第二个测试账户,确认无法看到第一个账户的会话。

在网络面板确认加载了 loader.js,iframe 来自 chat-develop.praxisusercontent.com,宿主令牌端点成功返回。API Key 只能出现在宿主到 Praxis 的服务端请求中,不应出现在浏览器网络记录里。

  • 框架被浏览器阻止:检查允许来源是否与页面来源一致;宿主 CSP 的 frame-src 要允许 https://chat-develop.praxisusercontent.com,script-src 要允许脚本 CDN。不要禁用整站 CSP 来排错。
  • 控制台提示 Invalid Praxis loader configuration:检查短号格式、脚本域名以及签发端点是否填写。
  • 宿主端点返回 HTTP 401 或 403:先恢复宿主登录或修正同源检查;不要把重试当成重新登录。

验证来源拒绝时,从另一个不在白名单中的 HTTPS 站点加载同一脚本,期望浏览器阻止嵌入。验证签发保护时,在退出宿主登录后再次访问签发端点,期望宿主拒绝请求;不要只检查按钮是否隐藏。

用户退出或切换账户时,重新加载宿主页,让后端按新登录态签发,避免沿用原账户界面。加载器的打开和关闭只影响界面显示,不是账户退出,也不会替你的宿主结束登录会话。

下一步

打开托管对话

给你的网站添加一个按钮,让已登录用户进入 Praxis 的独立 Hosted 对话页。宿主后端验证用户、签发链接并立即跳转;不需要在宿主页安装聊天组件。

准备智能体

准备 Bun、同组织的 API Key,以及一个已配置指令、运行环境和嵌入策略的服务智能体。智能体必须有短号;在 Develop 控制台的服务智能体中打开它,确认顶部显示短号,并复制公开 ID agent_…。

尚未保存嵌入策略时,进入接入与配置 → 配置 → 安全,选择运行环境,点保存安全设置。嵌入策略目前只能在控制台配置。以下示例使用 Develop API 和本地宿主 http://localhost:4242,对话则在托管域名中打开。

Hosted 是独立页面,不能作为 iframe 嵌进宿主。需要留在当前页面时,选择一行嵌入或 Kit 组件。

签发链接并跳转

把以下完整服务器保存为 server.ts。它只绑定本机,用 HTTP Basic 验证两个测试账户,并检查表单请求来源。PRAXIS_API_KEY 只存在后端;external_user_id 来自通过验证的账户,而不是表单字段。

import { timingSafeEqual } from "node:crypto";

const apiKey = process.env.PRAXIS_API_KEY;
const agentId = process.env.PRAXIS_AGENT_ID;
const password = process.env.DEMO_PASSWORD;
if (!apiKey || !agentId || !password) throw new Error("Set the three server variables");
const origin = "http://localhost:4242";
const noStore = { "Cache-Control": "no-store" };
function authenticate(request: Request): string | undefined {
  const supplied = Buffer.from(request.headers.get("Authorization") ?? "");
  for (const id of ["customer_123", "customer_456"]) {
    const expected = Buffer.from("Basic " + Buffer.from(`${id}:${password}`).toString("base64"));
    if (supplied.length === expected.length && timingSafeEqual(supplied, expected)) return id;
  }
}
Bun.serve({
  hostname: "127.0.0.1", port: 4242,
  async fetch(request) {
    const url = new URL(request.url);
    if (url.origin !== origin) return new Response("Use localhost:4242", { status: 403 });
    const userId = authenticate(request);
    if (!userId) return new Response("Sign in", {
      status: 401, headers: { ...noStore, "WWW-Authenticate": 'Basic realm="Local demo"' },
    });
    if (request.method === "GET" && url.pathname === "/") {
      return new Response(Bun.file("index.html"), { headers: noStore });
    }
    if (request.method !== "POST" || url.pathname !== "/api/praxis-hosted") {
      return new Response("Not found", { status: 404 });
    }
    if (request.headers.get("Origin") !== origin) {
      return new Response("Origin rejected", { status: 403 });
    }
    try {
      const response = await fetch("https://api-develop.deepraxis.com/agent/v1/embed_tokens", {
        method: "POST",
        headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
        body: JSON.stringify({ agent: agentId, external_user_id: userId, hosted: true }),
        signal: AbortSignal.timeout(30_000),
      });
      if (!response.ok) return new Response("Token issuance failed", {
        status: response.status, headers: noStore,
      });
      const data = await response.json();
      if (typeof data.hosted_url !== "string") throw new Error("Missing URL");
      return new Response(null, {
        status: 302, headers: { ...noStore, Location: data.hosted_url },
      });
    } catch {
      return new Response("Token service unavailable", { status: 502, headers: noStore });
    }
  },
});
console.log(origin);

hosted: true 让签发响应额外包含 hosted_url。成功签发的 HTTP 状态为 201;宿主返回 HTTP 302,并把该 URL 放进 Location,浏览器随即进入对话页。宿主不把 API Key 或签发响应中的 token 发给表单页面。

把实际智能体 ID、API Key 和你设置的本地测试密码放入服务端环境。下一节保存页面后,从同目录启动:

export PRAXIS_AGENT_ID="agent_REPLACE_ME"
# Set PRAXIS_API_KEY and DEMO_PASSWORD in this server process, then run:
bun server.ts

期望:终端显示本地地址。未经认证或来源不匹配的请求无法签发。用于已有应用时,将测试账户验证换成你的登录会话验证,把路由置于登录与 CSRF 保护之后;不要把这套测试账户机制用于线上登录。

添加打开按钮

保存为同目录的 index.html。普通表单提交可以直接跟随后端的跳转;不要通过后台 fetch 请求后就假设浏览器已经导航。

<!doctype html>
<html lang="zh">
<meta charset="utf-8">
<title>Order support</title>
<form action="/api/praxis-hosted" method="post">
  <button type="submit">Open order assistant</button>
</form>
</html>

打开 http://localhost:4242,以 customer_123 和所设测试密码登录,然后点按钮。期望:浏览器进入 chat-develop.praxisusercontent.com 下的对话页。每次点击都向宿主重新请求链接,不缓存上一次的结果。浏览器地址变化后,再检查是否出现可输入的对话区。

理解链接寿命

hosted_url 的 #c=… 是一次性兑换凭证,不是嵌入令牌。它在签发后 60 秒内有效,只能成功兑换一次。不要记录、收藏或分享带凭证的链接;每个用户应从宿主验证身份后进入。

托管页读取凭证后先移除 URL 片段,再自动向 POST /agent/v1/embed/exchange 提交 code,取得 token 和 expires_at。你的宿主不需要手动兑换,否则浏览器拿到的链接已被消费。

链接的 60 秒期限与对话令牌有效期是两件事。兑换成功后,页面在令牌到期前使用该令牌;Hosted 不自动刷新令牌。到期或收到吊销错误后,页面会提示回到原网站重新进入。刷新当前托管页也不能重新使用已经移除的一次性凭证,应回宿主重新点击按钮。

验证接入

在服务端可以用下面的请求检查签发形状。它只输出记录 ID、到期时间和是否存在链接,不输出凭证;这是独立的接口检查,不会替你导航浏览器。测试用户 ID 只用于这次受控检查,应用必须使用已验证身份。

set -o pipefail
curl --fail-with-body --silent --show-error \
  -X POST https://api-develop.deepraxis.com/agent/v1/embed_tokens \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{"agent":"agent_REPLACE_ME","external_user_id":"customer_123","hosted":true}' \
  | jq '{id, expires_at, has_hosted_url: (.hosted_url | type == "string")}'

再从按钮执行完整路径:网络面板应看到宿主 POST 返回 302,随后托管页兑换成功并显示对话。发送一条订单问题,确认收到真实回复;只看到加载界面不算成功。

用独立浏览器以 customer_456 进入,确认无法读取第一个账户的会话。无凭据访问宿主端点应返回 HTTP 401。验收链接寿命时,使用单独的测试签发,让链接超过 60 秒后再打开,期望要求回到宿主重新进入,不在原链接上反复重试。

失败处理与外观

兑换接口对过期、已用和未知凭证均返回 NOT_FOUND(HTTP 404)。这些情况都应重新从宿主进入。缺少短号或嵌入策略会使签发返回 HTTP 400;在控制台补齐前提后再试。出现 HTTP 429 时按 Retry-After 等待,再由宿主签发新链接。

外观在接入与配置 → 接入方式与外观 → iframe保存,Hosted 共享名称、主题和欢迎语等设置。持久化外观目前只能在控制台配置。Hosted 始终全页显示,不使用浮窗启动按钮。控制台的 Hosted → 打开预览用于预览,不替代宿主对真实登录用户的签发。

下一步

配置智能体

修改服务智能体的指令、模型和工具,再选择向终端用户提供哪一个版本。配置保存与嵌入版本选择是两个动作;固定版本后,保存新配置不会自动升级嵌入。

准备智能体

准备一个已有的服务智能体,以及有权管理它的组织账户。打开控制台「服务智能体 → 选择智能体 → 接入与配置 → 配置」。下面的控件名称对应当前中文控制台。

使用 API 时,从创建或查询响应取得公开 ID agent_…。在服务器设置 PRAXIS_API_BASE 为所用环境的 API 源站地址,PRAXIS_API_KEY 为该组织的 API Key;不要把 Key 放入网页。下例还需 Node.js(支持原生 fetch),并将 PRAXIS_AGENT 设置为真实公开 ID。

修改并保存配置

  1. 在「指令」写明角色、回答范围和无法回答时的处理方式。
  2. 在「模型」选择可用模型,例如目录中可用的 deepseek-flash。
  3. 在「工具与授权」启用所需工具,选择「自动执行」或「每次询问」。MCP 服务器在「MCP 与凭据」中配置;工具仍须在服务端核验用户权限。
  4. 检查底部保存栏中的改动分组,点击「保存」。

预期结果: 当前配置的版本号增加;重新打开「指令」能看到保存内容。未保存的修改离开页面时会提示处理。

单独保存嵌入设置

嵌入安全策略、凭据库选择与嵌入版本固定目前只能在控制台配置。

在「配置 → 安全」选择可用运行环境,填写你的网站来源,并设置令牌有效期与预算,然后点击「保存安全设置」。有效期界面单位为分钟,预算单位为美元;不要直接把接口的秒数或美分数填入界面。来源规则见保护嵌入接入。

需要凭据库时,在「MCP 与凭据」勾选后点击「保存凭据库」。预期结果: 两种保存均显示「已保存,不产生新版本」。它们不属于底部保存栏的配置草稿。

选择嵌入版本

进入「接入与配置 → 版本」。点击版本号查看与上一版本的差异,再选择「设为嵌入版本」或旧版本的「回滚到此版本」,确认后检查「嵌入中」标记。需要恢复跟随最新时点击「取消固定」。没有安全策略时,先完成上一步。

签发时显式指定的 version 优先于控制台固定版本;省略时使用固定版本,未固定才取最新版本。已经签发的令牌保持原版本,回滚不会改变它们。验证升级或回滚时,应重新签发令牌并开始新会话。

验证与下一步

进入「试用调试」,选择刚保存的版本,发送一个能区分新旧指令的问题,检查真实回复。随后在宿主网页重新取得令牌,开始新会话,并在「运营与管理 → 会话」核对版本。只看到界面加载不能证明新配置已被使用。

服务智能体目前不支持按消息切换模型、工具模式、语音输入和按终端用户的记忆;「配置 → 开场与外观」仍是占位,外观操作见自定义外观。

下一步:测试嵌入接入 · 智能体 API · Cookbook

嵌入令牌与用户

嵌入令牌让终端用户访问指定智能体及自己名下的会话。登录用户的令牌由宿主后端签发,浏览器消费;本页说明身份、有效期、刷新和单张令牌吊销。

准备同组织的服务智能体、已保存的嵌入策略和服务端 API Key。策略在控制台的接入与配置 → 配置 → 安全保存,目前只能在控制台配置。示例使用 Develop;完整网页接入见嵌入到网页,一次性跳转见 Hosted。

身份与访问范围

宿主后端用自己的登录系统确认用户,再以 external_user_id 传入稳定标识。同一组织、同一智能体、同一标识对应同一个终端用户;它不是组织成员 ID,也不是昵称。标识长 1–128 字符,仅允许字母、数字、下划线、点、冒号、@ 和连字符。

凭证谁使用用途
API Key宿主后端签发、查看和吊销令牌;不交给浏览器
嵌入令牌终端用户客户端访问指定智能体下自己的会话
可公开 key匿名接入页面获取匿名令牌,可写入网页源码
visitor_secret同一匿名访客的浏览器延续访客身份;必须保密

刷新令牌不会改变用户身份。更换登录账户则应移除旧组件,按新身份重新创建;界面中的确认按钮不能代替工具服务端的账户授权。

签发令牌

生产签发由宿主后端调用 POST /agent/v1/embed_tokens。以下是服务端独立检查,测试用户固定为 customer_123;真实宿主必须从验证后的登录会话取值,不接受浏览器自报身份。控制台的试用调试 → 模拟 → 重新签发只用于调试身份。

在服务端设置 PRAXIS_API_KEY,替换实际公开 ID。需要 cURL 和 jq。

set -o pipefail
curl --fail-with-body --silent --show-error \
  -X POST https://api-develop.deepraxis.com/agent/v1/embed_tokens \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{"agent":"agent_REPLACE_ME","external_user_id":"customer_123"}' \
  | jq '{id, expires_at, has_token: (.token | type == "string")}'

期望:HTTP 201,响应含 id、token、expires_at。id 标识本次签发,供后续吊销;token 是浏览器凭证;expires_at 是 ISO 8601 UTC 到期时间。示例只输出脱敏检查结果。每次调用都会新建签发记录,重复请求不幂等。

可选 ttl_seconds 省略时取策略默认值;范围从 60 秒到策略上限与 3600 秒中的较小值。budget_cents 不能超过策略单会话预算上限;allowed_origins 只能取策略已有条目的子集。版本按显式 version、策略固定版本、最新版本的次序选择,签发后固定。完整字段见 API 参考。

刷新与结束访问

Kit 调用 getToken(current) 时,首次传入 null,刷新时传入当前令牌。把以下回调放进快速开始的 api.getToken;宿主端点每次都验证用户仍已登录,再返回新令牌。

async function getToken(current) {
  const response = await fetch("/api/praxis-token", {
    method: "POST", credentials: "same-origin", cache: "no-store",
  });
  if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
  const data = await response.json();
  if (typeof data.token !== "string" || !data.token) throw new Error("Missing token");
  return data.token;
}

回调不需要把当前令牌发回宿主来证明身份。Kit 负责调用时机并在内存中持有令牌;不要把一个固定令牌写死在回调中。期望:刷新后仍访问同一用户的会话。

宿主退出登录时,先结束宿主会话,再移除旧 Kit 元素;移除会关闭它的连接,但不会在服务器吊销已签发的令牌。需要立即禁止原凭证访问时,另行吊销。Hosted 没有这条刷新路径,到期后必须回宿主重新进入。

匿名访客

公开问答不要求登录时,在接入与配置 → 密钥 → 可公开 key → 新建可公开 key创建 key,设置匿名限流和预算,并复制只显示一次的完整值。来源仍在配置 → 安全管理。这些设置目前只能在控制台配置;可公开 key 与服务端 API Key 不能互换。

以下是可独立保存的 index.html。替换 PRAXIS_PUBLISHABLE_KEY 为实际可公开 key,并从允许来源的网站提供此文件。它不需要登录用户的宿主签发端点;不要同时配置 getToken。

<!doctype html>
<html lang="zh">
<meta charset="utf-8">
<title>Public documentation assistant</title>
<script type="module" src="https://kit-develop.praxisusercontent.com/v0.2.8/praxis-kit.js" integrity="sha384-vey+VZZcFIWJq9Fmv/nmLxspKtXRiL8XiDOfg2g2QFvwZi5MA+tkiQvPBtoMS8eU" crossorigin="anonymous"></script>
<praxis-agent style="display:block;height:600px;width:100%"></praxis-agent>
<script type="module">
  await customElements.whenDefined("praxis-agent");
  document.querySelector("praxis-agent").setOptions({
    api: {
      url: "https://api-develop.deepraxis.com",
      publishableKey: "PRAXIS_PUBLISHABLE_KEY",
    },
    agent: { name: "Documentation assistant" },
  });
</script>
</html>

Kit 向 POST /agent/v1/embed_tokens/anonymous 提交 publishable_key,后续带上已有的 visitor_secret。接口不需要 API Key,成功返回 HTTP 200、token、expires_at;仅新访客另收到 visitor_secret。浏览器不能选择 external_user_id、预算、指令或 Hosted 选项。

Kit 将访客凭据保存在本地存储,存储不可用时退回页面内存。不要公开或共享它。期望:同一存储中的访客刷新后延续身份;独立浏览器存储产生独立身份。匿名身份不授予读取实名订单等账户数据的权限。

查看与吊销

只吊销需要停止使用的那张令牌。下面两种路径完成同一任务。

进入该智能体的运营与管理 → 签发记录,在终端用户中填写 customer_123,点筛选。从有效记录行点吊销,核对用户后点确认吊销。期望:记录状态变为已吊销;失败时页面提供重试。

期望:单张吊销返回带 revoked_at 的记录;重复吊销返回原记录。该令牌的事件流收到 session.error,错误码为 EMBED_TOKEN_REVOKED,随后关闭;其他令牌不受影响。用户级批量撤销、封禁和解封见管理终端用户。

错误与下一步

到期后重新验证宿主登录;吊销后停止原访问;来源不允许时修正配置;用户被封禁时交由管理者处理。不要无限重签来绕过拒绝。具体错误码、HTTP 状态和恢复条件见嵌入限制与错误。

传入上下文

把当前课程、选中的订单或用户偏好交给智能体,让回答贴合用户此刻的任务。页面状态用 readables 随消息发送;后端核实的信息用宿主上下文传入。

准备接入

准备一个已能回复的 Kit 页面、服务智能体和嵌入策略。下面的 HTML 由你的宿主网站提供;同源 POST /api/praxis-token 是你按嵌入令牌与用户实现的后端路由,验证登录态后返回 { token },不是 Praxis 的公开接口。示例使用 Develop 的 Kit 和 API,页面来源须已获准接入。

随消息传入页面状态

把下面的页面保存为网站中的 HTML。下拉框变化时更新登记值,发送消息时 Kit 取当前快照。

<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8" />
<title>课程助手</title>
<label>当前课程
  <select id="lesson">
    <option value="第三课:配送规则">第三课:配送规则</option>
    <option value="第四课:退货流程">第四课:退货流程</option>
  </select>
</label>
<button id="clear">停止传入课程</button>
<praxis-agent style="display:block;height:600px"></praxis-agent>
<script type="module">
  import "https://kit-develop.praxisusercontent.com/v1/praxis-kit.js";
  await customElements.whenDefined("praxis-agent");
  const chat = document.querySelector("praxis-agent");
  const lesson = document.querySelector("#lesson");
  const update = () => chat.setReadable("lesson", { title: lesson.value });
  update();
  lesson.addEventListener("change", update);
  document.querySelector("#clear").addEventListener("click", () => {
    chat.removeReadable("lesson");
  });
  chat.addEventListener("praxis.log", event => console.warn(event.detail));
  chat.setOptions({
    api: {
      url: "https://api-develop.deepraxis.com",
      getToken: async () => {
        const response = await fetch("/api/praxis-token", {
          method: "POST", credentials: "same-origin",
        });
        if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
        const { token } = await response.json();
        if (typeof token !== "string" || !token) throw new Error("Missing token");
        return token;
      },
    },
    agent: { name: "课程助手" },
  });
</script>
</html>

预期结果: 询问「我正在看哪一课」,智能体收到第三课的信息;切换课程再发消息,新消息携带第四课。点击「停止传入课程」后,后续消息不再附带该项。

setReadable(name, value) 接受字符串或可 JSON 序列化的值;名称须匹配 [A-Za-z0-9_.:-]{1,64}。同名登记覆盖当前值,removeReadable(name) 删除登记。非法名称、不能序列化的值会抛错,原登记保持不变。

每条消息最多 32 项,整个上下文 JSON 最多 4096 UTF-8 字节。超出时 Kit 从最早登记的项开始丢弃本次快照中的数据,并通过 praxis.log 发出 warn,data.names 列出丢弃项。登记本身保留;缩小值后可再次发送。

页面上下文附在本轮用户消息里,标记为不可信。每轮取新快照,但移除登记不会抹去旧消息中的信息。它不能证明用户身份、账户余额或订单归属。

传入后端核实的信息

在宿主后端签发令牌时传 system_message。下面是服务端请求:替换公开智能体 ID,测试用户 ID 换成宿主登录态确定的 ID;不要把 PRAXIS_API_KEY 或这段命令放进网页。

curl --fail-with-body https://api-develop.deepraxis.com/agent/v1/embed_tokens \
  -X POST \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "agent": "agent_example",
    "external_user_id": "customer_123",
    "system_message": [{"type": "text", "text": "客户偏好中文回复。"}]
  }'

预期结果: 响应为 HTTP 201,包含 token、id 和 expires_at。让宿主路由只把所需的短期凭据返回浏览器,用该令牌创建新会话,用户偏好会进入模型上下文。不要把令牌打印到共享日志。

system_message 是 { type: "text", text } 数组,文本合计最多 8192 UTF-8 字节;使用一个文本块可避免分块分隔符增加体积。它是模型可读信息,不是秘密存储。

已经开始的会话可以由 API Key 持有者通过发送事件追加 system.message;更换令牌不会改写已建立会话的上下文。

选择其他输入

需要传入的内容使用方式
每位用户都相同的角色和语气控制台「配置 → 指令」
只对一张令牌更换整套指令签发时传 agent_override.system;须先在「配置 → 安全」开启「允许宿主覆盖指令」;null 清空指令
只在被问及时读取的页面数据前端工具
订单等需要鉴权或最新查询的数据服务端工具,按终端用户限定数据范围

覆盖未获准时返回 HTTP 400、EMBED_FIELD_NOT_ALLOWED。先修正策略或去掉覆盖字段,再重新签发;重复同一请求不会解除限制。

验证传入结果

打开浏览器 Network,发送消息,检查请求中的 praxis_context.items:应有 name: "lesson" 和当前课程的字符串值。切换课程、删除登记后各发一次,确认请求随之变化;不要只凭模型回答判断是否传入成功。

后端上下文用新签发的令牌新建会话,询问回复语言并核对签发请求中的文本。模型回复用于体验验证;身份和权限仍由宿主后端及工具服务端校验。

下一步:前端工具 · 保护嵌入接入 · 智能体 API。

接入前端工具

让智能体按需读取当前页面,或执行页面上的动作。你在智能体里声明工具,在 Kit 页面实现同名回调;Kit 把结果送回会话,智能体继续回答。

准备接入

准备一个能回复的 Kit 页面、服务智能体的公开 ID(agent_…),以及宿主后端的 API Key。下面以读取课程选择为例,不需要视频、数据库或额外依赖。宿主网站须已有验证登录态的 POST /api/praxis-token 路由,返回 { token },详见嵌入令牌与用户。它是你的路由,不是 Praxis API。

前端工具适合按需读取。每条消息都需要的小事实,使用页面上下文。

在智能体声明工具

将以下文件保存为 register-tool.ts,替换 agent_example,在已注入 PRAXIS_API_KEY 的服务端运行 bun register-tool.ts。它先读取智能体,再通过公开 API 更新 tools,保留其他工具,并用当前 version 防止覆盖并发修改。

// register-tool.ts — run on the server with: bun register-tool.ts
const key = process.env.PRAXIS_API_KEY;
if (!key) throw new Error("Set PRAXIS_API_KEY on the server");
const url = "https://api-develop.deepraxis.com/agent/v1/agents/agent_example";
const headers = { Authorization: `Bearer ${key}`, "Content-Type": "application/json" };
const currentResponse = await fetch(url, { headers });
if (!currentResponse.ok) throw new Error(`Read failed: ${currentResponse.status}`);
const current = await currentResponse.json();
const tool = {
  type: "custom",
  name: "read_selected_lesson",
  description: "Read the selected lesson when the user asks which lesson is open.",
  input_schema: { type: "object", properties: {}, additionalProperties: false },
};
const tools = [...(current.tools ?? []).filter(
  (item: { type: string; name?: string }) =>
    !(item.type === "custom" && item.name === tool.name),
), tool];
const response = await fetch(url, {
  method: "POST", headers,
  body: JSON.stringify({ version: current.version, tools }),
});
if (!response.ok) throw new Error(`Update failed: ${response.status}`);
const saved = await response.json();
console.log({ id: saved.id, version: saved.version });

预期结果: 终端输出智能体 ID 和新版本。让宿主签发新版本的令牌,再开始新会话;已固定旧版本的令牌或已有会话不会自动换到新定义。若嵌入策略固定版本,在控制台更新接入版本,或签发时明确指定新 version。

工具名须匹配 ^[a-zA-Z0-9_-]{1,64}$,不能与其他自定义工具或内置工具重名。input_schema.type 必须是 object;描述要说明何时调用。仅在页面实现函数,不会把工具自动加入智能体。

实现页面回调

将下面的 HTML 放到宿主网站。它使用 Develop 的 Kit 和 API;页面来源需要在嵌入策略中获准。

<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8" />
<title>课程助手</title>
<label>当前课程
  <select id="lesson">
    <option value="第三课:配送规则">第三课:配送规则</option>
    <option value="第四课:退货流程">第四课:退货流程</option>
  </select>
</label>
<output id="tool-status" aria-live="polite"></output>
<praxis-agent style="display:block;height:600px"></praxis-agent>
<script type="module">
  import "https://kit-develop.praxisusercontent.com/v1/praxis-kit.js";
  await customElements.whenDefined("praxis-agent");
  const chat = document.querySelector("praxis-agent");
  const lesson = document.querySelector("#lesson");
  chat.onClientTool = async ({ name }) => {
    if (name !== "read_selected_lesson") throw new Error(`Unknown tool: ${name}`);
    return { title: lesson.value };
  };
  chat.addEventListener("praxis.tool.change", event => {
    const { name, status, isError } = event.detail;
    document.querySelector("#tool-status").textContent =
      `${name}: ${status}${isError ? " (error)" : ""}`;
  });
  chat.addEventListener("praxis.log", event => console.warn(event.detail));
  chat.setOptions({
    api: {
      url: "https://api-develop.deepraxis.com",
      getToken: async () => {
        const response = await fetch("/api/praxis-token", {
          method: "POST", credentials: "same-origin",
        });
        if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
        const { token } = await response.json();
        if (typeof token !== "string" || !token) throw new Error("Missing token");
        return token;
      },
    },
    agent: { name: "课程助手" },
  });
</script>
</html>

onClientTool 是元素的 JavaScript 属性,不能放进 HTML 属性或 setOptions。回调收到 { id, name, input },分别是调用编号、工具名和输入;参数应由你的函数校验。对未知工具名抛错,不要返回成功。

字符串返回值原样发送,其他可序列化值转成 JSON 文本,undefined 转成空字符串。抛错、Promise 拒绝或序列化失败会作为错误结果返回。不要在结果里放其他用户的数据或凭据。

验证调用与恢复

询问「请调用工具告诉我正在看哪一课」。预期结果: 页面状态依次显示 start、end,智能体根据工具返回的课程回答。换课后再次提问,返回当前选择。end 表示服务端已接收结果,不代表整轮回复结束。

现象检查与处理
没有调用确认新会话使用含工具定义的版本,描述明确说明触发情境
返回 not available on this page在调用前给元素赋上 onClientTool
回调失败修正输入校验或页面逻辑;错误结果会交给智能体
praxis.log 报结果提交失败回调不会自动重跑;核对网络及会话状态,勿盲目重做有副作用的动作

服务端最多等待 120 秒;超时记入错误结果后继续。Kit 会认领调用编号,并检查历史结果以避免重复执行,但这不是跨标签页事务保证。会改变业务数据的工具必须由后端鉴权并做幂等处理。

授权与下一步

前端 custom 工具不经过平台的工具确认卡。需要用户确认的页面动作,由你的界面处理;真正的数据权限必须在后端检查,不能依赖浏览器或模型参数。

配置工具授权 · 组件参考 · 更新智能体 API。

配置工具授权

为已启用的工具选择直接执行或每次询问,让用户在操作发生前确认意图。本页以订单 MCP 的查询和退货工具为例;业务权限仍须由工具服务端校验。

准备工具

准备一个已能接入的服务智能体,并在「配置 → MCP 与凭据」连接名为 orders 的 MCP 服务器。示例要求它提供已启用的 list_orders 和 request_return。替换为你实际的服务器和工具名称,不要用示例名称配置不存在的工具。

确认卡不能代替身份认证。公网 MCP 服务必须先认证平台调用方,再按终端用户限定数据范围,见保护嵌入接入。

选择执行策略

策略对普通已启用工具的效果
always_allow不询问,直接执行
always_ask暂停并等待用户确认,允许后执行;拒绝则不执行该调用

目前支持这两档。不要使用 auto:即使某处类型接受它,运行时也会拒绝执行。需要禁止使用一个工具时,关闭该工具;「每次询问」仍允许用户同意调用。

策略不绕过工具自身的强制交互、安全边界或业务检查。前端 custom 工具由你的页面执行,不经过这套确认策略。

设置工具与默认值

在当前控制台依次打开「服务智能体 → 你的智能体 → 接入与配置 → 配置 → 工具与授权」。

  1. 在「授权方式」找到 orders · 默认授权,选择「每次询问」。
  2. 将 list_orders 设为「自动执行」,request_return 设为「每次询问」。逐工具选择项只显示已配置的工具;未显示的工具可使用默认策略,或通过 API 添加逐工具配置。
  3. 保存配置。预期: 当前配置版本增加;原有会话继续使用原版本。

解析顺序为单工具 configs[].permission_policy → 工具集 default_config.permission_policy → 工具默认策略。未设置 MCP 策略时,服务器声明 annotations.readOnlyHint === true 才默认放行,否则默认询问。该提示仅影响缺省,显式配置优先。

内置工具集未设置默认策略时为 always_allow,但工具自身要求的交互仍然存在。对退款、删除和对外发送等动作,明确设置询问,不要仅依赖服务器提示。

验证确认过程

签发使用新版本的嵌入令牌并新建会话;若嵌入策略固定版本,先更新固定版本或在签发时指定新版本。保存智能体不会自动升级已有会话。

先让智能体调用查询工具,再请求一次测试退货。预期: 查询直接返回;退货显示 Kit 确认卡。拒绝后工具服务端不应执行退货;再次提出请求并允许后才执行。以工具服务端记录核实调用是否发生,不要只看智能体的回答。

Kit 自带确认卡,无须另写界面。嵌入用户的允许只针对这次调用,不会保存为永久工具权限。完整测试情境见测试嵌入接入。

下一步

保护工具服务 · 前端工具 · 更新智能体 API。

自定义外观

让聊天的配色、开场和输入区与网站一致。iframe 与 Hosted 使用控制台保存的同一份外观;Kit 组件使用你在网页中传入的选项。

准备接入

先准备能正常回复的服务智能体和嵌入策略。Kit 示例要求宿主网站已提供 POST /api/praxis-token,验证登录态并返回 { token };这是你的同源路由,详见嵌入令牌与用户。示例使用 Develop 地址,页面来源须获准接入。

接入方式外观在哪里配置
iframe / Hosted控制台保存,两个页面共用;刷新嵌入页后生效
Kit 组件网页的 setOptions;控制台的 Kit 编辑器只供预览和生成代码,不保存

保存托管外观

iframe / Hosted 的持久外观目前只能在控制台配置。当前控制台入口为「服务智能体 → 你的智能体 → 接入与配置 → 接入方式与外观 → iframe」。

  1. 在「主题」选择配色、强调色、灰阶、圆角和密度。
  2. 在「开场」填写欢迎语与推荐问题,在「输入区」设置输入提示和页脚。
  3. 点击「保存」。预期: 提示「已保存,嵌入页刷新后生效」。刷新 iframe,或重新打开 Hosted,检查同一组文字和配色。

不要去「配置 → 开场与外观」编辑:该位置目前是提示页。公开外观接口只提供读取,没有公开的保存接口。

设置 Kit 外观

把下面的 HTML 放在宿主网站。它同时设置主题、开场和输入框,并提供一个按钮验证运行中更新。

<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8" />
<title>订单助手</title>
<button id="dark">切换深色</button>
<praxis-agent style="display:block;height:600px"></praxis-agent>
<script type="module">
  import "https://kit-develop.praxisusercontent.com/v1/praxis-kit.js";
  await customElements.whenDefined("praxis-agent");
  const chat = document.querySelector("praxis-agent");
  chat.setOptions({
    api: {
      url: "https://api-develop.deepraxis.com",
      getToken: async () => {
        const response = await fetch("/api/praxis-token", {
          method: "POST", credentials: "same-origin",
        });
        if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
        const { token } = await response.json();
        if (typeof token !== "string" || !token) throw new Error("Missing token");
        return token;
      },
    },
    agent: { name: "订单助手" },
    theme: {
      colorScheme: "light", accent: "#2563eb", grayscale: "cool",
      radius: "lg", density: "normal", fontFamily: "system-ui, sans-serif",
      fontSize: 16,
    },
    header: { title: "订单服务", subtitle: "配送与退货咨询" },
    startScreen: {
      greeting: "今天想了解什么?",
      prompts: [{ label: "配送规则", prompt: "请介绍配送规则。" }],
    },
    composer: { placeholder: "输入订单问题", attachments: { enabled: false } },
    history: { enabled: true },
    footer: "订单信息以商家确认为准。",
  });
  document.querySelector("#dark").addEventListener("click", () => {
    chat.setOptions({ theme: { colorScheme: "dark" } });
  });
</script>
</html>

预期结果: 出现蓝色强调色、欢迎语和「配送规则」按钮;输入框不显示附件入口。点击「切换深色」后颜色切换,当前会话和消息保留。

再次调用 setOptions 会合并嵌套选项,数组整体替换。因此更新 theme.colorScheme 不需要重传令牌回调;更换推荐问题时,传入完整的 startScreen.prompts 数组。

调整主题与文字

选项可选值或用途
theme.colorSchemelight、dark、system;省略为浅色,system 跟随系统配色
theme.accent / theme.surface强调色 / 背景色;使用明确、不透明的 CSS 颜色,例如 #2563eb;不接受 var(...) 或透明色
theme.grayscaleneutral、warm、cool
theme.radiusnone、sm、md、lg、full
theme.densitycompact、normal、spacious
theme.fontFamily / theme.fontSize字体栈 / 正数像素字号;字体须由你的页面自行加载
startScreen.greeting / startScreen.prompts欢迎语 / 最多显示 4 个推荐问题;label 是按钮文字,点击后发送 prompt
composer.placeholder输入框提示
composer.attachments.enabledKit 附件入口开关,默认开启;不在保存的 iframe / Hosted 外观中提供
localezh 或 en;Kit 未指定时先取宿主页语言,再取浏览器语言

保存的外观 JSON 最多 8192 UTF-8 字节;欢迎语 200 字符、推荐问题最多 4 项,每项 label 40 字符、prompt 200 字符,输入提示 80 字符。头像和顶栏图片须使用 HTTPS 地址。只改组件配置时,详细属性边界见组件参考。

验证与下一步

在一个新会话中检查开场文字,点击推荐问题,确认实际发送的是 prompt。在已有消息的会话点击深色按钮,确认消息保留;另开页面检查控制台保存的外观,避免把 Kit 的本地配置误认为已保存。

外观更新不改变智能体指令或工具授权。若保存失败,按字段提示缩短文字、减少推荐问题或修正图片地址后重试。

组件参考 · 保护嵌入接入 · 智能体 API。

保护嵌入接入

把嵌入智能体接到网站时,用宿主登录态确定用户,用来源白名单限制浏览器接入,并在工具服务端检查权限。本页配置这些边界;测试情境与上线验收分别见测试嵌入接入和上线检查清单。

准备与责任

你需要能管理服务智能体的控制台账号、宿主网站的后端,以及修改网站响应头的权限。先完成嵌入策略和令牌签发,再按下面的责任检查接入。

平台边界宿主必须完成的工作
嵌入用户只能访问所属智能体下自己的会话从已验证登录态取得 external_user_id,拒绝浏览器自报的身份
跨用户、跨智能体资源访问返回 404API Key 仅放后端,签发路由验证登录态并防止跨站请求伪造
浏览器来源校验与 iframe 嵌入限制登记实际网站来源,并限制宿主页可以加载的脚本

保管身份与凭据

后端用 PRAXIS_API_KEY 签发短期嵌入令牌,浏览器只接收需要的令牌。每次签发和续期都重新验证宿主登录态;不要让请求体中的 external_user_id 决定用户是谁。不要把 API Key、嵌入令牌放进 URL、HTML 或日志。

Kit 的传输层在内存中持有令牌。匿名接入另有 visitor_secret,Kit 会尝试保存在 localStorage 以延续访客身份,存储不可用时回退到页面内存。publishableKey 可公开,visitor_secret 不可公开;匿名身份不等于你网站的登录账户。

登出时停止签发、移除聊天组件,并由后端撤销相应凭据;清空页面并不等于撤销。封禁终端用户会阻止新签发并撤销该用户现有令牌。Kit 可在到期前重新取令牌,Hosted 不自动续期;详见嵌入令牌与用户。

指令、system_message、页面上下文与工具结果都可能被模型复述。仅传入当前用户有权知道的信息,不要把密钥或其他用户的数据当作上下文传入。

限定页面来源

来源白名单目前只能在控制台配置。在当前控制台打开「服务智能体 → 你的智能体 → 接入与配置 → 配置 → 安全」,点击「添加来源」,填写例如 https://shop.example.com,然后点击「保存安全设置」。预期: 显示「已保存,不产生新版本」。

allowed_origins 最多 50 项,只填来源,不含路径、查询参数或片段。普通来源须为 HTTPS;https://*.example.com 只匹配一级子域,不匹配根域名或多级子域,且不支持端口。「允许 localhost」只用于本地 HTTP 调试,默认关闭。

签发时可以用 allowed_origins 选择策略中的非空子集,条目必须与策略完全一致。不要传空数组来表达「禁止所有来源」:令牌校验会回退读取策略白名单。修改策略也不等于撤销已签发令牌,需要立即收回访问时执行撤销。

来源不允许时返回 HTTP 403、EMBED_ORIGIN_NOT_ALLOWED。先修正来源配置再重新接入。来源检查约束浏览器;没有 Origin 的非浏览器请求不受这项检查保护,所以它不能代替令牌认证。

保护工具访问

MCP 服务器先验证平台调用凭据,再读取 X-Praxis-External-User-Id 作为终端用户身份。平台还会附带 X-Praxis-Agent-Id 和 X-Praxis-Session-Id;没有终端用户的会话不带这些身份头。

你的服务必须拒绝缺少所需身份的请求,每次查询都限定该用户的数据,修改订单前再验证归属和业务状态。不要相信模型传入的用户 ID,也不要仅凭公网请求带了身份头就信任它。通过 MCP 配置的 headers 认证调用方,才能使用平台附带的身份。

工具确认卡帮助确认意图;点击允许不会授予数据访问权。前端工具和页面上下文来自浏览器,不能承担权限校验。

配置宿主页保护

以下是 Develop 宿主页的响应头示例。适用于初始化脚本放在同源 JavaScript 文件中、Kit 从 CDN 加载的页面;按网站所需资源收窄图片等来源。若保留内联初始化脚本,须另为该脚本配置 CSP nonce 或哈希。

Content-Security-Policy: default-src 'self'; script-src 'self' https://kit-develop.praxisusercontent.com; style-src 'self' 'unsafe-inline' https://kit-develop.praxisusercontent.com; connect-src 'self' https://api-develop.deepraxis.com https://kit-develop.praxisusercontent.com; frame-src https://chat-develop.praxisusercontent.com; font-src 'self' https://kit-develop.praxisusercontent.com data:; img-src 'self' https: data: blob:; frame-ancestors 'none'; base-uri 'self';

Kit 主题使用内联样式,因此示例允许内联样式。连接来源要与 api.url 一致。frame-src 允许加载托管 iframe;frame-ancestors 'none' 则禁止其他网站嵌入你的宿主页,两者作用不同。

平台 iframe 的 frame-ancestors 来自来源白名单,空白名单禁止嵌入;Hosted 禁止被嵌入。Kit 使用 open Shadow DOM,只隔离样式,同页脚本可以读取它;不要把不可信脚本与聊天组件放在同一页面。

若使用 CDN 完整性校验,复制控制台生成的实际固定版本地址与 integrity,保留 crossorigin="anonymous"。不要给浮动的 /v1/ 地址配固定哈希,也不要把示意哈希当作发布值。

验证与下一步

保存后从获准的网站重新加载嵌入页,确认能发送消息。用浏览器 Network 检查宿主页响应中的 CSP,Console 中不应出现 Kit 加载或 API 连接被 CSP 阻止的错误。检查签发路由只返回短期凭据,工具日志中的用户范围与登录用户一致。

测试嵌入接入 · 上线检查清单 · 嵌入限制与错误。

管理终端用户

按宿主应用的用户 ID 查找终端用户,并在需要时停止或恢复其嵌入访问。操作范围是当前组织、当前智能体下的这个用户。

准备与选择操作

准备已有嵌入策略的服务智能体、组织管理权限,以及签发时使用的 external_user_id。先用隔离测试用户验证操作;撤销和封禁会中断其正在进行的对话。

目的操作后续访问
结束当前凭证的访问吊销令牌撤销该用户的全部现有令牌;宿主仍可签发新的
持续拒绝用户访问封禁撤销现有令牌,拒绝新签发
恢复签发资格解封允许新签发;已撤销的旧令牌不会恢复

只想撤销一张令牌时,使用令牌管理,不要执行用户级批量撤销。

查找并管理用户

  1. 打开「服务智能体 → 选择智能体 → 运营与管理 → 终端用户」。控件名称对应当前中文控制台。
  2. 在「按终端用户 ID 前缀搜索」输入 ID。列表显示首次出现、最后活跃、会话数、花费与状态。
  3. 点击用户 ID 打开详情;通过「最近会话」检查是否找对了用户。
  4. 在该用户行或详情中点击「吊销令牌」「封禁」或「解封」,核对确认框中的用户 ID,再点击对应确认按钮。

预期结果: 撤销显示「已吊销 N 张」;封禁后状态为「已封禁」;解封后为「正常」。列表中的花费和会话数据不会因这些操作被删除。

验证访问边界

撤销后,用旧令牌再次访问,应被拒绝;宿主重新确认登录身份后可以签发新令牌。封禁后,宿主的签发请求应收到 EMBED_USER_BLOCKED(HTTP 403)。解封后重新签发并发送消息,确认恢复;不要以旧令牌重试成功作为解封标准。

同时用另一个用户访问同一智能体,确认其访问未被本次操作影响。用户身份不能取自浏览器任意提交的 ID,应由宿主后端的登录态确定。

处理失败

查不到用户时,核对组织、公开智能体 ID 和签发时的完整用户 ID;控制台试用调试的数据不会进入客户用户列表。仅有前缀相似不能证明是同一个用户。

若封禁返回失败,重新读取状态后再决定是否重试:封禁状态可能已经保存,而撤销步骤尚未完成。不要在这种情况下显示“封禁完成”。记录 HTTP 状态和请求 ID,按限制与错误处理。

下一步

查看会话与用量 · 测试嵌入接入 · 智能体 API · Cookbook

查看会话与用量

从会话、用户和花费判断嵌入是否正常工作,再打开具体会话检查发生了什么。本页适合已经产生真实宿主访问的服务智能体。

准备检查记录

使用有权管理该智能体的组织账户,打开控制台「服务智能体 → 选择智能体 → 运营与管理」。下文使用当前中文控制台的控件名称。准备要检查的时间、终端用户 ID 和宿主接入方式。

先在宿主网页发送一条可识别的测试消息并收到回复,再查运营数据。控制台「试用调试」的记录不会进入客户运营统计,不能用它证明客户流量或花费已经入表。

查看概览

进入「概览」,选择 24 小时、7 天或 30 天。概览统计目前只能在控制台查看,没有对应的公开聚合 API。

检查内容用它回答什么
会话、活跃终端用户、花费、每会话花费访问量和每次服务的成本是否变化
好评率有评价的回复中,正面评价占比是多少
首字延迟 P50、错误率首次响应是否变慢,运行是否出现失败
来源与版本分布流量来自 Kit、iframe、Hosted、API 还是自动化;新版本是否被使用
需要关注、工具调用 Top 5来源被拦、预算触顶或工具调用是否异常

预期结果: 选择的时间范围内出现会话趋势和版本分布;没有对应样本的指标显示「—」,不是零。24 小时趋势按小时展示,其他范围按天展示。运行错误、来源拦截和工具计数按所选范围覆盖的 UTC 自然日汇总,首尾不足一天时也包含整日计数。

查找与回放会话

  1. 进入「会话」,输入完整终端用户 ID,点击「筛选」;需要时从「来源」选择接入方式。
  2. 点击对应会话,检查回放头部的用户、来源、版本和创建时间。
  3. 对照消息与工具调用,确认测试消息及其回复;点击「关闭回放」收起。

预期结果: 回放只读,不能发送消息。若要停止后续发言,点击「归档」并确认;归档后记录仍可查看,但不能继续发送消息。

检查用量与预算

进入「用量与预算」,查看本月花费、月度上限和触顶动作,再选择 7、30 或 90 天的每日花费。下方展示预算与限流、本月来源拆分及花费最高的终端用户 Top 20。本月按 UTC 自然月计算,界面金额为美元。

这些聚合报表与预算设置目前只能在控制台查看或配置。「修改上限」进入「配置 → 安全」;保存后返回核对。触顶动作为「停止新会话」时会拒绝新建会话;「只告警」不会拦截。「花费构成」尚未提供,不能从占位区域推断费用拆分。

验证与后续处理

用同一用户、会话和时间范围核对宿主消息、回放及来源。若版本与预期不符,先检查令牌签发时的版本;固定或回滚版本不改变已签发令牌。没有数据时先核对环境、组织和时间范围,不要反复创建测试会话来填充统计。

出现来源拦截或预算拒绝时,记录响应中的 X-Request-Id、时间、HTTP 状态和错误码;不要附上 API Key 或令牌。用户级处理见管理终端用户。

下一步:嵌入限制与错误 · 智能体 API · Cookbook

测试嵌入接入

上线前,用真实宿主网页验证正常对话和拒绝路径。下面列的是要执行的验收情境;控制台出现组件或触发 praxis.ready 只证明挂载,不证明认证和模型回复成功。

准备测试环境

准备隔离的服务智能体、可用模型与运行环境、已保存的嵌入策略,以及已经完成接入的宿主页面。将该页面的实际来源加入白名单,设置可承受的预算。使用两个宿主测试账户 A、B,分别由后端登录态得到不同的 external_user_id。

先在「服务智能体 → 选择智能体 → 接入与配置 → 试用调试」选择版本、发送问题,排除配置本身的问题。随后执行下表中的宿主测试;控制台模拟不替代宿主登录、域名和令牌端点测试,也不进入客户运营统计。

按情境验收

输入或操作预期结果与验证方法
A 登录宿主,发送一个课程问题页面出现真实回复;「运营与管理 → 会话」能找到相同消息、用户和来源
未登录时调用你的宿主签发端点宿主拒绝签发,不返回嵌入令牌
从白名单之外的网站加载访问被拒绝;接口可返回 EMBED_ORIGIN_NOT_ALLOWED(403)。iframe 也可能先被浏览器的嵌入策略拦截
签发时提交 ttl_seconds: 59返回 INVALID_PARAMS(400),不产生可用令牌
关闭宿主覆盖指令后提交 agent_override返回 EMBED_FIELD_NOT_ALLOWED(400);不能靠前端输入改变可信指令
用户被封禁后由宿主再次签发返回 EMBED_USER_BLOCKED(403)
达到同时运行的会话上限后再建会话返回 EMBED_RATE_LIMITED(429);等待运行结束后再试,不靠删除空闲记录释放名额
月度上限触顶,动作为「停止新会话」新建会话返回 EMBED_MONTHLY_CAP_REACHED(402);不要在真实用户智能体上制造这项条件
页面声明了前端工具工具被调用时有预期页面效果和结果;未知工具、参数无效或无业务权限时失败,不能只看确认卡

可用下面的独立脚本验证有效期下限。在服务器设置 PRAXIS_API_BASE(API 源站)、PRAXIS_API_KEY(组织 API Key)和 PRAXIS_AGENT(真实 agent_… ID)。用支持原生 fetch 的 Node.js 保存并执行 node test-token-limit.mjs。智能体必须已配置好策略,否则会先因缺少配置失败。

import assert from 'node:assert/strict';

const { PRAXIS_API_BASE, PRAXIS_API_KEY, PRAXIS_AGENT } = process.env;
assert(PRAXIS_API_BASE && PRAXIS_API_KEY && PRAXIS_AGENT, 'Set the three environment variables');
assert(PRAXIS_AGENT.startsWith('agent_'), 'Use the public agent ID');
const response = await fetch(`${PRAXIS_API_BASE.replace(/\/$/, '')}/agent/v1/embed_tokens`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${PRAXIS_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ agent: PRAXIS_AGENT, external_user_id: 'docs_test_a', ttl_seconds: 59 }),
});
assert.equal(response.status, 400, 'Invalid TTL must be rejected');
console.log({ status: response.status, requestId: response.headers.get('X-Request-Id') });

预期结果: 脚本正常退出并输出 status: 400;这里只验证拒绝状态,错误体应另核对 INVALID_PARAMS 和有效期字段提示。脚本不打印凭证,也不替代成功签发和真实回复测试。

验证身份与生命周期

  1. A 开始会话后退出,销毁 A 的组件。B 登录并新建组件,检查历史里没有 A 的会话;用 B 的令牌读取 A 的会话也必须被拒绝。只清空界面不等于切换身份。
  2. A 保持登录跨过一次令牌刷新,确认 getToken(current) 重新检查宿主身份,后续消息仍属于 A。单独用已过期令牌请求,应得到 EMBED_TOKEN_EXPIRED(401)。
  3. 在「运营与管理 → 签发记录」撤销 A 当前令牌,确认旧访问失败,组件不自动绕过撤销继续刷新。用户级批量撤销、封禁与解封按管理终端用户验证。
  4. 匿名接入用独立浏览器存储测试两位访客,再刷新同一访客页面检查身份延续;不要把 visitor_secret 分享给另一位访客。
  5. Hosted 接入分别测试兑换链接超过 60 秒、重复兑换,以及已打开页面的令牌到期;前两者被拒绝,后者不会自动刷新,应从宿主重新获取链接。

排错与清理

记录每项的时间、环境、组件版本、预期与实际结果,以及失败响应的 X-Request-Id、状态和错误码。错误事件可能只有 message;从浏览器网络面板补齐响应信息,记录中不含 Key、令牌或访客凭据。

完成后撤销本轮测试令牌,归档不再使用的测试会话,恢复临时修改的限额和封禁状态。归档不会删除记录。再用正常配置执行一次成功对话,确认清理没有留下访问障碍。

下一步:上线检查清单 · 限制与错误 · 智能体 API

上线检查清单

在嵌入首次对外开放,或更换域名、身份接入方式、组件版本后执行本清单。先完成测试嵌入接入,每项保存可复查的结果,再开放访问。

确认上线条件

  • 配置与版本一致。 控制台「接入与配置 → 版本」显示预期嵌入版本;宿主新签发令牌产生的会话使用该版本。若签发请求显式传 version,确认它没有覆盖你的版本选择。
  • 身份来自宿主登录态。 未登录请求无法从宿主取得令牌;A、B 两个测试用户互相看不到会话。API Key 仅在服务器,浏览器源码和响应中没有 PRAXIS_API_KEY 的真实值。
  • 匿名凭据使用正确。 若启用匿名访问,页面只公开 publishable key,已设置相应限额;不同浏览器存储的访客相互隔离,visitor_secret 不被分享。
  • 目标来源已验证。 用实际 HTTPS 域名加载并完成回复;白名单外来源失败。iframe 在目标页面可显示;Hosted 使用独立页面,不能嵌入 iframe。上线配置关闭不再需要的 localhost 访问。
  • 组件交付可重复。 已记录并验证使用的 Kit 版本、脚本地址及适用的完整性值;从空缓存加载成功。页面能发送消息并收到真实回复,而非仅收到 praxis.ready。
  • 拒绝路径可恢复。 到期、撤销、封禁和来源拒绝均已测试。宿主退出后销毁旧组件;Hosted 到期后能从宿主获取新链接,不循环使用失效链接。
  • 工具只执行允许的操作。 已测试有效输入、无效输入和无业务权限的用户;「每次询问」不会替代工具服务端鉴权。
  • 预算与监控可用。 核对美元预算、有效期、请求与活跃会话上限;月度触顶动作符合业务预期。在「运营与管理」能找到宿主测试会话、来源和用量。

准备回滚与支持

保留已验证的宿主部署和组件版本。配置异常时,在「版本」选择旧版本并点击「回滚到此版本」,重新签发后验证;已签发令牌不变。需要立即停止某个用户访问时,使用封禁或批量撤销,并确认旧令牌已失败。

为支持人员保存故障时间、环境、公开智能体 ID、会话 ID、HTTP 状态、错误码与 X-Request-Id,不保存令牌或 API Key。按限制与错误选择恢复动作,再从宿主完成一次对话验证。

相关参考:配置智能体 · 组件参考 · 智能体 API

组件参考

<praxis-agent> 在网页中呈现智能体对话。入口为 element.setOptions(options);本页按属性、方法、事件排列,完整接入步骤见嵌入到网页。

初始化

首次设置必须包含 api.url、agent.name,以及 api.getToken 或 api.publishableKey。缺少时抛出 TypeError。登录用户选择 getToken,匿名访客选择 publishableKey,请二选一;同时提供时使用 publishable key,不调用 getToken。

下面是 Develop 的匿名页面示例。先在该环境创建可公开 key、保存嵌入策略并允许宿主来源,再替换 PRAXIS_PUBLISHABLE_KEY,从该来源提供 HTML。公开 key 不是服务器 API Key。脚本地址、版本和完整性值须使用同一发布版本。

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Course assistant</title>
<script type="module"
  src="https://kit-develop.praxisusercontent.com/v0.2.8/praxis-kit.js"
  integrity="sha384-vey+VZZcFIWJq9Fmv/nmLxspKtXRiL8XiDOfg2g2QFvwZi5MA+tkiQvPBtoMS8eU"
  crossorigin="anonymous"></script>
<praxis-agent style="display:block;height:600px;width:100%"></praxis-agent>
<p id="status" role="status"></p>
<script type="module">
  await customElements.whenDefined('praxis-agent');
  const agent = document.querySelector('praxis-agent');
  const status = document.querySelector('#status');
  agent.addEventListener('praxis.ready', () => { status.textContent = 'Interface mounted'; });
  agent.addEventListener('praxis.error', () => { status.textContent = 'Check the failed network request'; });
  agent.setOptions({
    api: {
      url: 'https://api-develop.deepraxis.com',
      publishableKey: 'PRAXIS_PUBLISHABLE_KEY',
    },
    agent: { name: 'Course assistant' },
    locale: 'en',
  });
</script>
</html>

预期结果: 页面显示聊天界面,发送问题后收到真实回复。Interface mounted 只表示挂载;验证还要检查回复和用户隔离。

属性

api 与 agent

属性类型语义
api.urlstring必填,API 源站或以 /agent/v1 结尾的基础地址;初始化后不可变
api.getToken(current: string | null) => Promise<string>返回嵌入令牌;首次 current 为 null,刷新时传当前令牌。后端每次重新确认宿主登录态
api.publishableKeystring匿名令牌入口,替代 getToken;初始化后不可变。访客身份由组件保存的私有 visitor_secret 延续
agent.namestring必填,界面显示名称,不用于选择智能体
agent.avatarstring头像 URL;实际访问的智能体由凭证决定
onClientTool(call: { id, name, input }) => unknown | Promise<unknown>元素属性,不放入 setOptions。处理前端工具,未知工具应抛错

布局与内容

属性类型 / 默认值效果
layoutinline、sidebar、popup、modal;默认 inline对话容器形态
widthnumber;400 CSS 像素sidebar 宽度
launcher.typenone、bubble、mascot提供 launcher 时必填;默认 inline 为 none,其他布局为 bubble
launcher.image、launcher.labelstring启动按钮图片与文字
launcher.positionbottom-right、bottom-left启动按钮位置,默认右下
header.title、header.subtitlestring标题与副标题;标题默认使用 agent.name
header.iconstring支持的 Lucide 图标名或图片 URL
header.actions{ id: string; icon: string; label: string }[]自定义顶栏按钮,点击发出事件
history.enabledboolean;默认 true历史入口;关闭时 showHistory() 不生效
startScreen.greetingstring空会话问候语
startScreen.prompts{ label: string; prompt: string }[]显示前 4 个;点击发送对应 prompt
composer.placeholderstring默认随语言显示输入提示
composer.attachments.enabledboolean;默认 true是否允许选择、粘贴或拖入附件
disclaimer、footerstring对话说明与底部文字
autoScrollboolean新内容到达时是否自动滚动
localezh、en未指定时依次取宿主页面语言、浏览器语言;以 zh 开头用中文,否则英文;无语言信息用英文

theme

属性值 / 限制
colorSchemelight、dark、system;未设置时为浅色,system 跟随浏览器
accent、surface明确且不透明的 CSS 颜色;拒绝 var()、currentcolor、透明色,抛 TypeError
grayscaleneutral、warm、cool
radiusnone、sm、md、lg、full;基础圆角依次为 0、4、10、18、9999 CSS 像素
densitycompact、normal、spacious;基础间距依次为 3、4、5 CSS 像素
fontFamily非空字体族字符串,不能含 ; { } < >,否则抛 TypeError
fontSize正的有限数字,单位 CSS 像素;否则抛 RangeError

未设置 accent 时,浅色默认 #167A43,深色默认 #30a46c。颜色前景按对比度选择。

React 属性

React 的 PraxisAgent 使用以下属性。当前 React 绑定仍要求顶层 getToken,即使 options.api 的类型包含 publishableKey;匿名接入可直接使用上面的 Web Component。

属性 / 返回对象语义
getToken必填,与 api.getToken 相同的函数签名
options上述选项,但不含 api.getToken;省略时呈现未配置元素
layout、launcher分别覆盖 options.layout、options.launcher.type;launcher 取 none/bubble/mascot
className、style、children宿主元素样式与 React 子元素
onClientTool前端工具回调;与元素属性同义
ref / usePraxisAgent()ref 提供控制句柄;hook 返回稳定的 { ref, control }

方法

方法返回 / 行为
setOptions(partial)初始化或合并选项
sendUserMessage(text)Promise<"accepted" | "rejected" | "unknown">;空白、超长、等待审批或无法提交时可拒绝;accepted 不代表回复成功,unknown 时先检查会话,不要盲目重发
setComposerValue(text)替换草稿,不发送
focusComposer()聚焦输入框;历史面板打开时不生效
setThreadId(id)切换会话并关闭历史;null 回到空会话,不切换用户身份
showHistory()、hideHistory()打开、关闭历史面板
open()、close()、toggle()展开、收起或切换面板;inline 下无效果
setReadable(name, value)注册页面上下文;名称 1–64 字符,仅字母、数字与 _ . : -;值为字符串或可 JSON 序列化值
removeReadable(name)删除该页面上下文值

React 控制句柄包含上表从 sendUserMessage 到 toggle 的方法。onReady 前或卸载后调用会抛出未就绪错误,不会排队。React 页面上下文与工具用 usePraxisReadable、usePraxisTool,需要共享注册范围时使用 PraxisProvider,见下方指南。

事件

事件均为可冒泡、可穿过 Shadow DOM 的 CustomEvent。React 回调收到同一个事件对象,通过 event.detail 读取内容。

事件 / React 回调detail 与时机
praxis.ready / onReady无;界面已挂载,不代表认证或网络成功
praxis.error / onErrorKitError(code、status、retryable、message)或仅 { message };先检查字段是否存在
praxis.thread.change / onThreadChange{ threadId: string | null };当前会话变化
praxis.response.start / onResponseStart无;开始忙碌处理
praxis.response.end / onResponseEnd无;结束忙碌处理,不是成功断言
praxis.open、praxis.close / onOpen、onClose无;面板展开、收起
praxis.history.open、praxis.history.close / onHistoryOpen、onHistoryClose无;历史面板打开、关闭
praxis.header.action / onHeaderAction{ id: string };点击顶栏自定义按钮
praxis.tool.change / onToolChange{ id, name, status: "start" | "end", isError? };前端工具执行变化
praxis.log / onLog{ level: "info" | "warn" | "error", message, data? };上下文裁剪或工具诊断
praxis.frame.close无;请求宿主关闭 iframe;React 无对应回调属性

默认行为与生命周期

setOptions 递归合并对象、整体替换数组;显式 undefined 重置字段。api.url 和 api.publishableKey 初始化后不能改,变更会抛 TypeError;换后端或用户时销毁旧元素并重新创建。移除元素会释放连接与刷新计时器,不会撤销已签发令牌。

页面上下文快照最多 32 项、4096 UTF-8 字节,超出时从最早注册项开始省略并发出 log。Kit 的这些运行时选项与控制台保存的 iframe/Hosted 外观不是同一个配置入口,持久化外观有自己的字段与大小限制。

使用指南

嵌入令牌与用户 · 传入上下文 · 接入前端工具 · 自定义外观

嵌入限制与错误

本页适用于嵌入令牌、Kit、iframe 与 Hosted。表中的「来源」区分固定校验、控制台策略和组件行为;策略值以该智能体保存的配置为准。

限制

项目值与单位来源
external_user_id1–128 字符;仅英文字母、数字及 _ . : @ -签发校验
ttl_seconds60–3600 秒,同时不超过策略 ttl_max_seconds;省略用策略默认值签发校验与策略
system_message所有 text 块文本合计 ≤8192 UTF-8 字节签发校验
metadata≤16 个键;键 1–64 字符,字符串值 ≤512 字符签发校验
budget_cents0 至策略会话预算上限,单位美分;省略用策略默认值签发校验与策略
签发 allowed_origins只能取策略来源条目的子集,逐项精确匹配签发校验
策略来源≤50 条 HTTPS 来源;通配只匹配一级子域且不含端口嵌入策略
allow_localhost默认 false;开启后允许 HTTP localhost / 127.0.0.1(可带端口)嵌入策略
max_active_sessions_per_user1–100 个活跃会话/用户;空闲历史不计入嵌入策略
per_user_rate_per_min1–600 次/分钟/用户嵌入策略
blocked_end_users≤1000 个用户 ID嵌入策略
monthly_cap_cents非负整数美分;null 不限;按 UTC 自然月嵌入策略与月度统计
页面上下文≤32 项,完整 JSON ≤4096 UTF-8 字节接口校验;Kit 超出时裁剪最早项
会话标题 / 初始事件标题 ≤500 字符;初始事件 ≤50 条纯文本用户消息嵌入会话创建校验
附件选择器每条消息最多 5 个;每个 ≤10 MiB(10,485,760 字节);PNG、JPEG、GIF、WebP、PDF、TXT、MDKit 输入校验;新会话首条消息仍只接受纯文本
startScreen.prompts显示前 4 个Kit 组件
Hosted 兑换凭证60 秒内使用,仅能兑换 1 次Hosted 兑换校验

月度触顶动作 stop_new_sessions 拒绝新会话,alert_only 不拦截。控制台金额输入为美元,API 的预算字段为美分。会话预算耗尽后不能继续提交新的工作;保留工具确认和中断入口。

服务端错误

下列状态用于普通 HTTP 失败响应。事件流已经建立后,错误通过流内事件到达,不能再更改已发送的 HTTP 状态。「可重试」指原请求;修复配置或换取合法凭证后属于新的尝试。

EMBED_TOKEN_EXPIRED

401 · 访问阶段 · 不可直接重试。 令牌到期。可刷新 Kit 会重新取令牌,并对到期响应最多重放一次请求;Hosted 不刷新,须从宿主获取新链接。

EMBED_TOKEN_REVOKED

401 · 访问/事件流 · 不可重试。 令牌已撤销,Kit 停止刷新。结束旧访问,由宿主重新核验是否仍允许该用户进入;不要自动重签绕过撤销。

EMBED_ORIGIN_NOT_ALLOWED

403 · 来源检查 · 不可重试。 当前来源不被允许。核对实际 scheme、host、port,以及策略和签发时收紧的来源列表;修改后重新签发再验证。

EMBED_USER_BLOCKED

403 · 签发/新建会话 · 不可重试。 用户已封禁。由管理者在「运营与管理 → 终端用户」核查;确需恢复时解封并签发新令牌。

EMBED_RATE_LIMITED

429 · 请求/新建会话/匿名签发 · 可延后重试。 请求频率、活跃会话数或匿名每日会话额度触顶。分别等待限流窗口恢复、运行结束或下个 UTC 日;不能统一立即重试。

EMBED_BUDGET_EXHAUSTED

402 · 会话工作/匿名签发 · 不可重试。 会话预算或匿名每日花费用尽。由管理者核对预算;获准后以新令牌、新会话继续,或等待每日额度恢复,不循环重签规避上限。

EMBED_MONTHLY_CAP_REACHED

402 · 新建会话 · 不可重试。 月度上限已到且策略要求停止新会话。由管理者决定是否调整上限或触顶动作,或等到下个 UTC 月。

EMBED_FIELD_NOT_ALLOWED

400 · 签发/新建会话 · 不可重试。 使用了不允许的字段或初始事件。移除未获准的 agent_override;嵌入会话创建仅传 title、time_zone、initial_events,初始事件为纯文本 user.message(可带页面上下文)。

其他拒绝

码HTTP / 阶段恢复动作可重试
INVALID_PARAMS400 / 参数校验按字段提示修正类型、范围或大小否
UNAUTHENTICATED401 / 鉴权检查凭证是否缺失、失效;公开 key 是否停用否
NOT_FOUND404 / 资源或 Hosted 兑换核对组织、公开 ID、策略;失效或已用的 Hosted 链接需重新签发否
UPSTREAM_UNAVAILABLE502 / 上游调用延后重试;管理写操作先核对是否部分完成是

会话权限、归档和事件内容错误见智能体限制与错误。

Kit 错误与重试

praxis.error 的 detail 可能是含 code、status、retryable、message 的 KitError,也可能只有 { message }。status: 0 表示没有 HTTP 响应。

码状态 / 阶段恢复动作与可重试性
network_error、stream_disconnected0 / 网络或事件流恢复连接后可重试
http_error响应状态 / HTTP检查原始响应;无明确 retryable 时 429、5xx 可重试
invalid_token0 / 取令牌getToken 返回空值;修复返回值,可重试
invalid_response响应状态 / JSON 或历史检查返回格式、分页是否前进;不可自动重试
invalid_stream响应状态 / 事件流检查 SSE 内容与 JSON;不可自动重试
event_cursor_not_found200 / 历史恢复重新加载历史定位会话;不可自动重试
callback_error0 / 回调修复宿主异常;不可重试
aborted、disposed0 / 生命周期请求取消或组件已销毁;不重试旧实例

非 Hosted 取令牌仅对可重试失败按 1、2、4 秒退避,最多重试 3 次;撤销或预算拒绝停止该重试。传输层自行恢复事件流时最多重连 5 次;界面可能接管恢复,不应另叠无限重试。

诊断入口

保留失败时间、环境、公开 agent_… ID、HTTP 状态、错误码和 X-Request-Id,不记录凭证。在「运营与管理 → 签发记录」核对令牌状态,再用会话回放定位发生阶段。修复后按测试嵌入接入复测;字段契约见智能体 API。

智能体限制与错误

本页用于排查智能体配置和会话 API。嵌入令牌、来源、匿名配额与前端组件另见嵌入限制与错误;组织余额与配额见用量与费用。

请求与运行限制

项目值单位与含义
智能体名称 name1–256字符
指令 system100,000字符上限
智能体 / 会话 metadata16 / 64 / 512键值对 / 键字符数 / 值字符数
会话标题 title500字符上限
初始输入 initial_events50事件上限
会话与事件列表 limit1–100; 20范围;默认条数
会话预算 budget2000默认美分(20 美元)
会话时区 time_zoneUTC默认;传入时为 IANA 名称
自定义工具32 / 16 KiB数量上限 / 单个 input_schema 的 UTF-8 JSON 上限

预算对象使用 type: "limit",max_list_cost.currency: "USD",max_list_cost.amount 为正整数字符串,单位是美分;null 移除预算。它控制后续模型请求,不是费用预测。字符串长度按 JavaScript 字符串长度校验,非所有字符都占一个单位;工具 schema 的大小则按 UTF-8 字节计算。

按错误处理

原生 API 的错误体包含 error.type、error.message 和 request_id。服务端已分类的错误还可提供 error.code 与 error.retryable;请求解析阶段可能只有类型和消息。下列代码以实际响应为准,不能假设每个相同 HTTP 状态都表示同一个原因。

INVALID_PARAMS

HTTP 400。请求字段、格式或不支持的选项不合法。按错误消息修正请求,原样重试不会解决。

UNAUTHENTICATED

HTTP 401。凭证缺失或无效。确认当前环境和组织的 Key,重新认证后再请求。

SESSION_FORBIDDEN

HTTP 403。当前身份无权访问该会话。核对所属用户与组织,不自动重试,也不要通过换 ID 探测其他用户。

AGENT_NOT_FOUND

HTTP 404。智能体或版本不存在,或对当前身份不可见。使用创建响应中的 ID,并确认版本。

SESSION_NOT_FOUND

HTTP 404。会话不存在或不可见。核对返回的会话 ID 与调用身份,不能靠重试创建响应来恢复。

MODEL_NOT_ALLOWED

HTTP 400。选择的模型不在固定版本允许的模型集合中。使用允许的模型,或更新配置后创建新会话。

SESSION_CONFLICT

HTTP 409。会话状态与当前操作冲突。重新读取会话或待确认工具,再决定是否提交;不要重复提交已完成的工具结果。

SESSION_ARCHIVED

HTTP 409。会话已归档,不能继续发送消息。保留历史用于查询,新对话创建新会话。

INVALID_STATE

HTTP 409。当前状态不允许操作,例如删除仍在运行的会话。先中断或等到空闲、终止,再核对状态后操作。

INVALID_PAGE_TOKEN

HTTP 400。分页游标无效或不匹配当前查询。重新从第一页开始;后续原样传回 next_page,并保持筛选条件。

UPSTREAM_UNAVAILABLE

HTTP 502。上游暂时不可用,可在退避后重试。对创建或发消息先查询已有结果,避免重复产生任务。

SANDBOX_UNAVAILABLE

HTTP 503。运行环境暂时不可用,可在退避后重试。持续失败时记录请求标识并联系支持。

何时重试

error.retryable: false 时先处理原因。收到 HTTP 429 时读取 Retry-After,不要无间隔循环;临时上游或环境错误可以退避后重试。网络断开不能证明请求未执行,创建资源和发送消息前应先查询已知会话及事件。

事件流已经开始后,运行失败可能通过事件返回,不能只看建立连接时的 HTTP 成功状态。检查回复、工具待确认和会话状态;不要把接收消息成功等同于任务完成。

记录诊断信息

保留发生时间、环境、智能体与会话 ID、HTTP 状态、错误类型与代码,以及 X-Request-Id;若响应仅提供 request-id 或 request_id,同时记录该值。不要记录 Key、嵌入令牌或访客凭据。

随后查看会话与用量,对照 API 参考检查请求。需要复现时,从快速开始的最小请求缩小范围。

自动化

自动化把平台能力组合成脚本或工作流,由你主动运行,或由触发器自动启动。

对象用途
脚本一段 TypeScript 从头执行到尾,返回一个结果。
工作流YAML 描述的多步骤流程,保存检查点,支持等待和暂停。
动作有输入输出约定的可复用能力,由脚本或工作流调用。
运行一次脚本或工作流的执行,记录输入、输出、状态和调用。
触发器在指定时间或收到 webhook 时启动一个已发布的脚本或工作流。

选择入口

一次计算、批量处理或组合 API 调用,用脚本;跨时间等待、需要步骤检查点的任务,用工作流。把多个编排都会使用的能力封装成动作。动作没有独立运行入口,在脚本或工作流里调用它。

脚本失败后重跑会重新执行整段代码;已发生的效果不会回滚。需要自动执行时,在已发布目标上配置触发器。

先运行一次

在 Develop Console 创建 API Key,设置为 PRAXIS_API_KEY。在装有 curl 和 jq 的 Bash 中执行,使用 Bearer 认证完成一次加一计算,不调用模型:

export PRAXIS_API_URL="https://api-develop.deepraxis.ai"
: "${PRAXIS_API_KEY:?Set PRAXIS_API_KEY to your develop API Key}"
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"source":"export default async (input: { n: number }) => ({ answer: input.n + 1 });","language":"ts","input":{"n":41},"wait":60}' \
  | jq '{id,status,output,error}'

期望:status 为 completed,output 为 {"answer":42}。若仍在排队或运行,将返回的 id 存为 RUN_ID,再读取结果。继续按快速开始保存和发布脚本。

下一步

API 参考 → · Cookbook →

快速开始

本教程使用 develop、Bash、curl 和 jq。所有命令在同一个 Bash 会话中按顺序执行;不需要安装 SDK。脚本只做加法,不调用计费能力。

1. 创建 API Key

打开 Develop Console → API Keys,创建 Key,并将其保存到环境变量 PRAXIS_API_KEY。Key 的管理说明见平台管理。

export PRAXIS_API_URL="https://api-develop.deepraxis.ai"
: "${PRAXIS_API_KEY:?Set PRAXIS_API_KEY to your develop API Key}"
command -v curl
command -v jq
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs?limit=1" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" | jq -e '.data | type == "array"'

期望:两个命令都能找到,最后输出 true,退出码为 0;这同时验证 Key 能访问自动化运行列表。

2. 临时运行脚本

创建运行接受源码和输入。wait 表示本次 HTTP 请求等待运行终态的最长秒数,范围 0–60,默认 0。wait: 60 最多等待 60 秒,运行提前结束就提前返回;等待结束不会取消运行。HTTP 200 表示已到终态(也可能失败),202 表示仍在排队或执行,两者都要检查 status。

SOURCE='export default async (input: { n: number }) => ({ answer: input.n + 1 });'
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg source "$SOURCE" \
    '{source:$source,language:"ts",input:{n:41},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
jq -e '.id | startswith("run_")' <<< "$RUN"

期望:输出 true,RUN_ID 是新运行的 ID。源码接收 n: 41,返回 answer: 42。

HTTP 202 的响应仍是 Run 对象。下面是字段节选(id 以实际响应为准):

{
  "id": "run_...",
  "status": "running",
  "output": null,
  "error": null
}

保留 RUN_ID,按下一节 GET /automation/v1/runs/$RUN_ID,直到 completed、faulted 或 cancelled。不要因为等待结束就重复创建运行。

3. 读结果

用读取运行检查终态。下面的函数在约 3 分钟内轮询;脚本失败时打印真实错误并返回非零退出码。

read_result() {
  local result
  for attempt in $(seq 1 90); do
    result=$(curl --fail-with-body -sS \
      "$PRAXIS_API_URL/automation/v1/runs/$1" \
      -H "Authorization: Bearer $PRAXIS_API_KEY") || return 1
    case "$(jq -r '.status' <<< "$result")" in
      completed) jq -e '.output.answer == 42' <<< "$result"; return $? ;;
      faulted|cancelled) jq '{status,error}' <<< "$result"; return 1 ;;
    esac
    sleep 2
  done
  echo 'Still pending: retrieve the run again using its ID' >&2
  return 1
}
read_result "$RUN_ID"

期望:输出 true 且退出码为 0,证明运行已 completed,输出确实为 42。若仍未完成,保留 RUN_ID,再次读取;不要因为客户端等待结束就重复创建运行。

4. 保存为脚本并发布

把同一份源码保存为脚本草稿,读取当前 draft.revision 后发布版本。每次教程使用不同的名称。

命名空间由你自行取名,例如自己的项目名。下面的 docs-samples 只是示例;修改 NAMESPACE 后,后续请求会使用同一个命名空间。

NAMESPACE="docs-samples"
SCRIPT_NAME="hello-$(date +%s)-$RANDOM"
SCRIPT=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/scripts" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg namespace "$NAMESPACE" --arg name "$SCRIPT_NAME" --arg source "$SOURCE" \
    '{namespace:$namespace,name:$name,draft:$source}')")
jq -e '.id | startswith("scr_")' <<< "$SCRIPT"
DRAFT=$(curl --fail-with-body -sS \
  "$PRAXIS_API_URL/automation/v1/scripts/$NAMESPACE/$SCRIPT_NAME" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" | jq -er '.draft.revision')
curl --fail-with-body -sS \
  "$PRAXIS_API_URL/automation/v1/scripts/$NAMESPACE/$SCRIPT_NAME/versions" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg revision "$DRAFT" \
    '{version:"1.0.0",expected_draft_revision:$revision}')" \
  | jq -e '.version == "1.0.0"'

期望:两次检查均输出 true;版本 1.0.0 已发布。若返回 PRECONDITION_FAILED,报错原文「草稿已修改」,重新读取草稿,确认内容后用最新 revision 发布。

5. 按版本运行

TARGET="$NAMESPACE/$SCRIPT_NAME@1.0.0"
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg target "$TARGET" \
    '{target:$target,input:{n:41},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
read_result "$RUN_ID"

期望:再次输出 true,结果仍为 42。保留 TARGET,下一步可以给它添加触发器。用完后可归档脚本;归档会保留名称和历史。

API 参考 → · Cookbook →

概念

脚本和工作流描述要做什么;运行记录某一次执行;触发器决定何时执行。动作是执行期间调用的可复用能力。

定义、版本与标签

脚本与工作流共用 namespace/name,一个名字只能属于其中一种。草稿可以修改;发布的版本不可变,版本号不能复用。同版本同内容再次发布会返回已有版本。弃用版本保留历史,不等于删除。

版本和 dist-tag 遵循 npm 的引用方式:docs-samples/hello@1.0.0 固定版本,@^1.2.0、@~1.2、@1.x 选择范围,@latest 跟随标签。省略 @ 等价于 @latest。每次运行记录实际解析的版本。

正式版发布会按版本选择规则更新 latest;发布较低版本不会自动把它退回。预发布版本需显式指定标签,例如 beta。stable、beta 等标签可以移动;标签名不能是版本号或版本范围;latest 不能删除。移动标签影响之后解析它的运行,不改写已发布版本。

用发布版本管理不可变内容,用设置标签把标签指向已有版本。写标签不需要 If-Match。修改草稿则需要最新 ETag,发布草稿需要 expected_draft_revision。

动作没有语义版本号:兼容更新立即生效,调用记录保存实际修订。输入输出不兼容时使用新名字,避免破坏既有调用方。工作流发布时锁定脚本和子工作流依赖的精确版本,动作不锁定。

运行生命周期

状态含义
pending已受理,等待开始。
running正在执行。
waiting工作流正在等待事件、定时器或子运行。
suspended工作流被暂停。
completed成功结束,读取 output。
faulted执行失败,读取 error。
cancelled已取消。

脚本没有 waiting 和 suspended。取消请求尚未结束时,运行可能仍为 running,并带 cancel_requested_at。超时是 faulted,错误类型为 timeout,不是另一个状态。终态不会恢复;重跑创建新的根运行,rerun_of 指向原运行。

HTTP 成功表示请求被处理,不代表脚本成功。分别检查 status、output 和 error。脚本可查看调用与日志,工作流可查看步骤与等待点。

子运行与来源

脚本可以通过 praxis.automation.runs.create 启动子运行。子运行的 initiator 始终保留最初发起者(人、API Key 或触发器);父子关系由 parent_run_id 表达。按来源筛选子运行用 channel=run,按指定父运行查找用 parent_run_id 过滤;公开详情中的 parent 提供父运行摘要,root_run_id 指向根运行。

父运行被取消时向下级联;父正常结束时子运行可以继续。子运行 deadline 不会超出父运行剩余时间。同一目标在调用链上再次出现会被循环检测拒绝;深度和扇出上限见限制。

幂等与重试

创建运行接受 Idempotency-Key。作用域为同一 owner、HTTP 方法、路径和 key,保留 24 小时;请求指纹不包含 wait。网络重试应复用原 key 和请求内容;重放返回当前运行,并带 Idempotent-Replayed: true。有意再跑一次时使用新 key。

同 key 不同请求返回 IDEMPOTENCY_KEY_MISMATCH;尚在处理时返回 IDEMPOTENCY_IN_FLIGHT,详见创建运行的错误表。幂等不提供事务回滚,失败前已经完成的调用可能产生了效果。

费用归因

运行本身不单独收费。运行中调用模型、搜索等能力产生费用,按这些能力的原价计费,并归因到运行与触发器上显示。没有能力调用的纯计算脚本不会产生模型或搜索费用。

API 参考 → · Cookbook →

编写脚本

脚本默认导出一个接收输入的函数;返回值成为运行的 output。

执行本页前,先在同一个 Bash 会话完成快速开始第 1–3 步:设置 PRAXIS_API_URL 与 PRAXIS_API_KEY、定义 SOURCE 并临时运行、定义并调用 read_result 辅助动作。该动作检查结果是否为 42,与本页的加一示例对应。

入口与 schema

把下面的内容保存为 script.ts:

export default async (input: { n: number }): Promise<{ answer: number }> => {
  console.log("Adding one", input.n);
  return { answer: input.n + 1 };
};

输入 schema 从参数类型推导;输出 schema 从显式返回类型推导,Promise 会展开。支持基本类型、对象、数组、联合类型及可解析的本地类型别名。不能推导的类型会得到空 schema 和警告;不要把空 schema 当成已经验证了字段。省略返回类型时,不应依赖返回表达式自动推导出输出约束。

通过校验定义检查实际生成的 schema,再发布:

curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/validate" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source script.ts '{kind:"script",source:$source}')" \
  | jq -e '.valid == true and .input_schema.properties.n.type == "number" and .output_schema.properties.answer.type == "number"'

期望:输出 true,退出码 0。读取完整校验响应中的 errors 与 warnings 可以定位不能推导的类型。

运行与日志

RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source script.ts \
    '{source:$source,language:"ts",input:{n:41},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
read_result "$RUN_ID"
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs/$RUN_ID/logs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

期望:结果断言为 true;日志响应包含 Adding one 41。console.log/info/debug 记录为 info,console.warn 和 console.error 保留各自级别。日志不是返回值;需要交给调用方的数据必须 return。可通过日志接口读取,或使用 Accept: text/event-stream 订阅。

全局 praxis

运行时已经提供 praxis,无需 import、API Key 或安装包。它提供以下 SDK 入口;方法参数采用参考页中的 path、query、body 形状,例如 praxis.automation.runs.create({body:{target,input}})。能否调用仍取决于发起者权限。

入口能力参考
praxis.agentAgent
praxis.modelModel
praxis.fileFile
praxis.sandboxSandbox
praxis.searchSearch
praxis.databaseDatabase
praxis.automationAutomation
praxis.organizationOrganization
praxis.metaMeta

脚本不能直接访问外部网络。用平台提供的能力完成操作,并 await 调用以取得结果及处理异常。

调用平台内置动作

call("命名空间/名字", 输入) 接收 Action 引用和输入对象,返回 Action 的输出。它是当前运行里的一个步骤:有自己的输入、输出,并在调用记录里占一条,不会产生子运行。praxis 是平台命名空间;省略命名空间的裸名也只解析到平台 Action。调用总使用 Action 的当前修订,暂不支持 @版本;不再接受 命名空间::名字 写法。

调用失败时抛出错误,带 code、message、hint、调用记录序号 seq 和 request_id。多个 call 可以用 Promise.all 并发。praxis.* 只表示平台 SDK,call(...) 和 praxis.ocr(...) 这类写法已删除,运行时会报错并提示改用 call。

把下面内容保存为 chat.ts;它调用 praxis/chat。模型由动作内置并由维护者选择,调用时不选模型;praxis/ocr、praxis/image、praxis/embed 也一样。旧 model 字段已废弃,传入的值会被忽略。此调用会产生模型费用:

export default async (input: { prompt: string }) => {
  return await call("praxis/chat", {
    messages: [{ role: "user", content: input.prompt }],
    stream: false,
  });
};
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --rawfile source chat.ts \
    '{source:$source,language:"ts",input:{prompt:"Say hello in one sentence."},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs/$RUN_ID" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" | jq '{id,status,output,error}'

期望:终态为 completed,output 为模型结果,error 为 null。若仍是 pending 或 running,重复最后一条 GET,直到终态;faulted 时读取错误,不能把 HTTP 成功当成模型成功。动作输入输出的完整约定可用读取动作查看 praxis/chat。

调用自建动作

以下示例创建动作,再用它的完整名字调用。生成的脚本源码中引用是字符串字面量,便于发布前校验。

ACTION_NAME="add-one-$(date +%s)-$RANDOM"
curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/actions" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg name "$ACTION_NAME" '{
    namespace:"docs-samples",name:$name,
    input_schema:{type:"object",properties:{n:{type:"number"}},required:["n"],additionalProperties:false},
    output_schema:{type:"object",properties:{answer:{type:"number"}},required:["answer"],additionalProperties:false},
    implementation:{kind:"code",language:"ts",source:"export default async (input: { n: number }) => ({ answer: input.n + 1 });"}
  }')" | jq -e '.id | startswith("act_")'
SOURCE="export default async (input: { n: number }) => await call(\"docs-samples/$ACTION_NAME\", input);"
RUN=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/runs" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg source "$SOURCE" \
    '{source:$source,language:"ts",input:{n:41},wait:60}')")
RUN_ID=$(jq -er '.id' <<< "$RUN")
read_result "$RUN_ID"

期望:动作创建与运行结果均输出 true,结果为 42。用完后可归档动作。动作可复用,但不能在动作内部再编排其他动作、脚本或工作流。

调用另一个脚本或工作流

invoke("命名空间/名字@版本", 输入) 把另一个已发布的脚本或工作流作为子运行启动,等它结束后返回子运行的输出;省略 @版本 用最新版。和 call 的区别:call 是当前运行里的一个步骤,不产生子运行;invoke 产生一个有自己 ID、状态和日志的子运行,并在调用记录里用 child_run_id 指向它。

export default async (input: { urls: string[] }) => {
  // 多个 invoke 用 Promise.all 并发;子运行经队列启动,不受单次请求链长度限制。
  const pages = await Promise.all(input.urls.map(url => invoke("team/fetch-page", { url })));
  return { pages };
};
  • 等待有上限:invoke 同步等待子运行结束,等待时间受父运行 deadline 限制。到期时抛出 INVOKE_TIMEOUT,消息里带子运行 ID;子运行不会因此被取消,可以用该 ID 另行读取结果。需要等很久的子运行,请在工作流里用 run: 步骤——工作流在等待期间不占用计算。
  • 失败:子运行失败、被取消或无法创建时抛出错误,code 是子运行的错误码,message 带子运行 ID。目标不存在或已归档时立即失败,不会排队。
  • 取消与超时:父运行被取消或失败时,仍在等待中的子运行会一起被取消。
  • 查看子运行:列出运行时用 parent_run_id 筛选,得到每个子运行的 ID、目标、状态和耗时。
  • 限制:每个运行最多 100 个子运行,同时执行的最多 20 个,多出的排队等待;嵌套最多 10 层,不允许自己调用自己。

脚本不能自行指定独立运行(detach)的子运行;需要“启动后不等结果”时,调用 创建运行。

返回值和时间预算

返回 JSON 可序列化的值;不要返回 undefined、BigInt 或循环引用。序列化后的 UTF-8 返回值最多 1 MiB,超过会使运行失败。大结果应保存到文件等存储中,返回引用。

脚本墙钟时间默认最多 5 分钟;CPU 时间最多 30 秒,并受剩余 deadline 进一步约束。等待平台调用与纯计算的耗时不同,增加等待时长不能解决死循环。全部数值见限制与错误。

有依据的错误与修法

code报错原文修法
SCRIPT_ERROR脚本执行出错阅读 detail 中的原始异常及用户代码行号,修正后创建新运行。
CPU_LIMIT_EXCEEDED脚本 CPU 时间超出上限终止无限循环,缩小每批纯计算量。
PAYLOAD_TOO_LARGEPayload exceeds 1 MiB缩小 JSON 返回值,返回存储引用。
INPUT_INVALIDValue must be JSON serializable返回明确的 JSON 值,如对象或 null。

运行失败看 Run.error;创建请求被拒绝看 HTTP problem 响应。两者不能混用。

API 参考 → · Cookbook →

触发器

触发器把“何时执行”与脚本、工作流的内容分开。

执行本页前,先在同一个 Bash 会话完成快速开始第 1–3 步:设置 PRAXIS_API_URL 与 PRAXIS_API_KEY、定义 SOURCE 并临时运行、定义并调用 read_result 辅助函数。该函数检查结果是否为 42,与本页的加一示例对应。再完成第 4–5 步:保存并发布脚本、设置 TARGET。TARGET 必须指向这个接收 n、返回 answer 的已发布脚本。

定时执行

每个 schedule 只能选择一种规则:

规则用途
cron: "0 9 * * *"每天当地时间 09:00;五段,分钟精度,支持 L、W、#。
every: "PT5M"每隔五分钟;最短一分钟,按经过的时长计算。
atRFC 3339 绝对时间,必须带时区偏移或 Z;不能早于现在或晚于一年后。

timezone 使用 IANA 名称,API 默认 UTC。当地时间 cron 在夏令时春季跳过不存在的时刻,秋季重复的时刻只运行一次。every 按时长、at 按绝对时间执行,不使用这条 cron 夏令时规则。

先预览下次时间:

curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/triggers/preview" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"schedule":{"cron":"0 9 * * *"},"timezone":"Asia/Shanghai"}' \
  | jq -e '{next_fire_times} | select(.next_fire_times | length == 5)'

期望:输出五个未来时间,例如:

{
  "next_fire_times": [
    "2026-10-13T01:00:00.000Z",
    "2026-10-14T01:00:00.000Z",
    "2026-10-15T01:00:00.000Z",
    "2026-10-16T01:00:00.000Z",
    "2026-10-17T01:00:00.000Z"
  ]
}

日期随预览时间变化。末尾的 Z 表示 UTC。例如 2026-10-13T01:00:00.000Z 换算为上海时间(UTC+8)10 月 13 日 09:00。timezone 决定 cron 按哪个当地时区计算,返回的时间统一用 UTC 表达。

再创建触发器,并立即触发一次检查目标:

TRIGGER=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/triggers" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg target "$TARGET" '{
    name:"daily-hello",target:$target,kind:"schedule",input:{n:41},
    schedule:{cron:"0 9 * * *"},timezone:"Asia/Shanghai",
    overlap:"skip",catchup_window_seconds:300
  }')")
SCHEDULE_ID=$(jq -er '.id' <<< "$TRIGGER")
jq -e '.state == "enabled" and (.next_fire_times | length == 5)' <<< "$TRIGGER"
FIRING=$(curl --fail-with-body -sS -X POST \
  "$PRAXIS_API_URL/automation/v1/triggers/$SCHEDULE_ID/fire" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' --data '{}')
jq -e '.outcome == "started"' <<< "$FIRING"
read_result "$(jq -er '.run_id' <<< "$FIRING")"

期望:三次断言输出 true。started 只表示启动成功;read_result 用返回的 run_id 读取运行,确认终态成功且结果为 42。

target 写 @1.0.0 固定版本,写 @latest 跟随标签;每条触发记录保存实际版本。创建和修改时检查静态输入,触发时再次检查,避免新版本不再接受原输入。

重叠、延迟与有效时间

默认 overlap: "skip":上一次运行未结束时,本次记录为 skipped_overlap;allow 允许重叠启动,仍受运行并发限制。没有排队补齐、取消旧运行或历史 backfill 策略。

catchup_window_seconds 默认 300。调度延迟超过窗口记为 missed,不会启动;这不是恢复后重放所有错过任务的承诺。暂停期间不执行,恢复后继续未来调度。

用 start_at 与 end_at 设置有效时间范围(RFC 3339)。一次性 at 执行后、或达到结束时间后变为 expired。连续 5 次触发失败会自动暂停。通过暂停和恢复控制后续触发。

暂停上面创建的触发器,再恢复:

curl --fail-with-body -sS -X POST \
  "$PRAXIS_API_URL/automation/v1/triggers/$SCHEDULE_ID/pause" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' --data '{}' \
  | jq -e '.state == "paused" and (.next_fire_times | length == 0)'
curl --fail-with-body -sS -X POST \
  "$PRAXIS_API_URL/automation/v1/triggers/$SCHEDULE_ID/resume" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' --data '{}' \
  | jq -e '.state == "enabled" and (.next_fire_times | length == 5)'

期望:两次请求都输出 true;恢复后返回这个每日计划的下五次执行时间。

工作流 YAML 中的 cron/every schedule 在发布为 latest 时管理一个 source=definition 触发器;修改要重新发布定义,不能直接 PATCH 这个触发器。可以暂停或恢复它。schedule.after 与 schedule.on 暂不支持。

Webhook

创建时选 input_mode: "body",收到的 JSON 就是运行输入;非 JSON 请求体作为字符串。以下示例把 n:41 传给快速开始的脚本:

HOOK=$(curl --fail-with-body -sS "$PRAXIS_API_URL/automation/v1/triggers" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --arg target "$TARGET" '{
    name:"webhook-hello",target:$target,kind:"webhook",webhook:{input_mode:"body"}
  }')")
WEBHOOK_ID=$(jq -er '.id' <<< "$HOOK")
WEBHOOK_URL=$(jq -er '.webhook_url' <<< "$HOOK")
curl --fail-with-body -sS "$WEBHOOK_URL" \
  -H 'Content-Type: application/json' \
  -H "webhook-id: hello-$(date +%s)-$RANDOM" \
  --data '{"n":41}' | jq -e '.id | startswith("frg_")'

期望:HTTP 202,断言输出 true,响应含触发记录 ID。异步启动结果需要再查触发历史。

完整 URL 只在创建或轮换时返回;其中的 token 是凭据,无需 API Key,不要公开。旧 URL 在轮换后失效。错误 token、暂停或不存在的触发器都返回 404。请求体最多 1 MiB;相同 webhook-id 在 24 小时内去重。

签名头与 envelope

当前 webhook 入口验证 secret URL,不自动验证 Standard Webhooks、GitHub 或 Stripe 的 HMAC 签名。传入 webhook-signature 不代表它已经通过验签。

若目标需要读取头部,创建时使用 webhook: {input_mode:"envelope"},并让目标的输入 schema 接受下列形状(这不是快速开始的 n 输入):

{
  "body": { "n": 41 },
  "headers": { "content-type": "application/json", "webhook-id": "delivery-1" },
  "webhook_id": "delivery-1",
  "received_at": "2026-10-11T12:00:00.000Z"
}

webhook_id 未提供时为 null。只保留以下头:content-type、user-agent、webhook-id、webhook-timestamp、webhook-signature、x-github-event、x-github-delivery、x-hub-signature-256、stripe-signature;Authorization、Cookie 不会转交。

JSON body 会先被解析,envelope 不包含独立的原始请求字节。对于必须签署原始字节的协议,不能用 JSON.stringify(body) 代替原文验签。需要这种验证时,在你接收原始请求的服务中按发送方协议先验证,再转发到 secret URL。当前没有可直接套用到此 JSON envelope 的通用验签示例。

查看触发记录与清理

curl --fail-with-body -sS \
  "$PRAXIS_API_URL/automation/v1/triggers/$WEBHOOK_ID/firings" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  | jq -e '.data | length > 0'
curl --fail-with-body -sS \
  "$PRAXIS_API_URL/automation/v1/triggers/$WEBHOOK_ID/firings" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  | jq '.data[] | {outcome,run_id,resolved_version,error}'

期望:第一个请求输出 true,第二个列出记录。若异步处理尚未完成,稍后重新读取。started 表示已启动,failed 看 error,missed 表示超过窗口,skipped_overlap 表示前次未结束,deduped 表示命中去重。按 run_id 继续查看运行终态,记录保留 30 天。

测试完成后删除本页创建的两个触发器,避免继续定时执行:

for id in "$SCHEDULE_ID" "$WEBHOOK_ID"; do
  curl --fail-with-body -sS -X DELETE \
    "$PRAXIS_API_URL/automation/v1/triggers/$id" \
    -H "Authorization: Bearer $PRAXIS_API_KEY"
done

期望:curl 均退出 0。删除不影响已经启动的运行。

API 参考 → · Cookbook →

限制与错误

以下默认值约束单次运行及其子运行;调用方设置的更短 deadline 也约束后续执行。并发超限时排队,不会增加执行容量。

执行与数据

限制数值
嵌套深度10
每运行子运行总数 / 同时活跃子运行100 / 20
运行树总节点1,000
每 owner / 每定义并发运行100 / 20
动作 / 沙箱动作超时30 s / 10 min
脚本墙钟超时5 min
工作流默认 / 最大超时24 h / 7 days
创建运行 wait 上限60 s
载荷软阈值 / 硬上限256 KiB / 1 MiB
调用载荷内联阈值 / 检查点内联阈值32 KiB / 256 KiB
每运行任务执行数(含循环与重试)4,000
每运行持久执行步骤预算9,000
每 fork 分支 / 嵌套并发效果10 / 25
幂等保留时间24 h
运行日志保留期30 days
每页记录数100
标签数量 / key 长度 / value 长度50 / 40 / 500 characters
描述长度1,000 characters
SSE 心跳间隔20 s

载荷软阈值不是拒绝边界;硬上限为 1 MiB,按 JSON 编码后的 UTF-8 字节计算。内联阈值决定数据的存储方式,不代表脚本可以返回超过硬上限的结果。

脚本运行时

限制数值
CPU 时间最多 30 秒,并取剩余 deadline 中较小者
每脚本连接器请求1,000 次,包含日志和进度
同时执行的连接器操作20
单条日志4,096 UTF-8 字节,超出截断
调用摘要512 字符
脚本错误 detail1,000 字符
保留的用户堆栈帧最多 5 条

脚本输出必须可序列化为 JSON,编码失败会使运行失败。脚本没有检查点或事务回滚;具体报错原文与修法见编写脚本。

触发器

限制数值
每 owner 启用中的触发器100
every 最短间隔1 分钟
cron 精度五段,分钟
一次性 at 时间范围当前至一年后
默认补跑窗口300 秒
预览未来时间最多 5 个
连续触发失败后自动暂停5 次
webhook 请求体1 MiB
webhook-id 去重24 小时
触发记录保留期30 天

找到对应错误

HTTP 错误表示请求被拒绝;运行状态 faulted 表示执行失败,即使读取它的 HTTP 响应为 200。排查时保存运行 ID 与响应头 X-Request-Id。

例如 INTERVAL_TOO_SHORT 的报错原文为 every must be at least one minute,把间隔改为 PT1M 或更长;不要原样重试无效调度。

API 参考 → · Cookbook →

模型

统一接口访问 LLM、图像、语音、Embedding 模型;兼容 OpenAI 与 Anthropic 协议,现有 SDK 零改动接入。

出口

API 参考 → · Cookbook →

快速开始

用一个云 API Key 调用 deepseek-flash,读取实付费用,再找到同一次调用的记录。示例会产生少量费用。

1. 准备 Key 与终端

在控制台 → API Keys点击「创建 Key」,填写名称并创建,将 Key 安全注入当前终端的 PRAXIS_API_KEY 环境变量。不要写入代码或日志。已有 Key 可直接使用。

准备 Python 3.10+ 和 uv,在 macOS / Linux 终端执行:

test -n "${PRAXIS_API_KEY:-}"

期望:退出码 0;退出码 1 表示变量未设置,先设置再继续。示例默认使用 https://api.deepraxis.com;切换环境时,将 PRAXIS_API_BASE 设为对应 API 域名,不带末尾斜杠或 API 路径。

2. 发出请求并读取费用

保存为 quickstart.py,在该文件所在目录执行 uv run -q --no-project --with openai --with anthropic --with google-genai python quickstart.py。

import json
import os
from pathlib import Path
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["PRAXIS_API_KEY"],
    base_url=os.environ.get("PRAXIS_API_BASE", "https://api.deepraxis.com") + "/openai/v1",
    timeout=90.0,
    max_retries=0,
)
raw = client.chat.completions.with_raw_response.create(
    model="deepseek-flash",
    messages=[{"role": "user", "content": "Reply with exactly: Hello Praxis."}],
    max_tokens=256,
)
reply = raw.parse()
usage = reply.usage.model_dump()
request_id = raw.headers.get("x-request-id")
generation_id = raw.headers.get("x-praxis-generation-id")
assert raw.status_code == 200 and reply.choices[0].message.content
assert request_id and generation_id
assert isinstance(usage.get("cost"), (int, float)) and usage["cost"] >= 0
print(reply.choices[0].message.content)
print("usage:", usage)
print("X-Request-Id:", request_id)
print("X-Praxis-Generation-Id:", generation_id)
Path("quickstart-result.json").write_text(json.dumps({
    "request_id": request_id, "generation_id": generation_id, "cost": usage["cost"],
}))

期望:退出码 0,输出非空回答(通常为 Hello Praxis.)、usage 和两个 ID,并生成 quickstart-result.json。该文件只保存本次调用的 ID 与费用,不含 Key。

usage 包含输入和输出 token 数;usage.cost 是本次实付美元金额,不是每百万 token 的单价。不要把缺失费用当成零。字段定义见 Chat Completions API。

3. 找到这次调用

打开控制台 → 模型 → 调用日志。在「按 X-Request-Id / Generation ID 查找」输入框粘贴上一步的 X-Request-Id,按 Enter。此路径与控件名核对于 2026-10-13 的 Console。

期望:出现模型为 deepseek-flash 的本次调用,可查看状态、用量与费用。记录可能延迟几秒;使用与 Key 相同的账号及环境,并确保时间范围包含刚才的调用。

不使用浏览器时,可用下面的等价硬验证。保存为 verify-generation.py,在同一目录执行 uv run -q --no-project --with openai --with anthropic --with google-genai python verify-generation.py。

import json
import os
import time
from decimal import Decimal
from pathlib import Path
import httpx

saved = json.loads(Path("quickstart-result.json").read_text())
base = os.environ.get("PRAXIS_API_BASE", "https://api.deepraxis.com")
with httpx.Client(timeout=60.0) as client:
    for attempt in range(10):
        response = client.get(
            base + "/model/v1/generation",
            params={"id": saved["generation_id"]},
            headers={"Authorization": "Bearer " + os.environ["PRAXIS_API_KEY"]},
        )
        if response.status_code != 404:
            break
        time.sleep(3)
    response.raise_for_status()
    record = response.json()
    assert record["id"] == saved["generation_id"]
    assert record["model"] == "deepseek-flash"
    assert abs(Decimal(str(record["cost"])) - Decimal(str(saved["cost"]))) <= Decimal("0.0000005")
    print("Verified generation:", record["id"])
    print("cost:", record["cost"])
    print("X-Request-Id:", response.headers.get("x-request-id"))

期望:退出码 0,输出 Verified generation 和按美元六位小数记录的费用;脚本允许相应舍入差异。查询使用 X-Praxis-Generation-Id,不是 X-Request-Id 或响应正文的 id;详见生成记录 API。

失败分支

  • 404 查询记录:记录尚未可查时,脚本每 3 秒重试,最多 10 次;仍失败则保留两个 ID,检查 Key、环境及生成 ID 是否来自同一次调用,再查询。
  • Model "gpt-4o" does not exist.:模型 ID 不自动映射;使用本页的 deepseek-flash 或目录中的 ID。

接下来可使用流式响应或工具调用。

API 参考 → · Cookbook →

模型目录

模型与价格会变化,请从控制台 → 模型目录或实时 API 读取,不依赖静态清单。

1. 准备查询

准备 Python 3.10+ 与 uv。原生目录 GET /model/v1/models 无需 Key;下面同时查询 OpenAI 兼容目录,需要按快速开始设置 PRAXIS_API_KEY。

期望:已有 Key 环境变量。示例默认使用 https://api.deepraxis.com;切换环境时,设置 PRAXIS_API_BASE 为 API 域名,不带末尾斜杠或 API 路径。

2. 核对模型与协议

保存为 catalog.py,执行 uv run -q --no-project --with openai --with anthropic --with google-genai python catalog.py。

import os
import httpx

base = os.environ.get("PRAXIS_API_BASE", "https://api.deepraxis.com")
with httpx.Client(timeout=60.0) as client:
    response = client.get(base + "/model/v1/models")
    response.raise_for_status()
    models = response.json()["data"]
    model = next(item for item in models if item["id"] == "deepseek-flash")
    assert "openai.chat.completions" in model["supported_apis"]
    for field in ("id", "type", "supported_apis", "context_window", "pricing"):
        print(field + ":", model[field])
    print("X-Request-Id:", response.headers.get("x-request-id"))

    filtered = client.get(
        base + "/openai/v1/models",
        params={"api": "responses"},
        headers={"Authorization": "Bearer " + os.environ["PRAXIS_API_KEY"]},
    )
    filtered.raise_for_status()
    ids = [item["id"] for item in filtered.json()["data"]]
    assert "deepseek-flash" in ids
    print("Responses models:", ids)
    print("X-Request-Id:", filtered.headers.get("x-request-id"))

期望:退出码 0,打印 deepseek-flash 的类别、协议、上下文和价格,以及包含该 ID 的 Responses 列表。示例核对于 2026-10-13;以查询时的结果为准。若断言失败,不要向所选协议发送请求,先从最新列表选择支持它的模型。

3. 根据结果选择

先按 type 找任务类别,再用 supported_apis 确认要调用的协议;不能从模型名称推断协议。原生列表还可用 supported_api=openai.responses 筛选;OpenAI 兼容列表的 api=chat,embeddings 表示匹配任一协议。

再核对上下文和输出限制,以及输入、输出、缓存或非 token 计量的单价。模型与目录概念解释计费单位及可用率;列出模型 API提供字段定义。

期望:所选 ID 支持应用需要的协议,限制能容纳请求,价格单位与任务一致。最后按快速开始发起真实调用并核对实付费用。

API 参考 → · Cookbook →

概念

一次模型调用由模型 ID 指定要用的模型,由协议决定请求和响应的格式;平台在符合要求的线路中完成调用。

  • 模型与目录:区分模型类别、协议支持与计费单位。
  • 协议兼容:理解同一个地址下不同协议的适用范围。
  • 路由:理解线路选择、失败换线与参数要求。
  • 会话亲和:理解重复使用同一线路与缓存的关系。
  • BYOK:查看自带凭证的开放状态。

第一次调用从快速开始进入;选择模型从模型目录进入。

API 参考 → · Cookbook →

模型与目录

模型目录描述可以选择什么;一次调用的记录描述实际用了多少、付了多少。两者用途不同。

模型 ID 与能力

请求中的 model 使用目录返回的完整 id,例如 deepseek-flash。显示名称不是 ID;相同 ID 也不表示底层版本永远不变,滚动版本应以模型说明为准。

type 区分语言、图像、语音、视频、向量等模型类别,不能据此推断它支持所有相关接口。supported_apis 才是已开放协议的列表;选择 Chat、Responses 或 Messages 前分别核对它。输入和输出模态说明能处理什么内容,不等于所有文件格式都能原生读取。

上下文窗口限制一次输入与生成可容纳的内容,不是每次必然能输出的长度。原生目录的字段为 context_window;OpenAI 兼容目录使用 context_length。输出上限需另看模型说明与 API 参考。

标价与实付

目录 pricing 的金额为美元,单位由价格项决定。token 单价以每个 token 表达,展示为每百万 token 时乘以一百万;输入与输出分别定价。input_cache_read 是缓存命中时的输入读价,不保证每次都会命中。

并非所有模型按 token 计费:视频可按秒、图像可按张、OCR 可按页、转写可按音频分钟。价格项缺失不表示免费,不要把所有数值都乘以一百万。具体价格项与字段定义见列出模型 API。

Chat 响应的 usage.cost 是本次实付美元金额;用目录单价乘以 token 数只是估算,可能忽略缓存或其他计量。需要核对实际调用时,使用快速开始中的生成记录查询。

可用率

可用率衡量平台与上游能否完成有效请求:平台或上游原因的失败计入,调用方参数等原因造成的拒绝不计入。它不表示任意参数都能成功,也不保证下一次请求必然成功。

模型目录说明如何获取当前目录;本页不维护静态模型与价格清单。

API 参考 → · Cookbook →

路由

你选择模型,平台从支持该模型及所用协议的线路中选择一次调用的去向。同一个模型可以有多条线路;线路数量与状态会变化。

从请求到线路

平台先检查模型、协议和参数要求,再结合健康状态与优先级选择可用线路。备选线路必须支持同一种协议;Chat、Responses、Messages 之间不会为了换线而偷偷转换。

同优先级的可用线路还可能受会话亲和影响。优先级不是对某个供应商的永久绑定,也不是每次都选择最低价格的保证。

失败换线

当一次尝试因可重试的上游故障、限流或线路不可用而失败时,平台可自动尝试其他合适的线路。没有可用备选或尝试仍失败时,请求返回错误;不是无限重试,也不保证已开始的流式回答可以无缝续接。

调用方参数错误不靠换线修复。应用仍需检查完成状态,并按错误与限制处理失败。

参数要求与供应商选择

provider.require_parameters 要求线路声明支持请求中的相关工具与采样参数,避免把要求交给不支持它的线路。它限制候选线路,不会为模型增加能力,也不保证模型一定调用工具或生成某种回答;没有符合要求的线路时可拒绝请求。具体参数支持以 Chat API 参考为准。

provider 中指定供应商的 order、only、ignore 是 BYOK 专属选项;require_parameters 这种能力约束不属于供应商选择限制。平台模型由平台选线。BYOK 当前即将推出,不要依赖它指定平台供应商。

API 参考 → · Cookbook →

协议兼容

协议兼容让你沿用客户端的请求格式;不表示每个模型支持所有协议或官方的全部功能。以模型的 supported_apis 和各端点支持说明为准。

只有一个地址 https://api.deepraxis.com,按协议分入口:

入口协议
/openaiOpenAI 系协议
/anthropicAnthropic 系协议
/googleGoogle Gemini API

OpenAI Chat Completions

POST /openai/v1/chat/completions。常用的对话协议;流式(SSE)、工具调用与多模态输入是否可用取决于模型。 模型能力与字段差异见 Chat API;会话标识的作用见会话亲和。

OpenAI Responses

POST /openai/v1/responses 及 retrieve / delete / cancel / input_items。OpenAI 新一代有状态协议;Codex 等 Agent 走这里。支持范围见 Responses API。压缩响应与输入 token 计数端点当前未实现,调用返回 501。

OpenAI Files

/openai/v1/files:上传 user_data 文件供后续请求引用;可列出、读元数据、下载、删除。

OpenAI Images

POST /openai/v1/images/generations:使用目录声明支持图像生成的模型。

Anthropic Messages

POST /anthropic/v1/messages。Claude Code、Anthropic SDK 走这里。 具体支持范围见 Messages API。

Anthropic Files

/anthropic/v1/files:上传、列出、读元数据、删除;上传后不可下载(与官方一致)。

Google Gemini API(generateContent)

POST /google/v1beta/models/{model}:generateContent。目前用于 Gemini 原生协议的图像生成与编辑。

模型目录

用 GET /openai/v1/models?api=responses 查找支持 Responses 的模型,api=chat,embeddings 匹配任一协议。原生目录 GET /model/v1/models 及模型详情会返回每个模型的 supported_apis;原生列表可用 ?supported_api=openai.responses 筛选。

GET /openai/v1/models:列出当前 Key 可调用的模型,OpenAI 风格返回;Kilo Code 等客户端靠它自动发现模型。

Anthropic 客户端使用 GET /anthropic/v1/models,Google 客户端使用 GET /google/v1beta/models;分别返回对应协议的模型列表。

标准化地址

以上每个入口还有一个标准化长路径 /v1/router/<vendor>/...,语义完全相同,见 API 参考。

→ 深入:OpenAI 兼容接口 · Anthropic 兼容接口

API 参考 → · Cookbook →

会话亲和

会话亲和让同一会话后续请求,在条件允许时优先复用之前成功的线路。它是一种选路偏好,不是锁定供应商,也不是替应用保存对话历史。

为什么有用

多轮对话常重复系统提示、工具定义和早期消息。重复前缀留在同一条支持缓存的线路上,更有机会命中上游缓存,降低重复输入的费用与延迟。相同会话 ID 不保证命中缓存;内容变化、缓存失效或换线都可能让它失效。

如何识别同一会话

客户端可用请求体的 session_id 或请求头 x-session-id 标识会话;两者同时存在时,请求体优先。没有显式会话 ID 时,还可能使用 prompt_cache_key 或从对话前缀推导标识。自动推导受内容变化影响,因此长期多轮对话应保持显式标识一致,不同对话使用不同标识。

何时生效

缓存亲和要求该线路支持本次模型与协议、输入得到保留,并且缓存读取价格低于普通输入价格。成功完成的调用才更新亲和记录;后续请求仍须通过当前的健康与能力检查。

亲和只在同优先级范围内调整顺序,不绕过冷却或参数要求。距上次成功已空闲 10 分钟、原线路不再合适,或亲和记录不可用时,按正常路由继续;因此应用不能依赖它保证状态永远留在同一上游。

Chat 请求仍应携带需要的消息历史。有状态 Responses 的续写属于独立的协议能力,不能用会话 ID 代替 previous_response_id。参数说明见 Chat API 与 Responses API。

API 参考 → · Cookbook →

BYOK:自带模型凭证

BYOK(自带模型供应商凭证)即将推出,Console 尚未开放配置入口。目前请使用 Praxis 云 API Key 调用模型目录中的平台模型,由平台管理线路与计费;首次调用见快速开始。

API 参考 → · Cookbook →

指南

你想做什么去哪
把 Praxis 接进 SDK / 框架Cookbook → SDK
自带模型凭证(BYOK)BYOK
处理错误与限流错误与限流

OpenAI 兼容接口

OpenAI SDK 使用 https://api.deepraxis.com/openai/v1。请求按同协议转发;兼容不表示所有官方端点、字段和模型都可用。

选对地址

客户端基础地址模型目录
OpenAI SDKhttps://api.deepraxis.com/openai/v1/openai/v1/models
Anthropic SDKhttps://api.deepraxis.com/anthropic/anthropic/v1/models
Google GenAI SDKhttps://api.deepraxis.com/google/google/v1beta/models
Praxis 原生 APIhttps://api.deepraxis.com/model/v1见原生 API,包含 providerOptions

各兼容目录只列声明提供该协议的模型。出现在 OpenAI 目录里不等于同时支持 Chat Completions 和 Responses;未声明的操作返回 404 model_protocol_not_offered。

字段差异去哪里核对

应用使用的内容发请求前核对
messages、工具、采样、思考、供应商偏好Chat Completions 字段支持
input、响应存储、响应工具Responses 字段支持
usage.cost、生成 ID迁移页费用处理与生成记录
上传、文件用途上传文件
对话以外的端点操作支持情况与不支持清单及替代

未知顶层字段可能原样转发给同协议上游;请求被接受不证明字段已生效。已声明字段仍会校验。依赖一个选项前,请查看对应端点的字段支持说明。

1. 准备

需要 Python 3.10+、uv 和一个 Praxis Key。在控制台 → API Keys创建 Key,将它安全注入当前终端的 PRAXIS_API_KEY 环境变量;不要把 Key 写进代码或日志。保留原服务的配置与 Key,供回滚使用。以下命令在 macOS / Linux 的 Bash 中执行。

test -n "${PRAXIS_API_KEY:-}"

期望:退出码 0;退出码 1 表示变量未设置,先设置再继续。

2. 验证 Responses

示例使用 deepseek-flash。截至 2026-10-12,glm-5.3-flash 也提供 Responses;gpt-5.6-sol 提供 Chat Completions,但不提供 Responses。使用前在模型目录确认所需操作。

unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy
uv run -q --no-project --with openai --with anthropic python - <<'PY'
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.deepraxis.com/openai/v1",
    api_key=os.environ["PRAXIS_API_KEY"],
    timeout=90.0,
    max_retries=0,
)
models = client.models.list()
assert "deepseek-flash" in {m.id for m in models.data}
raw = client.responses.with_raw_response.create(
    model="deepseek-flash",
    input="Reply OK.",
    max_output_tokens=1024,
)
reply = raw.parse()
text = reply.output_text
print("HTTP", raw.status_code)
print("X-Request-Id:", raw.headers.get("x-request-id"))
print("X-Praxis-Generation-Id:", raw.headers.get("x-praxis-generation-id"))
print(text)
assert raw.status_code == 200 and text and text.strip()
assert raw.headers.get("x-request-id")
PY

期望:退出码 0,输出 HTTP 200、非空正文(通常为 OK.)和 X-Request-Id。断言同时检查模型存在与真实回复;不是只检查能否连接。保留请求 ID 便于排查,不要记录 Key。示例会产生少量调用费用。

期望:output_text 是最终正文,不把思考输出当作正文。若返回 model_protocol_not_offered,换支持 Responses 的模型;若提示 does not exist,从目录复制准确 ID。已有应用回滚时同时恢复原地址、Key 与模型,再运行原冒烟请求。

API 参考 → · Cookbook:从 OpenAI 迁移 →

Anthropic 兼容接口

Anthropic SDK 使用 https://api.deepraxis.com/anthropic。SDK 会追加 /v1/messages,基础地址不要再加 /v1。请求按同协议转发,可用能力取决于模型的声明。

兼容覆盖什么

/anthropic/v1/models 只列 Anthropic 兼容模型。不要假设其他协议目录里的 ID 在这里也可用。未声明的操作返回 404(anthropic.messages is not offered for …);换地址不会把不支持的模型能力转换出来。

字段差异去哪里核对

应用使用的内容需要核对的差异
thinking、output_config.effort思考选项取决于模型;部分模型不能关闭思考,见 Messages
缓存控制、工具、元数据、beta 或 workspace 请求头Messages 字段支持,不要假设所有官方扩展可用
count_tokens带 x-praxis-token-count: estimated 的校准估算,见统计 token
模型限额与能力模型列表,未知限额可能为 null
文件上传、过期时间上传文件
费用Messages 费用处理与生成记录

未知顶层字段可传到同协议上游,由上游决定是否接受;已声明字段会校验。成功响应不代表所有选项都已执行。操作支持见 API 列表,缺失操作的处理见迁移替代方案。

1. 准备

需要 Python 3.10+、uv 和一个 Praxis Key。在控制台 → API Keys创建 Key,将它安全注入当前终端的 PRAXIS_API_KEY 环境变量;不要把 Key 写进代码或日志。保留原服务的配置与 Key,供回滚使用。以下命令在 macOS / Linux 的 Bash 中执行。

test -n "${PRAXIS_API_KEY:-}"

期望:退出码 0;退出码 1 表示变量未设置,先设置再继续。

2. 验证 Messages 与 token 统计

unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy
uv run -q --no-project --with openai --with anthropic python - <<'PY'
import os
from anthropic import Anthropic

client = Anthropic(
    base_url="https://api.deepraxis.com/anthropic",
    api_key=os.environ["PRAXIS_API_KEY"],
    timeout=90.0,
    max_retries=0,
)
models = client.models.list()
assert "deepseek-flash" in {m.id for m in models.data}
raw = client.messages.with_raw_response.create(
    model="deepseek-flash",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Reply OK."}],
)
reply = raw.parse()
text = "".join(block.text for block in reply.content if block.type == "text")
print("HTTP", raw.status_code)
print("X-Request-Id:", raw.headers.get("x-request-id"))
print("X-Praxis-Generation-Id:", raw.headers.get("x-praxis-generation-id"))
print(text)
assert raw.status_code == 200 and text and text.strip()
assert raw.headers.get("x-request-id")
count = client.messages.with_raw_response.count_tokens(
    model="deepseek-flash",
    messages=[{"role": "user", "content": "Reply OK."}],
)
print("count_tokens:", count.parse().input_tokens)
print("X-Request-Id:", count.headers.get("x-request-id"))
print("x-praxis-token-count:", count.headers.get("x-praxis-token-count"))
assert count.status_code == 200
assert count.headers.get("x-praxis-token-count") == "estimated"
PY

期望:退出码 0,输出 HTTP 200、非空正文(通常为 OK.)和 X-Request-Id。断言同时检查模型存在与真实回复;不是只检查能否连接。保留请求 ID 便于排查,不要记录 Key。示例会产生少量调用费用。

期望:即使第一块是思考,仍能从 type == "text" 的块取得正文。第二次调用输出 token 估算与 estimated;不能把估算当成计费用量。

若返回 400(cannot disable thinking),移除关闭思考的要求或改选允许关闭的模型;若返回 anthropic.messages is not offered for …,选择 Anthropic 兼容且支持该操作的模型。已有应用回滚时恢复原地址、Key、模型与思考选项,再运行原 Messages 请求。

API 参考 → · Cookbook:从 Anthropic 迁移 →

流式响应

流式响应把一条回答拆成多个事件。显示收到的文本后,还要读取终态和用量,才能确认本轮完成。

本页使用 POST /openai/v1/chat/completions 的 deepseek-flash,并提供 POST /anthropic/v1/messages 的 claude-opus-5 示例;两者于 2026-10-12 验证通过。

1. 准备终端

将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY;获取方式见 Key 与鉴权。不要把 Key 写入代码。准备 Python 和 uv;TypeScript 示例使用 Bun。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

将每段 Python 保存为标明的文件名,以 uv run -q --no-project --with openai --with anthropic python <文件名> 执行。各段代码独立运行。

2. 用 Python 接收文本和末块用量

保存为 streaming.py,运行 uv run -q --no-project --with openai --with anthropic python streaming.py。

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')
text, finish, usage = '', None, None
with client.chat.completions.with_streaming_response.create(
    model='deepseek-flash', messages=[{'role':'user','content':'Reply with exactly: Hello streaming.'}],
    max_tokens=256, stream=True, stream_options={'include_usage':True},
) as response:
    print('X-Request-Id:', response.headers.get('x-request-id'))
    for chunk in response.parse():
        if chunk.usage is not None:
            usage = chunk.usage
        for choice in chunk.choices:
            piece = choice.delta.content or ''
            text += piece
            print(piece, end='', flush=True)
            if choice.finish_reason is not None:
                finish = choice.finish_reason
print('\nfinish_reason:', finish)
assert text.strip() and finish == 'stop', 'Incomplete text response'
assert usage is not None, 'No final usage received'
print('usage:', usage.model_dump())

期望:逐块输出 Hello streaming.,随后输出 finish_reason: stop 和包含输入、输出 token 数的 usage;退出码为 0。

Chat Completions 的读取顺序是:

  1. 收集 choices[].delta.content;role、思考或空增量可能没有正文,不应当作文本显示。
  2. 记录 finish_reason。本例的完整文本回答以 stop 结束;tool_calls 应进入工具调用流程。
  3. 设置 stream_options.include_usage=true 后,继续读取末块 usage。这一块的 choices 可以为空,不能直接访问 choices[0]。
  4. HTTP 事件流以 data: [DONE] 收尾;SDK 负责处理该标记并结束迭代。

不要在收到 finish_reason 时立即跳出循环,否则会漏掉之后的用量。断开连接不等于模型完成;没有终态或末块用量时,不应把部分文本和未知用量记为完整结果。

3. 用 cURL 检查事件顺序

在同一终端执行:

curl --fail-with-body --no-buffer --silent --show-error --include \
  "${PRAXIS_API_BASE:-https://api.deepraxis.com}/openai/v1/chat/completions" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"deepseek-flash","messages":[{"role":"user","content":"Reply with exactly: Hello streaming."}],"max_tokens":256,"stream":true,"stream_options":{"include_usage":true}}'

期望:HTTP 200,响应类型为 text/event-stream;正文增量后出现 finish_reason: "stop"、带 usage 的块,最后是 data: [DONE];cURL 退出码为 0。--no-buffer 避免 cURL 缓冲显示,但单独退出码 0 不能证明回答完整,仍需检查终态。

4. 使用 Anthropic Messages

Messages 依次发送 message_start、内容块的 content_block_start / content_block_delta / content_block_stop、message_delta 和 message_stop,中间可能有 ping。不要将它按 Chat Completions 的 choices 解析。输入用量在起始消息中,后续用量更新由 SDK 汇总;get_final_message() 返回完整结果。

保存为 streaming-anthropic.py,运行 uv run -q --no-project --with openai --with anthropic python streaming-anthropic.py。

import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/anthropic')
with client.messages.stream(model='claude-opus-5', max_tokens=256,
    messages=[{'role':'user','content':'Reply with exactly: Hello streaming.'}]) as stream:
    print('X-Request-Id:', stream.response.headers.get('x-request-id'))
    for text in stream.text_stream:
        print(text, end='', flush=True)
    message = stream.get_final_message()
assert message.stop_reason == 'end_turn'
assert any(block.type == 'text' and block.text.strip() for block in message.content)
print('\nusage:', message.usage.model_dump())

期望:输出同样的文本和最终用量,stop_reason 为 end_turn,退出码为 0。

实测失败与处理

把 text-embedding-3-small 用在本页聊天请求中时,实测返回 HTTP 404:

openai.chat.completions is not offered for text-embedding-3-small

这是模型不提供聊天端点,不是等待更多事件可以解决的问题。改用本页已验证的 deepseek-flash 后,文本、终态与末块用量均正常返回。

协议细节:OpenAI · Anthropic。

API 参考 → · Cookbook →

工具调用

模型返回工具名称和参数,由你的程序执行工具,再把结果交回模型。收到工具调用还不是完成回答。

本例查询本地演示数据:Paris 的温度固定为 22°C,不访问天气服务。deepseek-flash 在 GET /openai/v1/models 的 supported_parameters 中列有 tools 和 tool_choice;下面的两轮调用于 2026-10-12 验证通过。

1. 准备终端

将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY;获取方式见 Key 与鉴权。不要把 Key 写入代码。准备 Python 和 uv;TypeScript 示例使用 Bun。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

将每段 Python 保存为标明的文件名,以 uv run -q --no-project --with openai --with anthropic python <文件名> 执行。各段代码独立运行。

2. 定义工具并完成两轮调用

保存为 tool-calling.py,运行 uv run -q --no-project --with openai --with anthropic python tool-calling.py。

代码先声明 get_temperature 的名称、描述和参数,再要求模型调用它;验证参数后执行本地查询,将结果和原始 tool_call_id 一起回填,最后请求文字回答。两轮都设置 reasoning_effort="none"。

import json
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')
tools = [{'type':'function','function':{
    'name':'get_temperature', 'description':'Read the demo temperature for Paris.',
    'parameters':{'type':'object','properties':{'city':{'type':'string','enum':['Paris']}},
        'required':['city'],'additionalProperties':False}}}]
messages = [{'role':'user','content':'Use get_temperature for Paris, then tell me the temperature in Celsius.'}]
first = client.chat.completions.create(model='deepseek-flash', messages=messages,
    tools=tools, reasoning_effort='none', tool_choice={'type':'function','function':{'name':'get_temperature'}}, max_tokens=1024)
print('X-Request-Id:', first._request_id)
message = first.choices[0].message
assert first.choices[0].finish_reason == 'tool_calls' and message.tool_calls
messages.append(message.model_dump(exclude_none=True))
for call in message.tool_calls:
    assert call.function.name == 'get_temperature'
    args = json.loads(call.function.arguments)
    assert args == {'city':'Paris'}, args
    result = {'city':args['city'], 'temperature_c':22}
    messages.append({'role':'tool','tool_call_id':call.id,'content':json.dumps(result)})
second = client.chat.completions.create(model='deepseek-flash', messages=messages,
    tools=tools, reasoning_effort='none', tool_choice='none', max_tokens=1024)
print('X-Request-Id:', second._request_id)
answer = second.choices[0].message.content or ''
assert second.choices[0].finish_reason == 'stop' and '22' in answer, answer
print(answer)

期望:第一轮 finish_reason=tool_calls,函数参数是 {"city":"Paris"};第二轮回答包含 22,finish_reason=stop;退出码为 0。

保留第一轮完整 assistant 消息,为每个工具调用返回一条 role="tool" 消息。不要重造调用 ID,也不要只把结果作为普通用户文字发回。参数是 JSON 字符串,需要解析和校验;不要直接执行模型提供的任意函数名。这里用 tool_choice="none" 明确结束工具阶段;使用 auto 的实际应用应允许多轮工具调用,并设置轮数上限。

3. 用 TypeScript 执行同一流程

保存为 tool-calling.ts,运行 bun tool-calling.ts;使用内置 fetch,无需安装 SDK。

import assert from 'node:assert/strict';
const base = process.env.PRAXIS_API_BASE ?? 'https://api.deepraxis.com';
async function chat(body: object) {
  const response = await fetch(`${base}/openai/v1/chat/completions`, {
    method: 'POST', headers: {'Authorization': `Bearer ${process.env.PRAXIS_API_KEY}`, 'Content-Type': 'application/json'},
    body: JSON.stringify({model: 'deepseek-flash', max_tokens: 1024, reasoning_effort: 'none', ...body}),
  });
  console.log('X-Request-Id:', response.headers.get('x-request-id'));
  const data = await response.json();
  assert(response.ok, JSON.stringify(data));
  return data;
}
const tools = [{type: 'function', function: {name: 'get_temperature',
  description: 'Read the demo temperature for Paris.',
  parameters: {type: 'object', properties: {city: {type: 'string', enum: ['Paris']}},
    required: ['city'], additionalProperties: false}}}];
const messages: any[] = [{role: 'user', content: 'Use get_temperature for Paris, then tell me the temperature in Celsius.'}];
const first = await chat({messages, tools, tool_choice: {type: 'function', function: {name: 'get_temperature'}}});
const choice = first.choices[0];
assert.equal(choice.finish_reason, 'tool_calls');
assert(choice.message.tool_calls?.length);
messages.push(choice.message);
for (const call of choice.message.tool_calls) {
  assert.equal(call.function.name, 'get_temperature');
  const args = JSON.parse(call.function.arguments);
  assert.deepEqual(args, {city: 'Paris'});
  messages.push({role: 'tool', tool_call_id: call.id,
    content: JSON.stringify({city: args.city, temperature_c: 22})});
}
const second = await chat({messages, tools, tool_choice: 'none'});
assert.equal(second.choices[0].finish_reason, 'stop');
assert(second.choices[0].message.content.includes('22'));
console.log(second.choices[0].message.content);

期望:完成相同的工具调用与回填,最终回答包含 22,退出码为 0。

4. 使用 Anthropic Messages

Messages 用 input_schema 定义参数,返回 tool_use 内容块和 stop_reason="tool_use"。将完整 assistant 内容带回后,在下一条 role="user" 消息中放入 tool_result,用 tool_use_id 对应原调用。本例的 claude-opus-5 已实测通过。

保存为 tool-calling-anthropic.py,运行 uv run -q --no-project --with openai --with anthropic python tool-calling-anthropic.py。

import json
import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/anthropic')
tools = [{'name':'get_temperature','description':'Read the demo temperature for Paris.',
    'input_schema':{'type':'object','properties':{'city':{'type':'string','enum':['Paris']}},
        'required':['city'],'additionalProperties':False}}]
messages = [{'role':'user','content':'Use get_temperature for Paris, then tell me the temperature in Celsius.'}]
raw = client.messages.with_raw_response.create(model='claude-opus-5', max_tokens=1024,
    tools=tools, tool_choice={'type':'tool','name':'get_temperature'}, messages=messages)
print('X-Request-Id:', raw.headers.get('x-request-id'))
first = raw.parse()
assert first.stop_reason == 'tool_use'
messages.append({'role':'assistant','content':[block.model_dump(exclude_none=True) for block in first.content]})
results = []
for block in first.content:
    if block.type == 'tool_use':
        assert block.name == 'get_temperature' and block.input == {'city':'Paris'}
        results.append({'type':'tool_result','tool_use_id':block.id,
            'content':json.dumps({'city':'Paris','temperature_c':22})})
assert results
messages.append({'role':'user','content':results})
raw = client.messages.with_raw_response.create(model='claude-opus-5', max_tokens=1024,
    tools=tools, tool_choice={'type':'none'}, messages=messages)
print('X-Request-Id:', raw.headers.get('x-request-id'))
second = raw.parse()
answer = ''.join(block.text for block in second.content if block.type == 'text')
assert second.stop_reason == 'end_turn' and '22' in answer, answer
print(answer)

期望:第一轮请求工具,第二轮返回包含 22 的文本,stop_reason=end_turn,退出码为 0。

实测失败与处理

强制指定函数、但没有关闭思考时,deepseek-flash 返回 HTTP 400:

tool_choice a named function is not supported while thinking is enabled on this model. Set reasoning effort to "none" or use tool_choice "auto".

本页采用已验证的解决办法:显式设置 reasoning_effort="none"。仅第一轮设置、第二轮省略时,还曾收到 HTTP 400:

The selected thinking setting is not supported on this model route.

两轮都保留 reasoning_effort="none" 后通过。不要在重试时丢掉该参数。

协议细节:OpenAI · Anthropic。

API 参考 → · Cookbook →

结构化输出

结构化输出将回答约束为指定的 JSON 结构;接收端仍需检查终态、解析 JSON 并验证业务字段。

选择支持的模型

先查看 GET /openai/v1/models 中的 supported_parameters:候选模型应列有 response_format 和 structured_outputs。2026-10-12,gpt-5.6-sol 的 Chat Completions json_schema + strict 和 claude-opus-5 的 Messages output_config 均实测通过,下面分别使用它们。

deepseek-flash 虽列有上述参数,本次 json_schema 请求仍返回 400,因此本页不使用它演示 schema 约束。目录声明不能替代实际响应验证。json_object(JSON mode)只表达 JSON 格式要求,不等于 schema 约束;需要固定字段和类型时使用本页的 json_schema。

1. 准备终端

将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY;获取方式见 Key 与鉴权。不要把 Key 写入代码。准备 Python 和 uv;TypeScript 示例使用 Bun。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

将每段 Python 保存为标明的文件名,以 uv run -q --no-project --with openai --with anthropic python <文件名> 执行。各段代码独立运行。

2. 使用 json_schema 和 strict

保存为 structured-output.py,运行 uv run -q --no-project --with openai --with anthropic python structured-output.py。

response_format.json_schema 中同时给出 name、strict: true 和 schema。本例将所有属性列入 required,并用 additionalProperties: false 禁止额外字段。

import json
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')
schema = {'type':'object', 'properties':{
    'city':{'type':'string'}, 'temperature':{'type':'integer'}},
    'required':['city','temperature'], 'additionalProperties':False}
response = client.chat.completions.create(model='gpt-5.6-sol', max_tokens=1024,
    messages=[{'role':'user','content':'Return JSON for Paris with temperature 22.'}],
    response_format={'type':'json_schema','json_schema':{
        'name':'weather','strict':True,'schema':schema}})
print('X-Request-Id:', response._request_id)
choice = response.choices[0]
assert choice.finish_reason == 'stop' and not choice.message.refusal
value = json.loads(choice.message.content)
assert set(value) == {'city','temperature'}
assert isinstance(value['city'], str) and type(value['temperature']) is int
assert value == {'city':'Paris','temperature':22}, value
print(value)

期望:输出 {'city': 'Paris', 'temperature': 22},退出码为 0。代码不剥除 Markdown 围栏;只有直接可解析、字段和类型符合要求的 JSON 才通过。

先检查 finish_reason 和 refusal,再解析内容。被截断或拒绝的回答不能当成符合 schema 的业务结果;流式回答应先收齐文本再解析。

3. 用 TypeScript 验证相同结果

保存为 structured-output.ts,运行 bun structured-output.ts;无需安装 SDK。

import assert from 'node:assert/strict';
const base = process.env.PRAXIS_API_BASE ?? 'https://api.deepraxis.com';
const schema = {type: 'object', properties: {
  city: {type: 'string'}, temperature: {type: 'integer'}},
  required: ['city', 'temperature'], additionalProperties: false};
const response = await fetch(`${base}/openai/v1/chat/completions`, {
  method: 'POST', headers: {'Authorization': `Bearer ${process.env.PRAXIS_API_KEY}`, 'Content-Type': 'application/json'},
  body: JSON.stringify({model: 'gpt-5.6-sol', max_tokens: 1024,
    messages: [{role: 'user', content: 'Return JSON for Paris with temperature 22.'}],
    response_format: {type: 'json_schema', json_schema: {name: 'weather', strict: true, schema}}}),
});
console.log('X-Request-Id:', response.headers.get('x-request-id'));
const data = await response.json();
assert(response.ok, JSON.stringify(data));
const choice = data.choices[0];
assert.equal(choice.finish_reason, 'stop');
assert(!choice.message.refusal);
const value = JSON.parse(choice.message.content);
assert.deepEqual(value, {city: 'Paris', temperature: 22});
console.log(value);

期望:同样得到 city=Paris、整数 temperature=22,没有额外字段,退出码为 0。

4. 使用 Messages 的 output_config

Messages 将 schema 放在 output_config.format 下,type 为 json_schema;不要把 Chat Completions 的 response_format 或 json_schema.name 外壳搬过来。这里也不添加 OpenAI 的 strict 字段。

保存为 structured-output-anthropic.py,运行 uv run -q --no-project --with openai --with anthropic python structured-output-anthropic.py。

import json
import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/anthropic')
schema = {'type':'object', 'properties':{
    'city':{'type':'string'}, 'temperature':{'type':'integer'}},
    'required':['city','temperature'], 'additionalProperties':False}
raw = client.messages.with_raw_response.create(model='claude-opus-5', max_tokens=1024,
    messages=[{'role':'user','content':'Return JSON for Paris with temperature 22.'}],
    output_config={'format':{'type':'json_schema','schema':schema}})
print('X-Request-Id:', raw.headers.get('x-request-id'))
message = raw.parse()
assert message.stop_reason == 'end_turn'
value = json.loads(''.join(block.text for block in message.content if block.type == 'text'))
assert set(value) == {'city','temperature'}
assert isinstance(value['city'], str) and type(value['temperature']) is int
assert value == {'city':'Paris','temperature':22}, value
print(value)

期望:stop_reason=end_turn,JSON 验证通过并输出相同对象,退出码为 0。

实测失败与处理

deepseek-flash 的 schema 请求返回 HTTP 400,错误消息的固定部分为:

This response_format type is unavailable now

消息还会附带当次请求标识。切换为本页已实测通过的 gpt-5.6-sol 后通过。不要将 HTTP 200、Markdown 中的一段 JSON 或能解析的普通 JSON 当成 schema 约束已生效;保留示例中的完整验证。

协议细节:OpenAI · Anthropic。

API 参考 → · Cookbook →

调整思考强度

同一个“低”档,在 Chat、Messages 和 Google 中使用不同字段。先选模型支持的协议与档位,再查看响应中的思考用量;档位是提示,实际消耗以 usage 为准。

选择协议与档位

打开控制台 → 模型目录,点进模型详情的「协议与思考档位」。期望:看到该模型支持的调用方式和思考选项;没有“关闭”时,不要发送关闭思考的设置。

用 GET /model/v1/models 的 supported_apis 确认协议;Chat 目录 GET /openai/v1/models?api=chat 的 reasoning.supported_efforts 和 reasoning.mandatory 可用于程序检查。2026-10-13 的 Chat 目录将 glm-5.3、glm-5.3-flash 和 kimi-k3 标为 mandatory=true,即不能关闭思考;调用前仍需重查目录。不要假设每个模型都有全部档位。下列用量字段是本页所选模型实测返回的字段,不保证所有模型都提供明细。

调用方式设置思考的入口本页读取的用量
Chat Completionsreasoning_effort;也接受 reasoning 对象usage.completion_tokens_details.reasoning_tokens
Anthropic Messagesthinking 控制模式,output_config.effort 选择档位usage.output_tokens_details.thinking_tokens
Google generateContentgenerationConfig.thinkingConfig 中的 thinkingBudget 或 thinkingLevelusageMetadata.thoughtsTokenCount

Chat 的档位范围从 none 到 max,实际可选项以模型为准;none 表示关闭,不是“低”。Messages 的 enabled、adaptive、disabled 分别表达预算思考、自适应思考和关闭,不能把 thinking.effort 当作档位字段。Google 的预算和档位随模型而异,不要同时发送两种控制。

完整字段定义见 Chat、Messages、Google。

1. 准备终端

准备 Python 3.10+ 和 uv。将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY,不要写入代码或日志。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。退出码 1 表示变量未设置,先设置再继续。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

2. Chat:设为低并读取用量

deepseek-flash 支持 Chat 和 low。

保存为 reasoning-chat.py,运行 uv run -q --no-project --with openai --with anthropic --with google-genai python reasoning-chat.py。

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')
response = client.chat.completions.create(
    model='deepseek-flash', reasoning_effort='low', max_tokens=2048,
    messages=[{'role': 'user', 'content': 'What is 17 times 23? Answer briefly.'}])
assert response.choices[0].finish_reason == 'stop'
assert '391' in (response.choices[0].message.content or '')
details = response.usage.completion_tokens_details
assert details is not None and details.reasoning_tokens is not None, 'Reasoning usage was not reported'
print('X-Request-Id:', response._request_id)
print('answer:', response.choices[0].message.content)
print('reasoning_tokens:', details.reasoning_tokens)

期望:回答包含 391,终态为 stop,打印实际 reasoning_tokens,退出码 0。数字会随调用变化。

3. Messages:设为低并读取用量

这里选用 claude-opus-5,因为本次验证中它会返回思考 token 明细。deepseek-flash 的 Messages 响应未返回该明细;缺失不等于 0,也不能用 output_tokens 代替。

保存为 reasoning-messages.py,运行 uv run -q --no-project --with openai --with anthropic --with google-genai python reasoning-messages.py。

import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/anthropic')
raw = client.messages.with_raw_response.create(
    model='claude-opus-5', max_tokens=2048,
    thinking={'type': 'adaptive'}, output_config={'effort': 'low'},
    messages=[{'role': 'user', 'content': 'What is 17 times 23? Answer briefly.'}])
response = raw.parse()
answer = ''.join(block.text for block in response.content if block.type == 'text')
assert response.stop_reason == 'end_turn' and '391' in answer
usage = response.usage.model_dump()
details = usage.get('output_tokens_details') or {}
assert details.get('thinking_tokens') is not None, 'Thinking usage was not reported'
print('X-Request-Id:', raw.headers.get('x-request-id'))
print('answer:', answer)
print('thinking_tokens:', details['thinking_tokens'])

期望:回答包含 391,终态为 end_turn,打印实际 thinking_tokens,退出码 0。

4. Google:按模型选预算或档位

以下依次调用两个模型:gemini-2.5-flash 使用预算,gemini-3.1-flash-lite-preview 使用低档。使用 HTTP 是为了直接展示 Google 的字段名。

保存为 reasoning-google.py,运行 uv run -q --no-project --with openai --with anthropic --with google-genai python reasoning-google.py。

import json
import os
import httpx

base = os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com')
with httpx.Client(timeout=120) as client:
    for model, thinking in [
        ('gemini-2.5-flash', {'thinkingBudget': 1024}),
        ('gemini-3.1-flash-lite-preview', {'thinkingLevel': 'low'}),
    ]:
        response = client.post(base + '/google/v1beta/models/' + model + ':generateContent',
            headers={'Authorization': 'Bearer ' + os.environ['PRAXIS_API_KEY']},
            json={'contents': [{'role': 'user', 'parts': [{'text': 'What is 17 times 23? Answer briefly.'}]}],
                  'generationConfig': {'maxOutputTokens': 2048, 'thinkingConfig': thinking}})
        print('X-Request-Id:', response.headers.get('x-request-id'))
        response.raise_for_status()
        data = response.json()
        candidate = data['candidates'][0]
        answer = ''.join(part.get('text', '') for part in candidate['content']['parts'])
        assert candidate['finishReason'] == 'STOP' and '391' in answer
        assert 'thoughtsTokenCount' in data['usageMetadata'], 'Thinking usage was not reported'
        print(model, answer, json.dumps(data['usageMetadata']))

期望:两次回答都包含 391,终态为 STOP,各自打印 thoughtsTokenCount,退出码 0。

如何比较结果

保持模型和问题一致,多次采样,比较实际思考 token、完整回答、耗时与质量。预算不是跨模型统一的硬上限;“低”不保证每次都比“高”少用 token,也不保证更快。不要根据一条响应推断单调关系。用量字段缺失时,保留“未报告”,不要填 0。

实测失败与处理

glm-5.3 设置 reasoning_effort="none" 返回 HTTP 400,错误码 thinking_disable_unsupported:

glm-5.3 cannot disable thinking; omit the thinking setting or choose a level.

省略关闭设置或选择模型支持的档位,再请求。不要靠换成另一个协议的“关闭”字段绕过限制。

API 参考 → · Cookbook →

Responses API

只有声明 openai.responses 的模型才能使用 Responses。支持 Chat Completions 不代表支持 Responses。用 GET /openai/v1/models?api=responses 查询;2026-10-13 返回 deepseek-flash 和 glm-5.3-flash。

1. 准备终端

准备 Python 3.10+ 和 uv。将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY,不要写入代码或日志。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。退出码 1 表示变量未设置,先设置再继续。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

2. 接着上一条响应提问

平台会展开 previous_response_id 引用的历史。第二次调用只发送新问题和第一条响应 ID。两次使用相同 Key 和模型;示例检查第二条回答能取回未出现在新问题中的代码。

保存为 responses.py,运行 uv run -q --no-project --with openai --with anthropic --with google-genai python responses.py。

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')
models = client.models.list(extra_query={'api': 'responses'})
print('X-Request-Id:', models._request_id)
assert 'deepseek-flash' in [model.id for model in models]
first = client.responses.create(model='deepseek-flash',
    input='Remember this code: LIME-742. Reply OK.',
    reasoning={'effort': 'none'}, max_output_tokens=512)
print('X-Request-Id:', first._request_id)
assert first.status == 'completed' and first.id
second = client.responses.create(model='deepseek-flash',
    previous_response_id=first.id, input='What was the code? Reply with only the code.',
    reasoning={'effort': 'none'}, max_output_tokens=512)
print('X-Request-Id:', second._request_id)
assert second.status == 'completed' and 'LIME-742' in second.output_text
assert second.usage is not None
print('answer:', second.output_text)
print('usage:', second.usage.model_dump())

期望:两条响应均为 completed,第二条回答包含 LIME-742 并打印用量,退出码 0。复用历史不意味着输入免费,应逐轮查看 usage。

限制与实测失败

POST /openai/v1/responses/compact 与 POST /openai/v1/responses/input_tokens 当前返回 HTTP 501,错误码 not_implemented。不要依赖它们压缩对话或预先计数;由应用控制历史长度,完成后读取实际用量。

用 gpt-5.6-sol 请求实测返回 HTTP 404,错误码 model_protocol_not_offered。改选 Responses 筛选目录中的模型。若示例目录断言失败,先重查该目录再重试。其他网关的响应 ID 不随地址迁移,应重建必要的历史。

参数见生成响应,各操作支持状态见 OpenAI API 参考。

API 参考 → · Cookbook →

读取图片、PDF 和文本文件

先确认模型同时支持所选协议和输入类型。查看 GET /model/v1/models 的 supported_apis、architecture.input_modalities 和 x_praxis.input_modalities:native 表示原生输入,converted 表示经过转换,unsupported 表示不可用。支持图片不代表支持音频或视频。

本例用 deepseek-flash 读取内联图片,用 qwen3.8-flash 读取 PDF 和文本的 file_id 引用;后者声明原生 PDF 输入。文件上传和生命周期细节见文件服务指南。

1. 准备终端

准备 Python 3.10+ 和 uv。将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY,不要写入代码或日志。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。退出码 1 表示变量未设置,先设置再继续。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

2. 发送已知内容并校验回答

脚本在本地构造红色 PNG、单页 PDF 和文本文件。先校验图片颜色,再以 purpose=user_data 上传两个文档并询问校验码。每个上传文件都在 finally 中删除,包括回答检查失败的情况,无需自备文件。

保存为 multimodal.py,运行 uv run -q --no-project --with openai --with anthropic --with google-genai python multimodal.py。

import base64
import os
import struct
import zlib
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')

# Build a 128 x 128 red PNG using only the Python standard library.
def chunk(kind, data):
    return struct.pack('>I', len(data)) + kind + data + struct.pack('>I', zlib.crc32(kind + data))
image = (b'\x89PNG\r\n\x1a\n'
    + chunk(b'IHDR', struct.pack('>IIBBBBB', 128, 128, 8, 2, 0, 0, 0))
    + chunk(b'IDAT', zlib.compress((b'\x00' + b'\xff\x00\x00' * 128) * 128))
    + chunk(b'IEND', b''))
response = client.chat.completions.create(model='deepseek-flash', reasoning_effort='none', max_tokens=256,
    messages=[{'role': 'user', 'content': [
        {'type': 'text', 'text': 'Name the main color in this image in English.'},
        {'type': 'image_url', 'image_url': {'url': 'data:image/png;base64,' + base64.b64encode(image).decode()}},
    ]}])
print('X-Request-Id:', response._request_id)
answer = response.choices[0].message.content or ''
assert response.choices[0].finish_reason == 'stop' and 'red' in answer.lower(), answer
print('image:', answer)

# Build a one-page PDF with selectable text.
text = b'The verification code is LIME-742.'
stream = b'BT /F1 18 Tf 50 100 Td (' + text + b') Tj ET'
objects = [b'<< /Type /Catalog /Pages 2 0 R >>',
    b'<< /Type /Pages /Kids [3 0 R] /Count 1 >>',
    b'<< /Type /Page /Parent 2 0 R /MediaBox [0 0 400 200] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>',
    b'<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>',
    b'<< /Length ' + str(len(stream)).encode() + b' >>\nstream\n' + stream + b'\nendstream']
pdf, offsets = b'%PDF-1.4\n', [0]
for index, obj in enumerate(objects, 1):
    offsets.append(len(pdf))
    pdf += f'{index} 0 obj\n'.encode() + obj + b'\nendobj\n'
xref = len(pdf)
pdf += b'xref\n0 6\n0000000000 65535 f \n'
pdf += b''.join(f'{offset:010d} 00000 n \n'.encode() for offset in offsets[1:])
pdf += f'trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n'.encode()

for name, data, mime in [('code.pdf', pdf, 'application/pdf'), ('code.txt', text, 'text/plain')]:
    file = client.files.create(file=(name, data, mime), purpose='user_data')
    print('upload X-Request-Id:', file._request_id)
    try:
        response = client.chat.completions.create(model='qwen3.8-flash', reasoning_effort='none', max_tokens=256,
            messages=[{'role': 'user', 'content': [
                {'type': 'text', 'text': 'What is the verification code in the file? Reply with only the code.'},
                {'type': 'file', 'file': {'file_id': file.id}},
            ]}])
        print('chat X-Request-Id:', response._request_id)
        answer = response.choices[0].message.content or ''
        assert response.choices[0].finish_reason == 'stop' and 'LIME-742' in answer, answer
        print(name, answer)
    finally:
        deleted = client.files.delete(file.id)
        print('delete X-Request-Id:', deleted._request_id)
        assert deleted.deleted

期望:图片回答包含 red,code.pdf 和 code.txt 都返回 LIME-742,两个删除操作成功,退出码 0。仅 HTTP 成功不够,内容断言也必须通过。

实测失败与处理

同一 PDF 使用 deepseek-flash 时,HTTP 200,但回答说:

I wasn't able to read the attached PDF

该模型目录标注 PDF 为转换输入,这次未能证明成功读取。将文档请求切到原生 PDF 输入的 qwen3.8-flash 后,内容校验通过。遇到无法读取时,选已验证模型或直接提供相关文本;不要仅凭回答看似合理就认为文件已读到。请求结束前保持文件可用。

本页验证的 file_id 路径接受 PDF 和文本,不限于图片;这不代表所有模型支持所有文件类型。参数见 Chat Completions。

API 参考 → · Cookbook →

生成与编辑图像

使用 OpenAI Images API 的 gpt-image-2 生成和编辑图片。调用前查 GET /openai/v1/models?api=images;原生模型目录分别声明 openai.images.generations 和 openai.images.edits。

1. 准备终端

准备 Python 3.10+ 和 uv。将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY,不要写入代码或日志。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。退出码 1 表示变量未设置,先设置再继续。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

2. 生成并编辑

在可写目录运行。示例会产生两次图像调用费用:生成一次、编辑一次,并保存 circle.png 与 circle-edited.png;使用新目录避免覆盖同名文件。示例关闭自动重试。

保存为 images.py,运行 uv run -q --no-project --with openai --with anthropic --with google-genai python images.py。

import base64
import os
from pathlib import Path
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1',
    timeout=180, max_retries=0)
models = client.models.list(extra_query={'api': 'images'})
print('X-Request-Id:', models._request_id)
assert 'gpt-image-2' in [model.id for model in models]

def save(response, filename):
    print('X-Request-Id:', response._request_id)
    assert len(response.data) == 1 and response.data[0].b64_json
    image = base64.b64decode(response.data[0].b64_json, validate=True)
    assert image.startswith(b'\x89PNG\r\n\x1a\n'), 'Expected PNG output'
    Path(filename).write_bytes(image)
    assert response.usage is not None, 'Usage was not reported'
    print(filename, len(image), 'bytes; usage:', response.usage.model_dump())

save(client.images.generate(model='gpt-image-2',
    prompt='A single blue circle centered on a plain white background.',
    n=1, size='1024x1024', quality='low'), 'circle.png')
with open('circle.png', 'rb') as image:
    save(client.images.edit(model='gpt-image-2', image=image,
        prompt='Change the blue circle to a red circle. Keep the white background.',
        n=1, size='1024x1024', quality='low'), 'circle-edited.png')

期望:两个文件都是非空 PNG,每次响应包含一张图片和 token 用量,退出码 0。打开图片:第一张应为白底蓝圆,第二张应为白底红圆。文件签名检查验证格式,不代替画面检查。

3. 查看图像费用

gpt-image-2 按 token 计价,不是每张固定价。文本输入、图像输入、图像输出分别计价;编辑还会产生输入图像 token。查看响应中的用量,以及模型目录中的实时价格;账户汇总见用量与费用。

若目录断言失败,先停止生成,选择当前可用的图像模型。若输出断言失败,保留请求 ID 和响应错误,不要为排查文件问题反复生成。

操作细节:生成与编辑。

API 参考 → · Cookbook →

在对话中引用文件

上传、过期、下载与删除见文件服务。

对话中可用 file_id 引用以 purpose=user_data 上传的图片、文本或 PDF,通过消息中的 {"type":"file","file":{"file_id":"上传返回的 ID"}} 传入。文件被接受不代表所选模型能读出内容;用途为 vision 或 assistants 的文件不作为这里的图片输入。

2026-10-13 在 Develop 复核:qwen3.8-flash 能从文本和 PDF 中读出指定短语。deepseek-flash 引用同一 PDF 时返回 HTTP 200,但回答明确表示转换失败(provider_rejected)、未读取文件。遇到这种情况,换用已验证可读的模型,或把提取出的文本直接放入消息;不要把 HTTP 200 当成读取成功。

图片输入仍需选择支持图片的模型;下例使用 qwen3-vl-plus。

安装 openai,设置 PRAXIS_API_KEY。使用 Develop 时,另设 PRAXIS_API_BASE=https://api-develop.deepraxis.ai。示例上传一张小 PNG,获取描述后删除文件。

import base64
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["PRAXIS_API_KEY"],
    base_url=os.environ.get("PRAXIS_API_BASE", "https://api.deepraxis.com") + "/openai/v1",
)
image = base64.b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAABWklEQVR4nO3OQQ0AMBAEofVv+iqDxzRBALvtg/wgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwg7gEgaMOyrMtNTwAAAABJRU5ErkJggg=="
)
file = client.files.create(file=("pixel.png", image, "image/png"), purpose="user_data")
try:
    response = client.chat.completions.create(
        model="qwen3-vl-plus",
        max_tokens=80,
        messages=[{"role": "user", "content": [
            {"type": "text", "text": "Describe this image in one sentence."},
            {"type": "file", "file": {"file_id": file.id}},
        ]}],
    )
    print(response.choices[0].message.content)
finally:
    client.files.delete(file.id)

期望:输出一句图片描述;请求结束后文件被删除。

插件

插件在模型调用前后处理请求或响应,让模型可以使用经过转换的输入。它们不会改变模型本身的原生能力。file-parser 是第一个插件:在需要时,把文档或图片转换为文字,再交给你选择的模型。

同一套 plugins 参数适用于 /model/v1/chat/completions、/model/v1/messages 和 /model/v1/responses 的创建请求,支持流式和非流式。参数由平台消费,不会作为模型参数传给上游。

配置与默认行为

在请求体中加入 plugins 数组,每个配置用 id 指定插件:

{
  "plugins": [
    { "id": "file-parser", "enabled": true }
  ]
}

未传 plugins 或传 plugins: [] 都表示沿用默认配置,不表示关闭。当前文件解析默认开启;模型能原生处理的 PDF 和图片,在符合模型限制时直接传递。否则,文字 PDF 和 Office 文档在本地提取文字,扫描页使用 OCR,图片使用 OCR 和视觉描述。提取出的文字占用主模型的上下文窗口。

关闭转换使用:

{
  "plugins": [
    { "id": "file-parser", "enabled": false }
  ]
}

关闭后,原生支持的输入仍可使用;模型不支持的输入会被拒绝。配置会检查插件 ID、字段和取值,未知配置不会被静默忽略。

PDF 处理方式

pdf.engine行为
auto(默认模式)优先原生处理;需要转换时提取文字,并对扫描页或文字不足的页使用 OCR。
native只允许原生 PDF 输入。模型不支持或超过原生限制时拒绝,不自动转为文字。
ocr强制走文字转换,即使模型支持原生 PDF;有可用文字的页仍本地提取,需要识别的页才调用 OCR。

请求中省略 pdf.engine 会继承默认配置;没有租户或平台覆盖时即为自动模式。当前请求参数只接受显式的 native 或 ocr,不能用字符串 "auto" 覆盖已有默认。可在控制台将租户 PDF 默认方式设为自动。

{
  "plugins": [
    { "id": "file-parser", "pdf": { "engine": "ocr" } }
  ]
}

pdf.engine 只影响 PDF。可用 "image": { "describe": false } 关闭非原生图片转换;它不关闭原生视觉输入,也不影响文档。

配置优先级

从高到低:平台策略 > 租户锁定 > 本次请求 > 租户默认 > 插件默认。

平台策略决定的值不可被覆盖。租户锁定后,请求尝试改变锁定值会返回 PLUGIN_SETTING_LOCKED。没有锁定的字段可以在本次请求中覆盖,且不会修改租户默认配置。文件大小、页数等平台限制不可通过请求调整。

费用

  • 本地文字提取不产生额外模型用量;转换后的文字仍按主模型输入计费。
  • OCR 按实际处理页数计量,图片描述按描述模型的输入、缓存输入和输出用量计量;单价以所用模型的价格为准。
  • OCR、图片描述和主模型是各自的用量记录。主模型失败不会抹掉已经发生的转换用量。
  • 命中解析缓存不发起新的转换子调用,因此不新增这部分用量;下一轮主模型仍按实际输入和输出计费。

解析注明与下一轮带回

转换成功的响应会携带文件注明(annotations)。保留完整对象,不要修改哈希或解析内容:

{
  "type": "file",
  "file": {
    "hash": "<原样保留服务返回的哈希>",
    "name": "notes.pdf",
    "content": [{ "type": "text", "text": "解析出的文字" }]
  }
}
入口非流式位置流式位置
Chat Completionschoices[].message.annotationschoices[].delta.annotations,每个 choice 返回一次
Anthropic Messages响应消息顶层 annotationsmessage_start.message.annotations
Responsesoutput[] 中 message 的 content[] 内 output_text.annotationsresponse.content_part.done、response.output_item.done 和响应终态中的对应文字快照

已有的引用注明及其下标会保留;Responses 的未完成快照不插入文件注明,完成快照才附加。Messages 的顶层字段和 Responses 中的 type: "file" 是平台扩展;不要把文件注明变成 Anthropic content block。只有实际转换才产生这些注明,原生直传不产生。Responses 若只返回工具调用、拒绝块或空输出,不会为携带注明而合成文字消息。

下一轮保留原始用户输入和上一轮 assistant 内容,把注明一同带回:

// Chat Completions
messages.push(response.choices[0].message);

// Anthropic Messages:保留顶层扩展字段
messages.push({
  role: "assistant",
  content: response.content,
  annotations: response.annotations,
});

// Responses:保留 output_text 的 annotations
input.push(...response.output);

然后追加新的用户问题。流式客户端同样保留聚合结果中的注明;若自行组装消息,需从上述事件位置收集它们。SDK 类型未声明扩展字段时,使用其额外字段机制或读取原始 JSON。

带回内容与服务端解析结果、原文件、配置及模型能力匹配时,会跳过重新解析。修改内容、切换配置或缓存不可用时,不保证复用;客户端注明不能自行证明“已解析”。即使不再携带原文件,注明中的文字也会作为普通对话上下文送给模型。单独传 previous_response_id 不等于显式带回文件注明。

查看模型支持方式

GET /model/v1/models 的 architecture.input_modalities 使用 OpenRouter 形状,只列原生模态。原生 PDF 能力表示为 file,不会因启用插件而加入这一数组。

扩展字段 x_praxis.input_modalities 按输入类型标注 native、converted 或 unsupported:

{
  "architecture": { "input_modalities": ["text"] },
  "x_praxis": {
    "input_modalities": {
      "text": "native",
      "image": "converted",
      "pdf": "converted",
      "docx": "converted",
      "pptx": "converted",
      "xlsx": "converted",
      "audio": "unsupported",
      "video": "unsupported"
    }
  }
}

这是一个仅支持原生文字的对话模型的示例。标注由模型能力和已注册插件推导;converted 表示存在转换路径,实际请求还受租户设置、请求配置和限额约束。嵌入、OCR 等非对话调用不因此获得对话插件。音频、视频目前没有转换插件,只有模型原生支持时才可输入。

限制

  • 需要转换的文件必须提供内联内容,或使用当前入口支持的已授权文件引用;插件不下载任意外部 URL。
  • 当前支持 PDF、DOCX、PPTX、XLSX 和图片转换。Office 提取正文、幻灯片文字及单元格值,不解析嵌入对象或其中的图片。
  • 单文件最多 10 MiB,每请求累计最多 20 MiB;PDF 解析最多 100 页,付费 OCR 最多 20 页,每批最多 10 页;每请求最多 4 个付费子调用、同时最多 2 个。
  • 转换总时限为 90 秒;OCR 子调用最多 45 秒,图片描述最多 30 秒。流式转换期间可能先收到 : PRAXIS PROCESSING 保活注释,它不是模型输出。
  • 提取文字最多 1,000,000 字符,表格每 sheet 最多 1,000 行。模型自身的上下文和输入限制仍然适用。
  • Responses 仅在创建时转换新提交的输入;retrieve、cancel 等操作不重新转换。需要转换的 background: true 请求返回 PLUGIN_BACKGROUND_UNSUPPORTED。
  • 并发请求同时未命中缓存时,可能各自发起付费转换。主模型失败时,当前接口不保证返回已解析的文件注明。

会话亲和

同一对话的请求复用 x-session-id,新对话使用新 ID。会话亲和帮助符合条件的请求复用供应商,但不保证固定供应商或缓存命中。

会话 ID 不会替你保存 Chat Completions 的历史。每轮仍需发送必要消息;如需按响应 ID 接续,使用 Responses。

1. 准备终端

准备 Python 3.10+ 和 uv。将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY,不要写入代码或日志。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。退出码 1 表示变量未设置,先设置再继续。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

2. 两轮使用同一个 ID

示例生成一个 UUID,两轮都发送该请求头,并在第二轮带上第一轮的消息。

保存为 session-affinity.py,运行 uv run -q --no-project --with openai --with anthropic --with google-genai python session-affinity.py。

import os
import uuid
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')
session_id = str(uuid.uuid4())
messages = [{'role': 'user', 'content': 'Remember this code: LIME-742. Reply OK.'}]
for turn in range(2):
    response = client.chat.completions.create(model='deepseek-flash',
        messages=messages, reasoning_effort='none', max_tokens=256,
        extra_headers={'x-session-id': session_id})
    print('X-Request-Id:', response._request_id)
    assert response.choices[0].finish_reason == 'stop'
    answer = response.choices[0].message.content or ''
    assert answer
    print('turn', turn + 1, answer)
    if turn == 0:
        messages.append({'role': 'assistant', 'content': answer})
        messages.append({'role': 'user', 'content': 'What was the code? Reply with only the code.'})
    else:
        assert 'LIME-742' in answer, answer

期望:得到两条完成的响应、两个请求 ID,第二轮回答包含 LIME-742,退出码 0。这验证了请求和历史传递,不证明某个供应商或缓存命中;缓存用量另按 Prompt Cache 指南检查。

实测失败与处理

请求体传空 session_id 实测返回 HTTP 400:session_id must not be empty。使用非空会话 ID。请求体同时提供 session_id 时优先于请求头;避免在两个位置维护不同值,也不要让无关对话共用一个全局 ID。

字段细节:Chat Completions。

API 参考 → · Cookbook →

Prompt Cache 与成本

Prompt Cache 复用重复输入的前缀计算,不复用上一条回答。是否命中要看返回的缓存 token 数,不能凭回答相同或速度更快判断。

本页的 OpenAI 示例使用 deepseek-flash,不添加缓存控制参数。2026-10-12 实测:26,016 个输入 token 的第二次请求报告 25,856 个缓存 token;这是一组观测值,不是每次调用的保证。

1. 准备终端

将 Praxis 云 API Key 放入环境变量 PRAXIS_API_KEY;获取方式见 Key 与鉴权。不要把 Key 写入代码。准备 Python 和 uv;TypeScript 示例使用 Bun。

test -n "$PRAXIS_API_KEY"

期望:退出码为 0。示例默认使用 https://api.deepraxis.com。切换环境时,将 PRAXIS_API_BASE 设为相应 API 域名,不带末尾斜杠或 API 路径。

将每段 Python 保存为标明的文件名,以 uv run -q --no-project --with openai --with anthropic python <文件名> 执行。各段代码独立运行。

2. 连续请求相同前缀

将不常变的指令和资料放在前面,当前问题放在后面,保持模型、前缀内容和顺序一致。下面生成较长的演示资料并连续请求两次,会产生两次模型调用费用。

保存为 prompt-cache.py,运行 uv run -q --no-project --with openai --with anthropic python prompt-cache.py。

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/openai/v1')
prefix = '\n'.join(f'Record {i}: Paris is in France. The demo temperature is 22 Celsius.' for i in range(1500))
hits = []
for turn in range(2):
    response = client.chat.completions.create(model='deepseek-flash',
        messages=[{'role':'system','content':prefix},
            {'role':'user','content':'What country is Paris in? Reply with one word.'}],
        reasoning_effort='none', max_tokens=64)
    print('X-Request-Id:', response._request_id)
    assert response.choices[0].finish_reason == 'stop'
    usage = response.usage
    assert usage and usage.prompt_tokens_details is not None
    cached = usage.prompt_tokens_details.cached_tokens
    assert cached is not None
    hits.append(cached)
    print(turn + 1, 'prompt_tokens:', usage.prompt_tokens, 'cached_tokens:', cached)
assert hits[1] > 0, 'No cache hit observed on the second request'

期望:输出两次输入和缓存 token 数,第二次 cached_tokens > 0,退出码为 0。重复运行时第一次也可能命中,不要求第一次为零。

usage.prompt_tokens_details.cached_tokens 是输入中的缓存部分,已包含在 prompt_tokens 内,不能再相加。缓存只减少符合条件的输入费用,不使输出免费;成本按所选模型的缓存输入、普通输入和输出单价计算,不套用固定折扣。缓存最短前缀和保留时间由模型决定。

3. 用 TypeScript 检查命中

保存为 prompt-cache.ts,运行 bun prompt-cache.ts;无需安装 SDK。

import assert from 'node:assert/strict';
const base = process.env.PRAXIS_API_BASE ?? 'https://api.deepraxis.com';
const prefix = Array.from({length: 1500}, (_, i) =>
  `Record ${i}: Paris is in France. The demo temperature is 22 Celsius.`).join('\n');
const hits: number[] = [];
for (let turn = 0; turn < 2; turn++) {
  const response = await fetch(`${base}/openai/v1/chat/completions`, {
    method: 'POST', headers: {'Authorization': `Bearer ${process.env.PRAXIS_API_KEY}`, 'Content-Type': 'application/json'},
    body: JSON.stringify({model: 'deepseek-flash', max_tokens: 64, reasoning_effort: 'none',
      messages: [{role: 'system', content: prefix},
        {role: 'user', content: 'What country is Paris in? Reply with one word.'}]}),
  });
  console.log('X-Request-Id:', response.headers.get('x-request-id'));
  const data = await response.json();
  assert(response.ok, JSON.stringify(data));
  assert.equal(data.choices[0].finish_reason, 'stop');
  const cached = data.usage.prompt_tokens_details?.cached_tokens;
  assert.equal(typeof cached, 'number');
  hits.push(cached);
  console.log(turn + 1, 'prompt_tokens:', data.usage.prompt_tokens, 'cached_tokens:', cached);
}
assert(hits[1] > 0, 'No cache hit observed on the second request');

期望:第二次报告正数 cached_tokens,退出码为 0。

4. 观察 Anthropic cache_control

Messages 可在稳定文本块上设置 cache_control: {type: "ephemeral"}。本例使用 claude-opus-5,连续调用后分别读取 cache_creation_input_tokens(写入)和 cache_read_input_tokens(读取)。Messages 的 input_tokens 是未计入读写缓存部分的输入;总输入为这三项之和。

本次实测请求被接受,但两次读写缓存数均为零,因此此例用于观察缓存状态,不承诺命中。GET /openai/v1/models 的 supported_parameters 可查询参数声明,但不是每次缓存命中的证明;最终仍以用量字段为准。

保存为 prompt-cache-anthropic.py,运行 uv run -q --no-project --with openai --with anthropic python prompt-cache-anthropic.py。

import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ['PRAXIS_API_KEY'],
    base_url=os.environ.get('PRAXIS_API_BASE', 'https://api.deepraxis.com') + '/anthropic')
prefix = '\n'.join(f'Record {i}: Paris is in France. The demo temperature is 22 Celsius.' for i in range(1500))
hits = []
for turn in range(2):
    raw = client.messages.with_raw_response.create(model='claude-opus-5', max_tokens=64,
        system=[{'type':'text','text':prefix,'cache_control':{'type':'ephemeral'}}],
        messages=[{'role':'user','content':'What country is Paris in? Reply with one word.'}])
    print('X-Request-Id:', raw.headers.get('x-request-id'))
    message = raw.parse()
    assert message.stop_reason == 'end_turn'
    usage = message.usage
    assert usage.cache_read_input_tokens is not None
    assert usage.cache_creation_input_tokens is not None
    hits.append(usage.cache_read_input_tokens)
    print(turn + 1, 'input:', usage.input_tokens, 'write:', usage.cache_creation_input_tokens,
        'read:', usage.cache_read_input_tokens)
print('HIT' if hits[1] > 0 else 'MISS: no cached input reported')

期望:两次请求完成,输出读写统计和 HIT 或 MISS: no cached input reported,退出码为 0。只有读取 token 为正数才算观察到命中;退出码 0 只证明请求和统计读取成功。

实测未命中时怎么处理

本次 Messages 示例的实际结果是 cache_creation_input_tokens=0、cache_read_input_tokens=0,没有 HTTP 报错。不要把请求被接受等同于缓存生效。需要复现已验证的缓存命中时,运行上面的 deepseek-flash OpenAI 示例,并检查正数 cached_tokens。

为保持前缀可复用,确认两次请求使用相同模型和完整前缀;不要在前缀中加入时间戳或每次变化的标识。保留用量和 X-Request-Id 以便核对,不能用相同回答代替命中证据。

协议细节:OpenAI · Anthropic。

API 参考 → · Cookbook →

BYOK:接入你自己的模型密钥

BYOK 即将推出:控制台 → 模型 → BYOK 当前不可点击,标为「即将推出」,暂时无法在此导入自己的供应商密钥。当前可使用 Praxis 云 API Key,从模型目录选择模型,按模型快速开始调用。不要把供应商的 Key 当作 PRAXIS_API_KEY。

API 参考 → · Cookbook →

错误与限流

本页讲怎么办:错误码、重试策略、具体限流数字。原理见限流与错误,账号额度见配额与限流。

错误码速查

HTTP含义你该做什么
400请求参数有误检查请求体格式、字段名、模型 ID
401认证失败检查 API Key 是否完整、是否已过期
403权限不足或余额耗尽确认 Key 有效;检查账户余额
404路径或模型不存在检查 API 地址后缀;模型 ID 是否在目录中
429速率限制按 X-RateLimit-Reset 头等待后重试
500服务端错误稍后重试

常见 400 错误

STREAMING_UNPROVEN:该模型路由暂未开放流式。去掉 stream: true 或换模型。

模型不存在:到控制台 → 模型复制确切 ID。

缺少 max_tokens(Anthropic 协议):加 "max_tokens": 256。

重试策略

可重试:429(等重置)、500(指数退避 1s→2s→4s 最多 3 次)、网络瞬断(直接重试)。

不可重试:400(修正请求)、401(修复 Key)、404(修正地址或模型)。

速率限制

响应头含义
X-RateLimit-Limit-Requests每分钟最大请求数
X-RateLimit-Remaining-Requests本窗口剩余次数
X-RateLimit-Reset-Requests距离重置秒数

沙箱

它是什么

沙箱提供安全隔离的代码执行环境。你在沙箱里上传代码、运行脚本、安装依赖、访问文件——所有操作都在云端隔离实例中进行,不影响你的本地系统。

典型用途:

  • 让智能体执行代码并获取结果
  • 运行数据处理管道
  • 自动化测试

与其他服务的关系

  • 模型:沙箱里的代码可以调用模型 API
  • 智能体:智能体可以在沙箱中执行代码作为工具
  • 搜索:沙箱里的代码可以调用搜索 API

出口

快速开始 · 概念 · API 参考 →

快速开始

沙箱(Sandbox)提供安全隔离的代码执行环境——上传代码、运行、获取结果,全程在云端隔离实例中进行。

前提

一个 Praxis API Key。到 控制台 → API Keys 创建。

1. 查看可用环境

沙箱支持多种执行环境(内置环境只读,租户可自建):

curl https://api.deepraxis.ai/sandbox/v1/environments \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

2. 创建沙箱实例

通过 API 创建实例,指定环境和初始资源。

3. 执行代码

在沙箱中运行代码并获取输出。

4. 释放实例

使用完后释放,避免持续计费。

下一步

概念

概念一句话
环境(Environment)代码运行的隔离实例,包含预装工具和运行时
实例(Instance)一个正在运行的沙箱环境
生命周期创建 → 使用 → 释放,释放后不可恢复
网络限制部分环境限制出站网络(limited networking)

指南

你想做什么去哪
第一次使用沙箱快速开始
处理错误和限制限制与错误

限制与错误

通用错误码和重试策略见模型的错误与限流——同一套 HTTP 语义适用于所有服务。

沙箱特有的注意事项:

事项说明
实例数上限每个账户空间同时运行的沙箱实例有上限
执行超时单次代码执行有超时限制
网络限制部分环境限制出站网络(limited networking)
包安装失败会终止会话,错误码 ENVIRONMENT_PACKAGE_INSTALL_FAILED

遇到沙箱特有的错误,参考错误码文档并联系我们。

搜索

Roadmap

该服务尚未全面开放。本页先说明它是什么、解决什么问题,以及上线后的接入形态。

它是什么

搜索为 AI 应用提供网络搜索能力——传入查询,返回结构化结果。结果可以直接用于 RAG(检索增强生成)管道,或作为智能体的工具。

典型用途:

  • 让智能体能获取实时网络信息
  • 构建基于网络的问答系统
  • 补充知识库的时效性缺口

与其他服务的关系

  • 模型:搜索结果作为上下文传给模型生成回复
  • 智能体:搜索作为智能体的可用工具之一
  • 沙箱:沙箱里的代码可以调用搜索 API

出口

快速开始 · 概念 · API 参考 →

快速开始

搜索(Search)为 AI 应用提供网络搜索能力——返回结构化结果,可直接用于 RAG 或智能体工具调用。

前提

一个 Praxis API Key。到 控制台 → API Keys 创建。

1. 执行搜索

通过 Search API 执行一次搜索,获取结构化结果。

2. 在智能体中使用

搜索可以作为智能体的工具——智能体自主决定何时搜索、如何整合结果。

3. 构建知识库问答

搜索与模型结合,构建基于网络信息的问答系统。

下一步

概念

概念一句话
查询(Query)你传入的搜索关键词或问题
结果(Result)结构化的搜索返回,包含标题、摘要、链接
RAG检索增强生成——先搜索再让模型基于结果回答

指南

你想做什么去哪
第一次使用搜索快速开始
处理错误和限制限制与错误

限制与错误

通用错误码和重试策略见模型的错误与限流。

搜索特有的注意事项:

事项说明
搜索频率受账户空间速率限制约束
结果数量每次搜索返回的结果条数有上限
内容时效搜索结果反映当前网络内容,不保证时效性

遇到搜索特有的错误,参考错误码文档并联系我们。

云端浏览器

云端浏览器是一个在云上运行的 Chrome,可以用 Playwright 或 Puppeteer 直接连接操控,也可以在 Console 实时观看与接管。

三种用法

以下示例在 Node.js 中运行。先设置 PRAXIS_API_KEY,将代码保存为 .mjs 文件,再用 node 执行。只需安装客户端包,无需下载本地 Chrome。

export PRAXIS_API_KEY="你的 API Key"
npm install playwright @browserbasehq/sdk

一步连接

连接 wss://api-develop.deepraxis.ai/browser/v1/connect 即可新开一个浏览器。?session=<id> 连接已有会话;?name=<名称> 有就连接,没有就创建。名称可用 1–128 个字母、数字或 . _ : -,不能同时传 session 和 name。

import { chromium } from "playwright";

const headers = { Authorization: `Bearer ${process.env.PRAXIS_API_KEY}` };
const browser = await chromium.connectOverCDP(
  "wss://api-develop.deepraxis.ai/browser/v1/connect",
  { headers },
);
const cdp = await browser.newBrowserCDPSession();
const session = await cdp.send("Praxis.getSession");
console.log(session.sessionId, session.liveViewUrl, session.consoleUrl);
try {
  const page = await browser.contexts()[0].newPage();
  await page.goto("https://api-develop.deepraxis.ai/meta/v1/health");
  console.log(await page.textContent("body"));
} finally {
  await browser.close();
  // 用完立即释放,停止计费。
  const closed = await fetch(
    `https://api-develop.deepraxis.ai/browser/v1/sessions/${session.sessionId}`,
    { method: "POST", headers: { ...headers, "Content-Type": "application/json" },
      body: JSON.stringify({ status: "REQUEST_RELEASE" }) },
  );
  if (!closed.ok) throw new Error(await closed.text());
}

新建时还可带 context、persist、timeout(秒)、idle(毫秒)、labels 和 viewport。后两项用 JSON.stringify 配合 URLSearchParams 编码;连接已有会话时,这些新建参数不生效。创建会话返回的 connectUrl 自带凭证,连接时无需再带 API Key;不要公开此地址。

Browserbase 官方 SDK

使用自己的 API Key,把官方 SDK 的 baseURL 设为 https://api-develop.deepraxis.ai/browserbase。SDK 自动发送 X-BB-API-Key。无需 Browserbase 项目,projectId 可省略。

import Browserbase from "@browserbasehq/sdk";
import { chromium } from "playwright";

const bb = new Browserbase({
  apiKey: process.env.PRAXIS_API_KEY,
  baseURL: "https://api-develop.deepraxis.ai/browserbase",
});
const session = await bb.sessions.create({ timeout: 300, keepAlive: true });
try {
  const browser = await chromium.connectOverCDP(session.connectUrl);
  try {
    const page = await browser.contexts()[0].newPage();
    await page.goto("https://api-develop.deepraxis.ai/meta/v1/health");
    console.log(await page.textContent("body"));
  } finally {
    await browser.close();
  }
} finally {
  await bb.sessions.update(session.id, { status: "REQUEST_RELEASE" });
}

可调用的端点见 Browserbase 兼容 API。代理、反检测、扩展和 Selenium 不可用;region、solveCaptchas 当前接受但忽略,不会选区域或代解验证码。

REST 先创建再连接

POST /browser/v1/sessions 返回的 connectUrl 包含连接凭证,有效期覆盖会话寿命。直接交给 Playwright 或 Puppeteer,无需再传 API Key。

import { chromium } from "playwright";

const base = "https://api-develop.deepraxis.ai/browser/v1";
const headers = {
  Authorization: `Bearer ${process.env.PRAXIS_API_KEY}`,
  "Content-Type": "application/json",
};
const response = await fetch(`${base}/sessions`, {
  method: "POST", headers, body: JSON.stringify({ timeout: 300, keepAlive: true }),
});
if (!response.ok) throw new Error(await response.text());
console.log("X-Request-Id:", response.headers.get("X-Request-Id"));
const session = await response.json();
try {
  const browser = await chromium.connectOverCDP(session.connectUrl);
  try {
    const page = await browser.contexts()[0].newPage();
    await page.goto("https://api-develop.deepraxis.ai/meta/v1/health");
    console.log(await page.textContent("body"));
  } finally {
    await browser.close();
  }
} finally {
  const closed = await fetch(`${base}/sessions/${session.id}`, {
    method: "POST", headers, body: JSON.stringify({ status: "REQUEST_RELEASE" }),
  });
  if (!closed.ok) throw new Error(await closed.text());
}

完整请求参数见云端浏览器 API。Puppeteer 使用 puppeteer.connect({ browserWSEndpoint: session.connectUrl }) 连接同一地址。

实时观看与接管

打开 Console 的 浏览器 › 会话,选择会话即可观看并接管。程序也可通过 CDP 命令 Praxis.getSession 取得 sessionId、liveViewUrl 和 consoleUrl。需要用户完成登录或确认操作时,调用请求人工接管,完成后调用完成人工接管。

会话状态为 PENDING、RUNNING、ERROR、TIMED_OUT 或 COMPLETED。API 创建且 keepAlive=false 的会话在客户端断开且无人观看后结束;一步连接和 Console 打开的会话默认在无人连接或观看 5 分钟后释放。单次最长 6 小时。没有暂停或休眠;下次继续使用登录态,需要 Context。

登录态

先创建 Context,再在创建会话时传 browserSettings.context。persist: true 会在会话关闭时保存 cookie 与 localStorage;不保存 IndexedDB。同一登录态正在被写入时,另一会话可能收到 409 CONTEXT_LOCKED。

// 接上面的 REST 初始化代码;contextId 为创建 Context 返回的 id。
const response = await fetch(`${base}/sessions`, {
  method: "POST", headers,
  body: JSON.stringify({
    timeout: 300,
    browserSettings: { context: { id: contextId, persist: true } },
  }),
});

计费与限制

  • $0.12 / 浏览器小时,按秒计费,单会话 1 分钟起。平台原因导致的失败只按实际秒数计费;释放后不再计费。
  • 每个账户默认最多同时 10 个会话,每个会话最长 6 小时。
  • 不支持代理、验证码代解、反检测、选区域或扩展。代理、反检测和扩展等不支持的配置返回 501;region、solveCaptchas 当前接受但忽略。
  • 部分网站(如 Google、百度)会把云端浏览器识别为机器人。
  • 排查请求时保留响应头 X-Request-Id。

应用

应用把一个站点(静态文件、一段 Worker 代码,或两者)发布到平台上,并给它一个可访问的地址。每次发布产生一个版本,部署后成为线上版本。

存储放哪儿

订单、会话等运行数据放在应用自带的 d1、kv 或 r2 components 中,由应用代码执行业务规则。结构化内容用 Database,文件用 File,可检索知识用 Knowledge:它们是共享内容库,应用可以通过公开 API 读取,但不作为应用的私有运行库。

  • 运行时能力:应用里能做什么、不能做什么,以及各项限制。

运行时能力

下表是应用当前的运行时能力。

发布与部署

能力状态说明
Worker → 版本 → 部署支持创建版本不会上线;部署后才成为线上版本。创建版本时设置 deploy: true 可一步完成
回滚支持对旧版本再创建一次部署,无需重新上传文件
整站替换支持每个版本的文件清单必须完整列出站点的全部文件;只有内容变化的文件需要上传,没变的不会重传
灰度发布不支持一个部署只能让一个版本占 100%
保留版本数限制最近 100 个版本
单个脚本大小限制压缩后 10 MiB
部署超时限制10 分钟内没有完成即失败

静态资源

能力状态说明
内容寻址去重支持相同内容只存一份,重复发布不会重复上传
路径与大小限制每个版本最多 100000 个文件,单个文件最大 25 MiB
html_handling支持可选 auto-trailing-slash、force-trailing-slash、drop-trailing-slash、none
not_found_handling支持可选 none、404-page、single-page-application
_headers支持仅作用于静态资源的响应,最多 100 条规则,每行最多 2000 字符
_redirects支持仅作用于静态资源的响应,最多 2000 条静态规则和 100 条动态规则,每行最多 1000 字符

_headers 和 _redirects 不会作用于 Worker 代码生成的响应。需要跨域访问时,在代码里自己设置 CORS 头:

const CORS = {
  "access-control-allow-origin": "*",
  "access-control-allow-methods": "GET, POST, OPTIONS",
  "access-control-allow-headers": "content-type",
};

export default {
  async fetch(request) {
    if (request.method === "OPTIONS") return new Response(null, { status: 204, headers: CORS });
    return Response.json({ ok: true }, { headers: CORS });
  },
};

出站请求

能力状态说明
访问公网地址支持代码里的 fetch() 可以请求任意公网地址
访问平台上的其他应用支持请求其他应用的地址,得到的是对方应用的响应

绑定

类型状态说明
d1支持在创建版本时声明
kv_namespace支持在创建版本时声明
r2_bucket支持在创建版本时声明
secret_text支持在创建版本时声明;值通过密钥接口写入
plain_text支持在创建版本时声明
json支持在创建版本时声明
praxis_token支持启用 owner 组织的平台 API 访问,不注入 token
praxis_vault_ref不可用当前不能使用
其他类型(如 service、queue、durable_object_namespace、ai)不支持创建版本时返回 UNSUPPORTED

密钥

能力状态说明
写入密钥支持PUT /application/v1/workers/{id}/secrets/{name}。写入后会基于当前版本派生一个新版本并部署
读回密钥值不支持值只写不读,任何接口都不会返回

兼容标志

能力状态说明
compatibility_flags支持创建版本时传入,会原样生效
global_fetch_strictly_public固定开启每个版本都会带上,不能关闭;传入 global_fetch_private_origin 或 no_global_fetch_strictly_public 会返回 400

调用平台 API

在要部署的版本中声明绑定:

{ "type": "praxis_token", "name": "PRAXIS_API_TOKEN" }

应用可以不带 API Key 调用 Develop API:

export default {
  async fetch() {
    return fetch("https://api-develop.deepraxis.com/application/v1/workers");
  }
};

应用拥有与 owner 组织 API Key 相同的组织访问权限。其他组织的资源返回不存在。应用代码不会收到 token 或密钥,也不会生成 env.PRAXIS_API_TOKEN。未声明该绑定时,受保护接口返回 401,除非请求自行提供有效凭证。其他目标域名的请求保持原样。

Praxis SDK 使用同一条访问路径:省略 apiKey,将 baseUrl 设为 Develop API 地址,无需修改 SDK。已有应用需要部署声明该绑定的版本后才能启用。移除绑定会在新部署生效、路由缓存刷新后停止注入身份(最多 10 秒)。

文件

文件服务保存被素材库、知识库和对话引用的原始文件。上传后得到 file_id,再把它交给需要使用文件的服务。

素材库组织素材及其理解结果;知识库提取文档内容并建立检索索引。只上传文件,不会自动将它加入库,也不会自动让它可检索。

快速开始 · 概念 · 限额与错误

API 参考 → · Cookbook →

快速开始

安装 SDK,并将 API Key 设置在环境变量 PRAXIS_API_KEY 中。把下面的 Python 代码保存为 files.py。

使用 Develop 时,另设 PRAXIS_API_BASE=https://api-develop.deepraxis.ai;未设置时使用正式 API。Key 必须属于所选环境。

python -m pip install openai
python files.py
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["PRAXIS_API_KEY"],
    base_url=os.environ.get("PRAXIS_API_BASE", "https://api.deepraxis.com") + "/openai/v1",
)
file = client.files.create(
    file=("hello.txt", b"Hello, Praxis!\n", "text/plain"),
    purpose="user_data",
    expires_after={"anchor": "created_at", "seconds": 3600},
)
try:
    print("uploaded:", file.id)
    files = client.files.list(limit=100)
    print("listed:", any(item.id == file.id for item in files))
    content = client.files.content(file.id).read()
    assert content == b"Hello, Praxis!\n"
    print("downloaded:", content.decode().strip())
finally:
    deleted = client.files.delete(file.id)
    print("deleted:", deleted.deleted)

期望:输出上传的 ID、listed: True、downloaded: Hello, Praxis! 和 deleted: True。即使列出或下载失败,示例也会删除自己创建的文件。

API 参考 → · Cookbook →

概念

purpose 用途

purpose 记录文件的预期用途。上传接受三个值:

purpose用途与限制
user_data通用输入文件。在对话中引用图片时必须使用此值。
assistants用于智能体或知识库工作流的文件。上传不等于加入智能体或知识库;对话图片引用不接受此用途。
vision只接受 PNG、JPEG、GIF、WebP 图片(按内容识别),其他内容返回 400 FILE_TYPE_UNSUPPORTED;对话图片引用不接受此用途。

上传不接受 batch、fine-tune 和 evals。通过 Anthropic 上传的文件统一记为 user_data。

引用 file_id

同一账户的 /file/v1/files、/openai/v1/files 和 /anthropic/v1/files 共用文件 ID,可跨接口读取元数据和删除。上传文件的下载使用文件 API 或 OpenAI API;Anthropic 下载仅支持会话输出文件。

  • 对话:图片以 user_data 上传,用 type: "file" 与 file.file_id 引用。不要将 ID 填进 image_url.url。文本和 PDF 的文件 ID 当前不能作为对话图片输入。参见图片引用示例。
  • 素材库:用 file_id 导入,生成素材记录。参见素材 API。
  • 知识库:将 file_id 加入库后,才会提取内容并建立索引。参见知识库概念。
  • 智能体:用 type: "file"、file_id 和 mount_path 将文件加入会话资源。仅设置 purpose=assistants 不会挂载文件。参见会话资源 API。

会话输出文件的元数据可包含 scope、relative_path 和 revision。用 scope_id 列出某个会话的文件;Anthropic 使用该筛选时需要 anthropic-beta: managed-agents-2026-04-01 请求头。

过期

不设置过期时间的上传文件保留到被删除。expires_after 从 created_at 起计算秒数,响应中的 expires_at 是到期时间。过期文件不能再读取或引用。可用范围见限额与错误。

下载安全

下载内容被识别为 HTML、SVG、XML 或 JavaScript 时,以附件下发,并设置 X-Content-Type-Options: nosniff。类型按内容识别,不依赖扩展名:上传的 HTML 或 SVG 也可能被识别为纯文本并以纯文本返回。下载不会执行其中的内容。

分段下载

内容下载支持单个 Range 区间:bytes=0-1023、bytes=1024-(从某处到结尾)或 bytes=-1024(最后 1024 字节)。命中时返回 206 和 Content-Range;区间超出文件大小返回 416。所有下载响应都带 Accept-Ranges: bytes,可用于断点续传和音视频拖动。

API 参考 → · Cookbook →

限额与错误

限额

项目限额
单文件100 MiB(104,857,600 字节);不接受空文件
文件存储配额20 GiB(21,474,836,480 字节),账户内文件共享
OpenAI 或文件 API 的 expires_afteranchor: "created_at",3,600–2,592,000 秒(1 小时至 30 天)
文件 API 的 praxis[expires_in_seconds]3,600–7,776,000 秒(1 小时至 90 天)
Anthropic 表单 expires_in_seconds3,600–7,776,000 秒(1 小时至 90 天)

过期参数只能选一种。Anthropic 的 expires_in_seconds 只能放在表单中;放在查询参数里返回 400。参见上传参数。

未知字段

兼容接口按各自官方的方式拒绝未知参数,不会静默忽略:

接口可用参数其他参数
Anthropic 上传表单 file、expires_in_seconds;查询参数仅 beta400 invalid_request_error,如 purpose: Extra inputs are not permitted
OpenAI 上传表单 file、purpose、expires_after[anchor]、expires_after[seconds];无查询参数400 invalid_request_error,如 Unrecognized request argument supplied: foo,param 为该字段
OpenAI 列表查询参数 after、order、limit、purpose同上

praxis[expires_in_seconds] 与 scope_id 等扩展只在文件 API 中可用。

按内容识别 PDF、DOCX/XLSX/PPTX、PNG/JPEG/GIF/WebP、支持的音视频格式和 UTF-8 文本。改扩展名不能让任意二进制文件变成受支持类型。

错误处理

错误处理方式
PURPOSE_UNSUPPORTED传入 user_data、assistants 或 vision。
FILE_EMPTY上传非空文件。
FILE_TOO_LARGE将单文件控制在 100 MiB 以内。
FILE_TYPE_UNSUPPORTED使用支持的文件内容类型,仅修改扩展名无效;purpose=vision 只接受图片;损坏的 Office 文档或 ZIP 压缩包也会返回此错误。
VALIDATION_ERROR检查文件名与过期参数。
QUOTA_EXHAUSTED删除不再需要的文件后重试上传。
HTTP 404检查 ID、账户以及文件是否已过期或删除。
File is not an eligible image在对话中使用支持的图片,并以 purpose=user_data 重新上传。

过大的请求可能先被边缘拒绝,返回 HTTP 413 与 HTML 正文,而不是文件 API 的 JSON 错误。解析错误正文前先检查 HTTP 状态。

API 参考 → · Cookbook →

素材库

素材库为智能体存放、理解和检索图片、视频、音频与文档。一个库是一个隔离单元,素材、文件夹与检索都限定在库内。官方 Cloudinary SDK 可以直接使用。

快速开始 · 概念 · 限制与错误

API 参考 →

快速开始

准备 API Key,将下方代码保存为 quickstart.mjs,在 Node.js 中运行。示例建库、上传图片、按标签检索,并给出原图与缩略图地址。

npm install cloudinary@2
# Set PRAXIS_API_KEY in your environment.
node quickstart.mjs
import { v2 as cloudinary } from "cloudinary";

const base = "https://api.deepraxis.ai";
const key = process.env.PRAXIS_API_KEY;
if (!key) throw new Error("Set PRAXIS_API_KEY");
async function media(path, method = "GET", body) {
  const response = await fetch(base + "/media/v1" + path, {
    method,
    headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  });
  if (!response.ok) throw new Error(`Media HTTP ${response.status}`);
  return response.json();
}
const library = await media("/libraries", "POST", { name: "Agent media" });
cloudinary.config({
  cloud_name: library.id,
  oauth_token: key,
  upload_prefix: base + "/cloudinary",
});
const asset = await cloudinary.uploader.upload(
  "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAABWklEQVR4nO3OQQ0AMBAEofVv+iqDxzRBALvtg/wgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwg7gEgaMOyrMtNTwAAAABJRU5ErkJggg==",
  { public_id: "quickstart/pixel", tags: ["docs"] },
);
const found = await cloudinary.search.expression("resource_type:image AND tags:docs").execute();
const delivery = asset.secure_url;
const thumbnail = delivery.replace("/upload/", "/upload/w_160,c_fit/");
const understandings = await media(`/libraries/${library.id}/assets/${asset.asset_id}/understandings`);
const current = await media(`/libraries/${library.id}/assets/${asset.asset_id}`);
const matches = current.semantic === "ready"
  ? await media(`/libraries/${library.id}/search/semantic`, "POST", { query: "a small image", top_k: 5 })
  : null;
console.log({ library_id: library.id, asset_id: asset.asset_id, found, delivery, thumbnail, understandings, matches });

理解异步完成。上传响应不含处理状态;用 GET 读取素材,查看 processing 与 semantic。实测量级:图片约半分钟,3 秒视频约 50 秒,raw 文件立即就绪(不做理解)。首次读取可能为空;matches 为 null 时稍后重新读取素材,semantic 为 ready 后再检索。理解与语义检索可用;上传、表达式检索和原文件读取无需等待理解完成。

示例保留库供继续使用。用完后,把 LIBRARY_ID 设为输出的 library_id 并删除:

curl -X DELETE "https://api.deepraxis.ai/media/v1/libraries/${LIBRARY_ID}" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

API 参考 →

概念

一个库是素材的隔离单元。检索只会命中当前库的素材。

库与素材

素材有稳定的 asset_id。public_id 是交付地址中的名称,可以带路径。asset_folder 组织素材;移动文件夹不改变 public_id。按 asset_folder 列出素材只匹配这一层,不包含子文件夹;空字符串表示根。display_name 是显示名称,不决定交付地址。音频使用 resource_type=video,并带 is_audio=true。

库名最长 64 个字符,可以重名;库以 library_id 区分。更新素材的 tags、context 或库的 labels 都是整体替换:传入的值就是新的全部内容,传 (tags 传 [])即清空。

上传 0 字节文件返回 400 FILE_EMPTY;声明或识别为图片却读不出宽高(例如被截断)的文件返回 400 Invalid image file。其他任意文件按 raw 存放。

理解

理解可以产出描述、标签、图片文字、音频转写与视频帧内容。片段结果带起止时间。记录不可变;新版本可以替代旧记录,默认不返回被替代的记录。

整体状态为 pending(等待)、processing(处理中)、ready(完成)、partial(部分完成)或 failed(失败)。单项状态为 pending、running、ready、failed、unsupported(不支持)或 skipped(跳过)。关闭理解或不支持的内容不会阻止原文件读取。

语义检索

当 semantic 为 ready,即可自然语言检索。默认混合检索结合关键词与语义,也可只用语义。命中带匹配内容;音视频片段带起止毫秒,方便定位。从未完成理解的素材不进入语义结果;重新处理中的素材继续用上一版结果参与检索,直到新结果就绪。

文档与其他文件(raw)

PDF、文本等 raw 文件只存放,不做理解,semantic 为 unavailable,不进入语义检索,只能按元数据(public_id、display_name、tags、context 等)用表达式检索。需要按全文内容检索文档,请使用知识库。

删除与恢复

删除后立即不可见。库可在 7 天内恢复,到期后无法恢复。单个素材没有恢复接口。

与文件的区别

「文件」存放原始文件;素材库进一步组织、理解和检索内容。从文件导入是拷贝:两份文件独立,之后的修改或删除不会互相同步。

API 参考 →

限制与错误

视频超过 100 MiB 时不生成帧与音轨理解,也不生成预览海报;仍可存放和读取原文件。

限制数值
每账户库数100
每库素材数1000000
图片最大字节数(50 MiB)52428800
视频最大字节数(2 GiB)2147483648
文档最大字节数(100 MiB)104857600
单请求最大字节数;更大需分片100000000
非末片最小字节数;各片等长5242880
分片会话有效小时数24
视频帧与音轨理解源文件字节上限104857600
表达式检索每页上限500
表达式检索默认条数100
语义检索命中上限50
语义候选上限100
以图搜图上传图片最大字节数(10 MiB)10485760
每份素材文字向量上限(图片向量另计)61
故事板画面上限12
每库同时理解的任务数4
理解超时毫秒数600000
理解最大重试次数3
每素材标签上限1000
标签字符上限255
上下文字节上限1024
批量素材 ID 上限1000
库恢复天数7
库名字符上限;允许重名64
bge-m3 语义检索最低余弦相似度0.55

错误码与重试规则 →

知识库

知识库将文件内容分段并建立索引,让应用按语义、关键词和文件属性查找相关文本,适合问答检索、政策查询和给模型提供参考资料。

服务适合保存和查找什么
文件上传、保存和下载原始文件,提供文件 ID
素材库组织素材、管理文件夹与标签,并查找图片等素材
知识库从文件内容中查找相关文本分段,返回来源文件与内容

一个源文件可以加入多个知识库。从知识库移除文件不会删除源文件;删除源文件后,它会自动从所有知识库中移出,之后不再被搜索到。

  • 快速开始:用官方 openai Python SDK 上传、入库和搜索。
  • 概念:处理状态、分段、搜索与恢复。
  • 限制与错误:容量、文件格式与失败处理。

知识库 API → · OpenAI 兼容接口 →

快速开始

在服务端运行 Python,将 develop API Key 放入 PRAXIS_API_KEY 环境变量。示例连接 develop,每次创建一个知识库和两个文本文件,并在结束时删除它们。

python -m pip install openai

保存为 quickstart.py,运行 python quickstart.py。先上传政策文件,再建库并等待处理完成;upload_and_poll 将第二个文件的上传、添加和等待合成一次调用。上传成功后仍需检查入库状态。

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["PRAXIS_API_KEY"],
    base_url="https://api.deepraxis.com/openai/v1",
    timeout=120.0,
)
store = None
file_ids = []
try:
    uploaded = client.files.create(
        file=("refund-policy.txt", b"Refunds are available within 30 days of purchase."),
        purpose="assistants",
    )
    file_ids.append(uploaded.id)
    print("uploaded:", uploaded.id)

    store = client.vector_stores.create(
        name="Knowledge quickstart",
        file_ids=[uploaded.id],
    )
    print("store:", store.id)
    first = client.vector_stores.files.poll(
        uploaded.id, vector_store_id=store.id, poll_interval_ms=1000,
    )
    if first.status != "completed":
        raise RuntimeError(f"Ingestion failed: {first.last_error}")

    attached = client.vector_stores.files.upload_and_poll(
        vector_store_id=store.id,
        file=("refund-timing.txt", b"Refunds are processed in 5 business days."),
        poll_interval_ms=1000,
    )
    file_ids.append(attached.id)
    print("ingestion:", attached.status)
    if attached.status != "completed":
        raise RuntimeError(f"Ingestion failed: {attached.last_error}")

    results = client.vector_stores.search(
        vector_store_id=store.id,
        query="How many business days does a refund take?",
        max_num_results=5,
    )
    assert results.data, "Expected at least one matching chunk"
    for result in results.data:
        print(result.filename, result.score)
        for part in result.content:
            print(part.text)
    print("pagination:", results.has_more, results.next_page)
finally:
    try:
        if store is not None:
            deleted = client.vector_stores.delete(store.id)
            print("store deleted:", deleted.deleted)
    finally:
        for file_id in file_ids:
            deleted = client.files.delete(file_id)
            print("file deleted:", file_id, deleted.deleted)
        client.close()

输出包含文件 ID、知识库 ID、ingestion: completed、命中文本与清理结果。每个文件一条结果,命中的文本块按相关度排列。搜索不支持翻页;结果没有被截断时最后输出 pagination: False None。

2026-10-11 的 develop 实测中,文本、Markdown、PDF、DOCX、CSV、JSON 与 HTML 处理约需 4–21 秒;一张 PNG 约需 87 秒。这些是样例耗时,不是时延保证。服务允许处理至多 30 分钟;失败时查看 last_error。

示例删除知识库后,知识库会保留 7 天供恢复;源文件单独删除。若要继续使用这些资料,请在自己的程序中将清理步骤放到使用结束之后。

查看请求字段 → · 理解搜索结果 →

概念

知识库与文件

知识库使用 vs_ 开头的 ID。/knowledge/v1 和 /openai/v1/vector_stores 访问相同数据;前者额外提供恢复、失败重试、分段、事件和搜索解释。所有示例都使用 API Key,从服务端调用。

先通过文件服务上传,得到 file_id,再将其加入知识库。知识库内文件 ID 与源文件 ID 相同。重复加入已有文件会返回原记录,不重新处理,也不会应用新的属性或分段策略。批次也一样:已在库中的文件出现在批次里不会重新入库,批次的 attributes 和 chunking_strategy 只作用于新加入的文件。要改属性,用更新文件接口(POST /vector_stores/{id}/files/{file_id});要换分段策略,先移除该文件再重新加入。

处理状态

status含义下一步
in_progress内容正在处理轮询文件状态或使用 SDK 的 poll 方法
completed内容已可搜索发起搜索
failed处理失败查看 last_error,修正文件或通过原生 API retry
cancelled已取消处理先移除,再按需要重新添加

图片(PNG、JPEG、GIF、WebP)入库的内容是模型生成的画面描述,不是逐字 OCR:图中的文字可能被概括或遗漏,搜索命中的是这段描述。图片入库比文本慢,实测单张约数秒到数十秒,多个文件同时入库时更久。

原生 API 的 praxis.stage 给出 queued、parse、chunk、embed、index 进度。完成后 stage 为 null。知识库的 status 由处理情况与过期状态决定;空库也可为 completed,因此它不表示库内一定有内容。

批次把多个文件添加到同一知识库,最多 500 个。file_ids 和逐文件配置的 files 二选一。使用 files 时忽略顶层 attributes 和 chunking_strategy。取消只对进行中的批次生效;对已完成、已失败或已取消的批次调用取消,会原样返回当前状态。

分段

默认 auto 会转为每段 800 个估算 token、相邻重叠 400 token。static 可设置每段 100–4096 token;重叠不得超过段大小的一半。token 数为估算值,不等同于具体模型 tokenizer 的结果。

原生 API 可读取每段的文本、序号、估算 token、页码、UTF-16 起止位置和相邻重叠量;页码可能为 null。读取文件 content 得到的是分段文本,拼接时需考虑重复内容。usage_bytes 是已入库文件的源文件大小(字节),未完成入库的文件为 0;知识库的 usage_bytes 是其中所有文件之和。

搜索结果

/openai/v1/vector_stores/{id}/search 与 OpenAI 一致:每个文件一条结果,content 按相关度从高到低列出该文件命中的文本块,score 取其中最高分,max_num_results 限制的是文件数(默认 10、最多 50)。/knowledge/v1 的搜索按块返回,同一文件可能出现多次,适合直接拼接成模型的参考资料。

query 可为字符串或字符串数组;数组会以换行连接后共同搜索。filters 筛选文件属性,支持 eq、ne、gt、gte、lt、lte、in、nin 和 and/or 组合。比较按类型匹配,例如数字 1 与字符串 "1" 不相同。

默认混合语义与关键词检索,权重为 0.7/0.3。原生 API 可通过 ranking_options.hybrid_search 调整权重并用 praxis.explain: true 查看解释;在 OpenAI 兼容接口传 hybrid_search 会返回 400。只有 ranker 为 default-2024-11-15 时请求重排;none、auto 和省略时使用初始排序。重排的效果尚未单独实测。

score 在 0–1 之间,是同一次搜索内的相对排序依据,不是相关性概率,也没有默认下限:库里不相关的内容也会返回,分数可能与相关内容很接近(实测相关文件 0.70,不相关文件 0.61–0.62)。需要过滤时设置 ranking_options.score_threshold,低于该值的结果不返回;OpenAI 同样默认不设阈值。可以从 0.5 起试,再用自己的资料和典型问题看相关与不相关结果的分数,逐步上调到不再误删相关结果为止。

rewrite_query: true 时先改写查询再检索,见下方「模型」。has_more 表示还有更多结果被 max_num_results 截掉;搜索不支持翻页,next_page 恒为 null。原生结果附带 chunk_id、page、span;兼容结果不含这些字段和 praxis。

模型

每个知识库有三个模型角色,创建时可在原生 API 的 models 里指定,之后用更新接口修改;不指定就用默认模型。读取知识库时 models 返回各角色当前生效的模型。

角色作用默认模型
embedding给分段和查询生成向量bge-m3
rerankranking_options.ranker 为 default-2024-11-15 时重排结果bge-reranker-base
query_rewrite搜索设置 rewrite_query: true 时改写查询qwen3.8-flash

库中已有文件后不能更换 embedding(已有向量来自原模型),更换会返回 409;其他角色随时可改。模型必须在模型目录中并支持该角色,嵌入模型还必须输出 1024 维向量,否则返回 400。某个角色设为 null 恢复它的默认模型,models: null 恢复全部默认。OpenAI 兼容接口不返回 models,传入会返回 400(与 OpenAI 对未知参数的处理一致),但搜索同样按知识库设置的模型执行。

rewrite_query: true 时,搜索前先用 query_rewrite 模型把问题改写成简洁的检索查询(例如「退货以后多久能收到退款?」→「退款到账时间」),用改写结果检索,search_query 返回改写后的查询;query 为数组时逐条改写。每次改写是一次小模型调用,会产生费用,按模型用量计费,并增加约 1–2 秒延迟。改写失败、超过 5 秒或返回为空时,该条查询回退为原文继续搜索,不会让搜索失败。不传或为 false 时不调用模型。

更新、过期与删除

name 省略时默认为空字符串;创建时不接受 null,更新时 null 会清空名称。metadata 更新会替换全部内容;null 清空。description 被接受后丢弃,不能用来保存说明。未知写入字段当前会被忽略,拼写错误不一定报错。

expires_after 只接受 last_active_at 和 1–365 天;搜索和添加文件刷新活动时间,null 取消自动过期。删除后,知识库进入「删除中」:/knowledge/v1 读取返回 status: deleting 和清除时间 purge_at,默认列表不再显示(用 status=deleting 可以列出),搜索、更新和添加文件返回 409;7 天内可以 restore 恢复,也可以用 purge 立即永久删除(不可撤销)。/openai/v1/vector_stores 与 OpenAI 一致,删除后读取返回 404。删除知识库不会删除「文件」中的源文件。

API 参考 → · 上限与错误 →

限制与错误

容量

以下为当前 API 上限。下方耗时来自样例实测,不是服务保证。

项目上限
每个组织的未删除知识库100
每个知识库的文件10,000
单个源文件100,000,000 bytes (100 MB)
单个文件估算 token5,000,000
每批文件1–500
知识库名称256 characters
metadata / attributes 键数16
键长度 / 字符串值长度64 / 512 characters
静态分段大小100–4096 tokens
默认分段 / 重叠800 / 400 tokens
分段重叠上限≤ ½ chunk size
搜索返回结果数(兼容接口按文件、原生接口按分段)1–50; default 10
列表每页条数1–100; default 20
过期天数1–365
JSON 请求体1,000,000 bytes
入库截止时间30 minutes
处理重试次数3
删除后保留7 days

metadata 的值只能是字符串;attributes 的值可以是字符串、数字或布尔值。筛选条件在根条件以下最多嵌套 20 层。语义和关键词检索各取最多 50 个候选;搜索不提供下一页。

文件格式与处理耗时

知识库可以入库以下类型(按文件服务识别的类型判断):

类型入库内容
PDF文档文本
DOCX、PPTX、XLSX文档文本
PNG、JPEG、GIF、WebP模型生成的画面描述,不是逐字 OCR
HTML可见文本,去掉脚本和样式
纯文本、Markdown、CSV、JSON原文

能上传不代表能入库。文件服务接受、但知识库不能入库的类型有:音频(MP3、WAV、FLAC、OGG、M4A)、视频(MP4、WebM)、NDJSON、XML、YAML、SVG,以及识别为 Python、JavaScript 的源代码文件。它们加入知识库时不会被拒绝,而是在异步入库阶段失败,文件 status 为 failed,last_error.code 为 unsupported_file。加密的 PDF 或 Office 文件同样在入库阶段失败(invalid_file)。空文件、ZIP 和其他二进制文件在上传时就被文件服务拒绝。

2026-10-11 develop 实测:文本与文档约 4–21 秒入库完成;单张图片约 4–11 秒,7 个文件同时入库时一张 PNG 用了约 87 秒。

HTTP 错误

HTTPcode处理建议
400INVALID_PARAMS / LIMIT_EXCEEDED修正参数或减少数量。
401UNAUTHENTICATED检查 API Key;兼容响应 code 为 null。
404VECTOR_STORE_NOT_FOUND / FILE_NOT_FOUND / FILE_NOT_IN_STORE / BATCH_NOT_FOUND / CHUNK_NOT_FOUND核对资源 ID 与所属知识库。
409VECTOR_STORE_INVALID_STATE / FILE_INVALID_STATE检查删除、过期或文件处理状态。
413PAYLOAD_TOO_LARGEJSON 请求超过 1 MB;兼容响应 code 可能为 null。
429RATE_LIMITED稍后重试。
500— / null服务内部错误;保留请求 ID 并稍后重试。
501NOT_IMPLEMENTED当前不支持该操作。
503PROVIDER_UNAVAILABLE处理服务暂不可用,稍后重试。

原生错误通常包含 error.code、error.message 与 error.details。兼容错误包含 error.message、error.type、error.param 与 error.code,官方 SDK 按 HTTP 状态抛出对应异常。未知路径可能没有 code,401 可能没有 details,路径编码错误也可能返回 HTML。

异步失败

文件 status 为 failed 时查看 last_error.code:unsupported_file 表示无法解析该格式;invalid_file 表示内容无效、为空或加密;server_error 表示处理失败或超时。超过估算 token 上限当前映射为 invalid_file,但尚未实测超限文件。添加成功后仍应检查文件状态。原生 API 支持重试 failed 文件;本次实测未覆盖失败后的成功重试。

真正过期及删除 7 天后的清除尚未端到端观察。实测也未得到可用于承诺的知识库每秒请求额度。

完整错误对象 → · 搜索限制 →

数据库

Database 是什么、不是什么

Database 是 headless CMS 式的内容库:人、智能体和脚本通过公开 API 直接读写结构化的表与行。它保存行程、目录等内容,独立于使用这些内容的应用。它不是应用的私有运行库,也不是数据仓库。

角色谁用生命周期存在哪里
内容库人、智能体、脚本;应用也可通过公开 API 读取内容独立于应用发布进行编辑和复用结构化内容用 Database,文件用 File,可检索知识用 Knowledge
应用运行库管理订单、会话和业务规则的应用代码状态随应用运行而变化,由应用自己的业务约束管理应用自带的 d1、kv 或 r2 components
数据仓库分析人员和报表流水线积累历史数据用于分析专门的分析型数据仓库,不使用 Database

典型模式是内容库 → 构建 → 发布到应用:从 Database 读取行程内容,构建站点文件,再用 praxis.application.publish 上传、创建版本并部署。修改源内容不会更新这些已发布的文件;需要再次构建和发布才能包含修改。

每个库是一个隔离单元,包含自己的表、字段、行和事件。

通过 Database API 创建库与表结构;通过官方 supabase-js 读写行。同一库的两个行访问地址共享数据。

API 参考 → · Supabase 兼容接口 → · Cookbook →

快速开始

在 Node.js 22 或更高版本中运行,将 develop API Key 放入 PRAXIS_API_KEY 环境变量。每次运行都会创建一个新库。

npm install @supabase/supabase-js

将下面内容保存为 quickstart.mjs,运行 node quickstart.mjs。

import { createClient } from "@supabase/supabase-js";

const api = "https://api.deepraxis.com";
const key = process.env.PRAXIS_API_KEY;
if (!key) throw new Error("Set PRAXIS_API_KEY");

async function request(path, method, body) {
  const response = await fetch(`${api}/database/v1${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${key}`,
      "Content-Type": "application/json",
    },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  });
  const result = await response.json();
  if (!response.ok) throw new Error(`${response.status}: ${result.message}`);
  return result;
}

const database = await request("/databases", "POST", {
  name: "Agent tasks",
  labels: { purpose: "docs" },
});
await request(`/databases/${database.id}/tables`, "POST", {
  name: "tasks",
  fields: [
    { name: "title", type: "string", required: true, unique: true },
    { name: "done", type: "boolean" },
  ],
});

const supabase = createClient(`${api}/supabase/${database.id}/`, key, {
  auth: { persistSession: false, autoRefreshToken: false },
});

const inserted = await supabase.from("tasks")
  .insert({ title: "Write a summary", done: false }).select();
if (inserted.error) throw inserted.error;

const selected = await supabase.from("tasks")
  .select("*").order("title").limit(10);
if (selected.error) throw selected.error;
console.log({ database_id: database.id, rows: selected.data });

输出包含 database_id 和刚写入的行。保存库 ID 供后续请求使用。Supabase 地址末尾的斜杠不可省略。

继续更新与删除行 → · 理解字段与版本 →

用完后,可用下面的请求删除示例库。恢复规则见概念。

curl -X DELETE "https://api.deepraxis.com/database/v1/databases/${DATABASE_ID}" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

将 DATABASE_ID 设为示例输出的库 ID。

概念

Database 是什么、不是什么

Database 是 headless CMS 式的内容库:人、智能体和脚本通过公开 API 直接读写结构化的表与行。它保存行程、目录等内容,独立于使用这些内容的应用。它不是应用的私有运行库,也不是数据仓库。

角色谁用生命周期存在哪里
内容库人、智能体、脚本;应用也可通过公开 API 读取内容独立于应用发布进行编辑和复用结构化内容用 Database,文件用 File,可检索知识用 Knowledge
应用运行库管理订单、会话和业务规则的应用代码状态随应用运行而变化,由应用自己的业务约束管理应用自带的 d1、kv 或 r2 components
数据仓库分析人员和报表流水线积累历史数据用于分析专门的分析型数据仓库,不使用 Database

典型模式是内容库 → 构建 → 发布到应用:从 Database 读取行程内容,构建站点文件,再用 praxis.application.publish 上传、创建版本并部署。修改源内容不会更新这些已发布的文件;需要再次构建和发布才能包含修改。

库是隔离单元,表定义记录的结构,字段定义值的类型,行保存实际记录。

库、表、字段、行

API Key 决定你能访问哪些库。表和字段可用 ID 或名称寻址;关联字段使用表 ID,因此改名不会改变关联目标。库的标签用于分类和筛选。

默认行只包含主键 id 和用户字段,select=*、return=representation 及 RPC 都遵循这一规则。字段保留名只有 id;新字段不能以 _ 开头。created_at、updated_at、version 可作为普通字段。字段名以小写字母开头,后续只含小写字母、数字和下划线,最多 63 字;表名也允许下划线开头。已有下划线用户字段仍可读写,且优先于同名系统元数据;这时该表无法访问同名元数据。

系统元数据只读,需显式取用:select=*,_version,_created_at,_updated_at。* 不会展开它们。可用 _version=eq.3 过滤,或 order=_created_at.desc 排序。不指定 order 时,仍按内部创建时间再按 id 排序。过滤或排序使用旧元数据名且没有同名用户字段时返回 400:details 为 use _created_at(或对应的新名字),hint 为 system metadata moved to underscore columns。

时间字段可定义为 {"name":"created_at","type":"datetime","default":"now()"}。插入时省略该字段,就保存服务器当前 UTC 时间;读回字段定义仍显示 "now()"。显式 null 和 PATCH 中省略的字段不使用默认值。string/json 字段的 "now()" 默认值仍为字面量字符串。给已有表新增这个字段时,回填也会求值。

七种字段类型

类型保存的值
string字符串
integer整数
number数值
booleantrue 或 false
datetimeRFC 3339 日期时间,例如 2026-10-11T08:30:00+08:00;不存在的日期(如 2 月 30 日)返回 400。值统一按 UTC、3 位毫秒保存和读出,例如 2026-10-11T00:30:00.000Z;精度为毫秒,超过 3 位的小数秒四舍五入
json满足指定 JSON Schema 的 JSON 值
link同一库中另一张表的行 ID,或多个行 ID

required 要求写入非空值。新增带 default 的字段时,已有行一律用它回填;不带 default 的新字段在已有行上为空。在已有行的表上新增 required 字段时必须提供 default;把已有字段改为 required 时,若仍有行在该列为空则返回 400 VALIDATION_FAILED 并说明空行数,先补齐这些行再修改。unique 要求值唯一。前五种类型默认建立索引;不需要的字段可以设 indexed: false。json 和 link 不能设为索引字段。字段类型创建后不可修改。

批量写入与事务

一次 POST 最多插入 500 行。单次请求是原子的:任何一行失败,整批回滚。跨请求没有事务;需要一起成功的行应放在一次批量 POST 中。有关联依赖时先查询目标,再插入引用它的行;先查后插不能保证并发期间目标不变,仍需处理插入时的关联错误。

同一批次会按所有行的键的并集补齐缺失值为 null(PostgREST 行为),因此某行省略必填字段可能导致整批失败。发送 Prefer: missing=default 可让缺失的键使用字段默认值;显式传入的 null 仍为 null,没有默认值的必填字段仍必须提供。

默认 return=minimal:成功返回 201,body 为空。需要读回新增行时发送 Prefer: return=representation,或在 supabase-js 的 .insert(...) 后调用 .select()。

JSON Schema

json 字段必须在 options.schema 中提供 JSON Schema。例如,下面的字段只接受包含字符串 summary 的对象。

{
  "name": "detail",
  "type": "json",
  "options": {
    "schema": {
      "type": "object",
      "properties": { "summary": { "type": "string" } },
      "required": ["summary"],
      "additionalProperties": false
    }
  }
}

更新 schema 约束之后的写入,不会重写已有行。查询时可用 detail->>summary 读取字符串值。

关联与删除保护

link 的 options.linked_table_id 指向同一库中的目标表,multiple 决定保存单个行 ID 还是行 ID 数组。关联目标创建后不可更改。

{
  "name": "project",
  "type": "link",
  "options": { "linked_table_id": "tbl_example", "multiple": false }
}

把 tbl_example 换成已创建表的 ID。引用不存在的行会被拒绝;删除仍被其它行引用的行也会被拒绝;同一次删除中的行互相引用时可以一起删除。先解除引用,再删除目标,不会自动级联删除。

用 _version 避免覆盖他人的更新

新行的 _version 为 1,每次更新递增。更新时同时过滤行 ID 和读到的版本;没有返回行表示该条件不再匹配,需要重新读取后决定下一步。

const { data: row, error: readError } = await supabase.from("tasks")
  .select("*,_version").eq("id", rowId).single();
if (readError) throw readError;

const { data, error } = await supabase.from("tasks")
  .update({ done: true })
  .eq("id", row.id)
  .eq("_version", row._version)
  .select();
if (error) throw error;
if (data.length === 0) throw new Error("Read the row again before updating");

删除库后 7 天内恢复

删除库后不能再读写其中的行。在 7 天内调用恢复库可以重新使用库及保留的表和行;超过窗口无法恢复。此恢复能力针对整个库,不是单独删除的表、字段或行。

API 参考 → · 限制与错误 →

限制与错误

按以下边界规划库与查询。

限制上限
每库最多表数50
每表最多字段数64
每表最多索引字段数(含 unique)16
每表最多行数1000000
单行最多字节数2097152 (2 MiB)
单次插入最多行数500
单条查询最多参数值数100
删除库后可恢复天数7
单次查询最多扫描行数3000000

单条请求最长执行 8 秒,超时返回 57014(重试不会成功);给过滤和排序字段建索引,或缩小查询范围。不带过滤的计数总是很快。不指定 limit 时每次最多返回 1000 行,用 limit / offset 或 Range 翻页。大表上需要批量修改或删除时,按主键或索引字段的范围分批执行,例如每批几万行。单次批量插入可包含上述行数上限,不必将每行的字段数相乘作为查询参数数目。

错误码与重试属性 → 一次批量 POST 最多插入 500 行,单次请求是原子的:任何一行失败,整批回滚。跨请求没有事务;需要一起成功的行应放在一次批量 POST 中。有关联依赖时先查后插,并处理并发变化导致的关联错误。

同一批次按所有行的键的并集补齐缺失值为 null(PostgREST 行为)。发送 Prefer: missing=default 可改用字段默认值;显式 null 仍为 null,没有默认值的必填字段仍必须提供。默认 return=minimal 返回 201、body 为空;发送 Prefer: return=representation 才返回行。

按错误细节修正请求

保留字段名只有 id;新字段名不能以 _ 开头,这个前缀留给系统元数据。created_at、updated_at、version 可用作普通字段名。datetime 字段可用 "now()" 作为默认值。

details 指出字段或 JSON 路径及实际收到的值,批量校验还包含从 0 开始的行号。值最多显示 80 个字符,不回显整行。hint 给出修正方式,没有建议时为 null。下面是省略第 2 行必填 title 的错误示例:

{
  "code": "23514",
  "message": "row validation failed",
  "details": "row 1: title: required value (got null)",
  "hint": "Rows in one insert are filled to the union of their keys with null (PostgREST behavior). Send every key in every row, or send Prefer: missing=default to use field defaults."
}

唯一冲突返回 409,例如:

{
  "code": "23505",
  "message": "unique constraint violated",
  "details": "Key (email)=(a@b.c) already exists.",
  "hint": null
}

保险库

Vault 保管智能体调用第三方服务时需要的凭据,例如 GitHub 令牌、OpenAI API Key 或 Linear 的授权。每个库是一组 Secret,一个 Secret 是一个凭据。

Secret 只写:值写入后任何接口都不会再返回它。每个 Secret 都带一组允许的域名,只有发往这些域名的请求才能用到它。智能体和工具按名字引用 Secret,例如 GITHUB_TOKEN。

  • 快速开始:建库、加 Secret,再列出。
  • 概念:Secret 的种类、模板、导入、一键授权与续期。
  • 限制与错误:容量上限和错误处理。

API 参考 → · Claude 托管智能体凭据库 → · Cookbook →

快速开始

把 API Key 放进 PRAXIS_API_KEY 环境变量,依次运行下面三个请求。每次运行都会创建一个新库。请先安装 jq 以提取 ID;可设置 VAULT_NAME 自选显示名称。

1. 建库

VAULT_JSON=$(curl --fail-with-body -sS -X POST "https://api.deepraxis.com/vault/v1/vaults" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg name "${VAULT_NAME:-Agent credentials}" '{display_name: $name}')")
export VAULT_ID=$(printf '%s' "$VAULT_JSON" | jq -er '.id')
printf '%s\n' "$VAULT_JSON"

期望:HTTP 200,返回 type 为 vault、status 为 active、secret_count 为 0 的对象。命令已把返回的 vlt_ ID 保存到 VAULT_ID。

2. 加入 Secret

curl -X POST "https://api.deepraxis.com/vault/v1/vaults/$VAULT_ID/secrets" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "GITHUB_TOKEN", "value": "ghp_example", "allowed_hosts": ["api.github.com"]}'

期望:HTTP 200,返回 type 为 vault_secret、kind 为 static、version 为 1 的对象。响应里没有 value:值写入后不会再返回。这个 Secret 只能用在发往 api.github.com 的请求上。

3. 列出 Secret

curl "https://api.deepraxis.com/vault/v1/vaults/$VAULT_ID/secrets" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

期望:HTTP 200,data 中有一项,name 为 GITHUB_TOKEN;next_cursor 为 null。

清理

curl -X POST "https://api.deepraxis.com/vault/v1/vaults/$VAULT_ID/archive" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

curl -X DELETE "https://api.deepraxis.com/vault/v1/vaults/$VAULT_ID" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

期望:归档返回 HTTP 200、status: "archived",随后删除返回 HTTP 200、{"id": "<VAULT_ID>", "type": "vault_deleted"}。删除会一并删除库中的全部 Secret,不可恢复。

从模板开始、导入 .env 与一键授权 → · 完整请求与对象 →

概念

库是一组 Secret;Secret 是一个凭据,由名字、值和允许的域名组成。其余功能都只是产生或更新 Secret 的方式。

库与 Secret

API Key 决定你能访问哪些库。库有显示名称和最多 16 对 metadata 标签。

Secret 的名字在库内唯一,创建后不能修改,只能使用大写字母、数字和下划线,长度为 1–128 个字符,可以以下划线开头,但不能以数字开头,例如 GITHUB_TOKEN。可以用名字或 vsec_ 开头的 ID 寻址一个 Secret;非法名称查找也返回 404 SECRET_NOT_FOUND。

值只写:创建或替换后,任何接口都不再返回它。每次替换值,version 加一。

允许的域名

每个 Secret 必须声明 allowed_hosts,1–16 个;空数组返回 400 INVALID_PARAMS。只有发往这些域名的请求才能用到这个 Secret。

写法匹配
api.github.com只匹配这个域名
*.example.com匹配 example.com 的任意子域名,不匹配 example.com 本身
192.0.2.1只匹配这个 IPv4 地址

域名写入时统一转为小写,去掉末尾的点,国际化域名转为 punycode。重复项会去重。不提供“任意域名”。IPv6、单标签主机(包括 localhost)、*.internal、平台域名,以及回环、链路本地、私网与 CGNAT IPv4 均返回 400 INVALID_PARAMS。

两种 Secret

kind值从哪来状态
static你填写的值active
oauth一键授权得到的访问令牌,由平台续期active、refreshing、refresh_failed

两种 Secret 归档后状态都是 archived。

模板

模板为常用服务预填名字、允许的域名和种类,你只需填写值。GET /vault/v1/templates 列出全部模板,static 模板有 github、openai、anthropic、stripe,OAuth 模板有 linear、atlassian。GitHub 预填 GITHUB_TOKEN 和 api.github.com、uploads.github.com。添加 static Secret 时可传 template_id,其来源记录为 metadata.template。

metadata.source 和 metadata.template 是系统保留键,调用方不能写入,也不占自定义标签的 16 对限额。更新库或 Secret 的 metadata 按键合并:字符串写入,null 删除该键,省略的键保留。Secret 的 description: null 清除备注,省略则保留。

导入 .env

POST /vault/v1/vaults/{vault_id}/secrets:import 一次创建多个 static Secret。传 dotenv(KEY=VALUE 多行文本)或 lines 数组,二者选一;新建时,allowed_hosts 默认值应用到没有自带域名的行。同名 Secret 只替换值,只有该行自身的 allowed_hosts 才会改变原域名,顶层默认域名不会覆盖原域名。结果分别列出新建、替换和失败的行,失败行带行号与错误码,其余行照常写入。

一键授权

Linear(CIMD:客户端元数据文档)和 Atlassian(DCR:动态客户端注册)可以通过一键授权产生 oauth Secret,无需事先注册应用。需要应用注册的服务(GitHub、HubSpot、Asana、Vercel)请手动添加凭据;Asana 不在一键授权模板中。

  1. 调用 POST /vault/v1/authorizations,传支持授权的 template_id,或要授权的 resource_url。
  2. 把返回的 connect_url 交给最终用户。这个链接 30 分钟内有效、只能使用一次。
  3. 用户在确认页看到发起方、目标服务和申请的权限,同意后跳转到服务方完成授权。
  4. 完成后,库中出现名为 secret_name 的 oauth Secret。读取 GET /vault/v1/authorizations/{id} 可查看状态:pending、authorizing、completed、failed 或 expired。

库中已有同名 Secret 时,授权会原地更新它:ID 不变,version 加一。

需要授权后回到你的应用时,传 return_url。它必须在回跳地址白名单内:用 PUT /vault/v1/settings/return_urls 设置,最多 10 个 https 地址,精确匹配。回跳时会带上 authorization_id 和 status。

续期与校验

oauth Secret 的访问令牌由平台在过期前续期。服务方拒绝续期时,Secret 变为 refresh_failed,oauth.last_error 说明原因;重新授权或替换值后恢复为 active。服务方暂时不可用时,Secret 保持 active 并稍后重试。

POST …/secrets/{secret}/validate 用当前令牌向服务发一次探测,返回 valid、invalid 或 unknown,只适用于 oauth Secret。调用方收到服务的 401 后,可以调用 POST …/secrets/{secret}/refresh 立即续期;同样仅支持 oauth Secret。对 static Secret 校验返回 400 VALIDATE_UNSUPPORTED,续期返回 400 SECRET_KIND_MISMATCH。

审计事件

库的事件记录创建、更新、归档、删除、使用、续期和授权结果,不包含 Secret 的值。GET /vault/v1/vaults/{vault_id}/events 按时间倒序列出,可用 secret 按名称或 ID 过滤;未知 Secret 返回空列表。更新事件的 detail.changed 列出修改的字段,例如 value、allowed_hosts。

事件没有按天自动过期的保留期;归档保留事件,删除库或 Secret 会清除其历史事件,仅留下删除记录。limit 默认 50、最大 200;超限或无效 cursor 返回 400 INVALID_PARAMS。完整事件类型见事件参考。

归档与删除

归档库会同时归档库内全部 Secret 并清除它们的值,之后不能再写入。删除库会删除全部 Secret,不可恢复。单个 Secret 同样可以归档或删除。

与 Claude 托管智能体凭据库兼容

同一批库也可以用 Claude 托管智能体的官方 SDK 兼容接口访问(/anthropic/v1/vaults)。每个请求需要 API Key、anthropic-version: 2023-06-01,以及包含 managed-agents-2026-04-01 的 anthropic-beta;该头可用逗号分隔包含其它 beta。列表使用 page 而非 cursor 分页;凭据只能用 vsec_ ID 寻址。库的 display_name: null 返回 400。

错误体为 {type: "error", error: {type, message}, request_id};409 对应 conflict_error。409 等不可重试错误带 x-should-retry: false,不会触发 SDK 自动重试。

凭据类型对应的 Secret
environment_variable名字为 secret_name,允许的域名为 networking.allowed_hosts;networking 只支持 limited
static_bearer允许的域名为 mcp_server_url 的主机,名字自动生成为 MCP_<主机>
mcp_oauth暂不支持创建,返回 501;请改用一键授权

通过 /vault/v1 创建的 Secret 在该接口中显示为 environment_variable,injection_location 为只在请求头中替换。

限制与错误

按以下边界规划库与 Secret。

限制上限
每库有效 Secret 数50
Secret 值大小16384 bytes (16 KiB)
每个 Secret 的允许域名数16
单次导入行数50
授权创建 / OAuth 校验(每账户每分钟,各自计数)30 / 30
库名称长度255
metadata 键值对数16
metadata 键 / 值长度64 / 512
列表每页条数50 (最大 200)

同名 Secret 已存在返回 409 SECRET_CONFLICT;往已归档的库写入返回 409 VAULT_INVALID_STATE;超过有效 Secret 上限返回 400 VAULT_LIMIT_EXCEEDED;值超过 16 KiB 返回 413 SECRET_TOO_LARGE。一键授权创建与 OAuth 校验各按账户独立计数,每个固定一分钟窗口各最多 30 次;超出返回 429 RATE_LIMITED,等待下一窗口重试。授权链接过期返回 410 AUTHORIZATION_EXPIRED,需要重新发起授权。

通过 Claude 托管智能体的凭据库接口访问时,每库最多 20 个有效凭据,错误使用该接口自己的错误格式。

错误码与重试属性 →

账户与计费

管理你的 Praxis 账户——Key、用量、费用和配额。

你想做什么去哪
创建或管理 API KeyAPI Keys
查看调用量和费用用量与费用
充值或了解计费模式费用与充值
了解速率限制和配额配额与限制

API Keys

API Key 是你调用 Praxis API 的凭证——sk-praxis- 开头的一串字符。每个 Key 属于一个账户空间,通过它产生的调用都记在那个空间名下。

创建 Key

  1. 打开 控制台 → API Keys
  2. 点「创建 Key」
  3. 填写:
字段说明
名称必填,用于识别用途(如 prod-backend、my-app)
有效期30 / 60 / 90 天或永久,默认 90 天
  1. 点「创建」

创建成功后弹窗会展示完整 Key 并带「复制 Key」按钮——立即复制保存。关闭弹窗后列表只显示掩码,但你可以随时点行内复制图标重新获取完整 Key。

管理 Key

在 API Keys 列表页,你可以:

  • 复制:悬停 Key 行,点复制图标取回完整 Key
  • 禁用:临时停用某个 Key(不删除,之后可重新启用)
  • 删除:永久移除——已删除的 Key 立即失效,无法恢复

安全实践

  • 不要把 Key 写进代码——用环境变量(export PRAXIS_API_KEY=sk-praxis-…)
  • 不同环境用不同 Key——开发/测试/生产各自一个,出问题时只吊销受影响的那个
  • 定期轮换——长期运行的系统建议每 90 天换新 Key
  • 泄露了怎么办:立即删除该 Key,创建新的,更新你的应用

常见问题

症状原因解法
401 Invalid API keyKey 已删除/禁用,或复制不完整到控制台确认 Key 状态;重新完整复制
Key 到期了怎么办有效期到了创建新 Key 并更新到你的应用
能创建多少个 Key—每个账户空间可创建多个 Key,按需管理

定价

Praxis 的价格只有一条规则:以模型厂商的官网价为基准,Praxis 标价低于官网价。每个模型的具体价格在模型目录里,单位是美元每百万 token,不含税。

价格怎么定

  • 基准是厂商官网定价页的价格,Praxis 记录来源与日期。
  • Praxis 标价对所有客户相同,低于官网价;折扣对输入、输出、缓存创建、缓存读取四种 token 类型一致。
  • 厂商调价时,Praxis 标价同步更新;Praxis 主动调整标价提前 30 天通知。

怎么付费

  • 预付充值,按实际消耗扣减,用多少付多少;订阅也是用量的订阅,不卖许可。
  • 未消耗的余额可以兑付。
  • 用量与余额在控制台的用量与费用与充值页查看。

经合作伙伴购买

Praxis 可能与介绍您的合作伙伴存在分润安排,不影响您的价格:经合作伙伴购买不会比直接购买更贵。合作伙伴计划的规则在合作伙伴。

用量与费用

每一次 API 调用都有账、可追溯。控制台的用量页提供汇总视图和逐笔明细。

查看用量

打开 控制台 → 用量与费用。

汇总视图(默认):

  • 本月花费
  • 用量事件数
  • 按资源(模型)聚合的调用量和费用

逐笔明细:点「查看用量明细」或点击某个资源行,可以看到每一笔调用的:

  • 时间
  • 模型
  • 输入 / 输出 token 数
  • 费用

费用怎么算

按次计费、无闲置费。具体来说:

  • 输入 token 和 输出 token 分别计价(各模型单价不同,见模型目录)
  • 推理 token(思维链)计入输出 token
  • 不调用不收费——没有月费、没有最低消费

API 层获取费用

除了控制台,你还可以在 API 响应中直接获取本次调用的费用:

  • OpenAI 兼容协议:响应的 usage.cost 字段直接返回费用金额
  • Anthropic 协议:Python SDK 的 message.usage.cost 可读(JS SDK 的类型层不声明该字段,但协议层有)

这意味着你可以在自己的应用里实时追踪成本,不需要轮询控制台。

常见问题

症状原因解法
用量页显示为空还没有调用过发一次请求后刷新
明细和我的记录对不上推理 token 也计费看 usage 里的 reasoning_tokens
余额不足会怎样—调用会返回余额不足错误,到资金账户充值

费用与充值

Praxis 采用按次计费模式——每次 API 调用按实际 token 用量计费,不调用不收费。

计费模式

项目说明
计费方式按次按 token,输入和输出分别计价
闲置费无——不调用不产生费用
最低消费无
推理 token计入输出 token(推理模型的思维链也计费)
免费额度新账号自带 $1

各模型的具体单价见模型目录。

充值

  1. 打开 控制台 → 资金账户
  2. 点「充值」
  3. 选择金额和支付方式

充值后余额立即可用。

余额不足时

当余额耗尽,API 调用会返回余额不足错误(HTTP 402 或类似错误码),不会静默降级。你的应用应处理这个错误并提示用户充值。

监控花费

  • 实时:API 响应中的 usage.cost 字段直接返回本次调用费用
  • 汇总:控制台 → 用量与费用按月/按资源聚合
  • 逐笔:用量明细页每一笔调用都有时间、模型、token 数和费用

常见问题

症状原因解法
调用返回 402余额不足到资金账户充值
花费比预期高推理 token 也计费查看 usage.reasoning_tokens;换非推理模型
免费额度用完了—充值继续使用

配额与限制

本页讲你的账号有多少额度、怎么提升。原理见限流与错误,错误处理见错误与限流。

速率限制

Praxis API 对每个账户空间施加速率限制,防止滥用。超限时返回 HTTP 429(Too Many Requests),响应头包含:

响应头含义
X-RateLimit-Limit-Requests每分钟允许的请求数
X-RateLimit-Remaining-Requests本窗口剩余请求数
X-RateLimit-Reset-Requests距离重置的秒数

处理方式:收到 429 时按 Reset 头等待后重试,或减少并发。

模型调用限制

限制说明
并发请求每个账户空间同时进行的请求数有上限
上下文长度每个模型有自己的上下文上限(如 1M token),见模型目录
流式支持按模型路由逐个开通(报 STREAMING_UNPROVEN 表示该路由暂未开放流式)

提升配额

需要更高的速率限制或并发数?联系我们说明你的使用场景和需要的配额。

常见问题

症状原因解法
429 Too Many Requests速率限制按 X-RateLimit-Reset 等待后重试
上下文超限输入超过模型上限截断输入或换大上下文模型
STREAMING_UNPROVEN该模型路由暂不支持流式去掉 stream: true 或换模型

帮助与参考

这里保留跨章节的帮助资料。API 鉴权、端点、错误与版本已经归入顶层 API。

术语表

API Key——调用 Praxis API 的凭证,sk-praxis- 开头。

Agent(智能体)——有上下文、有记忆、能调用工具的 AI 服务。

Anthropic 兼容——Praxis 支持的协议之一,Anthropic SDK 改 base_url 即可迁移。

BYOK(Bring Your Own Key)——自带上游 Key,平台统一路由。

上下文窗口(Context Window)——模型一次能处理的 token 上限。

流式(Streaming)——逐 token 输出。部分路由暂未开放。

模型(Model)——大语言模型,Praxis 聚合多家供应商统一提供。

OpenAI 兼容——Praxis 支持的协议之一,OpenAI SDK 改 base_url 即可迁移。

推理模型(Reasoning Model)——回复前先"思考",响应含 thinking 块。

滚动入口(Rolling Entry)——始终指向最新版本的模型别名(如 deepseek-flash)。

版本快照——固定版本的模型 ID(如 deepseek-v4-flash)。

沙箱(Sandbox)——安全隔离的代码执行环境。

Session(会话)——智能体的对话上下文单元。

Token——模型处理文本的基本单位。1 个中文字 ≈ 1-2 token。

用量(Usage)——API 调用的统计记录,含 token 数和费用。

服务状态

状态页:https://status.deepraxis.ai(建设中,ticket-3549)。

各服务可用性

服务状态
API(模型调用)正常
控制台正常
Praxis App正常

服务异常时怎么办

  1. 先确认是不是自己的网络问题——试试 curl https://api.deepraxis.ai/openai/v1/models(预期返回 401 认证挑战,说明服务活着)
  2. 如果 API 确实不通,到控制台 → 帮助与反馈报告
  3. 附上时间点和错误信息原文,方便我们查日志

历史事件

(待建设:事件时间线将发布在状态页。)

常见问题

模型 ID 怎么填

到控制台 → 模型复制确切 ID。deepseek-flash 是滚动入口(始终最新 Flash),deepseek-v4-flash 是固定版本。

国内直连能访问吗

api.deepraxis.ai 通过 Cloudflare 全球网络分发,国内一般可直连。需要代理也支持标准 HTTP 代理。

余额为零会怎样

API 返回余额不足错误(HTTP 402),不会静默降级。充值后立即恢复。

Key 泄露了怎么办

到 API Keys 立即删除 → 创建新 Key → 更新应用。

流式不出字

可能是 STREAMING_UNPROVEN(换模型或去掉流式)、推理模型先思考(等几秒)、或网络瞬断(重试)。

免费额度有多少

新注册 $1。deepseek-flash 大约能跑几千次对话。

能同时用多个模型吗

可以——同一请求换 model 字段即可,不需要多个 Key。

和 OpenAI / Anthropic 官方什么关系

Praxis 提供 OpenAI / Anthropic 兼容协议——已有代码只改 base_url 即可迁移。详见迁移指南。

还有别的问题?

联系我们。

支持与反馈

控制台反馈

最直接的渠道:控制台 → 帮助与反馈。你可以:

  • 提交 Bug 报告(附上截图或错误信息更高效)
  • 提出功能建议
  • 查看你的反馈处理进度

工作日通常当天回复。

报 Bug 时请附上

信息为什么需要
你做了什么(操作步骤)复现问题
期望看到什么 vs 实际看到什么确认是 Bug 还是设计如此
错误信息原文定位根因
浏览器 / 操作系统排除环境因素
时间点(大约)查服务端日志

API 问题

如果是 API 调用报错,附上:

  • 请求的模型 ID
  • HTTP 状态码和错误响应原文
  • 请求时间(我们按时间查日志)
  • 你的 API Key 前缀(sk-praxis- 后的前几个字符,不要发完整 Key)

安全与合规

数据驻留

所有数据默认存储在 APAC(亚太) 区域。创建新资源(如 Durable Object)时默认使用 locationHint: "apac"。

文件与数据保留

数据类型保留策略
对话记录持久保存,直到你主动删除
API 调用日志按平台日志策略保留
上传文件持久保存,直到你主动删除
已删除的数据删除后无法恢复

API 安全

  • Key 管理:API Key 是你的调用凭证,不要写进代码或提交到版本控制。详见 API Keys。
  • 传输加密:所有 API 通信通过 HTTPS 加密。
  • 认证方式:API Key(Authorization: Bearer sk-praxis-…)或 x-api-key 头。

隐私政策与服务条款

备案信息

deepraxis.com 域名已完成 ICP 备案。境内访问经深圳 ECS 反代进入,备案号显示于页脚。