快速开始
本文把完整流程走一遍:项目和密钥、终端用户和令牌、带第一条消息的会话,以及流式返回的回复。所有调用都是普通的 HTTPS + JSON,任何语言都能用;示例使用 curl、JavaScript 和 Python。
1. 创建项目和密钥
用谷歌账号登录控制台,创建一个项目。项目创建后,控制台会只显示一次项目的第一个密钥:请把它复制到你后端的密钥存储里。之后可以在项目的密钥页面创建更多密钥,也可以作废任何一个。
控制台的项目页面还会显示要调用的 API 地址。下面的示例从环境变量读取这两项:
export NEWEYES_API_BASE="https://…" # the API base URL from the console
export NEWEYES_PROJECT_SECRET="…" # the secret you just copied
项目密钥代表整个项目。请只把它放在你的服务器上:不要放进应用、网页或代码仓库。
2. 创建终端用户并签发令牌
你的后端为产品的每一个用户创建一个终端用户,并把返回的 id 和自己的用户记录存在一起。每次调用都会创建一个新用户,所以每个用户只调用一次。用 issue_token 可在同一次调用里拿到该用户的第一个令牌:
- curl
- JavaScript
- Python
curl -s "$NEWEYES_API_BASE/v1/users" -X POST \
-H "Authorization: Bearer $NEWEYES_PROJECT_SECRET" \
-H "Content-Type: application/json" \
-d '{"issue_token": true}' \
| jq -r '.id, .token.token' # the user's id, then its token string
const response = await fetch(`${process.env.NEWEYES_API_BASE}/v1/users`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NEWEYES_PROJECT_SECRET}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ issue_token: true }),
});
const user = await response.json();
const userToken = user.token.token; // the token string
import os, requests
api = os.environ["NEWEYES_API_BASE"]
secret = os.environ["NEWEYES_PROJECT_SECRET"]
user = requests.post(
f"{api}/v1/users",
headers={"Authorization": f"Bearer {secret}"},
json={"issue_token": True},
).json()
user_token = user["token"]["token"] # the token string
{
"object": "user",
"id": "4f6d…",
"is_active": true,
"created_at": "2026-10-19T08:00:00Z",
"token": { "object": "token", "token": "…", "token_type": "bearer", "expires_at": "2026-10-19T09:00:00Z" }
}
令牌字符串是响应里的 token.token:用户的应用就是用它发请求,即 Authorization: Bearer <token>。
令牌默认有效一小时,也可以用 expires_in 指定其他时长(60 秒到 24 小时)。过期后,为该用户签发新的令牌:
curl -s "$NEWEYES_API_BASE/v1/users/$USER_ID/tokens" -X POST \
-H "Authorization: Bearer $NEWEYES_PROJECT_SECRET" \
-H "Content-Type: application/json" \
-d '{"expires_in": 3600}'
{ "object": "token", "token": "…", "token_type": "bearer", "expires_at": "2026-10-19T10:00:00Z" }
把令牌字符串交给你的应用。从这里开始,每个调用都用这个令牌,以该用户的身份进行;下面的示例从环境变量读取它:
export NEWEYES_USER_TOKEN="…" # the token string: token.token in the answer above
3. 用第一条消息开启会话
一个会话就是一次对话。创建会话时带上用户的第一条消息,并选择回复的形式:markdown 是格式化文字,a2ui(默认)是由你的应用渲染的界面数据。
- curl
- JavaScript
- Python
curl -s "$NEWEYES_API_BASE/v1/sessions" -X POST \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"output_format": "markdown", "text": "Plan a two-day trip to Kyoto in spring."}'
const api = process.env.NEWEYES_API_BASE;
const auth = { Authorization: `Bearer ${process.env.NEWEYES_USER_TOKEN}` };
const created = await fetch(`${api}/v1/sessions`, {
method: 'POST',
headers: { ...auth, 'Content-Type': 'application/json' },
body: JSON.stringify({
output_format: 'markdown',
text: 'Plan a two-day trip to Kyoto in spring.',
}),
});
const session = await created.json();
token = os.environ["NEWEYES_USER_TOKEN"]
auth = {"Authorization": f"Bearer {token}"}
session = requests.post(
f"{api}/v1/sessions",
headers=auth,
json={"output_format": "markdown", "text": "Plan a two-day trip to Kyoto in spring."},
).json()
{
"object": "session",
"id": "9c1e…",
"type": "text",
"status": "queued",
"output_format": "markdown",
"task_id": "exec-…",
"last_event_id": "0",
"created_at": "2026-10-19T08:01:00Z"
}
这条消息开启了一个任务:助手的回答,由 task_id 标识。last_event_id 表示这个任务在会话事件流上的起点。
4. 接收流式回复
打开会话的事件流,把 Last-Event-ID 设为这个 last_event_id,事件流就会从任务的第一帧开始发送:生成中的回复文字(text.delta)、助手执行的步骤(task.progress),以及一个结束帧(task.completed、task.failed 或 task.stopped),结束帧的 text 是完整回复。
- curl
- JavaScript
- Python
curl -N "$NEWEYES_API_BASE/v1/sessions/$SESSION_ID/events" \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN" \
-H "Last-Event-ID: 0"
// EventSource cannot send an Authorization header, so read the stream with fetch.
const stream = await fetch(`${api}/v1/sessions/${session.id}/events`, {
headers: { ...auth, 'Last-Event-ID': session.last_event_id },
});
const reader = stream.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
let end;
while ((end = buffer.indexOf('\n\n')) !== -1) {
const block = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
const fields = Object.fromEntries(
block
.split('\n')
.filter((line) => line && !line.startsWith(':'))
.map((line) => [line.slice(0, line.indexOf(':')), line.slice(line.indexOf(':') + 2)]),
);
if (!fields.event) continue; // a `: ping` keeps the connection alive
const data = JSON.parse(fields.data);
if (fields.event === 'text.delta') process.stdout.write(data.delta);
if (['task.completed', 'task.failed', 'task.stopped'].includes(fields.event)) {
reader.cancel();
}
}
}
import json
with requests.get(
f"{api}/v1/sessions/{session['id']}/events",
headers={**auth, "Last-Event-ID": session["last_event_id"]},
stream=True,
) as stream:
event = None
for line in stream.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event = line[len("event: "):]
elif line.startswith("data: ") and event:
data = json.loads(line[len("data: "):])
if event == "text.delta":
print(data["delta"], end="", flush=True)
if event in ("task.completed", "task.failed", "task.stopped"):
break
一帧在线路上是这样的:
id: 1760860860123-0:1
event: text.delta
data: {"task_id": "exec-…", "delta": "Day one: start early at Fushimi Inari"}
也可以直接在请求上接收任务:发送下一条消息时带上 "stream": true,响应就是这个任务自己的帧,以结束帧收尾。详见事件流。
5. 读取对话
回复也会保存在会话的消息列表里,你的应用之后不必重放事件流就能显示整个对话:
curl -s "$NEWEYES_API_BASE/v1/sessions/$SESSION_ID/messages" \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN"