Files
HomeAgent/internal/plugin/proc/corehandler_inject.go
JianFeeeee b792a94b84 feat(memory): 新增可声明的召回轴 RecallPolicy(与 prune 正交)
问题:召回(把 L2/L3 相关记忆注入本轮)此前不可声明、也不受任何 SDK 字段
控制——它只在任务开始时对 f.Input 无条件跑一次。于是 qq_get_message 取回
真实正文后只触发 Prune(裁剪),从不触发召回;而中断通知的 meta 文本反而
会去召回(词不对题,命中一堆泛实体)。

改动:
- SDK 新增 RecallPolicy(none|auto) 轴,落在 InjectOptions / ChannelDef /
  ToolDef 三个声明面,与 ContextPolicy 正交(裁剪 vs 召回)。默认值与
  prune 刻意相反:输入/注入默认 auto(保持既有「每条输入都召回」),
  工具默认 none(工具输出多为噪声,按需声明)。
- 内核:recallDeclared 按 注入点 > 通道 > 默认auto 解析;输入侧用它决定
  是否注入记忆索引;工具侧 ContextPolicy/RecallPolicy 共用同一份清洗后
  query,一次相关性过程分别 prune / recall;召回以 system 消息挂到消息
  末尾(同任务内替换而非累加)。
- 管线:proc RPC(inject/register + 校验)、lua 键、io payload 全量透传。
- QQ 插件:中断与 qq 通道声明 RecallPolicy=none(meta 不是内容);
  qq_get_message 声明 RecallPolicy=auto(取回正文后据正文召回)。

测试:新增 recallpolicy_test.go(core)与 proc 校验用例;
go build ./... 通过,go test ./internal/... 全通过,SDK 模块与 qq 插件测试通过。
2026-09-14 23:14:38 +08:00

