Praxis Cloud 开发者文档

2026-10-14

概览

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

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

开始使用

01

选择服务

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

02

创建云 Key

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

03

完成第一次调用

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

选择你需要的服务

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


智能体

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

快速开始

创建并运行第一个智能体

核心概念

智能体与运行

会话、记忆与协作

使用指南

配置职责与能力

提供文件与资料

使用工具与组织协作

接入自己的应用

技术参考

限制与错误处理

智能体 API


模型

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


沙箱

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

快速开始

创建沙箱并执行一个任务

核心概念

沙箱与生命周期

执行环境与资源

使用指南

执行命令 · 管理文件

使用浏览器与网络

回收环境与处理失败

技术参考

限制与错误处理

沙箱 API


搜索

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

快速开始

完成第一次搜索

核心概念

搜索范围与查询

结果、排序与来源

使用指南

设置搜索条件

读取与筛选结果

在应用中使用搜索

技术参考

限制与错误处理

搜索 API

管理你的云服务

身份与权限

管理云 Key 与授权范围

保护服务访问凭证

用量与费用

计量口径、用量查询

账单与费用管理

配额与限制

查询服务配额

了解请求与资源限制

观测与故障排查

请求标识、日志与排错

服务异常的定位路径

API

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

浏览所有 API →

第一个请求

(待补充)

选择服务

(待补充)

核心概念

Core concepts

智能体

智能体服务封装模型调用、工具执行等能力:你定义职责、提供资料与文件、配置工具与协作,由智能体服务管理底层能力调用,通过智能体 API 完成业务任务。

与其他服务的关系

智能体建立在模型、沙箱、搜索等云能力之上;这些能力也向构建者独立开放,见选择服务。

出口

API 参考 → · Cookbook →

快速开始

(待补充)

概念

(待补充)

指南

(待补充)

限制与错误

(待补充)

自动化

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

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

选择入口

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

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

先运行一次

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

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

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

下一步

API 参考 → · Cookbook →

快速开始

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

1. 创建 API Key

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

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

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

2. 临时运行脚本

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

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

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

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

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

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

3. 读结果

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

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

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

4. 保存为脚本并发布

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

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

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

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

5. 按版本运行

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

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

API 参考 → · Cookbook →

概念

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

定义、版本与标签

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

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

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

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

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

运行生命周期

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

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

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

子运行与来源

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

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

幂等与重试

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

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

费用归因

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

API 参考 → · Cookbook →

编写脚本

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

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

入口与 schema

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

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

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

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

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

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

运行与日志

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

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

全局 praxis

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

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

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

调用平台内置动作

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

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

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

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

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

调用自建动作

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

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

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

调用另一个脚本或工作流

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

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

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

返回值和时间预算

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

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

有依据的错误与修法

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

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

API 参考 → · Cookbook →

触发器

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

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

定时执行

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

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

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

先预览下次时间:

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

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

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

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

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

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

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

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

重叠、延迟与有效时间

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

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

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

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

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

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

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

Webhook

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

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

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

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

签名头与 envelope

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

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

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

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

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

查看触发记录与清理

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

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

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

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

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

API 参考 → · Cookbook →

限制与错误

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

执行与数据

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

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

脚本运行时

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

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

触发器

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

找到对应错误

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

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

API 参考 → · Cookbook →

模型

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

出口

API 参考 → · Cookbook →

快速开始

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

1. 准备 Key 与终端

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

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

test -n "${PRAXIS_API_KEY:-}"

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

2. 发出请求并读取费用

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

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

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

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

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

3. 找到这次调用

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

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

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

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

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

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

失败分支

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

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

API 参考 → · Cookbook →

模型目录

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

1. 准备查询

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

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

2. 核对模型与协议

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

import os
import httpx

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

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

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

3. 根据结果选择

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

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

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

API 参考 → · Cookbook →

概念

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

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

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

API 参考 → · Cookbook →

模型与目录

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

模型 ID 与能力

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

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

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

标价与实付

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

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

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

可用率

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

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

API 参考 → · Cookbook →

路由

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

从请求到线路

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

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

失败换线

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

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

参数要求与供应商选择

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

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

API 参考 → · Cookbook →

协议兼容

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

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

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

OpenAI Chat Completions

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

OpenAI Responses

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

OpenAI Files

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

OpenAI Images

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

Anthropic Messages

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

Anthropic Files

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

Google Gemini API(generateContent)

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

模型目录

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

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

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

标准化地址

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

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

API 参考 → · Cookbook →

会话亲和

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

为什么有用

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

如何识别同一会话

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

何时生效

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

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

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

API 参考 → · Cookbook →

BYOK:自带模型凭证

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

API 参考 → · Cookbook →

指南

OpenAI 兼容接口

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

选对地址

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

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

字段差异去哪里核对

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

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

1. 准备

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

test -n "${PRAXIS_API_KEY:-}"

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

2. 验证 Responses

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

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

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

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

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

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

Anthropic 兼容接口

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

兼容覆盖什么

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

字段差异去哪里核对

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

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

1. 准备

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

test -n "${PRAXIS_API_KEY:-}"

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

2. 验证 Messages 与 token 统计

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

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

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

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

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

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

流式响应

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

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

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

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

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

import os
from openai import OpenAI

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

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

Chat Completions 的读取顺序是:

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

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

3. 用 cURL 检查事件顺序

在同一终端执行:

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

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

4. 使用 Anthropic Messages

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

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

import os
from anthropic import Anthropic

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

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

实测失败与处理

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

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

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

协议细节:OpenAI · Anthropic。

API 参考 → · Cookbook →

工具调用

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

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

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

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

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

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

import json
import os
from openai import OpenAI

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

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

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

3. 用 TypeScript 执行同一流程

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

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

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

4. 使用 Anthropic Messages

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

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

import json
import os
from anthropic import Anthropic

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

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

实测失败与处理

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

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

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

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

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

协议细节:OpenAI · Anthropic。

API 参考 → · Cookbook →

结构化输出

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

选择支持的模型

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

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

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

2. 使用 json_schema 和 strict

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

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

import json
import os
from openai import OpenAI

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

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

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

3. 用 TypeScript 验证相同结果

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

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

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

4. 使用 Messages 的 output_config

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

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

import json
import os
from anthropic import Anthropic

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

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

实测失败与处理

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

This response_format type is unavailable now

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

协议细节:OpenAI · Anthropic。

API 参考 → · Cookbook →

调整思考强度

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

选择协议与档位

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

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

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

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

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

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

deepseek-flash 支持 Chat 和 low。

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

import os
from openai import OpenAI

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

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

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

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

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

import os
from anthropic import Anthropic

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

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

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

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

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

import json
import os
import httpx

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

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

如何比较结果

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

实测失败与处理

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

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

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

API 参考 → · Cookbook →

Responses API

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

2. 接着上一条响应提问

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

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

import os
from openai import OpenAI

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

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

限制与实测失败

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

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

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

API 参考 → · Cookbook →

读取图片、PDF 和文本文件

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

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

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

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

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

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

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

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

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

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

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

实测失败与处理

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

I wasn't able to read the attached PDF

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

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

API 参考 → · Cookbook →

生成与编辑图像

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

2. 生成并编辑

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

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

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

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

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

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

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

3. 查看图像费用

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

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

操作细节:生成与编辑。

API 参考 → · Cookbook →

在对话中引用文件

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

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

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

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

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

import base64
import os
from openai import OpenAI

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

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

插件

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

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

配置与默认行为

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

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

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

关闭转换使用:

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

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

PDF 处理方式

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

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

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

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

配置优先级

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

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

费用

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

解析注明与下一轮带回

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

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

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

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

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

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

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

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

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

查看模型支持方式

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

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

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

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

限制

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

会话亲和

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

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

