mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
时间线此前只写「→ 调用工具: a, b」,工具取回的正文下一轮彻底消失: 模型记得自己查过,却不记得查到了什么(刚说的话转头就忘)。 ToolDef 上的 Cleaner / NoMemory 声明一直存在,缺的是消费端。 - formatMergedTimeline 渲染 e.ToolResults:Cleaner 优先,NoMemory 工具跳过, 单条 160 字、每事件最多 3 条(+N more),预算估算同步计入 - 模态能力改由配置层声明(ProviderManager.SetCapabilities), 不再靠 provider.(ModalProvider) 断言穿过 adapter 与安全装饰器 - a2a 通道 RecallPolicy none→auto(NoMemory 仍 true:可读不写)
97 lines
3.4 KiB
Go
97 lines
3.4 KiB
Go
// Package llm 是 llm 模块:多源 Provider 管理(经 Lua 适配器做协议转换)。
|
||
//
|
||
// 依赖 config + lua:`initLLMProviders(cfg, luaVM, baseAPIKey)`。
|
||
package llm
|
||
|
||
import (
|
||
"log"
|
||
"os"
|
||
|
||
"github.com/JianFeeeee/HologramAgent/internal/agent/api"
|
||
"github.com/JianFeeeee/HologramAgent/internal/kernelboot"
|
||
"github.com/JianFeeeee/HologramAgent/internal/kernelmod"
|
||
luapkg "github.com/JianFeeeee/HologramAgent/internal/lua"
|
||
"github.com/JianFeeeee/HologramAgent/pkg/types"
|
||
)
|
||
|
||
// Module 提供多源 provider 管理。
|
||
type Module struct{}
|
||
|
||
func (Module) Name() string { return "llm" }
|
||
func (Module) Requires() []string { return []string{kernelmod.FacConfig, kernelmod.FacLua} }
|
||
func (Module) Provides() []string { return []string{kernelmod.FacLLM} }
|
||
|
||
func (Module) Init(ks *kernelboot.KernelState) error {
|
||
// ★ 敏感信息防火模块的装配点。
|
||
//
|
||
// 原 main 注释:"必须在任何 provider 注册之前装配"—— provider 的出向
|
||
// 装饰器在 ProviderManager.Register 里读 security.Global()。装配晚了,
|
||
// 先注册的那批 provider 就没被装饰(静默漏掉一条出网路径)。
|
||
//
|
||
// 实测:该装配**就在 initLLMProviders 内部**(不是 main 的独立语句)
|
||
// ⇒ 声明式下它天然"紧邻",不再依赖 main 里那两行的相对位置。
|
||
ks.BaseAPIKey = resolveBaseAPIKey(ks.Cfg)
|
||
ks.LLMManager = buildProviderManager(ks.Cfg, ks.LuaVM, ks.BaseAPIKey)
|
||
ks.LLMProvider = ks.LLMManager.Default()
|
||
return nil
|
||
}
|
||
|
||
// buildProviderManager 按配置注册全部 LLM 源(原 cmd/homed 的整块平移)。
|
||
func buildProviderManager(cfg *types.Config, luaVM *luapkg.VM, baseAPIKey string) *api.ProviderManager {
|
||
providerMgr := api.NewProviderManager()
|
||
adapterConcurrency := map[string]int{}
|
||
for _, src := range cfg.LLM.Sources {
|
||
if !api.IsValidSourceConfig(src.Name, src.BaseURL, src.Model, src.Adapter) {
|
||
log.Printf("[homed] skip invalid llm source %q (base_url=%q model=%q adapter=%q)",
|
||
src.Name, src.BaseURL, src.Model, src.Adapter)
|
||
continue
|
||
}
|
||
key := src.APIKey
|
||
if key == "" {
|
||
key = baseAPIKey
|
||
}
|
||
luaProvider := api.NewLuaAdaptedProvider(api.BaseConfig{
|
||
Model: src.Model,
|
||
BaseURL: src.BaseURL,
|
||
APIKey: key,
|
||
Temperature: cfg.LLM.Temperature,
|
||
MaxTokens: cfg.LLM.MaxTokens,
|
||
ContextWindow: src.ContextWindow,
|
||
MaxConcurrent: src.MaxConcurrent,
|
||
Priority: src.Priority,
|
||
Vision: src.Vision,
|
||
Audio: src.Audio,
|
||
}, luaVM, src.Name, src.Adapter)
|
||
providerMgr.Register(src.Name, luaProvider)
|
||
// ★ 能力是**配置事实**,由装配层登记;不让内核靠 adapter 上的
|
||
// 类型断言去猜(断言穿过 adapter + 安全装饰器,任一层漏实现方法
|
||
// 就会静默变成"没有视觉"—— 这是反复出现的老 bug)。
|
||
providerMgr.SetCapabilities(src.Name, api.ProviderCaps{
|
||
Vision: src.Vision,
|
||
Audio: src.Audio,
|
||
})
|
||
if src.Adapter != "" {
|
||
adapterConcurrency[src.Adapter] += src.MaxConcurrent
|
||
}
|
||
}
|
||
luaVM.ConfigureConcurrency(adapterConcurrency)
|
||
if cfg.LLM.Provider != "" {
|
||
providerMgr.SetDefault(cfg.LLM.Provider)
|
||
}
|
||
return providerMgr
|
||
}
|
||
|
||
// resolveBaseAPIKey 决定基础 key(配置 > LLM_API_KEY > DEEPSEEK_API_KEY)。
|
||
func resolveBaseAPIKey(cfg *types.Config) string {
|
||
apiKey := cfg.LLM.APIKey
|
||
if apiKey == "" {
|
||
apiKey = os.Getenv("LLM_API_KEY")
|
||
}
|
||
if apiKey == "" {
|
||
apiKey = os.Getenv("DEEPSEEK_API_KEY")
|
||
}
|
||
return apiKey
|
||
}
|
||
|
||
func init() { kernelmod.Register(Module{}) }
|