Files
HomeAgent/internal/sdk/llm_impl.go
JianFeeeee 5b0cd45093 feat: screensee 工具 — agent 查看远程设备屏幕内容
与 screensue(向用户屏幕显示)配对: screensue 是给用户看, screensee 是 agent 看。

服务端实现:
1. remotedevice 新增 screensee 工具:
   - 下发 homeagent-screensee 命令 → 设备截屏回传 jpeg base64
   - seeHandler 回调(agent 核心注入)用视觉模型自动描述屏幕内容
   - 未授权/离线/超时完整错误路径; 结果留档 cmdresult
2. SDK LLMMessage 扩展多模态 Blocks(text/image_url):
   - llm_impl 转换为 agentAPI.ContentBlock, 视觉模型可看图
3. describeScreen: 默认提示词描述窗口/文字/界面状态;
   provider 参数可指定视觉源(临时切换后恢复)

GUI 端需配套(已发群): onDeviceMsg 加 case "screensee",
desktopCapturer 截屏 → jpeg base64 data URL 回执(同 camerasue 抓拍模式)。

测试: 端到端模拟设备截屏回传+视觉回调验证; 全项目 go test 通过
2026-08-21 11:31:45 +08:00

160 lines
4.1 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 sdk
import (
"context"
"fmt"
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
internalConfig "gitcode.com/JianFeeeee/HomeAgent/internal/config"
luaVM "gitcode.com/JianFeeeee/HomeAgent/internal/lua"
)
type llmImpl struct {
mgr *agentAPI.ProviderManager
cfgReg *internalConfig.ConfigRegistry
lua *luaVM.VM
baseAPIKey string
}
func NewLLM(mgr *agentAPI.ProviderManager, cfgReg *internalConfig.ConfigRegistry, lua *luaVM.VM, baseAPIKey string) LLMAPI {
return &llmImpl{mgr: mgr, cfgReg: cfgReg, lua: lua, baseAPIKey: baseAPIKey}
}
func (l *llmImpl) ListSources() []string {
if l.mgr == nil {
return nil
}
return l.mgr.List()
}
func (l *llmImpl) SetSource(name string) error {
if l.mgr == nil {
return nil
}
return l.mgr.SetDefault(name)
}
func (l *llmImpl) CurrentSource() string {
if l.mgr == nil {
return ""
}
p := l.mgr.Default()
if p == nil {
return ""
}
return p.Name()
}
func (l *llmImpl) Chat(ctx context.Context, req *LLMCompletionRequest) (*LLMCompletionResponse, error) {
if l.mgr == nil {
return nil, fmt.Errorf("llm: provider manager not available")
}
p := l.mgr.Default()
if p == nil {
return nil, fmt.Errorf("llm: no default provider")
}
apiReq := &agentAPI.CompletionRequest{
Model: req.Model,
Temperature: req.Temperature,
MaxTokens: req.MaxTokens,
Stream: req.Stream,
Tools: req.Tools,
ToolChoice: req.ToolChoice,
DisableThinking: req.DisableThinking,
}
if len(req.Messages) > 0 {
apiReq.Messages = make([]agentAPI.Message, len(req.Messages))
for i, m := range req.Messages {
msg := agentAPI.Message{
Role: m.Role,
Content: m.Content,
ReasoningContent: m.ReasoningContent,
ToolCallID: m.ToolCallID,
}
// 多模态 Blockstext/image_url → agentAPI.ContentBlock
for _, b := range m.Blocks {
switch b.Type {
case "text":
msg.Blocks = append(msg.Blocks, agentAPI.ContentBlock{Type: "text", Text: b.Text})
case "image_url":
msg.Blocks = append(msg.Blocks, agentAPI.ContentBlock{
Type: "image_url",
ImageURL: &agentAPI.ImageURL{URL: b.ImageURL, Detail: "high"},
})
}
}
if len(m.ToolCalls) > 0 {
msg.ToolCalls = make([]agentAPI.ToolCall, len(m.ToolCalls))
for j, tc := range m.ToolCalls {
msg.ToolCalls[j] = agentAPI.ToolCall{ID: tc.ID, Name: tc.Name, Arguments: tc.Arguments}
}
}
apiReq.Messages[i] = msg
}
}
resp, err := p.Chat(ctx, apiReq)
if err != nil {
return nil, err
}
out := &LLMCompletionResponse{
Content: resp.Content,
ReasoningContent: resp.ReasoningContent,
FinishReason: resp.FinishReason,
TokenUsage: LLMTokenUsage{
Prompt: resp.TokenUsage.Prompt,
Completion: resp.TokenUsage.Completion,
Total: resp.TokenUsage.Total,
},
}
for _, tc := range resp.ToolCalls {
out.ToolCalls = append(out.ToolCalls, LLMToolCall{ID: tc.ID, Name: tc.Name, Arguments: tc.Arguments})
}
return out, nil
}
func (l *llmImpl) ReloadFromConfig() error {
if l.mgr == nil || l.cfgReg == nil || l.lua == nil {
return nil
}
cfg := l.cfgReg.ToConfig()
if cfg == nil {
return nil
}
l.mgr.Reset()
adapterConcurrency := map[string]int{}
for _, src := range cfg.LLM.Sources {
if !agentAPI.IsValidSourceConfig(src.Name, src.BaseURL, src.Model, src.Adapter) {
continue
}
key := src.APIKey
if key == "" {
key = l.baseAPIKey
}
provider := agentAPI.NewLuaAdaptedProvider(agentAPI.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,
}, l.lua, src.Name, src.Adapter)
l.mgr.Register(src.Name, provider)
if src.Adapter != "" {
adapterConcurrency[src.Adapter] += src.MaxConcurrent
}
}
if l.lua != nil {
l.lua.ConfigureConcurrency(adapterConcurrency)
}
if cfg.LLM.Provider != "" {
if l.mgr.Get(cfg.LLM.Provider) != nil {
_ = l.mgr.SetDefault(cfg.LLM.Provider)
}
}
return nil
}
var _ LLMAPI = (*llmImpl)(nil)