2. 两轮使用同一个 ID

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

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

import os
import uuid
from openai import OpenAI

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

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

实测失败与处理

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

字段细节:Chat Completions。

API 参考 → · Cookbook →

Prompt Cache 与成本

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

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

1. 准备终端

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

test -n "$PRAXIS_API_KEY"

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

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

2. 连续请求相同前缀

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

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

import os
from openai import OpenAI

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

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

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

3. 用 TypeScript 检查命中

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

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

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

4. 观察 Anthropic cache_control

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

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

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

import os
from anthropic import Anthropic

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

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

实测未命中时怎么处理

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

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

协议细节:OpenAI · Anthropic。

API 参考 → · Cookbook →

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

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

API 参考 → · Cookbook →

错误与限流

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

沙箱

快速开始

(待补充)

概念

(待补充)

指南

(待补充)

限制与错误

(待补充)

搜索

Roadmap

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

它是什么

(待补充)

与其他服务的关系

(待补充)

出口

API 参考 → · Cookbook →

快速开始

(待补充)

概念

(待补充)

指南

(待补充)

限制与错误

(待补充)

云端浏览器

云端浏览器是一个在云上运行的 Chrome,可以用 Playwright 或 Puppeteer 直接连接操控,也可以在 Console 实时观看与接管。

三种用法

以下示例在 Node.js 中运行。先设置 PRAXIS_API_KEY,将代码保存为 .mjs 文件,再用 node 执行。只需安装客户端包,无需下载本地 Chrome。

export PRAXIS_API_KEY="你的 API Key"
npm install playwright @browserbasehq/sdk

一步连接

连接 wss://api-develop.deepraxis.ai/browser/v1/connect 即可新开一个浏览器。?session=<id> 连接已有会话;?name=<名称> 有就连接,没有就创建。名称可用 1–128 个字母、数字或 . _ : -,不能同时传 session 和 name。

import { chromium } from "playwright";

const headers = { Authorization: `Bearer ${process.env.PRAXIS_API_KEY}` };
const browser = await chromium.connectOverCDP(
  "wss://api-develop.deepraxis.ai/browser/v1/connect",
  { headers },
);
const cdp = await browser.newBrowserCDPSession();
const session = await cdp.send("Praxis.getSession");
console.log(session.sessionId, session.liveViewUrl, session.consoleUrl);
try {
  const page = await browser.contexts()[0].newPage();
  await page.goto("https://api-develop.deepraxis.ai/meta/v1/health");
  console.log(await page.textContent("body"));
} finally {
  await browser.close();
  // 用完立即释放,停止计费。
  const closed = await fetch(
    `https://api-develop.deepraxis.ai/browser/v1/sessions/${session.sessionId}`,
    { method: "POST", headers: { ...headers, "Content-Type": "application/json" },
      body: JSON.stringify({ status: "REQUEST_RELEASE" }) },
  );
  if (!closed.ok) throw new Error(await closed.text());
}

新建时还可带 context、persist、timeout(秒)、idle(毫秒)、labels 和 viewport。后两项用 JSON.stringify 配合 URLSearchParams 编码;连接已有会话时,这些新建参数不生效。创建会话返回的 connectUrl 自带凭证,连接时无需再带 API Key;不要公开此地址。

Browserbase 官方 SDK

使用自己的 API Key,把官方 SDK 的 baseURL 设为 https://api-develop.deepraxis.ai/browserbase。SDK 自动发送 X-BB-API-Key。无需 Browserbase 项目,projectId 可省略。

import Browserbase from "@browserbasehq/sdk";
import { chromium } from "playwright";

const bb = new Browserbase({
  apiKey: process.env.PRAXIS_API_KEY,
  baseURL: "https://api-develop.deepraxis.ai/browserbase",
});
const session = await bb.sessions.create({ timeout: 300, keepAlive: true });
try {
  const browser = await chromium.connectOverCDP(session.connectUrl);
  try {
    const page = await browser.contexts()[0].newPage();
    await page.goto("https://api-develop.deepraxis.ai/meta/v1/health");
    console.log(await page.textContent("body"));
  } finally {
    await browser.close();
  }
} finally {
  await bb.sessions.update(session.id, { status: "REQUEST_RELEASE" });
}

可调用的端点见 Browserbase 兼容 API。代理、反检测、扩展和 Selenium 不可用;region、solveCaptchas 当前接受但忽略,不会选区域或代解验证码。

REST 先创建再连接

POST /browser/v1/sessions 返回的 connectUrl 包含连接凭证,有效期覆盖会话寿命。直接交给 Playwright 或 Puppeteer,无需再传 API Key。

import { chromium } from "playwright";

const base = "https://api-develop.deepraxis.ai/browser/v1";
const headers = {
  Authorization: `Bearer ${process.env.PRAXIS_API_KEY}`,
  "Content-Type": "application/json",
};
const response = await fetch(`${base}/sessions`, {
  method: "POST", headers, body: JSON.stringify({ timeout: 300, keepAlive: true }),
});
if (!response.ok) throw new Error(await response.text());
console.log("X-Request-Id:", response.headers.get("X-Request-Id"));
const session = await response.json();
try {
  const browser = await chromium.connectOverCDP(session.connectUrl);
  try {
    const page = await browser.contexts()[0].newPage();
    await page.goto("https://api-develop.deepraxis.ai/meta/v1/health");
    console.log(await page.textContent("body"));
  } finally {
    await browser.close();
  }
} finally {
  const closed = await fetch(`${base}/sessions/${session.id}`, {
    method: "POST", headers, body: JSON.stringify({ status: "REQUEST_RELEASE" }),
  });
  if (!closed.ok) throw new Error(await closed.text());
}

完整请求参数见云端浏览器 API。Puppeteer 使用 puppeteer.connect({ browserWSEndpoint: session.connectUrl }) 连接同一地址。

实时观看与接管

打开 Console 的 浏览器 › 会话,选择会话即可观看并接管。程序也可通过 CDP 命令 Praxis.getSession 取得 sessionId、liveViewUrl 和 consoleUrl。需要用户完成登录或确认操作时,调用请求人工接管,完成后调用完成人工接管。

会话状态为 PENDING、RUNNING、ERROR、TIMED_OUT 或 COMPLETED。API 创建且 keepAlive=false 的会话在客户端断开且无人观看后结束;一步连接和 Console 打开的会话默认在无人连接或观看 5 分钟后释放。单次最长 6 小时。没有暂停或休眠;下次继续使用登录态,需要 Context。

登录态

先创建 Context,再在创建会话时传 browserSettings.context。persist: true 会在会话关闭时保存 cookie 与 localStorage;不保存 IndexedDB。同一登录态正在被写入时,另一会话可能收到 409 CONTEXT_LOCKED。

// 接上面的 REST 初始化代码;contextId 为创建 Context 返回的 id。
const response = await fetch(`${base}/sessions`, {
  method: "POST", headers,
  body: JSON.stringify({
    timeout: 300,
    browserSettings: { context: { id: contextId, persist: true } },
  }),
});

计费与限制

  • $0.12 / 浏览器小时,按秒计费,单会话 1 分钟起。平台原因导致的失败只按实际秒数计费;释放后不再计费。
  • 每个账户默认最多同时 10 个会话,每个会话最长 6 小时。
  • 不支持代理、验证码代解、反检测、选区域或扩展。代理、反检测和扩展等不支持的配置返回 501;region、solveCaptchas 当前接受但忽略。
  • 部分网站(如 Google、百度)会把云端浏览器识别为机器人。
  • 排查请求时保留响应头 X-Request-Id。

文件

文件服务保存被素材库、知识库和对话引用的原始文件。上传后得到 file_id,再把它交给需要使用文件的服务。

