构建

开发者工作区

位于 /notebooks 的 Python 笔记本,在沙箱化的服务端内核上执行——真实的 CPython,预装 pip 与各智能体框架。可以从只读的框架示例出发,也可以自己写,并通过你的账号调用模型与知识库。

框架示例

工作区自带四个只读示例笔记本,位于 从示例中学习:

  • LangChain 基础 ——模型、提示词模板、输出解析器、LCEL 链、工具与智能体、记忆,以及 RAG。
  • LangGraph 基础 ——状态、节点、边、条件路由、环、人在环中,以及多智能体监督者。
  • LlamaIndex 基础 ——文档、节点、索引、检索器,以及查询引擎。
  • 综合运用 — 智能体技术栈 ——使用你的知识库、构建工具、组合技能、施加护栏、访问 MCP 服务,并搭起一个小型多智能体 RAG 服务。
每个单元格都是真实的框架代码——真正的 langchain, langgraph 和 llama_index 导入语句,在容器内核上执行。模型调用经由 zenstudio.chat_model(),一个真正的 LangChain BaseChatModel ,它通过平台路由,因此你的 IAM 模型规则、预算和追踪都会生效,而且 沙箱内部从不存在任何提供商密钥。四个示例在发布前都会以 CI 方式端到端执行验证。

只读与复刻

示例可以 运行 逐单元格执行,但不能编辑。点击 复刻到我的笔记本 即可把示例复制成一个归你账号所有、可编辑的笔记本——随意改动,自动保存。你自己的笔记本位于 我的笔记本 ,且仅你可见。

单元格如何执行

  • 每个代码单元格都是一个真实编辑器(CodeMirror)——用播放按钮或 Shift+Enter运行。stdout、最后一个表达式的值、错误和执行耗时都显示在单元格下方。
  • 单元格执行在一个 沙箱化容器内核 上——非 root 运行、根文件系统只读、能力集被剥离、出网限定在允许清单内。内核在你首次运行时启动,空闲后被回收。
  • 所有单元格共用一个解释器,与 Jupyter 一致:某个单元格里定义的变量在下一个里可见,且支持顶层 await 。
  • LangChain、LangGraph、LlamaIndex、pandas 和 numpy 已预装;其他任何包只需一条 !pip install 即可(写入落在每会话独立的可写层中)。

zenstudio 辅助模块

所谓 zenstudio 模块会注入到每个笔记本中。它把调用路由到你的账号——你的提供商密钥、你的 IAM 模型规则、你的预算,并记录在 追踪记录:

  • reply = await zenstudio.chat("…", provider="openrouter", model="openai/gpt-4o-mini") ——通过你在此处接入的任意 OpenAI 兼容提供商完成一次对话 集成 (openrouter、openai、gemini、groq、grok、qwen、ollama、vllm、nvidia)。
  • hits = await zenstudio.kb_search("refund policy", top_k=5) ——在你的 知识库上做混合检索(向量 + 关键词)。访问权限与别处遵循同一套规则:你只能检索自己拥有、被授权,或属于公开示例的知识库。
  • kbs = await zenstudio.list_knowledge_bases() ——你的账号可读取的知识库。
  • zenstudio.format_context(hits) ——把检索结果整理成带编号的上下文块,供提示词使用。
  • llm = zenstudio.chat_model(model="openai/gpt-4o-mini") ——一个真正的 LangChain BaseChatModel。凡是接受 LangChain 模型的地方都能用它: chain = prompt | llm | StrOutputParser()、LangGraph 节点等等。它支持通过 llm.bind_tools([...])进行工具调用,因此 LangGraph 的 create_react_agent 和 ToolNode 同样可用。
  • zenstudio.llama_llm(...) 和 zenstudio.kb_retriever(top_k=4) ——一个 LlamaIndex LLM,以及一个面向你知识库的检索器(托管式混合检索,无需配置嵌入模型)。
每个会话都有自己的容器:非 root 运行、根文件系统只读、剥离全部 Linux 能力、禁止提权、限制 CPU / 内存 / 进程数,出网限定在运维维护的允许清单内。内核是临时的——会话期间写入的文件在销毁时丢弃;笔记本本身存在数据库里。以上全部由运维在此启用和调整: 管理 → 开发者运行时.