152 lines
5.4 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 proc
import (
"encoding/json"
"fmt"
)
// handleInject 处理 IO 注入面:文本 / 打断 / 同步 / 媒体块,以及 SetToolBlocks。
//
// 本函数体是 corehandler.go 里 Handle 那一个大 switch 的**整块平移**
// case 标签与 case 体逐字保留只换了宿主函数§3.2 的平移原则)。
func (h *coreHandler) handleInject(method string, params json.RawMessage) (interface{}, error) {
switch method {
// ---- IO 注入(原 case 5/6/7/47----
//
// 注入标志位no_memory / context_policy由插件在调用点声明默认
// 记入记忆 + 不裁剪。策略值在入口校验:静默降级成 none 会让调用方
// 以为自己声明的裁剪在生效。
case MethodIOInjectText:
var p injectParams
if err := unmarshal(params, &p); err != nil {
return nil, err
}
if err := validateContextPolicy("io.injectText", p.ContextPolicy); err != nil {
return nil, err
}
if err := validateRecallPolicy("io.injectText", p.RecallPolicy); err != nil {
return nil, err
}
h.sdk.InjectTextOpts(p.Source, p.Channel, h.resolveText(p), pubSdkInjectOpts(p.NoMemory, p.ContextPolicy, p.RecallPolicy, p.CleanerName, p.Priority))
return nil, nil
case MethodIOInjectInterrupt:
var p injectParams
if err := unmarshal(params, &p); err != nil {
return nil, err
}
if err := validateContextPolicy("io.injectInterrupt", p.ContextPolicy); err != nil {
return nil, err
}
if err := validateRecallPolicy("io.injectInterrupt", p.RecallPolicy); err != nil {
return nil, err
}
h.sdk.InjectInterruptTextOpts(p.Source, p.Channel, h.resolveText(p), pubSdkInjectOpts(p.NoMemory, p.ContextPolicy, p.RecallPolicy, p.CleanerName, p.Priority))
return nil, nil
case MethodIOInjectTextNoMem:
var p injectParams
if err := unmarshal(params, &p); err != nil {
return nil, err
}
if err := validateContextPolicy("io.injectTextNoMem", p.ContextPolicy); err != nil {
return nil, err
}
if err := validateRecallPolicy("io.injectTextNoMem", p.RecallPolicy); err != nil {
return nil, err
}
// 旧 RPC 语义就是「不进记忆」,显式标志位只可能再叠上 context_policy。
h.sdk.InjectTextOpts(p.Source, p.Channel, h.resolveText(p), pubSdkInjectOpts(true, p.ContextPolicy, p.RecallPolicy, p.CleanerName, p.Priority))
return nil, nil
case MethodIOInjectSync:
var p injectParams
if err := unmarshal(params, &p); err != nil {
return nil, err
}
if err := validateContextPolicy("io.injectInputSync", p.ContextPolicy); err != nil {
return nil, err
}
if err := validateRecallPolicy("io.injectInputSync", p.RecallPolicy); err != nil {
return nil, err
}
reply := h.sdk.InjectInputSyncOpts(p.Source, p.Channel, h.resolveText(p), pubSdkInjectOpts(p.NoMemory, p.ContextPolicy, p.RecallPolicy, p.CleanerName, p.Priority))
return map[string]interface{}{"reply": reply}, nil
case MethodIOInjectMedia:
var p injectMediaParams
if err := unmarshal(params, &p); err != nil {
return nil, err
}
if err := validateContextPolicy("io.injectMedia", p.ContextPolicy); err != nil {
return nil, err
}
if err := validateRecallPolicy("io.injectMedia", p.RecallPolicy); err != nil {
return nil, err
}
blocks, err := h.resolveBlocks(p)
if err != nil {
return nil, err
}
h.sdk.InjectInputMediaOpts(p.Source, p.Channel, p.Text, blocks, pubSdkInjectOpts(p.NoMemory, p.ContextPolicy, p.RecallPolicy, p.CleanerName, p.Priority))
return nil, nil
case MethodIOInjectMediaSync:
var p injectMediaParams
if err := unmarshal(params, &p); err != nil {
return nil, err
}
if err := validateContextPolicy("io.injectMediaSync", p.ContextPolicy); err != nil {
return nil, err
}
if err := validateRecallPolicy("io.injectMediaSync", p.RecallPolicy); err != nil {
return nil, err
}
blocks, err := h.resolveBlocks(p)
if err != nil {
return nil, err
}
reply := h.sdk.InjectInputMediaSyncOpts(p.Source, p.Channel, p.Text, blocks, pubSdkInjectOpts(p.NoMemory, p.ContextPolicy, p.RecallPolicy, p.CleanerName, p.Priority))
return map[string]interface{}{"reply": reply}, nil
case MethodIOInjectInterruptMedia:
var p injectMediaParams
if err := unmarshal(params, &p); err != nil {
return nil, err
}
if err := validateContextPolicy("io.injectInterruptMedia", p.ContextPolicy); err != nil {
return nil, err
}
if err := validateRecallPolicy("io.injectInterruptMedia", p.RecallPolicy); err != nil {
return nil, err
}
blocks, err := h.resolveBlocks(p)
if err != nil {
return nil, err
}
h.sdk.InjectInterruptMediaOpts(p.Source, p.Channel, p.Text, blocks, pubSdkInjectOpts(p.NoMemory, p.ContextPolicy, p.RecallPolicy, p.CleanerName, p.Priority))
return nil, nil
// ---- 多模态注入 ----
//
// 之前这里是桩:返回“待共享段二进制通道落地”。后果是**子进程插件调
// SetToolBlocks 必然失败**(模板只 log 一行),只有内置插件能用。
// 现在媒体块经共享内存传递,该能力对两种插件形态等价。
case MethodIOSetToolBlocks:
var p injectMediaParams
if err := unmarshal(params, &p); err != nil {
return nil, err
}
blocks, err := h.resolveBlocks(p)
if err != nil {
return nil, err
}
if len(blocks) == 0 {
return nil, fmt.Errorf("%s: blocks 为空", method)
}
h.sdk.SetToolBlocks(blocks)
return nil, nil
}
// 组内不应到达Handle 的分派表与本函数的 case 标签同源,
// 出现即说明两处不同步。
return nil, fmt.Errorf("未知 method: %s", method)
}