素材库组织素材及其理解结果;知识库提取文档内容并建立检索索引。只上传文件,不会自动将它加入库,也不会自动让它可检索。

快速开始 · 概念 · 限额与错误

API 参考 → · Cookbook →

快速开始

安装 SDK,并将 API Key 设置在环境变量 PRAXIS_API_KEY 中。把下面的 Python 代码保存为 files.py。

使用 Develop 时,另设 PRAXIS_API_BASE=https://api-develop.deepraxis.ai;未设置时使用正式 API。Key 必须属于所选环境。

python -m pip install openai
python files.py
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["PRAXIS_API_KEY"],
    base_url=os.environ.get("PRAXIS_API_BASE", "https://api.deepraxis.ai") + "/openai/v1",
)
file = client.files.create(
    file=("hello.txt", b"Hello, Praxis!\n", "text/plain"),
    purpose="user_data",
    expires_after={"anchor": "created_at", "seconds": 3600},
)
try:
    print("uploaded:", file.id)
    files = client.files.list(limit=100)
    print("listed:", any(item.id == file.id for item in files))
    content = client.files.content(file.id).read()
    assert content == b"Hello, Praxis!\n"
    print("downloaded:", content.decode().strip())
finally:
    deleted = client.files.delete(file.id)
    print("deleted:", deleted.deleted)

期望:输出上传的 ID、listed: True、downloaded: Hello, Praxis! 和 deleted: True。即使列出或下载失败,示例也会删除自己创建的文件。

API 参考 → · Cookbook →

概念

purpose 用途

purpose 记录文件的预期用途。上传接受三个值:

purpose用途与限制
user_data通用输入文件。在对话中引用图片时必须使用此值。
assistants用于智能体或知识库工作流的文件。上传不等于加入智能体或知识库;对话图片引用不接受此用途。
vision只接受 PNG、JPEG、GIF、WebP 图片(按内容识别),其他内容返回 400 FILE_TYPE_UNSUPPORTED;对话图片引用不接受此用途。

上传不接受 batch、fine-tune 和 evals。通过 Anthropic 上传的文件统一记为 user_data。

引用 file_id

同一账户的 /file/v1/files、/openai/v1/files 和 /anthropic/v1/files 共用文件 ID,可跨接口读取元数据和删除。上传文件的下载使用文件 API 或 OpenAI API;Anthropic 下载仅支持会话输出文件。

  • 对话:图片以 user_data 上传,用 type: "file" 与 file.file_id 引用。不要将 ID 填进 image_url.url。文本和 PDF 的文件 ID 当前不能作为对话图片输入。参见图片引用示例。
  • 素材库:用 file_id 导入,生成素材记录。参见素材 API。
  • 知识库:将 file_id 加入库后,才会提取内容并建立索引。参见知识库概念。
  • 智能体:用 type: "file"、file_id 和 mount_path 将文件加入会话资源。仅设置 purpose=assistants 不会挂载文件。参见会话资源 API。

会话输出文件的元数据可包含 scope、relative_path 和 revision。用 scope_id 列出某个会话的文件;Anthropic 使用该筛选时需要 anthropic-beta: managed-agents-2026-04-01 请求头。

过期

不设置过期时间的上传文件保留到被删除。expires_after 从 created_at 起计算秒数,响应中的 expires_at 是到期时间。过期文件不能再读取或引用。可用范围见限额与错误。

下载安全

下载内容被识别为 HTML、SVG、XML 或 JavaScript 时,以附件下发,并设置 X-Content-Type-Options: nosniff。类型按内容识别,不依赖扩展名:上传的 HTML 或 SVG 也可能被识别为纯文本并以纯文本返回。下载不会执行其中的内容。

分段下载

内容下载支持单个 Range 区间:bytes=0-1023、bytes=1024-(从某处到结尾)或 bytes=-1024(最后 1024 字节)。命中时返回 206 和 Content-Range;区间超出文件大小返回 416。所有下载响应都带 Accept-Ranges: bytes,可用于断点续传和音视频拖动。

API 参考 → · Cookbook →

限额与错误

限额

项目限额
单文件100 MiB(104,857,600 字节);不接受空文件
文件存储配额20 GiB(21,474,836,480 字节),账户内文件共享
OpenAI 或文件 API 的 expires_afteranchor: "created_at",3,600–2,592,000 秒(1 小时至 30 天)
文件 API 的 praxis[expires_in_seconds]3,600–7,776,000 秒(1 小时至 90 天)
Anthropic 表单 expires_in_seconds3,600–7,776,000 秒(1 小时至 90 天)

过期参数只能选一种。Anthropic 的 expires_in_seconds 只能放在表单中;放在查询参数里返回 400。参见上传参数。

未知字段

兼容接口按各自官方的方式拒绝未知参数,不会静默忽略:

接口可用参数其他参数
Anthropic 上传表单 file、expires_in_seconds;查询参数仅 beta400 invalid_request_error,如 purpose: Extra inputs are not permitted
OpenAI 上传表单 file、purpose、expires_after[anchor]、expires_after[seconds];无查询参数400 invalid_request_error,如 Unrecognized request argument supplied: foo,param 为该字段
OpenAI 列表查询参数 after、order、limit、purpose同上

praxis[expires_in_seconds] 与 scope_id 等扩展只在文件 API 中可用。

按内容识别 PDF、DOCX/XLSX/PPTX、PNG/JPEG/GIF/WebP、支持的音视频格式和 UTF-8 文本。改扩展名不能让任意二进制文件变成受支持类型。

错误处理

错误处理方式
PURPOSE_UNSUPPORTED传入 user_data、assistants 或 vision。
FILE_EMPTY上传非空文件。
FILE_TOO_LARGE将单文件控制在 100 MiB 以内。
FILE_TYPE_UNSUPPORTED使用支持的文件内容类型,仅修改扩展名无效;purpose=vision 只接受图片;损坏的 Office 文档或 ZIP 压缩包也会返回此错误。
VALIDATION_ERROR检查文件名与过期参数。
QUOTA_EXHAUSTED删除不再需要的文件后重试上传。
HTTP 404检查 ID、账户以及文件是否已过期或删除。
File is not an eligible image在对话中使用支持的图片,并以 purpose=user_data 重新上传。

过大的请求可能先被边缘拒绝,返回 HTTP 413 与 HTML 正文,而不是文件 API 的 JSON 错误。解析错误正文前先检查 HTTP 状态。

API 参考 → · Cookbook →

素材库

素材库为智能体存放、理解和检索图片、视频、音频与文档。一个库是一个隔离单元,素材、文件夹与检索都限定在库内。官方 Cloudinary SDK 可以直接使用。

快速开始 · 概念 · 限制与错误

API 参考 →

快速开始

准备 API Key,将下方代码保存为 quickstart.mjs,在 Node.js 中运行。示例建库、上传图片、按标签检索,并给出原图与缩略图地址。

npm install cloudinary@2
# Set PRAXIS_API_KEY in your environment.
node quickstart.mjs
import { v2 as cloudinary } from "cloudinary";

