165 lines
8.3 KiB
Markdown
165 lines
8.3 KiB
Markdown
# 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/) 可以按会话看每次判断。页面用服务端事件实时追加。
|
||
|
||

|
||
|
||
左边列出每个会话:最近一条 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。
|