2026-10-22
直接运行智能体,或使用你需要的基础能力
智能体服务封装模型调用、工具执行等能力,通过智能体 API 完成业务任务。需要直接控制模型、沙箱或搜索时,也可以独立使用对应服务。
每项服务都有独立的快速开始、使用指南与 API。
定义职责、提供资料并运行任务。由智能体服务管理底层能力调用。
通过统一接口调用模型,生成文本、处理多模态输入。
在隔离环境中执行代码、操作文件和使用浏览器。
查询所需信息,获取搜索结果与来源。
智能体 / 模型 / 沙箱 / 搜索 —— 查阅请求参数、响应结构、错误码与代码示例。需要验证调用时,在当前页面展开调试面板,配置并发送请求。
一个 Praxis 账号。新注册账号自带 $1 免费额度,跑完本页绰绰有余,不需要先充值。
在 控制台 → API Keys 创建一个 Key。名称必填,有效期默认 90 天,创建后立即复制保存。
只需要一个地址: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);响应的 choices[0].message.content 是回复文本。usage 里有 token 数——注意 cost 字段直接给出本次调用的费用。
然后打开 控制台 → 用量与费用:你刚才这一次调用已经记在那里了。
Praxis Cloud 提供多个服务域。你的场景决定用哪个——很多场景只需要一个。
你要做什么:调用大语言模型(对话、写作、翻译、代码、分析)。
怎么用:通过 OpenAI 或 Anthropic 兼容 API 直接调用。把现有代码的 base_url 指向 Praxis 即可。
适合:已有应用要加 AI 能力、写 SDK 代码、接入第三方工具(Cursor、Cherry Studio 等)。
你要做什么:让 AI 在云端持续工作——有上下文、有记忆、能调用工具。
怎么用:通过 Agent API 创建会话、发送事件、获取回复。智能体可以记住之前的对话、执行多步任务。
适合:构建 AI 助手、客服机器人、有状态的工作流。
→ 智能体快速开始
你要做什么:在安全隔离的环境中运行代码。
怎么用:通过 Sandbox API 创建沙箱实例,上传代码并执行,获取结果。
适合:代码执行、自动化测试、数据处理管道。
→ 沙箱快速开始
你要做什么:给 AI 提供网络搜索能力。
怎么用:通过 Search API 执行搜索,返回结构化结果。
适合:构建有实时信息能力的 AI 应用。
→ 搜索快速开始
| 你的场景 | 推荐 |
|---|---|
| 只需要调模型(对话/生成) | 模型 |
| 需要 AI 有记忆和工具 | 智能体 |
| 需要安全执行代码 | 沙箱 |
| 需要搜索网络信息 | 搜索 |
| 组合使用 | 全部可以组合——智能体可以调用模型和搜索,沙箱可以运行任何代码 |
不确定的话:从模型开始——最简单、最直接,3 分钟就能跑通。
理解这几个概念,使用 Praxis 会更顺手。
Praxis Cloud 的所有服务(模型、智能体、沙箱、搜索)共用同一个 API Key。你不需要为不同服务创建不同的 Key——一个 Key 就够了。
Praxis 提供两种协议入口,你已有的代码不需要换库:
https://api.deepraxis.ai/openai/v1)——OpenAI SDK 改 base_url 即用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 建立一段服务智能体对话,并从事件中验证真实回复。本页只在服务端运行,不需要网页组件或嵌入策略。
准备 Bun 或支持原生 fetch 的 Node.js、Develop 组织的 API Key,以及该组织可用的 deepseek-flash 模型和余额。在服务端进程设置 PRAXIS_API_KEY;密钥创建见API Key。如果你的 Key 属于其他环境,先统一 API 地址、组织和模型,不能混用环境。
新建 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。
追加以下代码。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,也不代表已经得到回复。预算决定后续请求能否继续,不保证生成特定答案。
追加以下代码,调用 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 中出现已接收的用户事件。这是接收确认;不要把它当作智能体答案,也不要因为暂时没有答案就再次发送同一条消息。
追加以下代码,然后执行 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 | 网页内的独立框架 | 宿主签发端点、一行加载脚本 | 控制台保存,脚本可覆盖布局 | 一行嵌入 |
| Hosted | Praxis 托管的独立对话页 | 宿主验证身份、签发链接并跳转 | 与 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。
在 Develop 控制台进入服务智能体,选择已有智能体。在接入与配置 → 配置 → 指令保存指令;在配置 → 安全选择运行环境,打开允许 localhost,点保存安全设置。本页宿主页为 http://localhost:4242。请使用专用测试智能体,来源白名单暂留空,仅启用 localhost;令牌继承非空 HTTPS 来源列表时,本地请求会被拒绝。上线到 HTTPS 网站时,添加该网站的来源。
嵌入策略目前只能在控制台配置。复制公开智能体 ID(agent_…)备用。页面显示“已保存,不产生新版本”后,继续实际签发;保存提示本身不能证明对话可用。
新建目录,把代码保存为 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 保护之后,保留每次签发的身份核验。不要将本地测试账户机制用于线上登录。有效期和刷新见嵌入令牌与用户。
选择一种前端实现。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 只表示组件挂载;必须收到真实回复才能证明接入成功。
http://localhost:4242,用 customer_123 和所设测试密码登录。发送“你好,请介绍你能帮助处理的订单问题”,确认收到回复。customer_456,期望看不到第一个账户的会话。不能用两个同登录态的标签页代替此检查。curl --silent --output /dev/null --write-out "%{http_code}\n" \
-X POST http://localhost:4242/api/praxis-token \
-H "Origin: http://localhost:4242"以下是接口定义的失败分支,按网络面板中的实际响应检查:
EMBED_ORIGIN_NOT_ALLOWED(HTTP 403):核对实际页面来源;本地应启用 localhost,上线应保存 HTTPS 来源。在网页中插入一行加载脚本,显示 Praxis 托管的 iframe 对话界面。外观在控制台保存,用户留在你的网页中对话;“一行”只指前端安装,后端仍需为登录用户签发令牌。
准备一个可修改页面和后端的 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 的服务端请求中,不应出现在浏览器网络记录里。
frame-src 要允许 https://chat-develop.praxisusercontent.com,script-src 要允许脚本 CDN。不要禁用整站 CSP 来排错。Invalid Praxis loader configuration:检查短号格式、脚本域名以及签发端点是否填写。验证来源拒绝时,从另一个不在白名单中的 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。
deepseek-flash。预期结果: 当前配置的版本号增加;重新打开「指令」能看到保存内容。未保存的修改离开页面时会提示处理。
嵌入安全策略、凭据库选择与嵌入版本固定目前只能在控制台配置。
在「配置 → 安全」选择可用运行环境,填写你的网站来源,并设置令牌有效期与预算,然后点击「保存安全设置」。有效期界面单位为分钟,预算单位为美元;不要直接把接口的秒数或美分数填入界面。来源规则见保护嵌入接入。
需要凭据库时,在「MCP 与凭据」勾选后点击「保存凭据库」。预期结果: 两种保存均显示「已保存,不产生新版本」。它们不属于底部保存栏的配置草稿。
进入「接入与配置 → 版本」。点击版本号查看与上一版本的差异,再选择「设为嵌入版本」或旧版本的「回滚到此版本」,确认后检查「嵌入中」标记。需要恢复跟随最新时点击「取消固定」。没有安全策略时,先完成上一步。
签发时显式指定的 version 优先于控制台固定版本;省略时使用固定版本,未固定才取最新版本。已经签发的令牌保持原版本,回滚不会改变它们。验证升级或回滚时,应重新签发令牌并开始新会话。
进入「试用调试」,选择刚保存的版本,发送一个能区分新旧指令的问题,检查真实回复。随后在宿主网页重新取得令牌,开始新会话,并在「运营与管理 → 会话」核对版本。只看到界面加载不能证明新配置已被使用。
服务智能体目前不支持按消息切换模型、工具模式、语音输入和按终端用户的记忆;「配置 → 开场与外观」仍是占位,外观操作见自定义外观。
嵌入令牌让终端用户访问指定智能体及自己名下的会话。登录用户的令牌由宿主后端签发,浏览器消费;本页说明身份、有效期、刷新和单张令牌吊销。
准备同组织的服务智能体、已保存的嵌入策略和服务端 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" 和当前课程的字符串值。切换课程、删除登记后各发一次,确认请求随之变化;不要只凭模型回答判断是否传入成功。
后端上下文用新签发的令牌新建会话,询问回复语言并核对签发请求中的文本。模型回复用于体验验证;身份和权限仍由宿主后端及工具服务端校验。
让智能体按需读取当前页面,或执行页面上的动作。你在智能体里声明工具,在 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 工具不经过平台的工具确认卡。需要用户确认的页面动作,由你的界面处理;真正的数据权限必须在后端检查,不能依赖浏览器或模型参数。
为已启用的工具选择直接执行或每次询问,让用户在操作发生前确认意图。本页以订单 MCP 的查询和退货工具为例;业务权限仍须由工具服务端校验。
准备一个已能接入的服务智能体,并在「配置 → MCP 与凭据」连接名为 orders 的 MCP 服务器。示例要求它提供已启用的 list_orders 和 request_return。替换为你实际的服务器和工具名称,不要用示例名称配置不存在的工具。
确认卡不能代替身份认证。公网 MCP 服务必须先认证平台调用方,再按终端用户限定数据范围,见保护嵌入接入。
| 策略 | 对普通已启用工具的效果 |
|---|---|
always_allow | 不询问,直接执行 |
always_ask | 暂停并等待用户确认,允许后执行;拒绝则不执行该调用 |
目前支持这两档。不要使用 auto:即使某处类型接受它,运行时也会拒绝执行。需要禁止使用一个工具时,关闭该工具;「每次询问」仍允许用户同意调用。
策略不绕过工具自身的强制交互、安全边界或业务检查。前端 custom 工具由你的页面执行,不经过这套确认策略。
在当前控制台依次打开「服务智能体 → 你的智能体 → 接入与配置 → 配置 → 工具与授权」。
orders · 默认授权,选择「每次询问」。list_orders 设为「自动执行」,request_return 设为「每次询问」。逐工具选择项只显示已配置的工具;未显示的工具可使用默认策略,或通过 API 添加逐工具配置。解析顺序为单工具 configs[].permission_policy → 工具集 default_config.permission_policy → 工具默认策略。未设置 MCP 策略时,服务器声明 annotations.readOnlyHint === true 才默认放行,否则默认询问。该提示仅影响缺省,显式配置优先。
内置工具集未设置默认策略时为 always_allow,但工具自身要求的交互仍然存在。对退款、删除和对外发送等动作,明确设置询问,不要仅依赖服务器提示。
签发使用新版本的嵌入令牌并新建会话;若嵌入策略固定版本,先更新固定版本或在签发时指定新版本。保存智能体不会自动升级已有会话。
先让智能体调用查询工具,再请求一次测试退货。预期: 查询直接返回;退货显示 Kit 确认卡。拒绝后工具服务端不应执行退货;再次提出请求并允许后才执行。以工具服务端记录核实调用是否发生,不要只看智能体的回答。
Kit 自带确认卡,无须另写界面。嵌入用户的允许只针对这次调用,不会保存为永久工具权限。完整测试情境见测试嵌入接入。
让聊天的配色、开场和输入区与网站一致。iframe 与 Hosted 使用控制台保存的同一份外观;Kit 组件使用你在网页中传入的选项。
先准备能正常回复的服务智能体和嵌入策略。Kit 示例要求宿主网站已提供 POST /api/praxis-token,验证登录态并返回 { token };这是你的同源路由,详见嵌入令牌与用户。示例使用 Develop 地址,页面来源须获准接入。
| 接入方式 | 外观在哪里配置 |
|---|---|
| iframe / Hosted | 控制台保存,两个页面共用;刷新嵌入页后生效 |
| Kit 组件 | 网页的 setOptions;控制台的 Kit 编辑器只供预览和生成代码,不保存 |
iframe / Hosted 的持久外观目前只能在控制台配置。当前控制台入口为「服务智能体 → 你的智能体 → 接入与配置 → 接入方式与外观 → iframe」。
不要去「配置 → 开场与外观」编辑:该位置目前是提示页。公开外观接口只提供读取,没有公开的保存接口。
把下面的 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.colorScheme | light、dark、system;省略为浅色,system 跟随系统配色 |
theme.accent / theme.surface | 强调色 / 背景色;使用明确、不透明的 CSS 颜色,例如 #2563eb;不接受 var(...) 或透明色 |
theme.grayscale | neutral、warm、cool |
theme.radius | none、sm、md、lg、full |
theme.density | compact、normal、spacious |
theme.fontFamily / theme.fontSize | 字体栈 / 正数像素字号;字体须由你的页面自行加载 |
startScreen.greeting / startScreen.prompts | 欢迎语 / 最多显示 4 个推荐问题;label 是按钮文字,点击后发送 prompt |
composer.placeholder | 输入框提示 |
composer.attachments.enabled | Kit 附件入口开关,默认开启;不在保存的 iframe / Hosted 外观中提供 |
locale | zh 或 en;Kit 未指定时先取宿主页语言,再取浏览器语言 |
保存的外观 JSON 最多 8192 UTF-8 字节;欢迎语 200 字符、推荐问题最多 4 项,每项 label 40 字符、prompt 200 字符,输入提示 80 字符。头像和顶栏图片须使用 HTTPS 地址。只改组件配置时,详细属性边界见组件参考。
在一个新会话中检查开场文字,点击推荐问题,确认实际发送的是 prompt。在已有消息的会话点击深色按钮,确认消息保留;另开页面检查控制台保存的外观,避免把 Kit 的本地配置误认为已保存。
外观更新不改变智能体指令或工具授权。若保存失败,按字段提示缩短文字、减少推荐问题或修正图片地址后重试。
把嵌入智能体接到网站时,用宿主登录态确定用户,用来源白名单限制浏览器接入,并在工具服务端检查权限。本页配置这些边界;测试情境与上线验收分别见测试嵌入接入和上线检查清单。
你需要能管理服务智能体的控制台账号、宿主网站的后端,以及修改网站响应头的权限。先完成嵌入策略和令牌签发,再按下面的责任检查接入。
| 平台边界 | 宿主必须完成的工作 |
|---|---|
| 嵌入用户只能访问所属智能体下自己的会话 | 从已验证登录态取得 external_user_id,拒绝浏览器自报的身份 |
| 跨用户、跨智能体资源访问返回 404 | API 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。先用隔离测试用户验证操作;撤销和封禁会中断其正在进行的对话。
| 目的 | 操作 | 后续访问 |
|---|---|---|
| 结束当前凭证的访问 | 吊销令牌 | 撤销该用户的全部现有令牌;宿主仍可签发新的 |
| 持续拒绝用户访问 | 封禁 | 撤销现有令牌,拒绝新签发 |
| 恢复签发资格 | 解封 | 允许新签发;已撤销的旧令牌不会恢复 |
只想撤销一张令牌时,使用令牌管理,不要执行用户级批量撤销。
预期结果: 撤销显示「已吊销 N 张」;封禁后状态为「已封禁」;解封后为「正常」。列表中的花费和会话数据不会因这些操作被删除。
撤销后,用旧令牌再次访问,应被拒绝;宿主重新确认登录身份后可以签发新令牌。封禁后,宿主的签发请求应收到 EMBED_USER_BLOCKED(HTTP 403)。解封后重新签发并发送消息,确认恢复;不要以旧令牌重试成功作为解封标准。
同时用另一个用户访问同一智能体,确认其访问未被本次操作影响。用户身份不能取自浏览器任意提交的 ID,应由宿主后端的登录态确定。
查不到用户时,核对组织、公开智能体 ID 和签发时的完整用户 ID;控制台试用调试的数据不会进入客户用户列表。仅有前缀相似不能证明是同一个用户。
若封禁返回失败,重新读取状态后再决定是否重试:封禁状态可能已经保存,而撤销步骤尚未完成。不要在这种情况下显示“封禁完成”。记录 HTTP 状态和请求 ID,按限制与错误处理。
从会话、用户和花费判断嵌入是否正常工作,再打开具体会话检查发生了什么。本页适合已经产生真实宿主访问的服务智能体。
使用有权管理该智能体的组织账户,打开控制台「服务智能体 → 选择智能体 → 运营与管理」。下文使用当前中文控制台的控件名称。准备要检查的时间、终端用户 ID 和宿主接入方式。
先在宿主网页发送一条可识别的测试消息并收到回复,再查运营数据。控制台「试用调试」的记录不会进入客户运营统计,不能用它证明客户流量或花费已经入表。
进入「概览」,选择 24 小时、7 天或 30 天。概览统计目前只能在控制台查看,没有对应的公开聚合 API。
| 检查内容 | 用它回答什么 |
|---|---|
| 会话、活跃终端用户、花费、每会话花费 | 访问量和每次服务的成本是否变化 |
| 好评率 | 有评价的回复中,正面评价占比是多少 |
| 首字延迟 P50、错误率 | 首次响应是否变慢,运行是否出现失败 |
| 来源与版本分布 | 流量来自 Kit、iframe、Hosted、API 还是自动化;新版本是否被使用 |
| 需要关注、工具调用 Top 5 | 来源被拦、预算触顶或工具调用是否异常 |
预期结果: 选择的时间范围内出现会话趋势和版本分布;没有对应样本的指标显示「—」,不是零。24 小时趋势按小时展示,其他范围按天展示。运行错误、来源拦截和工具计数按所选范围覆盖的 UTC 自然日汇总,首尾不足一天时也包含整日计数。
预期结果: 回放只读,不能发送消息。若要停止后续发言,点击「归档」并确认;归档后记录仍可查看,但不能继续发送消息。
进入「用量与预算」,查看本月花费、月度上限和触顶动作,再选择 7、30 或 90 天的每日花费。下方展示预算与限流、本月来源拆分及花费最高的终端用户 Top 20。本月按 UTC 自然月计算,界面金额为美元。
这些聚合报表与预算设置目前只能在控制台查看或配置。「修改上限」进入「配置 → 安全」;保存后返回核对。触顶动作为「停止新会话」时会拒绝新建会话;「只告警」不会拦截。「花费构成」尚未提供,不能从占位区域推断费用拆分。
用同一用户、会话和时间范围核对宿主消息、回放及来源。若版本与预期不符,先检查令牌签发时的版本;固定或回滚版本不改变已签发令牌。没有数据时先核对环境、组织和时间范围,不要反复创建测试会话来填充统计。
出现来源拦截或预算拒绝时,记录响应中的 X-Request-Id、时间、HTTP 状态和错误码;不要附上 API Key 或令牌。用户级处理见管理终端用户。
上线前,用真实宿主网页验证正常对话和拒绝路径。下面列的是要执行的验收情境;控制台出现组件或触发 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 和有效期字段提示。脚本不打印凭证,也不替代成功签发和真实回复测试。
getToken(current) 重新检查宿主身份,后续消息仍属于 A。单独用已过期令牌请求,应得到 EMBED_TOKEN_EXPIRED(401)。visitor_secret 分享给另一位访客。记录每项的时间、环境、组件版本、预期与实际结果,以及失败响应的 X-Request-Id、状态和错误码。错误事件可能只有 message;从浏览器网络面板补齐响应信息,记录中不含 Key、令牌或访客凭据。
完成后撤销本轮测试令牌,归档不再使用的测试会话,恢复临时修改的限额和封禁状态。归档不会删除记录。再用正常配置执行一次成功对话,确认清理没有留下访问障碍。
在嵌入首次对外开放,或更换域名、身份接入方式、组件版本后执行本清单。先完成测试嵌入接入,每项保存可复查的结果,再开放访问。
version,确认它没有覆盖你的版本选择。PRAXIS_API_KEY 的真实值。visitor_secret 不被分享。praxis.ready。保留已验证的宿主部署和组件版本。配置异常时,在「版本」选择旧版本并点击「回滚到此版本」,重新签发后验证;已签发令牌不变。需要立即停止某个用户访问时,使用封禁或批量撤销,并确认旧令牌已失败。
为支持人员保存故障时间、环境、公开智能体 ID、会话 ID、HTTP 状态、错误码与 X-Request-Id,不保存令牌或 API Key。按限制与错误选择恢复动作,再从宿主完成一次对话验证。
<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.url | string | 必填,API 源站或以 /agent/v1 结尾的基础地址;初始化后不可变 |
api.getToken | (current: string | null) => Promise<string> | 返回嵌入令牌;首次 current 为 null,刷新时传当前令牌。后端每次重新确认宿主登录态 |
api.publishableKey | string | 匿名令牌入口,替代 getToken;初始化后不可变。访客身份由组件保存的私有 visitor_secret 延续 |
agent.name | string | 必填,界面显示名称,不用于选择智能体 |
agent.avatar | string | 头像 URL;实际访问的智能体由凭证决定 |
onClientTool | (call: { id, name, input }) => unknown | Promise<unknown> | 元素属性,不放入 setOptions。处理前端工具,未知工具应抛错 |
| 属性 | 类型 / 默认值 | 效果 |
|---|---|---|
layout | inline、sidebar、popup、modal;默认 inline | 对话容器形态 |
width | number;400 CSS 像素 | sidebar 宽度 |
launcher.type | none、bubble、mascot | 提供 launcher 时必填;默认 inline 为 none,其他布局为 bubble |
launcher.image、launcher.label | string | 启动按钮图片与文字 |
launcher.position | bottom-right、bottom-left | 启动按钮位置,默认右下 |
header.title、header.subtitle | string | 标题与副标题;标题默认使用 agent.name |
header.icon | string | 支持的 Lucide 图标名或图片 URL |
header.actions | { id: string; icon: string; label: string }[] | 自定义顶栏按钮,点击发出事件 |
history.enabled | boolean;默认 true | 历史入口;关闭时 showHistory() 不生效 |
startScreen.greeting | string | 空会话问候语 |
startScreen.prompts | { label: string; prompt: string }[] | 显示前 4 个;点击发送对应 prompt |
composer.placeholder | string | 默认随语言显示输入提示 |
composer.attachments.enabled | boolean;默认 true | 是否允许选择、粘贴或拖入附件 |
disclaimer、footer | string | 对话说明与底部文字 |
autoScroll | boolean | 新内容到达时是否自动滚动 |
locale | zh、en | 未指定时依次取宿主页面语言、浏览器语言;以 zh 开头用中文,否则英文;无语言信息用英文 |
| 属性 | 值 / 限制 |
|---|---|
colorScheme | light、dark、system;未设置时为浅色,system 跟随浏览器 |
accent、surface | 明确且不透明的 CSS 颜色;拒绝 var()、currentcolor、透明色,抛 TypeError |
grayscale | neutral、warm、cool |
radius | none、sm、md、lg、full;基础圆角依次为 0、4、10、18、9999 CSS 像素 |
density | compact、normal、spacious;基础间距依次为 3、4、5 CSS 像素 |
fontFamily | 非空字体族字符串,不能含 ; { } < >,否则抛 TypeError |
fontSize | 正的有限数字,单位 CSS 像素;否则抛 RangeError |
未设置 accent 时,浅色默认 #167A43,深色默认 #30a46c。颜色前景按对比度选择。
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 / onError | KitError(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_id | 1–128 字符;仅英文字母、数字及 _ . : @ - | 签发校验 |
ttl_seconds | 60–3600 秒,同时不超过策略 ttl_max_seconds;省略用策略默认值 | 签发校验与策略 |
system_message | 所有 text 块文本合计 ≤8192 UTF-8 字节 | 签发校验 |
metadata | ≤16 个键;键 1–64 字符,字符串值 ≤512 字符 | 签发校验 |
budget_cents | 0 至策略会话预算上限,单位美分;省略用策略默认值 | 签发校验与策略 |
签发 allowed_origins | 只能取策略来源条目的子集,逐项精确匹配 | 签发校验 |
| 策略来源 | ≤50 条 HTTPS 来源;通配只匹配一级子域且不含端口 | 嵌入策略 |
allow_localhost | 默认 false;开启后允许 HTTP localhost / 127.0.0.1(可带端口) | 嵌入策略 |
max_active_sessions_per_user | 1–100 个活跃会话/用户;空闲历史不计入 | 嵌入策略 |
per_user_rate_per_min | 1–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、MD | Kit 输入校验;新会话首条消息仍只接受纯文本 |
startScreen.prompts | 显示前 4 个 | Kit 组件 |
| Hosted 兑换凭证 | 60 秒内使用,仅能兑换 1 次 | Hosted 兑换校验 |
月度触顶动作 stop_new_sessions 拒绝新会话,alert_only 不拦截。控制台金额输入为美元,API 的预算字段为美分。会话预算耗尽后不能继续提交新的工作;保留工具确认和中断入口。
下列状态用于普通 HTTP 失败响应。事件流已经建立后,错误通过流内事件到达,不能再更改已发送的 HTTP 状态。「可重试」指原请求;修复配置或换取合法凭证后属于新的尝试。
401 · 访问阶段 · 不可直接重试。 令牌到期。可刷新 Kit 会重新取令牌,并对到期响应最多重放一次请求;Hosted 不刷新,须从宿主获取新链接。
401 · 访问/事件流 · 不可重试。 令牌已撤销,Kit 停止刷新。结束旧访问,由宿主重新核验是否仍允许该用户进入;不要自动重签绕过撤销。
403 · 来源检查 · 不可重试。 当前来源不被允许。核对实际 scheme、host、port,以及策略和签发时收紧的来源列表;修改后重新签发再验证。
403 · 签发/新建会话 · 不可重试。 用户已封禁。由管理者在「运营与管理 → 终端用户」核查;确需恢复时解封并签发新令牌。
429 · 请求/新建会话/匿名签发 · 可延后重试。 请求频率、活跃会话数或匿名每日会话额度触顶。分别等待限流窗口恢复、运行结束或下个 UTC 日;不能统一立即重试。
402 · 会话工作/匿名签发 · 不可重试。 会话预算或匿名每日花费用尽。由管理者核对预算;获准后以新令牌、新会话继续,或等待每日额度恢复,不循环重签规避上限。
402 · 新建会话 · 不可重试。 月度上限已到且策略要求停止新会话。由管理者决定是否调整上限或触顶动作,或等到下个 UTC 月。
400 · 签发/新建会话 · 不可重试。 使用了不允许的字段或初始事件。移除未获准的 agent_override;嵌入会话创建仅传 title、time_zone、initial_events,初始事件为纯文本 user.message(可带页面上下文)。
| 码 | HTTP / 阶段 | 恢复动作 | 可重试 |
|---|---|---|---|
INVALID_PARAMS | 400 / 参数校验 | 按字段提示修正类型、范围或大小 | 否 |
UNAUTHENTICATED | 401 / 鉴权 | 检查凭证是否缺失、失效;公开 key 是否停用 | 否 |
NOT_FOUND | 404 / 资源或 Hosted 兑换 | 核对组织、公开 ID、策略;失效或已用的 Hosted 链接需重新签发 | 否 |
UPSTREAM_UNAVAILABLE | 502 / 上游调用 | 延后重试;管理写操作先核对是否部分完成 | 是 |
会话权限、归档和事件内容错误见智能体限制与错误。
praxis.error 的 detail 可能是含 code、status、retryable、message 的 KitError,也可能只有 { message }。status: 0 表示没有 HTTP 响应。
| 码 | 状态 / 阶段 | 恢复动作与可重试性 |
|---|---|---|
network_error、stream_disconnected | 0 / 网络或事件流 | 恢复连接后可重试 |
http_error | 响应状态 / HTTP | 检查原始响应;无明确 retryable 时 429、5xx 可重试 |
invalid_token | 0 / 取令牌 | getToken 返回空值;修复返回值,可重试 |
invalid_response | 响应状态 / JSON 或历史 | 检查返回格式、分页是否前进;不可自动重试 |
invalid_stream | 响应状态 / 事件流 | 检查 SSE 内容与 JSON;不可自动重试 |
event_cursor_not_found | 200 / 历史恢复 | 重新加载历史定位会话;不可自动重试 |
callback_error | 0 / 回调 | 修复宿主异常;不可重试 |
aborted、disposed | 0 / 生命周期 | 请求取消或组件已销毁;不重试旧实例 |
非 Hosted 取令牌仅对可重试失败按 1、2、4 秒退避,最多重试 3 次;撤销或预算拒绝停止该重试。传输层自行恢复事件流时最多重连 5 次;界面可能接管恢复,不应另叠无限重试。
保留失败时间、环境、公开 agent_… ID、HTTP 状态、错误码和 X-Request-Id,不记录凭证。在「运营与管理 → 签发记录」核对令牌状态,再用会话回放定位发生阶段。修复后按测试嵌入接入复测;字段契约见智能体 API。
本页用于排查智能体配置和会话 API。嵌入令牌、来源、匿名配额与前端组件另见嵌入限制与错误;组织余额与配额见用量与费用。
| 项目 | 值 | 单位与含义 |
|---|---|---|
智能体名称 name | 1–256 | 字符 |
指令 system | 100,000 | 字符上限 |
智能体 / 会话 metadata | 16 / 64 / 512 | 键值对 / 键字符数 / 值字符数 |
会话标题 title | 500 | 字符上限 |
初始输入 initial_events | 50 | 事件上限 |
会话与事件列表 limit | 1–100; 20 | 范围;默认条数 |
会话预算 budget | 2000 | 默认美分(20 美元) |
会话时区 time_zone | UTC | 默认;传入时为 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 状态都表示同一个原因。
HTTP 400。请求字段、格式或不支持的选项不合法。按错误消息修正请求,原样重试不会解决。
HTTP 401。凭证缺失或无效。确认当前环境和组织的 Key,重新认证后再请求。
HTTP 403。当前身份无权访问该会话。核对所属用户与组织,不自动重试,也不要通过换 ID 探测其他用户。
HTTP 404。智能体或版本不存在,或对当前身份不可见。使用创建响应中的 ID,并确认版本。
HTTP 404。会话不存在或不可见。核对返回的会话 ID 与调用身份,不能靠重试创建响应来恢复。
HTTP 400。选择的模型不在固定版本允许的模型集合中。使用允许的模型,或更新配置后创建新会话。
HTTP 409。会话状态与当前操作冲突。重新读取会话或待确认工具,再决定是否提交;不要重复提交已完成的工具结果。
HTTP 409。会话已归档,不能继续发送消息。保留历史用于查询,新对话创建新会话。
HTTP 409。当前状态不允许操作,例如删除仍在运行的会话。先中断或等到空闲、终止,再核对状态后操作。
HTTP 400。分页游标无效或不匹配当前查询。重新从第一页开始;后续原样传回 next_page,并保持筛选条件。
HTTP 502。上游暂时不可用,可在退避后重试。对创建或发消息先查询已有结果,避免重复产生任务。
HTTP 503。运行环境暂时不可用,可在退避后重试。持续失败时记录请求标识并联系支持。
error.retryable: false 时先处理原因。收到 HTTP 429 时读取 Retry-After,不要无间隔循环;临时上游或环境错误可以退避后重试。网络断开不能证明请求未执行,创建资源和发送消息前应先查询已知会话及事件。
事件流已经开始后,运行失败可能通过事件返回,不能只看建立连接时的 HTTP 成功状态。检查回复、工具待确认和会话状态;不要把接收消息成功等同于任务完成。
保留发生时间、环境、智能体与会话 ID、HTTP 状态、错误类型与代码,以及 X-Request-Id;若响应仅提供 request-id 或 request_id,同时记录该值。不要记录 Key、嵌入令牌或访客凭据。
自动化把平台能力组合成脚本或工作流,由你主动运行,或由触发器自动启动。
| 对象 | 用途 |
|---|---|
| 脚本 | 一段 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,再读取结果。继续按快速开始保存和发布脚本。
本教程使用 develop、Bash、curl 和 jq。所有命令在同一个 Bash 会话中按顺序执行;不需要安装 SDK。脚本只做加法,不调用计费能力。
打开 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 能访问自动化运行列表。
创建运行接受源码和输入。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 分钟内轮询;脚本失败时打印真实错误并返回非零退出码。
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,再次读取;不要因为客户端等待结束就重复创建运行。
把同一份源码保存为脚本草稿,读取当前 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 发布。
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,下一步可以给它添加触发器。用完后可归档脚本;归档会保留名称和历史。
脚本和工作流描述要做什么;运行记录某一次执行;触发器决定何时执行。动作是执行期间调用的可复用能力。
脚本与工作流共用 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,详见创建运行的错误表。幂等不提供事务回滚,失败前已经完成的调用可能产生了效果。
运行本身不单独收费。运行中调用模型、搜索等能力产生费用,按这些能力的原价计费,并归因到运行与触发器上显示。没有能力调用的纯计算脚本不会产生模型或搜索费用。
脚本默认导出一个接收输入的函数;返回值成为运行的 output。
执行本页前,先在同一个 Bash 会话完成快速开始第 1–3 步:设置 PRAXIS_API_URL 与 PRAXIS_API_KEY、定义 SOURCE 并临时运行、定义并调用 read_result 辅助动作。该动作检查结果是否为 42,与本页的加一示例对应。
把下面的内容保存为 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,无需 import、API Key 或安装包。它提供以下 SDK 入口;方法参数采用参考页中的 path、query、body 形状,例如 praxis.automation.runs.create({body:{target,input}})。能否调用仍取决于发起者权限。
| 入口 | 能力参考 |
|---|---|
praxis.agent | Agent |
praxis.model | Model |
praxis.file | File |
praxis.sandbox | Sandbox |
praxis.search | Search |
praxis.database | Database |
praxis.automation | Automation |
praxis.organization | Organization |
praxis.meta | Meta |
脚本不能直接访问外部网络。用平台提供的能力完成操作,并 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、目标、状态和耗时。脚本不能自行指定独立运行(detach)的子运行;需要“启动后不等结果”时,调用 创建运行。
返回 JSON 可序列化的值;不要返回 undefined、BigInt 或循环引用。序列化后的 UTF-8 返回值最多 1 MiB,超过会使运行失败。大结果应保存到文件等存储中,返回引用。
脚本墙钟时间默认最多 5 分钟;CPU 时间最多 30 秒,并受剩余 deadline 进一步约束。等待平台调用与纯计算的耗时不同,增加等待时长不能解决死循环。全部数值见限制与错误。
| code | 报错原文 | 修法 |
|---|---|---|
SCRIPT_ERROR | 脚本执行出错 | 阅读 detail 中的原始异常及用户代码行号,修正后创建新运行。 |
CPU_LIMIT_EXCEEDED | 脚本 CPU 时间超出上限 | 终止无限循环,缩小每批纯计算量。 |
PAYLOAD_TOO_LARGE | Payload exceeds 1 MiB | 缩小 JSON 返回值,返回存储引用。 |
INPUT_INVALID | Value must be JSON serializable | 返回明确的 JSON 值,如对象或 null。 |
运行失败看 Run.error;创建请求被拒绝看 HTTP problem 响应。两者不能混用。
触发器把“何时执行”与脚本、工作流的内容分开。
执行本页前,先在同一个 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" | 每隔五分钟;最短一分钟,按经过的时长计算。 |
at | RFC 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 暂不支持。
创建时选 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 小时内去重。
当前 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。删除不影响已经启动的运行。
以下默认值约束单次运行及其子运行;调用方设置的更短 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 字符 |
| 脚本错误 detail | 1,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 Key 调用 deepseek-flash,读取实付费用,再找到同一次调用的记录。示例会产生少量费用。
在控制台 → 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 路径。
保存为 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。
打开控制台 → 模型 → 调用日志。在「按 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 读取,不依赖静态清单。
准备 Python 3.10+ 与 uv。原生目录 GET /model/v1/models 无需 Key;下面同时查询 OpenAI 兼容目录,需要按快速开始设置 PRAXIS_API_KEY。
期望:已有 Key 环境变量。示例默认使用 https://api.deepraxis.com;切换环境时,设置 PRAXIS_API_BASE 为 API 域名,不带末尾斜杠或 API 路径。
保存为 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;以查询时的结果为准。若断言失败,不要向所选协议发送请求,先从最新列表选择支持它的模型。
先按 type 找任务类别,再用 supported_apis 确认要调用的协议;不能从模型名称推断协议。原生列表还可用 supported_api=openai.responses 筛选;OpenAI 兼容列表的 api=chat,embeddings 表示匹配任一协议。
再核对上下文和输出限制,以及输入、输出、缓存或非 token 计量的单价。模型与目录概念解释计费单位及可用率;列出模型 API提供字段定义。
期望:所选 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 数只是估算,可能忽略缓存或其他计量。需要核对实际调用时,使用快速开始中的生成记录查询。
可用率衡量平台与上游能否完成有效请求:平台或上游原因的失败计入,调用方参数等原因造成的拒绝不计入。它不表示任意参数都能成功,也不保证下一次请求必然成功。
模型目录说明如何获取当前目录;本页不维护静态模型与价格清单。
你选择模型,平台从支持该模型及所用协议的线路中选择一次调用的去向。同一个模型可以有多条线路;线路数量与状态会变化。
平台先检查模型、协议和参数要求,再结合健康状态与优先级选择可用线路。备选线路必须支持同一种协议;Chat、Responses、Messages 之间不会为了换线而偷偷转换。
同优先级的可用线路还可能受会话亲和影响。优先级不是对某个供应商的永久绑定,也不是每次都选择最低价格的保证。
当一次尝试因可重试的上游故障、限流或线路不可用而失败时,平台可自动尝试其他合适的线路。没有可用备选或尝试仍失败时,请求返回错误;不是无限重试,也不保证已开始的流式回答可以无缝续接。
调用方参数错误不靠换线修复。应用仍需检查完成状态,并按错误与限制处理失败。
provider.require_parameters 要求线路声明支持请求中的相关工具与采样参数,避免把要求交给不支持它的线路。它限制候选线路,不会为模型增加能力,也不保证模型一定调用工具或生成某种回答;没有符合要求的线路时可拒绝请求。具体参数支持以 Chat API 参考为准。
provider 中指定供应商的 order、only、ignore 是 BYOK 专属选项;require_parameters 这种能力约束不属于供应商选择限制。平台模型由平台选线。BYOK 当前即将推出,不要依赖它指定平台供应商。
协议兼容让你沿用客户端的请求格式;不表示每个模型支持所有协议或官方的全部功能。以模型的 supported_apis 和各端点支持说明为准。
只有一个地址 https://api.deepraxis.com,按协议分入口:
| 入口 | 协议 |
|---|---|
/openai | OpenAI 系协议 |
/anthropic | Anthropic 系协议 |
/google | Google Gemini API |
POST /openai/v1/chat/completions。常用的对话协议;流式(SSE)、工具调用与多模态输入是否可用取决于模型。 模型能力与字段差异见 Chat API;会话标识的作用见会话亲和。
POST /openai/v1/responses 及 retrieve / delete / cancel / input_items。OpenAI 新一代有状态协议;Codex 等 Agent 走这里。支持范围见 Responses API。压缩响应与输入 token 计数端点当前未实现,调用返回 501。
/openai/v1/files:上传 user_data 文件供后续请求引用;可列出、读元数据、下载、删除。
POST /openai/v1/images/generations:使用目录声明支持图像生成的模型。
POST /anthropic/v1/messages。Claude Code、Anthropic SDK 走这里。 具体支持范围见 Messages API。
/anthropic/v1/files:上传、列出、读元数据、删除;上传后不可下载(与官方一致)。
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 兼容接口
会话亲和让同一会话后续请求,在条件允许时优先复用之前成功的线路。它是一种选路偏好,不是锁定供应商,也不是替应用保存对话历史。
多轮对话常重复系统提示、工具定义和早期消息。重复前缀留在同一条支持缓存的线路上,更有机会命中上游缓存,降低重复输入的费用与延迟。相同会话 ID 不保证命中缓存;内容变化、缓存失效或换线都可能让它失效。
客户端可用请求体的 session_id 或请求头 x-session-id 标识会话;两者同时存在时,请求体优先。没有显式会话 ID 时,还可能使用 prompt_cache_key 或从对话前缀推导标识。自动推导受内容变化影响,因此长期多轮对话应保持显式标识一致,不同对话使用不同标识。
缓存亲和要求该线路支持本次模型与协议、输入得到保留,并且缓存读取价格低于普通输入价格。成功完成的调用才更新亲和记录;后续请求仍须通过当前的健康与能力检查。
亲和只在同优先级范围内调整顺序,不绕过冷却或参数要求。距上次成功已空闲 10 分钟、原线路不再合适,或亲和记录不可用时,按正常路由继续;因此应用不能依赖它保证状态永远留在同一上游。
Chat 请求仍应携带需要的消息历史。有状态 Responses 的续写属于独立的协议能力,不能用会话 ID 代替 previous_response_id。参数说明见 Chat API 与 Responses API。
| 你想做什么 | 去哪 |
|---|---|
| 把 Praxis 接进 SDK / 框架 | Cookbook → SDK |
| 自带模型凭证(BYOK) | BYOK |
| 处理错误与限流 | 错误与限流 |
OpenAI SDK 使用 https://api.deepraxis.com/openai/v1。请求按同协议转发;兼容不表示所有官方端点、字段和模型都可用。
| 客户端 | 基础地址 | 模型目录 |
|---|---|---|
| OpenAI SDK | https://api.deepraxis.com/openai/v1 | /openai/v1/models |
| Anthropic SDK | https://api.deepraxis.com/anthropic | /anthropic/v1/models |
| Google GenAI SDK | https://api.deepraxis.com/google | /google/v1beta/models |
| Praxis 原生 API | https://api.deepraxis.com/model/v1 | 见原生 API,包含 providerOptions |
各兼容目录只列声明提供该协议的模型。出现在 OpenAI 目录里不等于同时支持 Chat Completions 和 Responses;未声明的操作返回 404 model_protocol_not_offered。
| 应用使用的内容 | 发请求前核对 |
|---|---|
messages、工具、采样、思考、供应商偏好 | Chat Completions 字段支持 |
input、响应存储、响应工具 | Responses 字段支持 |
usage.cost、生成 ID | 迁移页费用处理与生成记录 |
| 上传、文件用途 | 上传文件 |
| 对话以外的端点 | 操作支持情况与不支持清单及替代 |
未知顶层字段可能原样转发给同协议上游;请求被接受不证明字段已生效。已声明字段仍会校验。依赖一个选项前,请查看对应端点的字段支持说明。
需要 Python 3.10+、uv 和一个 Praxis Key。在控制台 → API Keys创建 Key,将它安全注入当前终端的 PRAXIS_API_KEY 环境变量;不要把 Key 写进代码或日志。保留原服务的配置与 Key,供回滚使用。以下命令在 macOS / Linux 的 Bash 中执行。
test -n "${PRAXIS_API_KEY:-}"期望:退出码 0;退出码 1 表示变量未设置,先设置再继续。
示例使用 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 与模型,再运行原冒烟请求。
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 列表,缺失操作的处理见迁移替代方案。
需要 Python 3.10+、uv 和一个 Praxis Key。在控制台 → API Keys创建 Key,将它安全注入当前终端的 PRAXIS_API_KEY 环境变量;不要把 Key 写进代码或日志。保留原服务的配置与 Key,供回滚使用。以下命令在 macOS / Linux 的 Bash 中执行。
test -n "${PRAXIS_API_KEY:-}"期望:退出码 0;退出码 1 表示变量未设置,先设置再继续。
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 请求。
流式响应把一条回答拆成多个事件。显示收到的文本后,还要读取终态和用量,才能确认本轮完成。
本页使用 POST /openai/v1/chat/completions 的 deepseek-flash,并提供 POST /anthropic/v1/messages 的 claude-opus-5 示例;两者于 2026-10-12 验证通过。
将 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 <文件名> 执行。各段代码独立运行。
保存为 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 的读取顺序是:
choices[].delta.content;role、思考或空增量可能没有正文,不应当作文本显示。finish_reason。本例的完整文本回答以 stop 结束;tool_calls 应进入工具调用流程。stream_options.include_usage=true 后,继续读取末块 usage。这一块的 choices 可以为空,不能直接访问 choices[0]。data: [DONE] 收尾;SDK 负责处理该标记并结束迭代。不要在收到 finish_reason 时立即跳出循环,否则会漏掉之后的用量。断开连接不等于模型完成;没有终态或末块用量时,不应把部分文本和未知用量记为完整结果。
在同一终端执行:
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 不能证明回答完整,仍需检查终态。
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 后,文本、终态与末块用量均正常返回。
模型返回工具名称和参数,由你的程序执行工具,再把结果交回模型。收到工具调用还不是完成回答。
本例查询本地演示数据:Paris 的温度固定为 22°C,不访问天气服务。deepseek-flash 在 GET /openai/v1/models 的 supported_parameters 中列有 tools 和 tool_choice;下面的两轮调用于 2026-10-12 验证通过。
将 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 <文件名> 执行。各段代码独立运行。
保存为 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 的实际应用应允许多轮工具调用,并设置轮数上限。
保存为 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。
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" 后通过。不要在重试时丢掉该参数。
结构化输出将回答约束为指定的 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。
将 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 <文件名> 执行。各段代码独立运行。
保存为 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 的业务结果;流式回答应先收齐文本再解析。
保存为 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。
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 约束已生效;保留示例中的完整验证。
同一个“低”档,在 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 Completions | reasoning_effort;也接受 reasoning 对象 | usage.completion_tokens_details.reasoning_tokens |
| Anthropic Messages | thinking 控制模式,output_config.effort 选择档位 | usage.output_tokens_details.thinking_tokens |
| Google generateContent | generationConfig.thinkingConfig 中的 thinkingBudget 或 thinkingLevel | usageMetadata.thoughtsTokenCount |
Chat 的档位范围从 none 到 max,实际可选项以模型为准;none 表示关闭,不是“低”。Messages 的 enabled、adaptive、disabled 分别表达预算思考、自适应思考和关闭,不能把 thinking.effort 当作档位字段。Google 的预算和档位随模型而异,不要同时发送两种控制。
准备 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 路径。
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。数字会随调用变化。
这里选用 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。
以下依次调用两个模型: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.
省略关闭设置或选择模型支持的档位,再请求。不要靠换成另一个协议的“关闭”字段绕过限制。
只有声明 openai.responses 的模型才能使用 Responses。支持 Chat Completions 不代表支持 Responses。用 GET /openai/v1/models?api=responses 查询;2026-10-13 返回 deepseek-flash 和 glm-5.3-flash。
准备 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 路径。
平台会展开 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 参考。
先确认模型同时支持所选协议和输入类型。查看 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 输入。文件上传和生命周期细节见文件服务指南。
准备 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 路径。
脚本在本地构造红色 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。
使用 OpenAI Images API 的 gpt-image-2 生成和编辑图片。调用前查 GET /openai/v1/models?api=images;原生模型目录分别声明 openai.images.generations 和 openai.images.edits。
准备 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 路径。
在可写目录运行。示例会产生两次图像调用费用:生成一次、编辑一次,并保存 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。打开图片:第一张应为白底蓝圆,第二张应为白底红圆。文件签名检查验证格式,不代替画面检查。
gpt-image-2 按 token 计价,不是每张固定价。文本输入、图像输入、图像输出分别计价;编辑还会产生输入图像 token。查看响应中的用量,以及模型目录中的实时价格;账户汇总见用量与费用。
若目录断言失败,先停止生成,选择当前可用的图像模型。若输出断言失败,保留请求 ID 和响应错误,不要为排查文件问题反复生成。
上传、过期、下载与删除见文件服务。
对话中可用 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.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。没有锁定的字段可以在本次请求中覆盖,且不会修改租户默认配置。文件大小、页数等平台限制不可通过请求调整。
转换成功的响应会携带文件注明(annotations)。保留完整对象,不要修改哈希或解析内容:
{
"type": "file",
"file": {
"hash": "<原样保留服务返回的哈希>",
"name": "notes.pdf",
"content": [{ "type": "text", "text": "解析出的文字" }]
}
}| 入口 | 非流式位置 | 流式位置 |
|---|---|---|
| Chat Completions | choices[].message.annotations | choices[].delta.annotations,每个 choice 返回一次 |
| Anthropic Messages | 响应消息顶层 annotations | message_start.message.annotations |
| Responses | output[] 中 message 的 content[] 内 output_text.annotations | response.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 等非对话调用不因此获得对话插件。音频、视频目前没有转换插件,只有模型原生支持时才可输入。
: PRAXIS PROCESSING 保活注释,它不是模型输出。background: true 请求返回 PLUGIN_BACKGROUND_UNSUPPORTED。同一对话的请求复用 x-session-id,新对话使用新 ID。会话亲和帮助符合条件的请求复用供应商,但不保证固定供应商或缓存命中。
会话 ID 不会替你保存 Chat Completions 的历史。每轮仍需发送必要消息;如需按响应 ID 接续,使用 Responses。
准备 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 路径。
示例生成一个 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。
Prompt Cache 复用重复输入的前缀计算,不复用上一条回答。是否命中要看返回的缓存 token 数,不能凭回答相同或速度更快判断。
本页的 OpenAI 示例使用 deepseek-flash,不添加缓存控制参数。2026-10-12 实测:26,016 个输入 token 的第二次请求报告 25,856 个缓存 token;这是一组观测值,不是每次调用的保证。
将 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 <文件名> 执行。各段代码独立运行。
将不常变的指令和资料放在前面,当前问题放在后面,保持模型、前缀内容和顺序一致。下面生成较长的演示资料并连续请求两次,会产生两次模型调用费用。
保存为 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 内,不能再相加。缓存只减少符合条件的输入费用,不使输出免费;成本按所选模型的缓存输入、普通输入和输出单价计算,不套用固定折扣。缓存最短前缀和保留时间由模型决定。
保存为 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。
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 以便核对,不能用相同回答代替命中证据。
BYOK 即将推出:控制台 → 模型 → BYOK 当前不可点击,标为「即将推出」,暂时无法在此导入自己的供应商密钥。当前可使用 Praxis 云 API Key,从模型目录选择模型,按模型快速开始调用。不要把供应商的 Key 当作 PRAXIS_API_KEY。
| HTTP | 含义 | 你该做什么 |
|---|---|---|
| 400 | 请求参数有误 | 检查请求体格式、字段名、模型 ID |
| 401 | 认证失败 | 检查 API Key 是否完整、是否已过期 |
| 403 | 权限不足或余额耗尽 | 确认 Key 有效;检查账户余额 |
| 404 | 路径或模型不存在 | 检查 API 地址后缀;模型 ID 是否在目录中 |
| 429 | 速率限制 | 按 X-RateLimit-Reset 头等待后重试 |
| 500 | 服务端错误 | 稍后重试 |
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 | 距离重置秒数 |
沙箱(Sandbox)提供安全隔离的代码执行环境——上传代码、运行、获取结果,全程在云端隔离实例中进行。
一个 Praxis API Key。到 控制台 → API Keys 创建。
沙箱支持多种执行环境(内置环境只读,租户可自建):
curl https://api.deepraxis.ai/sandbox/v1/environments \
-H "Authorization: Bearer $PRAXIS_API_KEY"通过 API 创建实例,指定环境和初始资源。
在沙箱中运行代码并获取输出。
使用完后释放,避免持续计费。
| 概念 | 一句话 |
|---|---|
| 环境(Environment) | 代码运行的隔离实例,包含预装工具和运行时 |
| 实例(Instance) | 一个正在运行的沙箱环境 |
| 生命周期 | 创建 → 使用 → 释放,释放后不可恢复 |
| 网络限制 | 部分环境限制出站网络(limited networking) |
搜索(Search)为 AI 应用提供网络搜索能力——返回结构化结果,可直接用于 RAG 或智能体工具调用。
一个 Praxis API Key。到 控制台 → API Keys 创建。
通过 Search API 执行一次搜索,获取结构化结果。
搜索可以作为智能体的工具——智能体自主决定何时搜索、如何整合结果。
搜索与模型结合,构建基于网络信息的问答系统。
| 概念 | 一句话 |
|---|---|
| 查询(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;不要公开此地址。
使用自己的 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 当前接受但忽略,不会选区域或代解验证码。
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 } },
}),
});501;region、solveCaptchas 当前接受但忽略。X-Request-Id。下表是应用当前的运行时能力。
| 能力 | 状态 | 说明 |
|---|---|---|
| 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 |
在要部署的版本中声明绑定:
{ "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,再把它交给需要使用文件的服务。
素材库组织素材及其理解结果;知识库提取文档内容并建立检索索引。只上传文件,不会自动将它加入库,也不会自动让它可检索。
安装 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.pyimport 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。即使列出或下载失败,示例也会删除自己创建的文件。
purpose 记录文件的预期用途。上传接受三个值:
| purpose | 用途与限制 |
|---|---|
user_data | 通用输入文件。在对话中引用图片时必须使用此值。 |
assistants | 用于智能体或知识库工作流的文件。上传不等于加入智能体或知识库;对话图片引用不接受此用途。 |
vision | 只接受 PNG、JPEG、GIF、WebP 图片(按内容识别),其他内容返回 400 FILE_TYPE_UNSUPPORTED;对话图片引用不接受此用途。 |
上传不接受 batch、fine-tune 和 evals。通过 Anthropic 上传的文件统一记为 user_data。
同一账户的 /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,可用于断点续传和音视频拖动。
| 项目 | 限额 |
|---|---|
| 单文件 | 100 MiB(104,857,600 字节);不接受空文件 |
| 文件存储配额 | 20 GiB(21,474,836,480 字节),账户内文件共享 |
OpenAI 或文件 API 的 expires_after | anchor: "created_at",3,600–2,592,000 秒(1 小时至 30 天) |
文件 API 的 praxis[expires_in_seconds] | 3,600–7,776,000 秒(1 小时至 90 天) |
Anthropic 表单 expires_in_seconds | 3,600–7,776,000 秒(1 小时至 90 天) |
过期参数只能选一种。Anthropic 的 expires_in_seconds 只能放在表单中;放在查询参数里返回 400。参见上传参数。
兼容接口按各自官方的方式拒绝未知参数,不会静默忽略:
| 接口 | 可用参数 | 其他参数 |
|---|---|---|
| Anthropic 上传 | 表单 file、expires_in_seconds;查询参数仅 beta | 400 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 Key,将下方代码保存为 quickstart.mjs,在 Node.js 中运行。示例建库、上传图片、按标签检索,并给出原图与缩略图地址。
npm install cloudinary@2
# Set PRAXIS_API_KEY in your environment.
node quickstart.mjsimport { 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"一个库是素材的隔离单元。检索只会命中当前库的素材。
素材有稳定的 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,即可自然语言检索。默认混合检索结合关键词与语义,也可只用语义。命中带匹配内容;音视频片段带起止毫秒,方便定位。从未完成理解的素材不进入语义结果;重新处理中的素材继续用上一版结果参与检索,直到新结果就绪。
PDF、文本等 raw 文件只存放,不做理解,semantic 为 unavailable,不进入语义检索,只能按元数据(public_id、display_name、tags、context 等)用表达式检索。需要按全文内容检索文档,请使用知识库。
删除后立即不可见。库可在 7 天内恢复,到期后无法恢复。单个素材没有恢复接口。
「文件」存放原始文件;素材库进一步组织、理解和检索内容。从文件导入是拷贝:两份文件独立,之后的修改或删除不会互相同步。
视频超过 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 |
| 素材库 | 组织素材、管理文件夹与标签,并查找图片等素材 |
| 知识库 | 从文件内容中查找相关文本分段,返回来源文件与内容 |
一个源文件可以加入多个知识库。从知识库移除文件不会删除源文件;删除源文件后,它会自动从所有知识库中移出,之后不再被搜索到。
在服务端运行 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 |
rerank | ranking_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 上限。下方耗时来自样例实测,不是服务保证。
| 项目 | 上限 |
|---|---|
| 每个组织的未删除知识库 | 100 |
| 每个知识库的文件 | 10,000 |
| 单个源文件 | 100,000,000 bytes (100 MB) |
| 单个文件估算 token | 5,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 个候选;搜索不提供下一页。
知识库可以入库以下类型(按文件服务识别的类型判断):
| 类型 | 入库内容 |
|---|---|
| 文档文本 | |
| 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 | code | 处理建议 |
|---|---|---|
| 400 | INVALID_PARAMS / LIMIT_EXCEEDED | 修正参数或减少数量。 |
| 401 | UNAUTHENTICATED | 检查 API Key;兼容响应 code 为 null。 |
| 404 | VECTOR_STORE_NOT_FOUND / FILE_NOT_FOUND / FILE_NOT_IN_STORE / BATCH_NOT_FOUND / CHUNK_NOT_FOUND | 核对资源 ID 与所属知识库。 |
| 409 | VECTOR_STORE_INVALID_STATE / FILE_INVALID_STATE | 检查删除、过期或文件处理状态。 |
| 413 | PAYLOAD_TOO_LARGE | JSON 请求超过 1 MB;兼容响应 code 可能为 null。 |
| 429 | RATE_LIMITED | 稍后重试。 |
| 500 | — / null | 服务内部错误;保留请求 ID 并稍后重试。 |
| 501 | NOT_IMPLEMENTED | 当前不支持该操作。 |
| 503 | PROVIDER_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 是 headless CMS 式的内容库:人、智能体和脚本通过公开 API 直接读写结构化的表与行。它保存行程、目录等内容,独立于使用这些内容的应用。它不是应用的私有运行库,也不是数据仓库。
| 角色 | 谁用 | 生命周期 | 存在哪里 |
|---|---|---|---|
| 内容库 | 人、智能体、脚本;应用也可通过公开 API 读取 | 内容独立于应用发布进行编辑和复用 | 结构化内容用 Database,文件用 File,可检索知识用 Knowledge |
| 应用运行库 | 管理订单、会话和业务规则的应用代码 | 状态随应用运行而变化,由应用自己的业务约束管理 | 应用自带的 d1、kv 或 r2 components |
| 数据仓库 | 分析人员和报表流水线 | 积累历史数据用于分析 | 专门的分析型数据仓库,不使用 Database |
典型模式是内容库 → 构建 → 发布到应用:从 Database 读取行程内容,构建站点文件,再用 praxis.application.publish 上传、创建版本并部署。修改源内容不会更新这些已发布的文件;需要再次构建和发布才能包含修改。
每个库是一个隔离单元,包含自己的表、字段、行和事件。
通过 Database API 创建库与表结构;通过官方 supabase-js 读写行。同一库的两个行访问地址共享数据。
在 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 是 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 | 数值 |
boolean | true 或 false |
datetime | RFC 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 字段必须在 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 为 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 天内调用恢复库可以重新使用库及保留的表和行;超过窗口无法恢复。此恢复能力针对整个库,不是单独删除的表、字段或行。
按以下边界规划库与查询。
| 限制 | 上限 |
|---|---|
| 每库最多表数 | 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。
把 API Key 放进 PRAXIS_API_KEY 环境变量,依次运行下面三个请求。每次运行都会创建一个新库。请先安装 jq 以提取 ID;可设置 VAULT_NAME 自选显示名称。
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。
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 的请求上。
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,不可恢复。
库是一组 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。
| 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 清除备注,省略则保留。
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 不在一键授权模板中。
POST /vault/v1/authorizations,传支持授权的 template_id,或要授权的 resource_url。connect_url 交给最终用户。这个链接 30 分钟内有效、只能使用一次。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 托管智能体的官方 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 个有效凭据,错误使用该接口自己的错误格式。
API Key 是你调用 Praxis API 的凭证——sk-praxis- 开头的一串字符。每个 Key 属于一个账户空间,通过它产生的调用都记在那个空间名下。
| 字段 | 说明 |
|---|---|
| 名称 | 必填,用于识别用途(如 prod-backend、my-app) |
| 有效期 | 30 / 60 / 90 天或永久,默认 90 天 |
创建成功后弹窗会展示完整 Key 并带「复制 Key」按钮——立即复制保存。关闭弹窗后列表只显示掩码,但你可以随时点行内复制图标重新获取完整 Key。
在 API Keys 列表页,你可以:
export PRAXIS_API_KEY=sk-praxis-…)| 症状 | 原因 | 解法 |
|---|---|---|
| 401 Invalid API key | Key 已删除/禁用,或复制不完整 | 到控制台确认 Key 状态;重新完整复制 |
| Key 到期了怎么办 | 有效期到了 | 创建新 Key 并更新到你的应用 |
| 能创建多少个 Key | — | 每个账户空间可创建多个 Key,按需管理 |
每一次 API 调用都有账、可追溯。控制台的用量页提供汇总视图和逐笔明细。
打开 控制台 → 用量与费用。
汇总视图(默认):
逐笔明细:点「查看用量明细」或点击某个资源行,可以看到每一笔调用的:
按次计费、无闲置费。具体来说:
除了控制台,你还可以在 API 响应中直接获取本次调用的费用:
usage.cost 字段直接返回费用金额message.usage.cost 可读(JS SDK 的类型层不声明该字段,但协议层有)这意味着你可以在自己的应用里实时追踪成本,不需要轮询控制台。
| 症状 | 原因 | 解法 |
|---|---|---|
| 用量页显示为空 | 还没有调用过 | 发一次请求后刷新 |
| 明细和我的记录对不上 | 推理 token 也计费 | 看 usage 里的 reasoning_tokens |
| 余额不足会怎样 | — | 调用会返回余额不足错误,到资金账户充值 |
Praxis 采用按次计费模式——每次 API 调用按实际 token 用量计费,不调用不收费。
| 项目 | 说明 |
|---|---|
| 计费方式 | 按次按 token,输入和输出分别计价 |
| 闲置费 | 无——不调用不产生费用 |
| 最低消费 | 无 |
| 推理 token | 计入输出 token(推理模型的思维链也计费) |
| 免费额度 | 新账号自带 $1 |
各模型的具体单价见模型目录。
充值后余额立即可用。
当余额耗尽,API 调用会返回余额不足错误(HTTP 402 或类似错误码),不会静默降级。你的应用应处理这个错误并提示用户充值。
usage.cost 字段直接返回本次调用费用| 症状 | 原因 | 解法 |
|---|---|---|
| 调用返回 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 | 正常 |
curl https://api.deepraxis.ai/openai/v1/models(预期返回 401 认证挑战,说明服务活着)(待建设:事件时间线将发布在状态页。)
到控制台 → 模型复制确切 ID。deepseek-flash 是滚动入口(始终最新 Flash),deepseek-v4-flash 是固定版本。
api.deepraxis.ai 通过 Cloudflare 全球网络分发,国内一般可直连。需要代理也支持标准 HTTP 代理。
API 返回余额不足错误(HTTP 402),不会静默降级。充值后立即恢复。
到 API Keys 立即删除 → 创建新 Key → 更新应用。
可能是 STREAMING_UNPROVEN(换模型或去掉流式)、推理模型先思考(等几秒)、或网络瞬断(重试)。
新注册 $1。deepseek-flash 大约能跑几千次对话。
可以——同一请求换 model 字段即可,不需要多个 Key。
Praxis 提供 OpenAI / Anthropic 兼容协议——已有代码只改 base_url 即可迁移。详见迁移指南。
联系我们。
最直接的渠道:控制台 → 帮助与反馈。你可以:
工作日通常当天回复。
| 信息 | 为什么需要 |
|---|---|
| 你做了什么(操作步骤) | 复现问题 |
| 期望看到什么 vs 实际看到什么 | 确认是 Bug 还是设计如此 |
| 错误信息原文 | 定位根因 |
| 浏览器 / 操作系统 | 排除环境因素 |
| 时间点(大约) | 查服务端日志 |
如果是 API 调用报错,附上:
sk-praxis- 后的前几个字符,不要发完整 Key)所有数据默认存储在 APAC(亚太) 区域。创建新资源(如 Durable Object)时默认使用 locationHint: "apac"。
| 数据类型 | 保留策略 |
|---|---|
| 对话记录 | 持久保存,直到你主动删除 |
| API 调用日志 | 按平台日志策略保留 |
| 上传文件 | 持久保存,直到你主动删除 |
| 已删除的数据 | 删除后无法恢复 |
Authorization: Bearer sk-praxis-…)或 x-api-key 头。deepraxis.com 域名已完成 ICP 备案。境内访问经深圳 ECS 反代进入,备案号显示于页脚。