const base = "https://api.deepraxis.ai";
const key = process.env.PRAXIS_API_KEY;
if (!key) throw new Error("Set PRAXIS_API_KEY");
async function media(path, method = "GET", body) {
  const response = await fetch(base + "/media/v1" + path, {
    method,
    headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  });
  if (!response.ok) throw new Error(`Media HTTP ${response.status}`);
  return response.json();
}
const library = await media("/libraries", "POST", { name: "Agent media" });
cloudinary.config({
  cloud_name: library.id,
  oauth_token: key,
  upload_prefix: base + "/cloudinary",
});
const asset = await cloudinary.uploader.upload(
  "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAABWklEQVR4nO3OQQ0AMBAEofVv+iqDxzRBALvtg/wgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwgzg/i/CDOD+L8IM4P4vwg7gEgaMOyrMtNTwAAAABJRU5ErkJggg==",
  { public_id: "quickstart/pixel", tags: ["docs"] },
);
const found = await cloudinary.search.expression("resource_type:image AND tags:docs").execute();
const delivery = asset.secure_url;
const thumbnail = delivery.replace("/upload/", "/upload/w_160,c_fit/");
const understandings = await media(`/libraries/${library.id}/assets/${asset.asset_id}/understandings`);
const current = await media(`/libraries/${library.id}/assets/${asset.asset_id}`);
const matches = current.semantic === "ready"
  ? await media(`/libraries/${library.id}/search/semantic`, "POST", { query: "a small image", top_k: 5 })
  : null;
console.log({ library_id: library.id, asset_id: asset.asset_id, found, delivery, thumbnail, understandings, matches });

理解异步完成。上传响应不含处理状态;用 GET 读取素材,查看 processing 与 semantic。实测量级:图片约半分钟,3 秒视频约 50 秒,raw 文件立即就绪(不做理解)。首次读取可能为空;matches 为 null 时稍后重新读取素材,semantic 为 ready 后再检索。理解与语义检索可用;上传、表达式检索和原文件读取无需等待理解完成。

示例保留库供继续使用。用完后,把 LIBRARY_ID 设为输出的 library_id 并删除:

curl -X DELETE "https://api.deepraxis.ai/media/v1/libraries/${LIBRARY_ID}" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

API 参考 →

概念

一个库是素材的隔离单元。检索只会命中当前库的素材。

库与素材

素材有稳定的 asset_id。public_id 是交付地址中的名称,可以带路径。asset_folder 组织素材;移动文件夹不改变 public_id。按 asset_folder 列出素材只匹配这一层,不包含子文件夹;空字符串表示根。display_name 是显示名称,不决定交付地址。音频使用 resource_type=video,并带 is_audio=true。

库名最长 64 个字符,可以重名;库以 library_id 区分。更新素材的 tags、context 或库的 labels 都是整体替换:传入的值就是新的全部内容,传 (tags 传 [])即清空。

上传 0 字节文件返回 400 FILE_EMPTY;声明或识别为图片却读不出宽高(例如被截断)的文件返回 400 Invalid image file。其他任意文件按 raw 存放。

理解

理解可以产出描述、标签、图片文字、音频转写与视频帧内容。片段结果带起止时间。记录不可变;新版本可以替代旧记录,默认不返回被替代的记录。

整体状态为 pending(等待)、processing(处理中)、ready(完成)、partial(部分完成)或 failed(失败)。单项状态为 pending、running、ready、failed、unsupported(不支持)或 skipped(跳过)。关闭理解或不支持的内容不会阻止原文件读取。

语义检索

当 semantic 为 ready,即可自然语言检索。默认混合检索结合关键词与语义,也可只用语义。命中带匹配内容;音视频片段带起止毫秒,方便定位。从未完成理解的素材不进入语义结果;重新处理中的素材继续用上一版结果参与检索,直到新结果就绪。

文档与其他文件(raw)

PDF、文本等 raw 文件只存放,不做理解,semantic 为 unavailable,不进入语义检索,只能按元数据(public_id、display_name、tags、context 等)用表达式检索。需要按全文内容检索文档,请使用知识库。

删除与恢复

删除后立即不可见。库可在 7 天内恢复,到期后无法恢复。单个素材没有恢复接口。

与文件的区别

「文件」存放原始文件;素材库进一步组织、理解和检索内容。从文件导入是拷贝:两份文件独立,之后的修改或删除不会互相同步。

API 参考 →

限制与错误

视频超过 100 MiB 时不生成帧与音轨理解,也不生成预览海报;仍可存放和读取原文件。

限制数值
每账户库数100
每库素材数1000000
图片最大字节数(50 MiB)52428800
视频最大字节数(2 GiB)2147483648
文档最大字节数(100 MiB)104857600
单请求最大字节数;更大需分片100000000
非末片最小字节数;各片等长5242880
分片会话有效小时数24
视频帧与音轨理解源文件字节上限104857600
表达式检索每页上限500
表达式检索默认条数100
语义检索命中上限50
语义候选上限100
以图搜图上传图片最大字节数(10 MiB)10485760
每份素材文字向量上限(图片向量另计)61
故事板画面上限12
每库同时理解的任务数4
理解超时毫秒数600000
理解最大重试次数3
每素材标签上限1000
标签字符上限255
上下文字节上限1024
批量素材 ID 上限1000
库恢复天数7
库名字符上限;允许重名64
bge-m3 语义检索最低余弦相似度0.55

错误码与重试规则 →

知识库

知识库将文件内容分段并建立索引,让应用按语义、关键词和文件属性查找相关文本,适合问答检索、政策查询和给模型提供参考资料。

服务适合保存和查找什么
文件上传、保存和下载原始文件,提供文件 ID
素材库组织素材、管理文件夹与标签,并查找图片等素材
知识库从文件内容中查找相关文本分段,返回来源文件与内容

一个源文件可以加入多个知识库。从知识库移除文件不会删除源文件;删除源文件后,它会自动从所有知识库中移出,之后不再被搜索到。

  • 快速开始:用官方 openai Python SDK 上传、入库和搜索。
  • 概念:处理状态、分段、搜索与恢复。
  • 限制与错误:容量、文件格式与失败处理。

知识库 API → · OpenAI 兼容接口 →

快速开始

在服务端运行 Python,将 develop API Key 放入 PRAXIS_API_KEY 环境变量。示例连接 develop,每次创建一个知识库和两个文本文件,并在结束时删除它们。

python -m pip install openai

保存为 quickstart.py,运行 python quickstart.py。先上传政策文件,再建库并等待处理完成;upload_and_poll 将第二个文件的上传、添加和等待合成一次调用。上传成功后仍需检查入库状态。

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["PRAXIS_API_KEY"],
    base_url="https://api.deepraxis.ai/openai/v1",
    timeout=120.0,
)
store = None
file_ids = []
try:
    uploaded = client.files.create(
        file=("refund-policy.txt", b"Refunds are available within 30 days of purchase."),
        purpose="assistants",
    )
    file_ids.append(uploaded.id)
    print("uploaded:", uploaded.id)

    store = client.vector_stores.create(
        name="Knowledge quickstart",
        file_ids=[uploaded.id],
    )
    print("store:", store.id)
    first = client.vector_stores.files.poll(
        uploaded.id, vector_store_id=store.id, poll_interval_ms=1000,
    )
    if first.status != "completed":
        raise RuntimeError(f"Ingestion failed: {first.last_error}")

    attached = client.vector_stores.files.upload_and_poll(
        vector_store_id=store.id,
        file=("refund-timing.txt", b"Refunds are processed in 5 business days."),
        poll_interval_ms=1000,
    )
    file_ids.append(attached.id)
    print("ingestion:", attached.status)
    if attached.status != "completed":
        raise RuntimeError(f"Ingestion failed: {attached.last_error}")

    results = client.vector_stores.search(
        vector_store_id=store.id,
        query="How many business days does a refund take?",
        max_num_results=5,
    )
    assert results.data, "Expected at least one matching chunk"
    for result in results.data:
        print(result.filename, result.score)
        for part in result.content:
            print(part.text)
    print("pagination:", results.has_more, results.next_page)
finally:
    try:
        if store is not None:
            deleted = client.vector_stores.delete(store.id)
            print("store deleted:", deleted.deleted)
    finally:
        for file_id in file_ids:
            deleted = client.files.delete(file_id)
            print("file deleted:", file_id, deleted.deleted)
        client.close()

