mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-30 14:32:52 +00:00
fix: 模型思考模式配置 + Unicode 截断 + 审计修复 (13 files)
模型模式: - 新增 LLMConfig/Source.ThinkingEnabled 配置,通过 ExtraBody 控制 DeepSeek thinking mode,默认关闭 - SeedDefaults/ToConfig 读写 core.llm.thinking_enabled - deepseek.lua 移除硬编码 temperature=0 Unicode 截断: - truncateStr 改按 rune 计数,修复中文截断乱码 审计修复 (Critical): - graph.go: defer rows.Close 在 for 循环 → 显式 Close (连接池泄漏) - cli/openclaw/plugin.go: bare type assertion → comma-ok (panic) - channel.go: payload["type"].(string) → comma-ok (panic) - webui/handler.go: .(string) → fmt.Sprint (panic) - agent.go: 添加 nil provider 错误返回 审计修复 (High): - events/bus.go: copy handler slice under RLock (data race) - webui/handler.go: SSE 通过 channel 串行化写入 (data race) - timer/plugin.go: time.Sleep → select with stopCh (Stop 阻塞) - provider.go: stream ch <- 添加 select ctx.Done (goroutine 泄漏) - main.go: outputCh goroutine 添加 ctx.Done 退出路径
This commit is contained in:
@ -265,13 +265,17 @@ func (p *OpenAIProvider) ChatStream(ctx context.Context, req *CompletionRequest)
|
||||
}
|
||||
|
||||
if err := decoder.Decode(&line); err != nil {
|
||||
break
|
||||
return
|
||||
}
|
||||
|
||||
if len(line.Choices) > 0 {
|
||||
ch <- StreamChunk{
|
||||
select {
|
||||
case ch <- StreamChunk{
|
||||
Content: line.Choices[0].Delta.Content,
|
||||
Done: line.Choices[0].FinishReason != nil,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -498,9 +502,13 @@ func (p *LuaAdaptedProvider) ChatStream(ctx context.Context, req *CompletionRequ
|
||||
continue
|
||||
}
|
||||
if len(raw.Choices) > 0 {
|
||||
ch <- StreamChunk{
|
||||
select {
|
||||
case ch <- StreamChunk{
|
||||
Content: raw.Choices[0].Delta.Content,
|
||||
Done: raw.Choices[0].FinishReason != nil,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
continue
|
||||
@ -509,7 +517,11 @@ func (p *LuaAdaptedProvider) ChatStream(ctx context.Context, req *CompletionRequ
|
||||
// Lua 返回了变换后的统一格式
|
||||
var chunk StreamChunk
|
||||
if err := json.Unmarshal([]byte(unified), &chunk); err == nil {
|
||||
ch <- chunk
|
||||
select {
|
||||
case ch <- chunk:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
|
||||
agentPkg "gitcode.com/JianFeeeee/HomeAgent/internal/agent"
|
||||
@ -89,6 +90,9 @@ type Agent struct {
|
||||
// 进行中的 LLM 请求取消函数,interceptLoop 可调用以在请求中打断
|
||||
cancelLLM context.CancelFunc
|
||||
llmMu sync.Mutex
|
||||
|
||||
// 模型思考模式(thinking/reasoning)
|
||||
thinkingEnabled bool
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
@ -115,6 +119,7 @@ type AgentConfig struct {
|
||||
ContextSavePath string // 上下文持久化路径,空则不持久化
|
||||
StageHost *StageHost
|
||||
EventBus *events.Bus
|
||||
ThinkingEnabled bool
|
||||
}
|
||||
|
||||
func New(cfg AgentConfig) *Agent {
|
||||
@ -156,6 +161,7 @@ func New(cfg AgentConfig) *Agent {
|
||||
selfInputCh: make(chan string, 64),
|
||||
childResults: make(map[string]string),
|
||||
interceptCh: make(chan string, 64),
|
||||
thinkingEnabled: cfg.ThinkingEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,6 +389,10 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if a.provider == nil {
|
||||
return "", nil, fmt.Errorf("agent: no LLM provider configured")
|
||||
}
|
||||
|
||||
memContext := a.buildMemoryContext(input)
|
||||
sysPrompt := a.buildSystemPrompt(memContext, input)
|
||||
tools := a.buildToolDefs()
|
||||
@ -418,14 +428,16 @@ func (a *Agent) process(input string, stageCtx *sdk.StageContext) (response stri
|
||||
log.Printf("[agent] interrupt injected before LLM call (turn %d)", turn)
|
||||
}
|
||||
|
||||
eb := map[string]interface{}{}
|
||||
if !a.thinkingEnabled {
|
||||
eb["thinking"] = map[string]interface{}{"type": "disabled"}
|
||||
}
|
||||
req := &agentAPI.CompletionRequest{
|
||||
Messages: msgs,
|
||||
MaxTokens: 4096,
|
||||
Tools: tools,
|
||||
ToolChoice: "auto",
|
||||
ExtraBody: map[string]interface{}{
|
||||
"thinking": map[string]interface{}{"type": "disabled"},
|
||||
},
|
||||
ExtraBody: eb,
|
||||
}
|
||||
|
||||
// 可取消的 LLM 调用:interceptLoop 通过 cancelLLM 打断进行中的请求
|
||||
@ -1839,6 +1851,10 @@ func (a *Agent) executeSpawnChild(tc agentAPI.ToolCall) string {
|
||||
|
||||
// runChildTask 后台运行子 Agent 任务,完成后将结果存储并通过 selfInputCh 通知主 Agent
|
||||
func (a *Agent) runChildTask(taskID, task string) {
|
||||
if a.provider == nil {
|
||||
log.Printf("[child] %s failed: no LLM provider configured", taskID)
|
||||
return
|
||||
}
|
||||
log.Printf("[child] %s started: %s", taskID, truncateStr(task, 80))
|
||||
|
||||
sysPrompt := fmt.Sprintf(`你是 HomeAgent 的子任务助手。
|
||||
@ -1871,14 +1887,16 @@ func (a *Agent) runChildTask(taskID, task string) {
|
||||
|
||||
var finalResult string
|
||||
for turn := 0; turn < 5; turn++ {
|
||||
eb := map[string]interface{}{}
|
||||
if !a.thinkingEnabled {
|
||||
eb["thinking"] = map[string]interface{}{"type": "disabled"}
|
||||
}
|
||||
req := &agentAPI.CompletionRequest{
|
||||
Messages: msgs,
|
||||
MaxTokens: 4096,
|
||||
Tools: childTools,
|
||||
ToolChoice: "auto",
|
||||
ExtraBody: map[string]interface{}{
|
||||
"thinking": map[string]interface{}{"type": "disabled"},
|
||||
},
|
||||
ExtraBody: eb,
|
||||
}
|
||||
|
||||
resp, err := a.provider.Chat(a.ctx, req)
|
||||
@ -2038,10 +2056,17 @@ func getFloat(m map[string]interface{}, key string) float64 {
|
||||
}
|
||||
|
||||
func truncateStr(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
if utf8.RuneCountInString(s) <= max {
|
||||
return s
|
||||
}
|
||||
return s[:max] + "..."
|
||||
var truncated int
|
||||
for i := range s {
|
||||
if truncated >= max {
|
||||
return s[:i] + "..."
|
||||
}
|
||||
truncated++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// drainInterrupt 非阻塞读取 interceptCh 中的一条打断消息。
|
||||
|
||||
@ -246,10 +246,11 @@ func (m *IOManager) InjectInterrupt(source, channel string, payload map[string]i
|
||||
if payload == nil {
|
||||
payload = map[string]interface{}{}
|
||||
}
|
||||
evtType, _ := payload["type"].(string)
|
||||
m.interruptCh <- &InputEvent{
|
||||
RequestID: m.nextRequestID(),
|
||||
Source: source,
|
||||
Type: payload["type"].(string),
|
||||
Type: evtType,
|
||||
Payload: payload,
|
||||
OutputChannel: channel,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user