这个工作区的定位

开发者工作区是平台其余部分的自由形态补充:先在这里用 Python、配上真实的模型调用与检索把思路草拟出来,再把它在这里重建成可长期运行、可部署的系统—— 集群画布 以及 智能体构建器.

只有一个运行时,且默认关闭

笔记本单元格执行在真实的容器内核上——完整的 CPython、可用的 pip install,以及真正的智能体框架。这里 没有浏览器内的降级方案:在管理员启用服务端运行时之前,打开笔记本看到的是一块写着“需要运行时”的面板并附上要执行的命令,而不是一个半残的编辑器。

bash
docker compose --profile notebooks up -d --build

然后在此处开启: 管理 → 开发者运行时。启用时会自动生成签名密钥。

安装库

内核镜像自带 build-essential ,并把 PIP_USER=1 指向每会话独立的可写 ~/.local,因此带 C 扩展的包也能正常编译安装:

python
!pip install polars duckdb scikit-learn

安装只能访问出网允许清单内的主机

内核没有直连互联网——唯一的出口是一个默认拒绝的代理。PyPI 始终放通,所以常规安装没问题。凡是从别处拉取的—— pip install git+https://github.com/…、从 Hugging Face 下载模型、私有索引源——都会失败,直到管理员把该主机加进 管理 → 开发者运行时 → 出网允许清单。在那里保存会写入清单并重启代理,任一步没成功界面都会告知你。
环境变量取值用途
NOTEBOOK_RUNTIME_ENABLED开 / 关总开关
NOTEBOOK_RUNTIME_BACKENDdocker | k8s | e2b内核在哪里启动
NOTEBOOK_RUNTIME_IMAGE镜像引用内核镜像
NOTEBOOK_GATEWAY_URLurl浏览器连接的 WebSocket 网关
NOTEBOOK_RUNTIME_SECRETsecret会话令牌签名密钥;留空则自动生成
NOTEBOOK_CRON_TOKENtoken由回收空闲会话的外部 cron 出示

docker 后端是单机的

它在应用所在的那台机器上启动内核容器。要把内核分散到多个节点,请使用 k8s 后端。

内核网络隔离

内核位于内部网络中, 没有直连互联网的能力。唯一出口是一个带允许清单、默认拒绝的出网代理,因此笔记本无法把数据外传到任意主机——pip 安装也只对你放通的主机有效。

调用你已部署的智能体与集群

辅助模块也能访问你已经建好的东西,于是笔记本可以编排它们,而不必重新实现一遍:

调用返回
await zenstudio.chat(prompt, model=…)一次原始的模型补全
await zenstudio.kb_search(query)知识库命中结果
await zenstudio.list_knowledge_bases()你可读取的集合
await zenstudio.run_swarm(swarm_id, input)某个已保存集群的最终输出
await zenstudio.list_agents()你的智能体与集群,含 id
await zenstudio.run_agent(agent_id, prompt)不可用 ——返回 501。见下文。
python
import zenstudio

catalog = await zenstudio.list_agents()
swarm_id = catalog["swarms"][0]["id"]

result = await zenstudio.run_swarm(swarm_id, "Summarise ticket 48213")
print(result)

为什么这样设计

run_swarm 并不是 chat 多绕几步的版本。它严格按你的配置运行你所构建的东西,因此笔记本得到的行为与画布一致,而不是一个会逐渐跑偏的 Python 复刻。提供商密钥绝不进入沙箱——调用由平台代理,始终受 IAM 模型规则、预算和追踪约束。

run_agent 返回 501,且一直如此

内核使用运行时会话令牌认证;而对话路由只接受用户 JWT 或内部运行密钥,所以这个调用从笔记本里从来没成功过。列在这里是因为辅助模块里确实有这个函数、你迟早会找到它,而不是因为它可用。

想从笔记本里运行某个已保存的智能体,把它包成一个单节点集群。 把智能体导入集群节点时,它的提示词、模型、工具和知识库都会复制到该节点上,因此运行结果忠实于该智能体。唯一值得知道的差别是:这份复制是一个 快照,所以之后对该智能体的修改不会传到由它搭起的集群里——重新导入该节点才能同步。