输出包含文件 ID、知识库 ID、ingestion: completed、命中文本与清理结果。每个文件一条结果,命中的文本块按相关度排列。搜索不支持翻页;结果没有被截断时最后输出 pagination: False None。

2026-10-11 的 develop 实测中,文本、Markdown、PDF、DOCX、CSV、JSON 与 HTML 处理约需 4–21 秒;一张 PNG 约需 87 秒。这些是样例耗时,不是时延保证。服务允许处理至多 30 分钟;失败时查看 last_error。

示例删除知识库后,知识库会保留 7 天供恢复;源文件单独删除。若要继续使用这些资料,请在自己的程序中将清理步骤放到使用结束之后。

查看请求字段 → · 理解搜索结果 →

概念

知识库与文件

知识库使用 vs_ 开头的 ID。/knowledge/v1 和 /openai/v1/vector_stores 访问相同数据;前者额外提供恢复、失败重试、分段、事件和搜索解释。所有示例都使用 API Key,从服务端调用。

先通过文件服务上传,得到 file_id,再将其加入知识库。知识库内文件 ID 与源文件 ID 相同。重复加入已有文件会返回原记录,不重新处理,也不会应用新的属性或分段策略。批次也一样:已在库中的文件出现在批次里不会重新入库,批次的 attributes 和 chunking_strategy 只作用于新加入的文件。要改属性,用更新文件接口(POST /vector_stores/{id}/files/{file_id});要换分段策略,先移除该文件再重新加入。

处理状态

status含义下一步
in_progress内容正在处理轮询文件状态或使用 SDK 的 poll 方法
completed内容已可搜索发起搜索
failed处理失败查看 last_error,修正文件或通过原生 API retry
cancelled已取消处理先移除,再按需要重新添加

图片(PNG、JPEG、GIF、WebP)入库的内容是模型生成的画面描述,不是逐字 OCR:图中的文字可能被概括或遗漏,搜索命中的是这段描述。图片入库比文本慢,实测单张约数秒到数十秒,多个文件同时入库时更久。

原生 API 的 praxis.stage 给出 queued、parse、chunk、embed、index 进度。完成后 stage 为 null。知识库的 status 由处理情况与过期状态决定;空库也可为 completed,因此它不表示库内一定有内容。

批次把多个文件添加到同一知识库,最多 500 个。file_ids 和逐文件配置的 files 二选一。使用 files 时忽略顶层 attributes 和 chunking_strategy。取消只对进行中的批次生效;对已完成、已失败或已取消的批次调用取消,会原样返回当前状态。

分段

默认 auto 会转为每段 800 个估算 token、相邻重叠 400 token。static 可设置每段 100–4096 token;重叠不得超过段大小的一半。token 数为估算值,不等同于具体模型 tokenizer 的结果。

原生 API 可读取每段的文本、序号、估算 token、页码、UTF-16 起止位置和相邻重叠量;页码可能为 null。读取文件 content 得到的是分段文本,拼接时需考虑重复内容。usage_bytes 是已入库文件的源文件大小(字节),未完成入库的文件为 0;知识库的 usage_bytes 是其中所有文件之和。

搜索结果

/openai/v1/vector_stores/{id}/search 与 OpenAI 一致:每个文件一条结果,content 按相关度从高到低列出该文件命中的文本块,score 取其中最高分,max_num_results 限制的是文件数(默认 10、最多 50)。/knowledge/v1 的搜索按块返回,同一文件可能出现多次,适合直接拼接成模型的参考资料。

query 可为字符串或字符串数组;数组会以换行连接后共同搜索。filters 筛选文件属性,支持 eq、ne、gt、gte、lt、lte、in、nin 和 and/or 组合。比较按类型匹配,例如数字 1 与字符串 "1" 不相同。

默认混合语义与关键词检索,权重为 0.7/0.3。原生 API 可通过 ranking_options.hybrid_search 调整权重并用 praxis.explain: true 查看解释;在 OpenAI 兼容接口传 hybrid_search 会返回 400。只有 ranker 为 default-2024-11-15 时请求重排;none、auto 和省略时使用初始排序。重排的效果尚未单独实测。

score 在 0–1 之间,是同一次搜索内的相对排序依据,不是相关性概率,也没有默认下限:库里不相关的内容也会返回,分数可能与相关内容很接近(实测相关文件 0.70,不相关文件 0.61–0.62)。需要过滤时设置 ranking_options.score_threshold,低于该值的结果不返回;OpenAI 同样默认不设阈值。可以从 0.5 起试,再用自己的资料和典型问题看相关与不相关结果的分数,逐步上调到不再误删相关结果为止。

rewrite_query: true 时先改写查询再检索,见下方「模型」。has_more 表示还有更多结果被 max_num_results 截掉;搜索不支持翻页,next_page 恒为 null。原生结果附带 chunk_id、page、span;兼容结果不含这些字段和 praxis。

模型

每个知识库有三个模型角色,创建时可在原生 API 的 models 里指定,之后用更新接口修改;不指定就用默认模型。读取知识库时 models 返回各角色当前生效的模型。

角色作用默认模型
embedding给分段和查询生成向量bge-m3
rerankranking_options.ranker 为 default-2024-11-15 时重排结果bge-reranker-base
query_rewrite搜索设置 rewrite_query: true 时改写查询qwen3.8-flash

库中已有文件后不能更换 embedding(已有向量来自原模型),更换会返回 409;其他角色随时可改。模型必须在模型目录中并支持该角色,嵌入模型还必须输出 1024 维向量,否则返回 400。某个角色设为 null 恢复它的默认模型,models: null 恢复全部默认。OpenAI 兼容接口不返回 models,传入会返回 400(与 OpenAI 对未知参数的处理一致),但搜索同样按知识库设置的模型执行。

rewrite_query: true 时,搜索前先用 query_rewrite 模型把问题改写成简洁的检索查询(例如「退货以后多久能收到退款?」→「退款到账时间」),用改写结果检索,search_query 返回改写后的查询;query 为数组时逐条改写。每次改写是一次小模型调用,会产生费用,按模型用量计费,并增加约 1–2 秒延迟。改写失败、超过 5 秒或返回为空时,该条查询回退为原文继续搜索,不会让搜索失败。不传或为 false 时不调用模型。

更新、过期与删除

name 省略时默认为空字符串;创建时不接受 null,更新时 null 会清空名称。metadata 更新会替换全部内容;null 清空。description 被接受后丢弃,不能用来保存说明。未知写入字段当前会被忽略,拼写错误不一定报错。

expires_after 只接受 last_active_at 和 1–365 天;搜索和添加文件刷新活动时间,null 取消自动过期。删除后,知识库进入「删除中」:/knowledge/v1 读取返回 status: deleting 和清除时间 purge_at,默认列表不再显示(用 status=deleting 可以列出),搜索、更新和添加文件返回 409;7 天内可以 restore 恢复,也可以用 purge 立即永久删除(不可撤销)。/openai/v1/vector_stores 与 OpenAI 一致,删除后读取返回 404。删除知识库不会删除「文件」中的源文件。

API 参考 → · 上限与错误 →

限制与错误

容量

以下为当前 API 上限。下方耗时来自样例实测,不是服务保证。

项目上限
每个组织的未删除知识库100
每个知识库的文件10,000
单个源文件100,000,000 bytes (100 MB)
单个文件估算 token5,000,000
每批文件1–500
知识库名称256 characters
metadata / attributes 键数16
键长度 / 字符串值长度64 / 512 characters
静态分段大小100–4096 tokens
默认分段 / 重叠800 / 400 tokens
分段重叠上限≤ ½ chunk size
搜索返回结果数(兼容接口按文件、原生接口按分段)1–50; default 10
列表每页条数1–100; default 20
过期天数1–365
JSON 请求体1,000,000 bytes
入库截止时间30 minutes
处理重试次数3
删除后保留7 days

