Files
homeagent-sdk/tools/apidoc/gensite/keywords.go
JianFeeeee 9b6abe1b73 fix(docs): 中文搜不到英文注释的 API —— 补关键词层
## 问题(实测)

SDK 里 100 个有摘要的符号中 **66 个是英文注释**,例如:

    RegisterTool registers a tool that the LLM can call.

于是搜「注册工具」——中文受众最自然的问法——**RegisterTool 得分 0,一条都搜不到**。
更糟的是逐字匹配把噪声顶上来了:搜「注册工具」返回 24 条,排第一的是
`SetToolBlocks`(描述里有「工具」二字),`RegisterTool` 根本不在列表里。

## 修法

不改源码注释(那会让代码与文档脱节),而是在检索索引上加一层**人工标注的
中文功能词**:`tools/apidoc/keywords.json`。

- `rules`:按符号名前缀/子串批量覆盖(`Register*` 全带「注册」,`*Memory*` 带「记忆」)
- `symbols`:逐符号补充(重点 API、或规则覆盖不到的)

词只进 `api-index.json` 的 `g` 字段,**不影响页面展示**;检索结果里会显示
(「为何命中」),读者能理解排序依据。

同时把中文逐字匹配从主信号降为**弱信号**(要求 60% 以上字符命中)——
它正是噪声来源:凡是含「工具」二字的说明都会被「注册工具」匹上。

## 结果

| 查询 | 修改前 | 修改后 |
|---|---|---|
| 注册工具 | 24 条,RegisterTool 缺席 | **7 条,RegisterTool 第一** |
| 崩溃 | 1 条 | 2 条(SetAutoRestart + AutoRestart)|
| InjectText | 7 条(含重复)| 6 条 |
| memory.recall | 1 条 | 1 条(不变)|

顺带修掉索引重复:接口会同时作为 `type` 符号与接口本身被加两次
(`MemoryAPI` 等 11 个各重复一条)。现在接口只走接口那条路径,索引 200 → 189 条。

keywords.json 是**可选**的:读不到只警告不中断,检索退化为原行为。
2026-09-24 13:00:32 +08:00

95 lines
2.6 KiB
Go
Raw 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.

package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
// 中文检索关键词。
//
// 问题:SDK 里 100 个有摘要的符号中 **66 个是英文注释**
// (`RegisterTool registers a tool that the LLM can call.`),
// 于是搜「注册工具」找不到 RegisterTool —— 而受众主要是中文。
//
// 解法不是翻译源码注释(那会让代码与文档脱节),而是给检索索引补一层
// **人工标注的功能词**:不影响页面展示,只让中文能搜到。
//
// 词表在 tools/apidoc/keywords.json。规则用「前缀 → 词」批量覆盖
// (Register* 全带「注册」),少数重点符号再逐条补充。
type keywordTable struct {
Rules []keywordRule `json:"rules"`
Symbols map[string][]string `json:"symbols"`
}
type keywordRule struct {
// Prefix 匹配符号名前缀;Contains 匹配名字里是否含该子串。二选一。
Prefix string `json:"prefix,omitempty"`
Contains string `json:"contains,omitempty"`
Words []string `json:"words"`
}
// loadKeywords 读词表。找不到就返回空表(不报错中断)——
// 检索关键词是**增强**,缺失时退化为原行为,不该让构建失败。
func loadKeywords() (*keywordTable, error) {
path := keywordPath()
data, err := os.ReadFile(path)
if err != nil {
return &keywordTable{}, err
}
var t keywordTable
if err := json.Unmarshal(data, &t); err != nil {
return &keywordTable{}, fmt.Errorf("解析 %s: %w", path, err)
}
return &t, nil
}
// lookup 返回某符号的中文检索词(可能为空)。
func (t *keywordTable) lookup(name, docBrief string) []string {
if t == nil {
return nil
}
seen := map[string]bool{}
var out []string
add := func(ws []string) {
for _, w := range ws {
w = strings.TrimSpace(w)
if w == "" || seen[w] {
continue
}
seen[w] = true
out = append(out, w)
}
}
for _, r := range t.Rules {
if r.Prefix != "" && strings.HasPrefix(name, r.Prefix) {
add(r.Words)
}
if r.Contains != "" && strings.Contains(name, r.Contains) {
add(r.Words)
}
}
add(t.Symbols[name])
return out
}
// keywordPath 找 keywords.json:可执行文件旁 → 源码目录 → 工作目录。
func keywordPath() string {
candidates := []string{}
if exe, err := os.Executable(); err == nil {
candidates = append(candidates, filepath.Join(filepath.Dir(exe), "keywords.json"))
}
candidates = append(candidates,
filepath.Join("tools", "apidoc", "keywords.json"),
"keywords.json",
)
for _, c := range candidates {
if _, err := os.Stat(c); err == nil {
return c
}
}
return candidates[0]
}