审批节点会被拒绝,而不是等待

笔记本单元格是无人值守的,所以走到人工审批关卡的集群会快速失败,而不是一直挂到运行超时。

注意

示例笔记本以只读方式提供。复刻一份即可得到可编辑的副本——这样无论你怎么改副本,原件始终是一份可用的参考。

把笔记本发布为 API

笔记本可以被你自己的系统调用。打开它并点击 发布 即可生成 API 密钥;此后任何能发 HTTP 请求的东西都能运行它。

  • 密钥名称 ——方便你之后辨认。建议为每个调用方各生成一个,这样撤销某个集成不会影响其他。
  • 入口函数 ——以请求体为参数调用的函数,默认为 entrypoint。留空则从头到尾运行整个笔记本,返回最后一个表达式的值。

入口函数可以定义在任意单元格里。它接收你 POST 过来的 JSON,位于 inputs ,其返回值即为响应内容:

python
async def entrypoint(inputs):
    date = inputs.get("date")
    hits = await zenstudio.kb_search(f"incidents on {date}", top_k=5)
    summary = await zenstudio.chat(
        f"Summarise these incidents:\n{zenstudio.format_context(hits)}"
    )
    return {"date": date, "summary": summary}
bash
curl -X POST https://your-instance/api/notebook/run \
  -H "Authorization: Bearer nbk_…" \
  -H "Content-Type: application/json" \
  -d '{"inputs": {"date": "2026-07-28"}}'
字段类型含义
inputsobject传给入口函数。可选——不需要就省略。
asyncboolean设为 true 会立即返回 202 和一个 runId,而不是等待

同步调用最多等待 110 秒。如果运行仍在继续,它会返回 202 以及一个 runId ,而不是一直挂着连接——轮询方式与 async 运行的轮询相同:

bash
curl -X POST https://your-instance/api/notebook/run/status \
  -H "Authorization: Bearer nbk_…" \
  -H "Content-Type: application/json" \
  -d '{"runId": "…"}'

状态取值为 queued, running, succeeded (附带 result)或 error (附带 error).

为什么这样设计

该端点在与手动运行相同的受治理批处理内核上运行笔记本——同一个沙箱、同一份出网允许清单、同样的 IAM 模型规则、预算与追踪。发布改变的是谁能触发笔记本,而不是它被允许做什么。

密钥只显示一次

密钥以 SHA-256 哈希存储,明文无法恢复——对话框显示时就要复制下来。丢了怎么办?生成一个替代密钥,再撤销旧的。撤销会保留该行记录(含最后使用时间和运行次数)作为审计痕迹,而不是删掉它;每次运行都会归属到触发它的那个密钥。

版本控制(Git)

点击 版本 即可把笔记本提交到你的 GitHub 或 GitLab 仓库。它以广泛使用的 percent 格式写成一个纯 Python 文件——与 Jupytext、VS Code 和 PyCharm 都能识别的 # %% 单元格标记完全一致:

python
# %% [markdown]
# ## Load yesterday's incidents

# %%
hits = await zenstudio.kb_search("incidents", top_k=5)
  • 提交 会写入该笔记本,外加一份小的 .json 清单,记录它的发布方式——入口函数和密钥前缀,绝不含密钥本身。
  • 历史 会列出每次提交及其链接,页头会显示 有未提交的更改 或 已是最新 ,这样你一眼就能看出仓库与正在运行的版本是否一致。
  • 恢复 会读回该提交处的文件并替换笔记本的单元格。未提交的内容会丢失,所以它会先询问你。

为什么这样设计

把单元格存成一坨 JSON,理论上能做版本管理,实际上毫无用处——改一个词就重写整行,没人看得懂 diff。提交真实的 Python 意味着对已发布笔记本的 pull request 与对其他代码的 pull request 长得一样,而这本来就是把它放进 git 的唯一理由。

注意

该仓库就是 BI 用于仪表板和语义模型的那个按用户维度的连接——从任一处接入都可以。已发布的笔记本也会包含在那里的批量 立即导出 同步中。令牌加密存储,且不会被读回。