metadata 的值只能是字符串;attributes 的值可以是字符串、数字或布尔值。筛选条件在根条件以下最多嵌套 20 层。语义和关键词检索各取最多 50 个候选;搜索不提供下一页。

文件格式与处理耗时

知识库可以入库以下类型(按文件服务识别的类型判断):

类型入库内容
PDF文档文本
DOCX、PPTX、XLSX文档文本
PNG、JPEG、GIF、WebP模型生成的画面描述,不是逐字 OCR
HTML可见文本,去掉脚本和样式
纯文本、Markdown、CSV、JSON原文

能上传不代表能入库。文件服务接受、但知识库不能入库的类型有:音频(MP3、WAV、FLAC、OGG、M4A)、视频(MP4、WebM)、NDJSON、XML、YAML、SVG,以及识别为 Python、JavaScript 的源代码文件。它们加入知识库时不会被拒绝,而是在异步入库阶段失败,文件 status 为 failed,last_error.code 为 unsupported_file。加密的 PDF 或 Office 文件同样在入库阶段失败(invalid_file)。空文件、ZIP 和其他二进制文件在上传时就被文件服务拒绝。

2026-10-11 develop 实测:文本与文档约 4–21 秒入库完成;单张图片约 4–11 秒,7 个文件同时入库时一张 PNG 用了约 87 秒。

HTTP 错误

HTTPcode处理建议
400INVALID_PARAMS / LIMIT_EXCEEDED修正参数或减少数量。
401UNAUTHENTICATED检查 API Key;兼容响应 code 为 null。
404VECTOR_STORE_NOT_FOUND / FILE_NOT_FOUND / FILE_NOT_IN_STORE / BATCH_NOT_FOUND / CHUNK_NOT_FOUND核对资源 ID 与所属知识库。
409VECTOR_STORE_INVALID_STATE / FILE_INVALID_STATE检查删除、过期或文件处理状态。
413PAYLOAD_TOO_LARGEJSON 请求超过 1 MB;兼容响应 code 可能为 null。
429RATE_LIMITED稍后重试。
500— / null服务内部错误;保留请求 ID 并稍后重试。
501NOT_IMPLEMENTED当前不支持该操作。
503PROVIDER_UNAVAILABLE处理服务暂不可用,稍后重试。

原生错误通常包含 error.code、error.message 与 error.details。兼容错误包含 error.message、error.type、error.param 与 error.code,官方 SDK 按 HTTP 状态抛出对应异常。未知路径可能没有 code,401 可能没有 details,路径编码错误也可能返回 HTML。

异步失败

文件 status 为 failed 时查看 last_error.code:unsupported_file 表示无法解析该格式;invalid_file 表示内容无效、为空或加密;server_error 表示处理失败或超时。超过估算 token 上限当前映射为 invalid_file,但尚未实测超限文件。添加成功后仍应检查文件状态。原生 API 支持重试 failed 文件;本次实测未覆盖失败后的成功重试。

真正过期及删除 7 天后的清除尚未端到端观察。实测也未得到可用于承诺的知识库每秒请求额度。

完整错误对象 → · 搜索限制 →

数据库

Database 为智能体保存需要查询和更新的结构化记录,例如任务、客户线索和行程。每个库是一个隔离单元,包含自己的表、字段、行和事件。

通过 Database API 创建库与表结构;通过官方 supabase-js 读写行。同一库的两个行访问地址共享数据。

API 参考 → · Supabase 兼容接口 → · Cookbook →

快速开始

在 Node.js 22 或更高版本中运行,将 develop API Key 放入 PRAXIS_API_KEY 环境变量。每次运行都会创建一个新库。

npm install @supabase/supabase-js

将下面内容保存为 quickstart.mjs,运行 node quickstart.mjs。

import { createClient } from "@supabase/supabase-js";

const api = "https://api.deepraxis.ai";
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.ai/database/v1/databases/${DATABASE_ID}" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

将 DATABASE_ID 设为示例输出的库 ID。

概念

库是隔离单元,表定义记录的结构,字段定义值的类型,行保存实际记录。

库、表、字段、行

API Key 决定你能访问哪些库。表和字段可用 ID 或名称寻址;关联字段使用表 ID,因此改名不会改变关联目标。库的标签用于分类和筛选。

默认行只包含主键 id 和用户字段,select=*、return=representation 及 RPC 都遵循这一规则。字段保留名只有 id;新字段不能以 _ 开头。created_at、updated_at、version 可作为普通字段。字段名以小写字母开头,后续只含小写字母、数字和下划线,最多 63 字;表名也允许下划线开头。已有下划线用户字段仍可读写,且优先于同名系统元数据;这时该表无法访问同名元数据。

系统元数据只读,需显式取用:select=*,_version,_created_at,_updated_at。* 不会展开它们。可用 _version=eq.3 过滤,或 order=_created_at.desc 排序。不指定 order 时,仍按内部创建时间再按 id 排序。过滤或排序使用旧元数据名且没有同名用户字段时返回 400:details 为 use _created_at(或对应的新名字),hint 为 system metadata moved to underscore columns。

时间字段可定义为 {"name":"created_at","type":"datetime","default":"now()"}。插入时省略该字段,就保存服务器当前 UTC 时间;读回字段定义仍显示 "now()"。显式 null 和 PATCH 中省略的字段不使用默认值。string/json 字段的 "now()" 默认值仍为字面量字符串。给已有表新增这个字段时,回填也会求值。

七种字段类型

类型保存的值
string字符串
integer整数
number数值
booleantrue 或 false
datetimeRFC 3339 日期时间,例如 2026-10-11T08:30:00+08:00;不存在的日期(如 2 月 30 日)返回 400。值统一按 UTC、3 位毫秒保存和读出,例如 2026-10-11T00:30:00.000Z;精度为毫秒,超过 3 位的小数秒四舍五入
json满足指定 JSON Schema 的 JSON 值
link同一库中另一张表的行 ID,或多个行 ID

required 要求写入非空值。新增带 default 的字段时,已有行一律用它回填;不带 default 的新字段在已有行上为空。在已有行的表上新增 required 字段时必须提供 default;把已有字段改为 required 时,若仍有行在该列为空则返回 400 VALIDATION_FAILED 并说明空行数,先补齐这些行再修改。unique 要求值唯一。前五种类型默认建立索引;不需要的字段可以设 indexed: false。json 和 link 不能设为索引字段。字段类型创建后不可修改。

批量写入与事务

一次 POST 最多插入 500 行。单次请求是原子的:任何一行失败,整批回滚。跨请求没有事务;需要一起成功的行应放在一次批量 POST 中。有关联依赖时先查询目标,再插入引用它的行;先查后插不能保证并发期间目标不变,仍需处理插入时的关联错误。

同一批次会按所有行的键的并集补齐缺失值为 null(PostgREST 行为),因此某行省略必填字段可能导致整批失败。发送 Prefer: missing=default 可让缺失的键使用字段默认值;显式传入的 null 仍为 null,没有默认值的必填字段仍必须提供。

默认 return=minimal:成功返回 201,body 为空。需要读回新增行时发送 Prefer: return=representation,或在 supabase-js 的 .insert(...) 后调用 .select()。

JSON Schema

json 字段必须在 options.schema 中提供 JSON Schema。例如,下面的字段只接受包含字符串 summary 的对象。

{
  "name": "detail",
  "type": "json",
  "options": {
    "schema": {
      "type": "object",
      "properties": { "summary": { "type": "string" } },
      "required": ["summary"],
      "additionalProperties": false
    }
  }
}

更新 schema 约束之后的写入,不会重写已有行。查询时可用 detail->>summary 读取字符串值。

关联与删除保护

