# 接入前端工具 (/docs/developers/agent/embed/client-tools)



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

## 准备接入 [#prepare]

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

前端工具适合按需读取。每条消息都需要的小事实，使用[页面上下文](/docs/developers/agent/embed/context#page-context)。

## 在智能体声明工具 [#declare-tool]

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

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

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

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

## 实现页面回调 [#handle-call]

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

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

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

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

## 验证调用与恢复 [#verify]

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

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

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

## 授权与下一步 [#next]

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

[配置工具授权](/docs/developers/agent/embed/tool-authorization) · [组件参考](/docs/developers/agent/embed/component-reference) · [更新智能体 API](/docs/api/native/agent/updateAgent)。
