mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-15 12:15:11 +00:00
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 是**可选**的:读不到只警告不中断,检索退化为原行为。
This commit is contained in:
@ -268,18 +268,6 @@ const ContextPolicyPrune
|
|||||||
|
|
||||||
<small>`plugin.go:45`</small>
|
<small>`plugin.go:45`</small>
|
||||||
|
|
||||||
### `IOInjector`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type IOInjector interface { InjectInterruptText(source, channel, text string) InjectText(source, channel, text string) InjectTextNoMemory(source, channel, text string) // I …
|
|
||||||
```
|
|
||||||
|
|
||||||
IOInjector provides methods for injecting input and interrupts into the agent pipeline.
|
|
||||||
All methods accept (source, channel) where channel is the target output channel
|
|
||||||
for routing the agent's response.
|
|
||||||
|
|
||||||
<small>`plugin.go:220`</small>
|
|
||||||
|
|
||||||
### `PluginSDK.InjectInputMedia`
|
### `PluginSDK.InjectInputMedia`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|||||||
@ -44,18 +44,6 @@ EventHandler processes a system event.
|
|||||||
|
|
||||||
<small>`plugin.go:273`</small>
|
<small>`plugin.go:273`</small>
|
||||||
|
|
||||||
### `EventSubscriber`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type EventSubscriber interface { Subscribe(eventType EventType, handler EventHandler) func() }
|
|
||||||
```
|
|
||||||
|
|
||||||
EventSubscriber allows plugins to subscribe to kernel events.
|
|
||||||
This is a restricted interface: plugins can subscribe but the kernel
|
|
||||||
controls which events are delivered.
|
|
||||||
|
|
||||||
<small>`plugin.go:278`</small>
|
|
||||||
|
|
||||||
### `EventType`
|
### `EventType`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|||||||
@ -89,16 +89,6 @@ AutoRestart 返回插件是否允许自动重启。
|
|||||||
|
|
||||||
<small>`plugin.go:791`</small>
|
<small>`plugin.go:791`</small>
|
||||||
|
|
||||||
### `Plugin`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Plugin interface { Name() string Start(sdk *PluginSDK) error Stop() error }
|
|
||||||
```
|
|
||||||
|
|
||||||
Plugin is the interface every plugin must implement.
|
|
||||||
|
|
||||||
<small>`plugin.go:13`</small>
|
|
||||||
|
|
||||||
### `PluginSDK.PluginMgr`
|
### `PluginSDK.PluginMgr`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -110,17 +100,6 @@ May be nil if the host did not wire it.
|
|||||||
|
|
||||||
<small>`plugin.go:653`</small>
|
<small>`plugin.go:653`</small>
|
||||||
|
|
||||||
### `PluginMgrAPI`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type PluginMgrAPI interface { // ReloadOne 重载单个插件(停止后重新加载)。 ReloadOne(name string) error // ListLoadedPlugins 列出已加载插件。 ListLoa …
|
|
||||||
```
|
|
||||||
|
|
||||||
PluginMgrAPI 提供插件管理能力(外部插件可调用)。
|
|
||||||
由 bridge 注入 dispatch 实现,走 C ABI CORE_PLUGIN_RELOAD_ONE 等。
|
|
||||||
|
|
||||||
<small>`plugin.go:284`</small>
|
|
||||||
|
|
||||||
### `PluginSDK.PluginName`
|
### `PluginSDK.PluginName`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|||||||
@ -48,13 +48,3 @@ LLM returns the LLM provider API (may be nil if not available).
|
|||||||
|
|
||||||
<small>`plugin.go:432`</small>
|
<small>`plugin.go:432`</small>
|
||||||
|
|
||||||
### `LLMAPI`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type LLMAPI interface { ListSources() []string SetSource(name string) error CurrentSource() string }
|
|
||||||
```
|
|
||||||
|
|
||||||
LLMAPI provides access to the LLM provider manager.
|
|
||||||
|
|
||||||
<small>`llm.go:4`</small>
|
|
||||||
|
|
||||||
|
|||||||
@ -238,16 +238,6 @@ DocMemory returns the document memory API (may be nil if not available).
|
|||||||
|
|
||||||
<small>`plugin.go:418`</small>
|
<small>`plugin.go:418`</small>
|
||||||
|
|
||||||
### `DocMemoryAPI`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type DocMemoryAPI interface { Query(text string, topK int) []*Doc Insert(doc *Doc) error // InsertWithMedia 写入文档并关联媒体。attachments 里带 Data 的会落进 …
|
|
||||||
```
|
|
||||||
|
|
||||||
DocMemoryAPI provides access to the document vector store.
|
|
||||||
|
|
||||||
<small>`memory.go:74`</small>
|
|
||||||
|
|
||||||
### `Entity`
|
### `Entity`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -258,22 +248,6 @@ Entity represents a named entity in the knowledge graph.
|
|||||||
|
|
||||||
<small>`memory.go:13`</small>
|
<small>`memory.go:13`</small>
|
||||||
|
|
||||||
### `PluginSDK.Knowledge`
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (s *PluginSDK) Knowledge() KnowledgeAPI
|
|
||||||
```
|
|
||||||
|
|
||||||
Knowledge returns the knowledge store API (may be nil if not available).
|
|
||||||
|
|
||||||
**示例插件里的真实用法**
|
|
||||||
|
|
||||||
| 插件 | 位置 | 代码 |
|
|
||||||
|---|---|---|
|
|
||||||
| [`recoverydiag`](../examples/index.md#recoverydiag) | `example/recoverydiag/plugin.go:978` | `if p.sdk != nil && p.sdk.Knowledge() != nil {` |
|
|
||||||
|
|
||||||
<small>`plugin.go:425`</small>
|
|
||||||
|
|
||||||
### `Knowledge`
|
### `Knowledge`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -290,15 +264,21 @@ Knowledge represents a knowledge entry.
|
|||||||
|
|
||||||
<small>`knowledge.go:11`</small>
|
<small>`knowledge.go:11`</small>
|
||||||
|
|
||||||
### `KnowledgeAPI`
|
### `PluginSDK.Knowledge`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type KnowledgeAPI interface { Search(query string, topK int) ([]*Knowledge, error) Add(name, content string) error List() ([]string, error) }
|
func (s *PluginSDK) Knowledge() KnowledgeAPI
|
||||||
```
|
```
|
||||||
|
|
||||||
KnowledgeAPI provides access to the knowledge store.
|
Knowledge returns the knowledge store API (may be nil if not available).
|
||||||
|
|
||||||
<small>`knowledge.go:4`</small>
|
**示例插件里的真实用法**
|
||||||
|
|
||||||
|
| 插件 | 位置 | 代码 |
|
||||||
|
|---|---|---|
|
||||||
|
| [`recoverydiag`](../examples/index.md#recoverydiag) | `example/recoverydiag/plugin.go:978` | `if p.sdk != nil && p.sdk.Knowledge() != nil {` |
|
||||||
|
|
||||||
|
<small>`plugin.go:425`</small>
|
||||||
|
|
||||||
### `MediaAttachment`
|
### `MediaAttachment`
|
||||||
|
|
||||||
@ -330,16 +310,6 @@ Memory returns the graph memory API (may be nil if not available).
|
|||||||
|
|
||||||
<small>`plugin.go:404`</small>
|
<small>`plugin.go:404`</small>
|
||||||
|
|
||||||
### `MemoryAPI`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type MemoryAPI interface { Recall(query []string, depth int) ([]Entity, []Relation, error) Commit(triples []Triple) error Introspect() (map[string]interface{}, error) Merg …
|
|
||||||
```
|
|
||||||
|
|
||||||
MemoryAPI provides access to the graph memory (entity-relation store).
|
|
||||||
|
|
||||||
<small>`memory.go:4`</small>
|
|
||||||
|
|
||||||
### `PersonProfile`
|
### `PersonProfile`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -370,17 +340,6 @@ Social returns the social graph API (may be nil if not available).
|
|||||||
|
|
||||||
<small>`plugin.go:439`</small>
|
<small>`plugin.go:439`</small>
|
||||||
|
|
||||||
### `SocialAPI`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type SocialAPI interface { GetPerson(name string) (*PersonProfile, error) GetTrait(name, trait string) (string, bool) GetRelations(name string) ([]SocialRelation, error) G …
|
|
||||||
```
|
|
||||||
|
|
||||||
SocialAPI provides read-only access to the social graph (person profiles and relationships).
|
|
||||||
External plugins can query person traits and social networks but cannot modify them.
|
|
||||||
|
|
||||||
<small>`memory.go:100`</small>
|
|
||||||
|
|
||||||
### `SocialRelation`
|
### `SocialRelation`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -409,16 +368,6 @@ TextMemory returns the text memory API (may be nil if not available).
|
|||||||
|
|
||||||
<small>`plugin.go:411`</small>
|
<small>`plugin.go:411`</small>
|
||||||
|
|
||||||
### `TextMemoryAPI`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type TextMemoryAPI interface { Append(evt TextEvent) error }
|
|
||||||
```
|
|
||||||
|
|
||||||
TextMemoryAPI provides access to chronological text event storage.
|
|
||||||
|
|
||||||
<small>`memory.go:43`</small>
|
|
||||||
|
|
||||||
### `Triple`
|
### `Triple`
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|||||||
@ -195,11 +195,3 @@ sett 在 New 时一次性写入且无 setter,故不需要加锁。
|
|||||||
|
|
||||||
<small>`plugin.go:401`</small>
|
<small>`plugin.go:401`</small>
|
||||||
|
|
||||||
### `SettingsAPI`
|
|
||||||
|
|
||||||
```go
|
|
||||||
type SettingsAPI interface { // Get reads the plugin's own config value (config_<name> table). Get(key string) (interface{}, error) // Set writes a config value to the plugi …
|
|
||||||
```
|
|
||||||
|
|
||||||
<small>`settings.go:3`</small>
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -65,6 +65,17 @@
|
|||||||
else if (name.indexOf(ql) === 0) s += 600;
|
else if (name.indexOf(ql) === 0) s += 600;
|
||||||
else if (name.indexOf(ql) > 0) s += 350;
|
else if (name.indexOf(ql) > 0) s += 350;
|
||||||
|
|
||||||
|
// 中文检索关键词(keywords.json 产出,字段 g)。
|
||||||
|
// 为什么需要:SDK 里 66/100 个符号是英文注释(`RegisterTool registers a
|
||||||
|
// tool that the LLM can call.`),懂中文的人搜「注册工具」会一条都找不到。
|
||||||
|
// 关键词命中给较高权重(仅次于名称精确命中),因为它就是为「按功能找」准备的。
|
||||||
|
var kws = item.g || [];
|
||||||
|
for (var ki = 0; ki < kws.length; ki++) {
|
||||||
|
var kw = String(kws[ki]).toLowerCase();
|
||||||
|
if (kw === ql) { s += 480; break; }
|
||||||
|
if (kw.indexOf(ql) >= 0) { s += 300; break; }
|
||||||
|
}
|
||||||
|
|
||||||
// 限定符:PluginSDK.RegisterTool / IOInjector.InjectText。
|
// 限定符:PluginSDK.RegisterTool / IOInjector.InjectText。
|
||||||
// 额外支持「去掉 API/SDK 后缀」与「去掉点号」两种写法,
|
// 额外支持「去掉 API/SDK 后缀」与「去掉点号」两种写法,
|
||||||
// 因为读者习惯写 `memory.recall`(RPC 名),而 Go 名是 `MemoryAPI.Recall`。
|
// 因为读者习惯写 `memory.recall`(RPC 名),而 Go 名是 `MemoryAPI.Recall`。
|
||||||
@ -94,13 +105,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 中文按字匹配:中文没有词边界,逐字命中比整串更实用。
|
// 中文按字匹配:中文没有词边界,逐字命中比整串更实用。
|
||||||
|
// 注意只把它当作**弱信号**:光靠逐字会把「注册工具」匹到凡是含「工具」
|
||||||
|
// 字样的任何东西(实测 ContextPolicyNone 的说明里有「工具调用」也会命中)。
|
||||||
|
// 所以阈值卡在 60% 以上才算有效命中。
|
||||||
if (/[\u4e00-\u9fa5]/.test(q)) {
|
if (/[\u4e00-\u9fa5]/.test(q)) {
|
||||||
var hit = 0;
|
var hit = 0;
|
||||||
|
var hay = desc + " " + kws.join(" ");
|
||||||
for (var i = 0; i < q.length; i++) {
|
for (var i = 0; i < q.length; i++) {
|
||||||
if (desc.indexOf(q[i]) >= 0) hit++;
|
if (hay.indexOf(q[i]) >= 0) hit++;
|
||||||
}
|
}
|
||||||
if (hit === q.length) s += 100; // 全部字都出现
|
var ratio = hit / q.length;
|
||||||
else s += hit * 8;
|
if (ratio >= 0.6) s += Math.round(hit * 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 公开 API 略优先于「仅内置」——后者通常是噪声。
|
// 公开 API 略优先于「仅内置」——后者通常是噪声。
|
||||||
@ -173,6 +188,10 @@
|
|||||||
var d = el("span", "api-desc", it.d);
|
var d = el("span", "api-desc", it.d);
|
||||||
li.appendChild(d);
|
li.appendChild(d);
|
||||||
}
|
}
|
||||||
|
// 关键词是给检索用的;显示出来能让读者明白“为什么这条被匹配到”。
|
||||||
|
if (it.g && it.g.length) {
|
||||||
|
li.appendChild(el("span", "api-kw", it.g.slice(0, 6).join(" · ")));
|
||||||
|
}
|
||||||
if (it.f) {
|
if (it.f) {
|
||||||
li.appendChild(el("span", "api-loc", it.f + (it.l ? ":" + it.l : "")));
|
li.appendChild(el("span", "api-loc", it.f + (it.l ? ":" + it.l : "")));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,14 @@
|
|||||||
color: var(--md-default-fg-color--light);
|
color: var(--md-default-fg-color--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 「为何命中」的中文关键词(keywords.json 产出)。 */
|
||||||
|
.api-kw {
|
||||||
|
flex-basis: 100%;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--md-accent-fg-color);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
.api-loc {
|
.api-loc {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
font-size: 0.68rem;
|
font-size: 0.68rem;
|
||||||
|
|||||||
94
tools/apidoc/gensite/keywords.go
Normal file
94
tools/apidoc/gensite/keywords.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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]
|
||||||
|
}
|
||||||
@ -180,7 +180,7 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先收集所有待渲染的符号名,再扫 example/ 取真实用法。
|
// 收集所有待渲染的符号名,再扫 example/ 取真实用法。
|
||||||
want := map[string]bool{}
|
want := map[string]bool{}
|
||||||
for _, s := range pkg.Symbols {
|
for _, s := range pkg.Symbols {
|
||||||
if s.Exported {
|
if s.Exported {
|
||||||
@ -192,25 +192,41 @@ func main() {
|
|||||||
}
|
}
|
||||||
usages := scanUsages(*exDir, want)
|
usages := scanUsages(*exDir, want)
|
||||||
|
|
||||||
|
// 中文检索关键词(弥补英文注释搜不到中文的问题)。
|
||||||
|
kw, err := loadKeywords()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("警告:未加载中文检索关键词(%v),中文检索将只能命中中文注释", err)
|
||||||
|
}
|
||||||
|
|
||||||
used := map[string]bool{}
|
used := map[string]bool{}
|
||||||
var index []indexEntry
|
var index []indexEntry
|
||||||
|
|
||||||
for _, sec := range sections {
|
for _, sec := range sections {
|
||||||
|
// 本节的接口(先算出来,下面要用它排除重复的 type 符号)。
|
||||||
|
var ifaces []Interface
|
||||||
|
ifaceNames := map[string]bool{}
|
||||||
|
for _, it := range pkg.Interfaces {
|
||||||
|
if sec.Match(Symbol{Kind: "type", Name: it.Name}) {
|
||||||
|
ifaces = append(ifaces, it)
|
||||||
|
ifaceNames[it.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var picked []Symbol
|
var picked []Symbol
|
||||||
for _, s := range pkg.Symbols {
|
for _, s := range pkg.Symbols {
|
||||||
if !s.Exported || !sec.Match(s) || used[key(s)] {
|
if !s.Exported || !sec.Match(s) || used[key(s)] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// 接口已作为 ifaces 单独渲染(带方法表),这里跳过它的 `type`
|
||||||
|
// 符号,否则同一个接口会既出现在符号区、又出现在接口区,
|
||||||
|
// 检索索引里也会出现两条。
|
||||||
|
if s.Kind == "type" && ifaceNames[s.Name] {
|
||||||
|
used[key(s)] = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
picked = append(picked, s)
|
picked = append(picked, s)
|
||||||
used[key(s)] = true
|
used[key(s)] = true
|
||||||
}
|
}
|
||||||
// 该章节涉及的接口(其方法单独列在接口下,避免重复)。
|
|
||||||
var ifaces []Interface
|
|
||||||
for _, it := range pkg.Interfaces {
|
|
||||||
if sec.Match(Symbol{Kind: "type", Name: it.Name}) {
|
|
||||||
ifaces = append(ifaces, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(picked) == 0 && len(ifaces) == 0 {
|
if len(picked) == 0 && len(ifaces) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -224,6 +240,7 @@ func main() {
|
|||||||
N: s.Name, S: s.Signature, D: s.DocBrief,
|
N: s.Name, S: s.Signature, D: s.DocBrief,
|
||||||
K: s.Kind, R: s.Recv, P: sec.File,
|
K: s.Kind, R: s.Recv, P: sec.File,
|
||||||
B: s.BuiltinOnly, F: s.File, L: s.Line,
|
B: s.BuiltinOnly, F: s.File, L: s.Line,
|
||||||
|
G: kw.lookup(s.Name, s.DocBrief),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 接口本身与其方法也要进索引。此前只加了顶层符号,导致
|
// 接口本身与其方法也要进索引。此前只加了顶层符号,导致
|
||||||
@ -238,6 +255,7 @@ func main() {
|
|||||||
N: m.Name, S: m.Signature, D: m.DocBrief,
|
N: m.Name, S: m.Signature, D: m.DocBrief,
|
||||||
K: "method", R: it.Name, P: sec.File,
|
K: "method", R: it.Name, P: sec.File,
|
||||||
B: m.BuiltinOnly, F: m.File, L: m.Line,
|
B: m.BuiltinOnly, F: m.File, L: m.Line,
|
||||||
|
G: kw.lookup(m.Name, m.DocBrief),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,6 +271,7 @@ func main() {
|
|||||||
index = append(index, indexEntry{
|
index = append(index, indexEntry{
|
||||||
N: c.Name, S: "const " + c.Name, D: c.DocBrief,
|
N: c.Name, S: "const " + c.Name, D: c.DocBrief,
|
||||||
K: "const", P: "constants", F: c.File, L: c.Line,
|
K: "const", P: "constants", F: c.File, L: c.Line,
|
||||||
|
G: kw.lookup(c.Name, c.DocBrief),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,6 +319,14 @@ type indexEntry struct {
|
|||||||
B bool `json:"b"` // 仅内置
|
B bool `json:"b"` // 仅内置
|
||||||
F string `json:"f"` // 源文件
|
F string `json:"f"` // 源文件
|
||||||
L int `json:"l"` // 行号
|
L int `json:"l"` // 行号
|
||||||
|
// G 是中文检索关键词(同义词/功能词)。
|
||||||
|
//
|
||||||
|
// 为什么需要:SDK 里 100 个有摘要的符号中 **66 个是英文注释**
|
||||||
|
// (`RegisterTool registers a tool that the LLM can call.`),
|
||||||
|
// 于是搜「注册工具」根本找不到 RegisterTool —— 而中文是主要受众。
|
||||||
|
// 这些词由 tools/apidoc/keywords.json 维护,不改源码注释,
|
||||||
|
// 也不依赖机器翻译。
|
||||||
|
G []string `json:"g,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func key(s Symbol) string {
|
func key(s Symbol) string {
|
||||||
@ -522,16 +549,19 @@ func renderExamples(usages map[string][]Usage) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// dedupeIndex 按「接收者.名称」去重。无接收者的用「类别.名称」。
|
// dedupeIndex 按「接收者.名称.类别」去重。
|
||||||
// 同名但不同接收者(如 MemoryAPI.Recall 与 IOInjector.InjectText)都保留。
|
//
|
||||||
|
// 为什么带类别:接口会被两条路径加进索引——一次作为 `type` 符号(来自
|
||||||
|
// pkg.Symbols),一次作为接口本身(来自 pkg.Interfaces)。两者名字相同、
|
||||||
|
// 接收者都为空,只用「名称」做键漏不掉;带上类别才能区分(并且保留两条
|
||||||
|
// 也不算错,但会让结果重复,所以统一按 kind 去重)。
|
||||||
|
//
|
||||||
|
// 同名但不同接收者的(如 MemoryAPI.Recall 与 IOInjector.InjectText)都保留。
|
||||||
func dedupeIndex(in []indexEntry) []indexEntry {
|
func dedupeIndex(in []indexEntry) []indexEntry {
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
out := in[:0]
|
out := in[:0]
|
||||||
for _, e := range in {
|
for _, e := range in {
|
||||||
k := e.R + "." + e.N
|
k := e.K + "|" + e.R + "." + e.N
|
||||||
if e.R == "" {
|
|
||||||
k = e.K + "." + e.N
|
|
||||||
}
|
|
||||||
if seen[k] {
|
if seen[k] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
159
tools/apidoc/keywords.json
Normal file
159
tools/apidoc/keywords.json
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
{
|
||||||
|
"_doc": [
|
||||||
|
"中文检索关键词:只影响**检索**,不影响页面展示。",
|
||||||
|
"",
|
||||||
|
"背景:SDK 里 100 个有摘要的符号中 66 个是英文注释,中文搜不到。",
|
||||||
|
"例如 `RegisterTool registers a tool that the LLM can call.` —— 搜「注册工具」命中 0。",
|
||||||
|
"",
|
||||||
|
"规则:",
|
||||||
|
" rules[].prefix 符号名前缀匹配(如 Register 开头的都带「注册」)",
|
||||||
|
" rules[].contains 符号名含该子串即匹配",
|
||||||
|
" symbols[名] 逐符号补充(用于重点 API 或规则覆盖不到的)",
|
||||||
|
"",
|
||||||
|
"写法要求:词要贴近读者会敲的字,宁多勿少;不必去重,代码会去。",
|
||||||
|
"注意别把词撒太宽(如给所有 Get* 都加「查询」会让结果变噪)。"
|
||||||
|
],
|
||||||
|
|
||||||
|
"rules": [
|
||||||
|
{ "prefix": "RegisterTool", "words": ["注册工具", "添加工具", "暴露工具", "工具定义", "让模型调用"] },
|
||||||
|
{ "prefix": "RegisterStage", "words": ["注册阶段", "阶段钩子", "挂钩子", "干预流程", "管道钩子"] },
|
||||||
|
{ "prefix": "RegisterInputChannel", "words": ["注册输入通道", "收消息", "接收消息", "外部消息源"] },
|
||||||
|
{ "prefix": "RegisterOutputChannel", "words": ["注册输出通道", "发消息", "投递消息", "输出目的地"] },
|
||||||
|
{ "prefix": "RegisterPluginAPI", "words": ["注册插件接口", "暴露接口"] },
|
||||||
|
{ "prefix": "RegisterStopHandler", "words": ["停止回调", "关闭清理", "插件停止"] },
|
||||||
|
{ "prefix": "RegisterOnRemoveHandler", "words": ["卸载回调", "删除清理", "插件卸载", "onRemove"] },
|
||||||
|
{ "prefix": "RegisterDef", "words": ["注册配置项", "声明配置", "配置定义"] },
|
||||||
|
|
||||||
|
{ "prefix": "InjectText", "words": ["注入文本", "注入消息", "投喂输入", "灌入内容"] },
|
||||||
|
{ "prefix": "InjectInterrupt", "words": ["中断注入", "插话", "打断", "抢占"] },
|
||||||
|
{ "prefix": "InjectInput", "words": ["注入输入", "投喂输入", "模拟用户输入"] },
|
||||||
|
{ "prefix": "InjectMedia", "words": ["注入媒体", "发图片", "发音频"] },
|
||||||
|
|
||||||
|
{ "prefix": "Set", "words": ["设置", "装配"] },
|
||||||
|
|
||||||
|
{ "contains": "AutoRestart", "words": ["自动重启", "崩溃自愈", "故障恢复"] },
|
||||||
|
{ "contains": "Priority", "words": ["优先级", "中断级别"] },
|
||||||
|
{ "contains": "Policy", "words": ["策略"] },
|
||||||
|
{ "contains": "Memory", "words": ["记忆", "内存"] },
|
||||||
|
{ "contains": "Knowledge", "words": ["知识库", "知识"] },
|
||||||
|
{ "contains": "Recall", "words": ["召回", "检索记忆"] },
|
||||||
|
{ "contains": "Media", "words": ["媒体", "图片", "音频"] },
|
||||||
|
{ "contains": "Channel", "words": ["通道", "频道"] },
|
||||||
|
{ "contains": "Event", "words": ["事件", "订阅"] },
|
||||||
|
{ "contains": "Stage", "words": ["阶段", "钩子"] },
|
||||||
|
{ "contains": "Tool", "words": ["工具"] },
|
||||||
|
{ "contains": "Doc", "words": ["文档"] },
|
||||||
|
{ "contains": "Social", "words": ["社交", "关系", "联系人"] },
|
||||||
|
{ "contains": "Purge", "words": ["清除", "删除记忆"] },
|
||||||
|
{ "contains": "PluginMgr", "words": ["插件管理", "重载插件", "禁用插件"] }
|
||||||
|
],
|
||||||
|
|
||||||
|
"symbols": {
|
||||||
|
"RegisterTool": ["怎么注册工具", "工具怎么加", "自定义工具"],
|
||||||
|
"UnregisterOutputChannel": ["注销输出通道", "删除通道"],
|
||||||
|
"SetToolBlocks": ["工具返回图片", "多模态返回", "让模型看图"],
|
||||||
|
"ContentBlock": ["多模态内容块", "图片块", "音频块"],
|
||||||
|
"SetAutoRestart": ["崩溃后重启", "是否自动重启"],
|
||||||
|
"AutoRestart": ["查询是否自动重启"],
|
||||||
|
|
||||||
|
"ToolDef": ["工具定义", "工具描述", "参数表"],
|
||||||
|
"ToolHandler": ["工具实现", "处理函数"],
|
||||||
|
"ToolCall": ["工具调用"],
|
||||||
|
"ToolResult": ["工具结果", "返回值"],
|
||||||
|
|
||||||
|
"Stage": ["阶段名", "管线阶段"],
|
||||||
|
"StageHandler": ["阶段处理函数"],
|
||||||
|
"StageContext": ["阶段上下文", "读写消息"],
|
||||||
|
"StageScope": ["阶段作用域", "只看自己的调用"],
|
||||||
|
|
||||||
|
"InjectOptions": ["注入选项", "是否记入记忆", "是否裁剪上下文"],
|
||||||
|
"ContextPolicyNone": ["不裁剪上下文", "默认裁剪策略"],
|
||||||
|
"ContextPolicyPrune": ["裁剪上下文", "归档丢弃"],
|
||||||
|
"RecallPolicyNone": ["不召回", "关闭召回"],
|
||||||
|
"RecallPolicyAuto": ["自动召回", "默认召回"],
|
||||||
|
|
||||||
|
"MemoryAPI": ["图记忆接口", "三元组记忆"],
|
||||||
|
"TextMemoryAPI": ["文本记忆接口", "事件流水"],
|
||||||
|
"DocMemoryAPI": ["文档记忆接口", "文档向量"],
|
||||||
|
"KnowledgeAPI": ["知识库接口"],
|
||||||
|
"LLMAPI": ["调用模型接口", "LLM 接口"],
|
||||||
|
"SocialAPI": ["社交接口", "人物画像"],
|
||||||
|
"SettingsAPI": ["配置接口"],
|
||||||
|
"IOInjector": ["注入器接口", "投喂输入"],
|
||||||
|
"PluginMgrAPI": ["插件管理接口"],
|
||||||
|
"EventSubscriber": ["事件订阅接口"],
|
||||||
|
"Plugin": ["插件接口", "插件契约"],
|
||||||
|
|
||||||
|
"Entity": ["实体", "记忆主体"],
|
||||||
|
"Relation": ["关系", "三元组关系"],
|
||||||
|
"Triple": ["三元组"],
|
||||||
|
"Doc": ["文档对象"],
|
||||||
|
"MediaAttachment": ["媒体附件"],
|
||||||
|
"ConfigDef": ["配置项定义"],
|
||||||
|
"ChannelDef": ["通道定义"],
|
||||||
|
"PersonProfile": ["人物画像"],
|
||||||
|
"SocialRelation": ["社交关系"],
|
||||||
|
|
||||||
|
"Recall": ["召回记忆", "查记忆"],
|
||||||
|
"Commit": ["写入记忆", "提交三元组"],
|
||||||
|
"Introspect": ["查看记忆", "记忆概览"],
|
||||||
|
"MergeEntities": ["合并实体"],
|
||||||
|
"Purge": ["清除记忆", "删除记忆"],
|
||||||
|
"Insert": ["插入文档"],
|
||||||
|
"InsertWithMedia": ["插入带媒体文档"],
|
||||||
|
"Query": ["查询文档"],
|
||||||
|
"Remove": ["删除文档"],
|
||||||
|
"Stats": ["统计"],
|
||||||
|
"Append": ["追加文本"],
|
||||||
|
"Add": ["添加知识"],
|
||||||
|
"Search": ["搜索知识"],
|
||||||
|
"List": ["列出"],
|
||||||
|
"Get": ["读取配置"],
|
||||||
|
"Set": ["写入配置"],
|
||||||
|
"Defs": ["配置定义列表"],
|
||||||
|
"Dump": ["导出配置"],
|
||||||
|
"Plugins": ["插件配置"],
|
||||||
|
|
||||||
|
"PluginName": ["插件名"],
|
||||||
|
"PluginStop": ["停止插件"],
|
||||||
|
"PluginStart": ["启动插件"],
|
||||||
|
"Start": ["启动"],
|
||||||
|
"Stop": ["停止", "关闭"],
|
||||||
|
"Name": ["名称"],
|
||||||
|
"Events": ["事件订阅", "监听事件"],
|
||||||
|
"PluginMgr": ["插件管理器", "重载插件"],
|
||||||
|
"ReloadOne": ["重载单个插件"],
|
||||||
|
"ReloadPlugins": ["重载全部插件"],
|
||||||
|
"ListLoadedPlugins": ["已加载插件列表"],
|
||||||
|
"ListDisabledPlugins": ["已禁用插件列表"],
|
||||||
|
"IsPluginDisabled": ["查插件是否被禁用"],
|
||||||
|
"DisablePlugin": ["禁用插件"],
|
||||||
|
"EnablePlugin": ["启用插件"],
|
||||||
|
"RemovePlugin": ["卸载插件", "删除插件"],
|
||||||
|
|
||||||
|
"GetPerson": ["查人物"],
|
||||||
|
"GetTrait": ["查特征"],
|
||||||
|
"GetRelations": ["查关系"],
|
||||||
|
"GetNetwork": ["查关系网"],
|
||||||
|
"ListPersons": ["列出人物"],
|
||||||
|
"CurrentSource": ["当前模型来源"],
|
||||||
|
"ListSources": ["列出模型来源"],
|
||||||
|
"SetSource": ["切换模型"],
|
||||||
|
"DataDir": ["数据目录"],
|
||||||
|
"GetCore": ["读核心配置"],
|
||||||
|
"SetCore": ["写核心配置"],
|
||||||
|
"GetPlugin": ["读插件配置"],
|
||||||
|
"SetPlugin": ["写插件配置"],
|
||||||
|
"ListCore": ["列出核心配置"],
|
||||||
|
"ListPlugin": ["列出插件配置"],
|
||||||
|
|
||||||
|
"CapText": ["文本能力"],
|
||||||
|
"CapFile": ["文件能力"],
|
||||||
|
"CapImage": ["图片能力"],
|
||||||
|
"CapAudio": ["音频能力"],
|
||||||
|
"CapStructured": ["结构化能力"],
|
||||||
|
"SDKVersion": ["SDK 版本"],
|
||||||
|
"ValidContextPolicy": ["校验裁剪策略"],
|
||||||
|
"ValidRecallPolicy": ["校验召回策略"]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user