link 的 options.linked_table_id 指向同一库中的目标表,multiple 决定保存单个行 ID 还是行 ID 数组。关联目标创建后不可更改。

{
  "name": "project",
  "type": "link",
  "options": { "linked_table_id": "tbl_example", "multiple": false }
}

把 tbl_example 换成已创建表的 ID。引用不存在的行会被拒绝;删除仍被其它行引用的行也会被拒绝;同一次删除中的行互相引用时可以一起删除。先解除引用,再删除目标,不会自动级联删除。

用 _version 避免覆盖他人的更新

新行的 _version 为 1,每次更新递增。更新时同时过滤行 ID 和读到的版本;没有返回行表示该条件不再匹配,需要重新读取后决定下一步。

const { data: row, error: readError } = await supabase.from("tasks")
  .select("*,_version").eq("id", rowId).single();
if (readError) throw readError;

const { data, error } = await supabase.from("tasks")
  .update({ done: true })
  .eq("id", row.id)
  .eq("_version", row._version)
  .select();
if (error) throw error;
if (data.length === 0) throw new Error("Read the row again before updating");

删除库后 7 天内恢复

删除库后不能再读写其中的行。在 7 天内调用恢复库可以重新使用库及保留的表和行;超过窗口无法恢复。此恢复能力针对整个库,不是单独删除的表、字段或行。

API 参考 → · 限制与错误 →

限制与错误

按以下边界规划库与查询。

限制上限
每库最多表数50
每表最多字段数64
每表最多索引字段数(含 unique)16
每表最多行数1000000
单行最多字节数2097152 (2 MiB)
单次插入最多行数500
单条查询最多参数值数100
删除库后可恢复天数7
单次查询最多扫描行数3000000

单条请求最长执行 8 秒,超时返回 57014(重试不会成功);给过滤和排序字段建索引,或缩小查询范围。不带过滤的计数总是很快。不指定 limit 时每次最多返回 1000 行,用 limit / offset 或 Range 翻页。大表上需要批量修改或删除时,按主键或索引字段的范围分批执行,例如每批几万行。单次批量插入可包含上述行数上限,不必将每行的字段数相乘作为查询参数数目。

错误码与重试属性 → 一次批量 POST 最多插入 500 行,单次请求是原子的:任何一行失败,整批回滚。跨请求没有事务;需要一起成功的行应放在一次批量 POST 中。有关联依赖时先查后插,并处理并发变化导致的关联错误。

同一批次按所有行的键的并集补齐缺失值为 null(PostgREST 行为)。发送 Prefer: missing=default 可改用字段默认值;显式 null 仍为 null,没有默认值的必填字段仍必须提供。默认 return=minimal 返回 201、body 为空;发送 Prefer: return=representation 才返回行。

按错误细节修正请求

保留字段名只有 id;新字段名不能以 _ 开头,这个前缀留给系统元数据。created_at、updated_at、version 可用作普通字段名。datetime 字段可用 "now()" 作为默认值。

details 指出字段或 JSON 路径及实际收到的值,批量校验还包含从 0 开始的行号。值最多显示 80 个字符,不回显整行。hint 给出修正方式,没有建议时为 null。下面是省略第 2 行必填 title 的错误示例:

{
  "code": "23514",
  "message": "row validation failed",
  "details": "row 1: title: required value (got null)",
  "hint": "Rows in one insert are filled to the union of their keys with null (PostgREST behavior). Send every key in every row, or send Prefer: missing=default to use field defaults."
}

唯一冲突返回 409,例如:

{
  "code": "23505",
  "message": "unique constraint violated",
  "details": "Key (email)=(a@b.c) already exists.",
  "hint": null
}

保险库

Vault 保管智能体调用第三方服务时需要的凭据,例如 GitHub 令牌、OpenAI API Key 或 Linear 的授权。每个库是一组 Secret,一个 Secret 是一个凭据。

Secret 只写:值写入后任何接口都不会再返回它。每个 Secret 都带一组允许的域名,只有发往这些域名的请求才能用到它。智能体和工具按名字引用 Secret,例如 GITHUB_TOKEN。

  • 快速开始:建库、加 Secret,再列出。
  • 概念:Secret 的种类、模板、导入、一键授权与续期。
  • 限制与错误:容量上限和错误处理。

API 参考 → · Claude 托管智能体凭据库 → · Cookbook →

快速开始

把 API Key 放进 PRAXIS_API_KEY 环境变量,依次运行下面三个请求。每次运行都会创建一个新库。请先安装 jq 以提取 ID;可设置 VAULT_NAME 自选显示名称。

1. 建库

VAULT_JSON=$(curl --fail-with-body -sS -X POST "https://api.deepraxis.ai/vault/v1/vaults" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg name "${VAULT_NAME:-Agent credentials}" '{display_name: $name}')")
export VAULT_ID=$(printf '%s' "$VAULT_JSON" | jq -er '.id')
printf '%s\n' "$VAULT_JSON"

期望:HTTP 200,返回 type 为 vault、status 为 active、secret_count 为 0 的对象。命令已把返回的 vlt_ ID 保存到 VAULT_ID。

2. 加入 Secret

curl -X POST "https://api.deepraxis.ai/vault/v1/vaults/$VAULT_ID/secrets" \
  -H "Authorization: Bearer $PRAXIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "GITHUB_TOKEN", "value": "ghp_example", "allowed_hosts": ["api.github.com"]}'

期望:HTTP 200,返回 type 为 vault_secret、kind 为 static、version 为 1 的对象。响应里没有 value:值写入后不会再返回。这个 Secret 只能用在发往 api.github.com 的请求上。

3. 列出 Secret

curl "https://api.deepraxis.ai/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.ai/vault/v1/vaults/$VAULT_ID/archive" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

curl -X DELETE "https://api.deepraxis.ai/vault/v1/vaults/$VAULT_ID" \
  -H "Authorization: Bearer $PRAXIS_API_KEY"

期望:归档返回 HTTP 200、status: "archived",随后删除返回 HTTP 200、{"id": "<VAULT_ID>", "type": "vault_deleted"}。删除会一并删除库中的全部 Secret,不可恢复。

从模板开始、导入 .env 与一键授权 → · 完整请求与对象 →

概念

库是一组 Secret;Secret 是一个凭据,由名字、值和允许的域名组成。其余功能都只是产生或更新 Secret 的方式。

库与 Secret

API Key 决定你能访问哪些库。库有显示名称和最多 16 对 metadata 标签。

Secret 的名字在库内唯一,创建后不能修改,只能使用大写字母、数字和下划线,长度为 1–128 个字符,可以以下划线开头,但不能以数字开头,例如 GITHUB_TOKEN。可以用名字或 vsec_ 开头的 ID 寻址一个 Secret;非法名称查找也返回 404 SECRET_NOT_FOUND。

值只写:创建或替换后,任何接口都不再返回它。每次替换值,version 加一。

允许的域名

每个 Secret 必须声明 allowed_hosts,1–16 个;空数组返回 400 INVALID_PARAMS。只有发往这些域名的请求才能用到这个 Secret。

写法匹配
api.github.com只匹配这个域名
*.example.com匹配 example.com 的任意子域名,不匹配 example.com 本身
192.0.2.1只匹配这个 IPv4 地址

域名写入时统一转为小写,去掉末尾的点,国际化域名转为 punycode。重复项会去重。不提供“任意域名”。IPv6、单标签主机(包括 localhost)、*.internal、平台域名,以及回环、链路本地、私网与 CGNAT IPv4 均返回 400 INVALID_PARAMS。

两种 Secret

kind值从哪来状态
static你填写的值active
oauth一键授权得到的访问令牌,由平台续期active、refreshing、refresh_failed

两种 Secret 归档后状态都是 archived。

模板

