Files
laya-hook/README.zh-CN.md
2026-09-24 14:27:07 +08:00

165 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Laya Router Hook
[English](README.md)
本地常驻 [Laya](https://github.com/NandhaKishorM/laya) multilingual 模型。Codex 或 Claude Code 收到 prompt 之前,hook 会补上一条 System-1 提示:任务复杂度,以及要不要做计划、读仓库、查文档、仔细推理。
它不切换模型。当前会话用哪一个模型,仍由 Codex / Claude Code 自己的配置决定。
```text
Prompt
-> UserPromptSubmit hook(只依赖标准库,失败则放行)
-> http://127.0.0.1:8765
-> Laya multilingual
-> additionalContext
-> Codex / Claude Code
```
浏览器打开 [http://127.0.0.1:8765/](http://127.0.0.1:8765/) 可以按会话看每次判断。页面用服务端事件实时追加。
![看板](docs/assets/dashboard.png)
左边列出每个会话:最近一条 prompt、客户端、判断次数、系统一给出的等级标签。右侧展开某个会话的多次判断,每次显示问题原文、五个轴的概率和模型给出的自然语言结论。新行由服务端事件直接推上来,无需手动刷新。
Cursor 会加载 Claude Code 的 hook,但当前事件无法把上下文注入对话。Hook 发现 `CURSOR_VERSION` 时直接返回。
## 安装
需要 macOS、[uv](https://docs.astral.sh/uv/getting-started/installation/) 和 Python 3.10+。
```bash
./install.sh
```
脚本会:
1. 在 `~/.laya-router/.venv` 创建虚拟环境,并把本仓库安装进去
2. 下载 `convaiinnovations/laya` 的 `multilingual` 权重
3. 注册 launchd 用户服务 `com.laya-router`(登录自启,进程退出后拉起)
4. 检测到 Codex 或 Claude Code 时写入 hook
安装的是普通副本,不是可编辑安装。仓库如果在 `~/Documents` 下,launchd 直接 import 那个目录会卡在文件打开上。`laya-router restart` 会把当前仓库同步进虚拟环境再重启。
只注册一边:
```bash
./install.sh --only codex
./install.sh --only claude
```
Codex 第一次会要求信任 hook。启动 `codex` 后运行 `/hooks`。命令字符串不变时,修改 Python 不会再次要求审查。
## 日常命令
命令在 `~/.laya-router/.venv/bin`。
```bash
laya-router status
laya-router logs
laya-router logs -f
laya-router route "帮我把这个函数的变量名改掉"
laya-router restart
laya-router stop
laya-router start
```
卸掉 hook 和服务,保留运行目录:
```bash
./uninstall.sh
```
连同配置、日志、虚拟环境一起删除:
```bash
./uninstall.sh --purge
```
## 配置
`~/.laya-router/config.toml` 在首次安装时生成,之后不会被覆盖。
| 键 | 含义 |
| --- | --- |
| `host` | 监听地址。保持 `127.0.0.1`。 |
| `port` | 默认 `8765`。 |
| `device` | `auto`、`mps`、`cuda` 或 `cpu`。`auto` 预热失败会回退。 |
| `log_prompts` | 是否把原文写入 `data/routes.jsonl`。 |
| `request_timeout` | 用户 prompt 的 hook 等待秒数,默认 `2.5`。 |
| `event_timeout` | 工具 hook 的等待秒数,默认 `1.0`。 |
| `max_prompt_chars` | 送进模型的最大字符数。超长时保留头尾。 |
| `trajectory` | 工具调用之后,以及改未读过的文件之前,注入一句短提示。 |
| `gate` | 收紧有风险的 Bash 和编辑。不会返回 allow。 |
| `stop_check` | 改完代码却没验证时,不让这一轮停下来。 |
| `skills` | 每条用户 prompt 最多推荐一个已安装的 skill。 |
| `skill_inject` | `suggest` 只给名字和路径。`content` 在同一会话里附上一次 `SKILL.md` 正文。 |
改完配置或服务端代码后执行 `laya-router restart`。
## 一次判断里有什么
一次前向同时问五个问题:
- `task_level`:`simple`、`normal`、`complex`
- `needs_plan`、`needs_repo`、`needs_web`、`needs_deep_reasoning`:0 到 1 的概率
`laya-multilingual` 没有自带温度校准,界面上的 0.90 不是校准后的 90%。注入给 Agent 的文本也写明了这只是提示。没有使用 ordinal `score`,Laya 自己的基准里它是最弱的一种输出。
每次调用追加到 `~/.laya-router/data/routes.jsonl`(时间、客户端、目录、会话、prompt、分数、耗时、问题版本)。关掉原文记录后只留 SHA-256。这份日志是以后按自己的编程任务做微调的材料。
服务挂了时,hook 输出 `{}` 并退出 0,Codex 和 Claude Code 照常工作。原因写在 `~/.laya-router/logs/hook.log`。
## 运行过程中
同一条 hook 命令也监听 `PreToolUse`、`PostToolUse` 和 `Stop`。会话轨迹放在常驻服务的内存里。
- `PreToolUse` 遇到 Bash 或编辑:风险高就拒绝;风险中等时,Claude Code 会询问,Codex 只注入警告,因为 Codex 不接受 `permissionDecision: ask`。要改一个还没读过的文件时,会提示先读。
- `PostToolUse`:每次 Bash 或编辑都会注入一句短提示。连续两条完全相同的提示会去掉。连续失败两次,或者 `is_stuck` 很高,就要求先停下来重新规划。`Read`、`Grep`、`Glob` 只记账,不插话。
- `Stop`:代码改过、之后没有跑测试或构建时,hook 最多拦一次,让 agent 继续做验证。`stop_hook_active` 一律放行,避免死循环。
`UserPromptSubmit` 上还会再做一轮 skill 推荐,最多一个。名单来自 `~/.claude/skills`、`~/.codex/skills`、`~/.agents/skills`、项目里的 skill 目录,以及 Claude 插件的安装路径。frontmatter 里 `disable-model-invocation: true` 的 skill 会跳过。第一问给全部候选排序,并用三个是否题决定这条 prompt 是否需要 skill。第二问只重读前三名。任一阈值低于 0.30 就不推荐。
`laya-mcp` 用 stdio 提供 `laya_judge`。想让模型自己调用,在 Claude Code 里允许 `mcp__laya`。安装时如果 `PATH` 上有对应命令,会执行 `claude mcp add` 和 `codex mcp add`。
升级之后,Codex 要再跑一次 `/hooks`,信任新加的 hook。
## 开发
```bash
PYTHONPATH=src uv run --no-project --with pytest python -m pytest
```
单元测试不加载模型。服务起来之后,用 `laya-router route` 做真实推理。
| 文件 | 作用 | 生效 |
| --- | --- | --- |
| `src/laya_router/questions.py` | 问题措辞。改措辞时把 `QUESTIONS_VERSION` 加一。 | `laya-router restart` |
| `src/laya_router/render.py` | 注入给 Agent 的文本 | `laya-router restart`,然后下一条 prompt |
| `src/laya_router/hook.py` | 用户 prompt 和工具事件、超时、失败放行 | `laya-router restart`,然后下一条 prompt |
| `src/laya_router/server.py` | 加载模型、`/route`、看板 | `laya-router restart` |
| `src/laya_router/dashboard.html` | 浏览器页面 | `laya-router restart` |
不要给服务加 `--reload`。开发模式会再起进程,把模型加载多次。
## 安全
这个仓库里没有 API Key、账号 token 或模型权重。安装程序也不会上传 `~/.claude/settings.json` 或 `~/.codex`。
服务没有鉴权,默认只听 `127.0.0.1`。`/route` 接受任意 prompt,看板会显示已经记下的原文。把 `host` 改成其他地址后,谁能访问这个端口,谁就能看到 prompt 并调用模型。如果绑定地址不是回环地址,进程会在标准错误里警告。不要把这个端口暴露到公网,也不要在没有自行加上鉴权之前做反向代理。
`routes.jsonl` 默认保存完整 prompt,包括你贴进 Codex 或 Claude Code 的内容。`log_prompts = false` 时只保存 SHA-256。运行目录是仓库外面的 `~/.laya-router/`,不要把它提交进 git。
安装程序会改写 `~/.codex/hooks.json`,并往 `~/.claude/settings.json` 合并 `UserPromptSubmit`、`PreToolUse`、`PostToolUse` 和 `Stop`。Claude 的这个文件里常常有 API 凭证。程序会保留原有字段,改文件前写一份带时间戳的 `*.bak-*`,卸载时只删自己的 hook。公开任何本机配置之前先看 diff。
其他网站的页面读不到这些接口:服务没有发送 `Access-Control-Allow-Origin`。监听地址仍然应该保持默认。
Hook 只附加分数和建议,不会把用户 prompt 再抄进注入指令里。
## 限度
multilingual 基础模型适合按领域微调,不是开箱即用的编程路由器。公开的 typed-decision 数字里,基础 checkpoint 在微调之前接近随机。复杂请求被标成 `simple` 是预期中的情况。先积累日志再微调,然后再考虑用它来选模型。
## 许可证
Apache 2.0。Laya 模型和库本身也是 Apache-2.0。