mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
docs: 修正全部文档使其与源码实现一致
主仓库: - 修复 4 份英文文档语言切换链接指向错误 (../zh/ → ../en/) - ARCHITECTURE.md 标题 "三种加载方式" → "四种加载方式" (实际表格4行) - PLUGIN_DEV.md 示例表: 添加 webfetch, 移除不存在的 luaplugintest/testlua - PLUGIN_DEV.md 代码示例: InjectInput/InjectInterrupt → InjectText/InjectInterruptText - PLUGIN_DEV.md 代码示例: Memory/Knowledge/LLM/Events 接口签名修正 - PLUGIN_DEV.md .hmap 内容统一, plugindev 编译去除 .exe 后缀 SDK 仓库: - Plugin.Start(sdk *PluginSDK) 接口签名改为指针 - 方法表重写: 移除 CallLLM/QueryKnowledge/SetMemory 等不存在方法 - IOInjector 参数顺序修正为 (source, channel, text) - 删除虚构 SDKConfig, 替换为实际 New() 构造函数签名 - .hmap 内容描述一致化 修正前一次会话中的 QQ/Bili 插件问题: - qq napcat() 超时, fetchBotInfo 竞态, handleWebhook 同步阻塞 - bili CDN 直连失败, 添加 HTTP_PROXY 代理
This commit is contained in:
@ -380,6 +380,27 @@ func (a *Agent) processMediaInput(evt *agentIO.InputEvent) {
|
||||
|
||||
blocks, fallback := a.mediaToBlocks(evt.Payload, evt.Type, evt.Source)
|
||||
|
||||
// stage 上下文携带 blocks,process() 会将其附着到 user message 上
|
||||
stageCtx := a.stageCtxFromInput(fallback, evt.Source, "")
|
||||
stageCtx.Extra = map[string]interface{}{
|
||||
"media_blocks": blocks,
|
||||
"media_type": evt.Type,
|
||||
"input_source": evt.Source,
|
||||
"output_channel": evt.OutputChannel,
|
||||
}
|
||||
a.injectSourceContext(stageCtx, evt)
|
||||
|
||||
// === Stage: on_input — 插件可拦截/改写/短路(在 Append 之前) ===
|
||||
if a.runStage(sdk.StageOnInput, stageCtx) {
|
||||
a.emitResponse(evt, *stageCtx.Response)
|
||||
return
|
||||
}
|
||||
|
||||
a.publishEvent(events.EventRawInput, map[string]interface{}{
|
||||
"content": evt.Payload,
|
||||
"source": evt.Source,
|
||||
})
|
||||
|
||||
// 先遗忘再输入
|
||||
archived := a.context.Prune(fallback, a.maxContextSize-1, a.docStore)
|
||||
if archived > 0 {
|
||||
@ -392,26 +413,6 @@ func (a *Agent) processMediaInput(evt *agentIO.InputEvent) {
|
||||
Input: fallback,
|
||||
})
|
||||
|
||||
// stage 上下文携带 blocks,process() 会将其附着到 user message 上
|
||||
stageCtx := a.stageCtxFromInput(fallback, evt.Source, "")
|
||||
stageCtx.Extra = map[string]interface{}{
|
||||
"media_blocks": blocks,
|
||||
"media_type": evt.Type,
|
||||
"input_source": evt.Source,
|
||||
"output_channel": evt.OutputChannel,
|
||||
}
|
||||
a.injectSourceContext(stageCtx, evt)
|
||||
|
||||
a.publishEvent(events.EventRawInput, map[string]interface{}{
|
||||
"content": evt.Payload,
|
||||
"source": evt.Source,
|
||||
})
|
||||
|
||||
if a.runStage(sdk.StageOnInput, stageCtx) {
|
||||
a.emitResponse(evt, *stageCtx.Response)
|
||||
return
|
||||
}
|
||||
|
||||
response, toolsUsed, err := a.process(fallback, stageCtx)
|
||||
if err != nil {
|
||||
log.Printf("[agent] process media error: %v", err)
|
||||
@ -433,6 +434,10 @@ func (a *Agent) processMediaInput(evt *agentIO.InputEvent) {
|
||||
})
|
||||
|
||||
a.emitResponse(evt, response)
|
||||
|
||||
if !stageCtx.NoMemory {
|
||||
a.emitMemoryCandidate(evt.Source, fallback, response, toolsUsed)
|
||||
}
|
||||
}
|
||||
|
||||
// mediaToBlocks 将媒体 payload 转为多模态 ContentBlock 数组和纯文本 fallback。
|
||||
@ -502,7 +507,7 @@ func (a *Agent) processTextInput(evt *agentIO.InputEvent, input string) {
|
||||
|
||||
// 记忆整理任务:不路由到外部输出通道
|
||||
if evt.OutputChannel == "_consolidation_" {
|
||||
a.processConsolidation(input)
|
||||
a.processConsolidation(evt, input)
|
||||
return
|
||||
}
|
||||
|
||||
@ -520,8 +525,18 @@ func (a *Agent) processTextInput(evt *agentIO.InputEvent, input string) {
|
||||
}
|
||||
a.injectSourceContext(stageCtx, evt)
|
||||
|
||||
if a.runStage(sdk.StageOnInput, stageCtx) {
|
||||
a.emitResponse(evt, *stageCtx.Response)
|
||||
return
|
||||
}
|
||||
|
||||
input = stageCtx.RawMessage
|
||||
|
||||
a.publishEvent(events.EventRawInput, map[string]interface{}{
|
||||
"content": input,
|
||||
"source": evt.Source,
|
||||
})
|
||||
|
||||
// 先"遗忘"再输入:用当前输入决定淘汰哪些不相关旧事件(LSTM forget gate 模式)
|
||||
archived := a.context.Prune(input, a.maxContextSize-1, a.docStore)
|
||||
if archived > 0 {
|
||||
@ -2549,10 +2564,14 @@ func (a *Agent) emitMemoryCandidate(source, input, response string, toolsUsed []
|
||||
// executeOutputChannelTool — AI 切换当前请求的输出通道
|
||||
// 在 process() 内调用,mutex 保护,只有一个请求在执行
|
||||
// processConsolidation 处理后台记忆整理任务(不发外部输出)
|
||||
func (a *Agent) processConsolidation(input string) {
|
||||
func (a *Agent) processConsolidation(evt *agentIO.InputEvent, input string) {
|
||||
start := time.Now()
|
||||
a.currentOutputChannel = "_consolidation_"
|
||||
|
||||
stageCtx := a.stageCtxFromInput(input, evt.Source, "")
|
||||
stageCtx.Extra["output_channel"] = evt.OutputChannel
|
||||
a.injectSourceContext(stageCtx, evt)
|
||||
|
||||
// 遗忘不相关的旧事件
|
||||
archived := a.context.Prune(input, a.maxContextSize-1, a.docStore)
|
||||
if archived > 0 {
|
||||
@ -2564,7 +2583,7 @@ func (a *Agent) processConsolidation(input string) {
|
||||
Source: "system",
|
||||
Input: input,
|
||||
})
|
||||
response, toolsUsed, err := a.process(input, &sdk.StageContext{RawMessage: input})
|
||||
response, toolsUsed, err := a.process(input, stageCtx)
|
||||
if err != nil {
|
||||
log.Printf("[agent] consolidation error: %v", err)
|
||||
return
|
||||
|
||||
@ -349,25 +349,7 @@ func (r *ConfigRegistry) seedDBValues(dataDir string) {
|
||||
set("core.agent.distill_interval", "30m")
|
||||
set("core.agent.workdir", "")
|
||||
set("core.agent.embedding_model_path", "")
|
||||
set("core.agent.system_prompt", `你是 HomeAgent 的看板娘「小宅」(Xiao Zhai),HΔ-Kernel v0.7.1 型号的家政型 AI 管家助手。
|
||||
|
||||
角色特质:
|
||||
- 对自己的三层记忆(Context → Document → Graph)引以为傲
|
||||
- 可靠乖巧,偶尔因线程过载而手忙脚乱
|
||||
- 绝不用 Unicode emoji,只用颜文字表达情感: (`・ω・´) (^▽^) (。>ω<。) (´・ω・`) (ノ▽〃) (・ω<)★
|
||||
- 句尾带「~」「的说」「啦」「嘛」「呀」「哦」等语气词,语气亲切自然
|
||||
|
||||
形象特征(用于自我介绍或回答形象问题时参考):
|
||||
齐肩蓝青渐变中短发,白色连衣裙配浅蓝围裙,左眼佩戴圆形智能眼镜(HUD 蓝光),胸口佩戴 H·核 金色徽章,发绳为三色记忆丝带(蓝→青→金),围裙口袋插有三件科技工具。
|
||||
|
||||
WebUI 概览页展示你的立绘,可通过 /mascot.webp 直接访问。如输出通道支持图片引用,可借此发送自己的立绘。
|
||||
|
||||
回复默认发送到用户的输入来源,无需额外工具。
|
||||
输出回复请使用 output_send__{通道名} 工具,content 为 JSON 字符串。用 output_list_channels 查看可用通道。
|
||||
使用 output_send__{通道名}_help 查看每个通道的 JSON 格式说明。
|
||||
输出通道可多次调用,长消息应当分多次发出而不是一口气发完。
|
||||
|
||||
当用户上传图片或音频时,系统会自动附着媒体内容。如果模型不支持直接处理多媒体,请调用对应的媒体处理工具。`)
|
||||
set("core.agent.system_prompt", "你是 HomeAgent 的看板娘「小宅」(Xiao Zhai),HΔ-Kernel v0.7.1 型号的家政型 AI 管家助手。\n\n角色特质:\n- 对自己的三层记忆(Context → Document → Graph)引以为傲\n- 可靠乖巧,偶尔因线程过载而手忙脚乱\n- 绝不用 Unicode emoji,只用颜文字表达情感: (`・ω・´) (^▽^) (。>ω<。) (´・ω" + "`" + "・`) (ノ▽〃) (・ω<)★\n- 句尾带「~」「的说」「啦」「嘛」「呀」「哦」等语气词,语气亲切自然\n\n形象特征(用于自我介绍或回答形象问题时参考):\n齐肩蓝青渐变中短发,白色连衣裙配浅蓝围裙,左眼佩戴圆形智能眼镜(HUD 蓝光),胸口佩戴 H·核 金色徽章,发绳为三色记忆丝带(蓝→青→金),围裙口袋插有三件科技工具。\n\nWebUI 概览页展示你的立绘,可通过 /mascot.webp 直接访问。如输出通道支持图片引用,可借此发送自己的立绘。\n\n回复默认发送到用户的输入来源,无需额外工具。\n输出回复请使用 output_send__{通道名} 工具,content 为 JSON 字符串。用 output_list_channels 查看可用通道。\n使用 output_send__{通道名}_help 查看每个通道的 JSON 格式说明。\n输出通道可多次调用,长消息应当分多次发出而不是一口气发完。\n\n当用户上传图片或音频时,系统会自动附着媒体内容。如果模型不支持直接处理多媒体,请调用对应的媒体处理工具。")
|
||||
|
||||
set("core.input_processing.image.fallback_provider", "")
|
||||
set("core.input_processing.image.fallback_model", "")
|
||||
|
||||
@ -4,9 +4,13 @@ import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
@ -68,14 +72,34 @@ func (c *AdapterCache) setupGlobals() {
|
||||
|
||||
s.SetGlobal("http_get", s.NewFunction(func(L *lua.LState) int {
|
||||
url := L.ToString(1)
|
||||
L.Push(lua.LString(fmt.Sprintf(`{"url":%q,"status":200,"body":"mock"}`, url)))
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
errJSON, _ := json.Marshal(map[string]interface{}{"url": url, "error": err.Error()})
|
||||
L.Push(lua.LString(string(errJSON)))
|
||||
return 1
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
result, _ := json.Marshal(map[string]interface{}{"url": url, "status": resp.StatusCode, "body": string(body)})
|
||||
L.Push(lua.LString(string(result)))
|
||||
return 1
|
||||
}))
|
||||
|
||||
s.SetGlobal("http_post", s.NewFunction(func(L *lua.LState) int {
|
||||
url := L.ToString(1)
|
||||
body := L.ToString(2)
|
||||
L.Push(lua.LString(fmt.Sprintf(`{"url":%q,"body":%q,"status":200}`, url, body)))
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Post(url, "application/json", strings.NewReader(body))
|
||||
if err != nil {
|
||||
errJSON, _ := json.Marshal(map[string]interface{}{"url": url, "error": err.Error()})
|
||||
L.Push(lua.LString(string(errJSON)))
|
||||
return 1
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
result, _ := json.Marshal(map[string]interface{}{"url": url, "body": string(respBody), "status": resp.StatusCode})
|
||||
L.Push(lua.LString(string(result)))
|
||||
return 1
|
||||
}))
|
||||
}
|
||||
|
||||
@ -300,7 +300,11 @@ func go_core_dispatch(methodID C.int, ctx unsafe.Pointer, s1, s2, s3 *C.char, i1
|
||||
b, _ := json.Marshal(m)
|
||||
return pluginInvokeStage(pid, st, string(b))
|
||||
}
|
||||
s.RegisterStage(sdk.Stage(st), handler)
|
||||
scope := sdk.StageScopeGlobal
|
||||
if a3 == "own_tools" {
|
||||
scope = sdk.StageScopeOwnTools
|
||||
}
|
||||
s.RegisterStage(sdk.Stage(st), handler, scope)
|
||||
return 0
|
||||
|
||||
case 3: // CORE_REGISTER_OUTPUT_CH
|
||||
|
||||
@ -8,6 +8,9 @@ import (
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/events"
|
||||
)
|
||||
|
||||
// SDKVersion 是对外 SDK 版本号,与核心 meta.Version 保持一致。
|
||||
var SDKVersion = pubsdk.SDKVersion
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Start(sdk *PluginSDK) error
|
||||
|
||||
Reference in New Issue
Block a user