模板为常用服务预填名字、允许的域名和种类,你只需填写值。GET /vault/v1/templates 列出全部模板,static 模板有 github、openai、anthropic、stripe,OAuth 模板有 linear、atlassian。GitHub 预填 GITHUB_TOKEN 和 api.github.com、uploads.github.com。添加 static Secret 时可传 template_id,其来源记录为 metadata.template。

metadata.source 和 metadata.template 是系统保留键,调用方不能写入,也不占自定义标签的 16 对限额。更新库或 Secret 的 metadata 按键合并:字符串写入,null 删除该键,省略的键保留。Secret 的 description: null 清除备注,省略则保留。

导入 .env

POST /vault/v1/vaults/{vault_id}/secrets:import 一次创建多个 static Secret。传 dotenv(KEY=VALUE 多行文本)或 lines 数组,二者选一;新建时,allowed_hosts 默认值应用到没有自带域名的行。同名 Secret 只替换值,只有该行自身的 allowed_hosts 才会改变原域名,顶层默认域名不会覆盖原域名。结果分别列出新建、替换和失败的行,失败行带行号与错误码,其余行照常写入。

一键授权

Linear(CIMD:客户端元数据文档)和 Atlassian(DCR:动态客户端注册)可以通过一键授权产生 oauth Secret,无需事先注册应用。需要应用注册的服务(GitHub、HubSpot、Asana、Vercel)请手动添加凭据;Asana 不在一键授权模板中。

  1. 调用 POST /vault/v1/authorizations,传支持授权的 template_id,或要授权的 resource_url。
  2. 把返回的 connect_url 交给最终用户。这个链接 30 分钟内有效、只能使用一次。
  3. 用户在确认页看到发起方、目标服务和申请的权限,同意后跳转到服务方完成授权。
  4. 完成后,库中出现名为 secret_name 的 oauth Secret。读取 GET /vault/v1/authorizations/{id} 可查看状态:pending、authorizing、completed、failed 或 expired。

库中已有同名 Secret 时,授权会原地更新它:ID 不变,version 加一。

需要授权后回到你的应用时,传 return_url。它必须在回跳地址白名单内:用 PUT /vault/v1/settings/return_urls 设置,最多 10 个 https 地址,精确匹配。回跳时会带上 authorization_id 和 status。

续期与校验

oauth Secret 的访问令牌由平台在过期前续期。服务方拒绝续期时,Secret 变为 refresh_failed,oauth.last_error 说明原因;重新授权或替换值后恢复为 active。服务方暂时不可用时,Secret 保持 active 并稍后重试。

POST …/secrets/{secret}/validate 用当前令牌向服务发一次探测,返回 valid、invalid 或 unknown,只适用于 oauth Secret。调用方收到服务的 401 后,可以调用 POST …/secrets/{secret}/refresh 立即续期;同样仅支持 oauth Secret。对 static Secret 校验返回 400 VALIDATE_UNSUPPORTED,续期返回 400 SECRET_KIND_MISMATCH。

审计事件

库的事件记录创建、更新、归档、删除、使用、续期和授权结果,不包含 Secret 的值。GET /vault/v1/vaults/{vault_id}/events 按时间倒序列出,可用 secret 按名称或 ID 过滤;未知 Secret 返回空列表。更新事件的 detail.changed 列出修改的字段,例如 value、allowed_hosts。

事件没有按天自动过期的保留期;归档保留事件,删除库或 Secret 会清除其历史事件,仅留下删除记录。limit 默认 50、最大 200;超限或无效 cursor 返回 400 INVALID_PARAMS。完整事件类型见事件参考。

归档与删除

归档库会同时归档库内全部 Secret 并清除它们的值,之后不能再写入。删除库会删除全部 Secret,不可恢复。单个 Secret 同样可以归档或删除。

与 Claude 托管智能体凭据库兼容

同一批库也可以用 Claude 托管智能体的官方 SDK 兼容接口访问(/anthropic/v1/vaults)。每个请求需要 API Key、anthropic-version: 2023-06-01,以及包含 managed-agents-2026-04-01 的 anthropic-beta;该头可用逗号分隔包含其它 beta。列表使用 page 而非 cursor 分页;凭据只能用 vsec_ ID 寻址。库的 display_name: null 返回 400。

错误体为 {type: "error", error: {type, message}, request_id};409 对应 conflict_error。409 等不可重试错误带 x-should-retry: false,不会触发 SDK 自动重试。

凭据类型对应的 Secret
environment_variable名字为 secret_name,允许的域名为 networking.allowed_hosts;networking 只支持 limited
static_bearer允许的域名为 mcp_server_url 的主机,名字自动生成为 MCP_<主机>
mcp_oauth暂不支持创建,返回 501;请改用一键授权

通过 /vault/v1 创建的 Secret 在该接口中显示为 environment_variable,injection_location 为只在请求头中替换。

限制与错误

按以下边界规划库与 Secret。

限制上限
每库有效 Secret 数50
Secret 值大小16384 bytes (16 KiB)
每个 Secret 的允许域名数16
单次导入行数50
授权创建 / OAuth 校验(每账户每分钟,各自计数)30 / 30
库名称长度255
metadata 键值对数16
metadata 键 / 值长度64 / 512
列表每页条数50 (最大 200)

同名 Secret 已存在返回 409 SECRET_CONFLICT;往已归档的库写入返回 409 VAULT_INVALID_STATE;超过有效 Secret 上限返回 400 VAULT_LIMIT_EXCEEDED;值超过 16 KiB 返回 413 SECRET_TOO_LARGE。一键授权创建与 OAuth 校验各按账户独立计数,每个固定一分钟窗口各最多 30 次;超出返回 429 RATE_LIMITED,等待下一窗口重试。授权链接过期返回 410 AUTHORIZATION_EXPIRED,需要重新发起授权。

通过 Claude 托管智能体的凭据库接口访问时,每库最多 20 个有效凭据,错误使用该接口自己的错误格式。

错误码与重试属性 →

账户与计费

身份与权限

(操作指南,待补充。)

定价

Praxis 的价格只有一条规则:以模型厂商的官网价为基准,Praxis 标价低于官网价。每个模型的具体价格在模型目录里,单位是美元每百万 token,不含税。

价格怎么定

  • 基准是厂商官网定价页的价格,Praxis 记录来源与日期。
  • Praxis 标价对所有客户相同,低于官网价;折扣对输入、输出、缓存创建、缓存读取四种 token 类型一致。
  • 厂商调价时,Praxis 标价同步更新;Praxis 主动调整标价提前 30 天通知。

怎么付费

  • 预付充值,按实际消耗扣减,用多少付多少;订阅也是用量的订阅,不卖许可。
  • 未消耗的余额可以兑付。
  • 用量与余额在控制台的用量与费用与充值页查看。

经合作伙伴购买

Praxis 可能与介绍您的合作伙伴存在分润安排,不影响您的价格:经合作伙伴购买不会比直接购买更贵。合作伙伴计划的规则在合作伙伴。

用量查询

(操作指南,待补充。)

费用与充值

(操作指南,待补充。)

配额与限制

本页讲你的账号有多少额度、怎么提升。原理见限流与错误,错误处理见错误与限流。

(操作指南,待补充。)

帮助与参考

这里保留跨章节的帮助资料。API 鉴权、端点、错误与版本已经归入顶层 API。

术语表

(参考,待补充。)

服务状态

状态页:https://status.deepraxis.ai(建设中,ticket-3549)。

常见问题

(待补充:模型 id 怎么填 · 国内网络 · 余额为零会怎样 · Key 泄露 · 流式不出字。)

支持与反馈

安全与合规

(待补充:数据驻留 APAC · 文件保留期 · 隐私政策 / 服务条款链接 · 备案。)