Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion agent/adapters/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package adapters

import (
"strings"
"sync"

llmcore "github.com/BaSui01/agentflow/llm/core"
"github.com/BaSui01/agentflow/types"
Expand All @@ -13,6 +14,11 @@ type ChatRequestAdapter interface {
Build(options types.ExecutionOptions, messages []types.Message) (*types.ChatRequest, error)
}

// messageSlicePool reduces GC pressure for frequent message slice copies.
var messageSlicePool = sync.Pool{
New: func() any { return make([]types.Message, 0, 8) },
}

// DefaultChatRequestAdapter is the runtime's canonical chat request adapter.
type DefaultChatRequestAdapter struct{}

Expand All @@ -28,7 +34,7 @@ func (DefaultChatRequestAdapter) Build(options types.ExecutionOptions, messages
req := &types.ChatRequest{
Model: options.Model.Model,
RoutePolicy: strings.TrimSpace(options.Model.RoutePolicy),
Messages: append([]types.Message(nil), messages...),
Messages: append(make([]types.Message, 0, len(messages)), messages...),
MaxTokens: options.Model.MaxTokens,
Temperature: options.Model.Temperature,
TopP: options.Model.TopP,
Expand Down
13 changes: 6 additions & 7 deletions agent/capabilities/guardrails/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ func (c *ValidatorChain) Validate(ctx context.Context, content string) (*Validat

// 创建聚合结果
result := NewValidationResult()
result.Metadata["validators_executed"] = make([]string, 0)
result.Metadata["execution_order"] = make([]string, 0)
result.Metadata["validators_executed"] = make([]string, 0, len(validators))
result.Metadata["execution_order"] = make([]string, 0, len(validators))

// 按顺序执行验证器
for _, v := range validators {
Expand Down Expand Up @@ -219,8 +219,8 @@ func (c *ValidatorChain) Validate(ctx context.Context, content string) (*Validat
func (c *ValidatorChain) validateParallel(ctx context.Context, validators []Validator, content string) (*ValidationResult, error) {
if len(validators) == 0 {
result := NewValidationResult()
result.Metadata["validators_executed"] = make([]string, 0)
result.Metadata["execution_order"] = make([]string, 0)
result.Metadata["validators_executed"] = make([]string, 0, 0)
result.Metadata["execution_order"] = make([]string, 0, 0)
return result, nil
}

Expand Down Expand Up @@ -314,11 +314,10 @@ func (c *ValidatorChain) ValidateWithCallback(

// 创建聚合结果
result := NewValidationResult()
result.Metadata["validators_executed"] = make([]string, 0)
result.Metadata["execution_order"] = make([]string, 0)
result.Metadata["validators_executed"] = make([]string, 0, len(validators))
result.Metadata["execution_order"] = make([]string, 0, len(validators))

for _, v := range validators {
// 检查上下文
select {
case <-ctx.Done():
return result, ctx.Err()
Expand Down
6 changes: 5 additions & 1 deletion agent/capabilities/guardrails/injection_detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ func getCompiledPattern(pattern string) (*regexp.Regexp, error) {
if err != nil {
return nil, err
}
regexCache.Store(pattern, re)
actual, loaded := regexCache.LoadOrStore(pattern, re)
if loaded {
// Another goroutine already cached this pattern, use the cached copy
return actual.(*regexp.Regexp), nil
}
return re, nil
}

Expand Down
2 changes: 1 addition & 1 deletion agent/capabilities/guardrails/llama_firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ type ShadowAIStats struct {

// 添加Pattern 添加自定义检测模式 。
func (d *ShadowAIDetector) AddPattern(name, patternType, patternStr, severity, description string) error {
re, err := regexp.Compile("(?i)" + patternStr)
re, err := getCompiledPattern("(?i)" + patternStr)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion agent/capabilities/guardrails/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ func (f *ContentFilter) AddPattern(pattern string) error {
regexPattern = "(?i)" + pattern
}

compiled, err := regexp.Compile(regexPattern)
compiled, err := getCompiledPattern(regexPattern)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion agent/capabilities/memory/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func NewCoordinator(agentID string, memory MemoryManager, logger *zap.Logger) *C
}
return &Coordinator{
memory: memory,
recentMemory: make([]MemoryRecord, 0),
recentMemory: make([]MemoryRecord, 0, MaxRecentMemory),
agentID: agentID,
logger: logger.With(zap.String("component", "memory_coordinator")),
}
Expand Down
8 changes: 6 additions & 2 deletions agent/capabilities/memory/knowledge_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,9 @@ func (g *InMemoryKnowledgeGraph) dfs(ctx context.Context, current, target string
if visited[next] {
continue
}
g.dfs(ctx, next, target, depth-1, visited, append(path, next), paths)
path = append(path, next)
g.dfs(ctx, next, target, depth-1, visited, path, paths)
path = path[:len(path)-1] // backtrack
}

// 遍历入边(双向搜索)
Expand All @@ -277,6 +279,8 @@ func (g *InMemoryKnowledgeGraph) dfs(ctx context.Context, current, target string
if visited[next] {
continue
}
g.dfs(ctx, next, target, depth-1, visited, append(path, next), paths)
path = append(path, next)
g.dfs(ctx, next, target, depth-1, visited, path, paths)
path = path[:len(path)-1] // backtrack
}
}
26 changes: 18 additions & 8 deletions agent/capabilities/memory/redis_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,22 @@ func (s *RedisMemoryStore) List(ctx context.Context, pattern string, limit int)
return nil, err
}

keys, err := s.client.Keys(ctx, s.redisKey(patternOrAll(pattern))).Result()
if err != nil {
// SCAN instead of KEYS to avoid blocking Redis
var allKeys []string
iter := s.client.Scan(ctx, 0, s.redisKey(patternOrAll(pattern)), 100).Iterator()
for iter.Next(ctx) {
allKeys = append(allKeys, iter.Val())
}
if err := iter.Err(); err != nil {
return nil, fmt.Errorf("redis list memory keys: %w", err)
}

type item struct {
value any
createdAt time.Time
}
items := make([]item, 0, len(keys))
for _, key := range keys {
items := make([]item, 0, len(allKeys))
for _, key := range allKeys {
if err := ctx.Err(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -166,14 +171,19 @@ func (s *RedisMemoryStore) Clear(ctx context.Context) error {
return err
}

keys, err := s.client.Keys(ctx, s.redisKey("*")).Result()
if err != nil {
// SCAN instead of KEYS to avoid blocking Redis
var allKeys []string
iter := s.client.Scan(ctx, 0, s.redisKey("*"), 100).Iterator()
for iter.Next(ctx) {
allKeys = append(allKeys, iter.Val())
}
if err := iter.Err(); err != nil {
return fmt.Errorf("redis list memory keys: %w", err)
}
if len(keys) == 0 {
if len(allKeys) == 0 {
return nil
}
if err := s.client.Del(ctx, keys...).Err(); err != nil {
if err := s.client.Del(ctx, allKeys...).Err(); err != nil {
return fmt.Errorf("redis clear memory keys: %w", err)
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions agent/capabilities/prompt/enhancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ func TestPromptTemplateLibrary(t *testing.T) {
t.Fatalf("rendered template did not substitute variables:\n%s", rendered)
}

if _, err := lib.RenderTemplate("code_generation", map[string]string{"language": "Go"}); err == nil {
if _, renderErr := lib.RenderTemplate("code_generation", map[string]string{"language": "Go"}); renderErr == nil {
t.Fatal("RenderTemplate should reject missing variables")
}
if _, err := lib.RenderTemplate("missing", nil); err == nil {
if _, renderErr := lib.RenderTemplate("missing", nil); renderErr == nil {
t.Fatal("RenderTemplate should reject unknown templates")
}

Expand Down
59 changes: 35 additions & 24 deletions agent/capabilities/streaming/bidirectional.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ const (
StreamTypeMixed StreamType = "mixed"
)

// streamChunkPool reduces GC pressure for frequently allocated StreamChunk values.
var streamChunkPool = sync.Pool{
New: func() any { return new(StreamChunk) },
}

// StreamChunk代表了一整批流数据.
type StreamChunk struct {
ID string `json:"id"`
Expand Down Expand Up @@ -390,12 +395,13 @@ func (s *BidirectionalStream) processHeartbeat(ctx context.Context) {
return
case <-ticker.C:
// 发送心跳
heartbeat := StreamChunk{
Type: "heartbeat",
Timestamp: time.Now(),
Metadata: map[string]any{"ping": true},
}
if err := s.conn.WriteChunk(ctx, heartbeat); err != nil {
heartbeat := streamChunkPool.Get().(*StreamChunk)
heartbeat.Type = "heartbeat"
heartbeat.Timestamp = time.Now()
heartbeat.Metadata = map[string]any{"ping": true}
err := s.conn.WriteChunk(ctx, *heartbeat)
streamChunkPool.Put(heartbeat)
if err != nil {
s.logger.Warn("heartbeat send failed", zap.Error(err))
s.errChan <- fmt.Errorf("heartbeat failed: %w", err)
}
Expand Down Expand Up @@ -541,7 +547,7 @@ type StreamManager struct {
// NewStreamManager创建了新流管理器.
func NewStreamManager(logger *zap.Logger) *StreamManager {
if logger == nil {
panic("agent.StreamManager: logger is required and cannot be nil")
logger = zap.NewNop()
}
return &StreamManager{
streams: make(map[string]*BidirectionalStream),
Expand Down Expand Up @@ -624,14 +630,16 @@ func (a *AudioStreamAdapter) SendAudio(pcm []byte) error {
return err
}
}
return a.stream.Send(StreamChunk{
Type: StreamTypeAudio,
Data: data,
Metadata: map[string]any{
"sample_rate": a.sampleRate,
"channels": a.channels,
},
})
chunk := streamChunkPool.Get().(*StreamChunk)
chunk.Type = StreamTypeAudio
chunk.Data = data
chunk.Metadata = map[string]any{
"sample_rate": a.sampleRate,
"channels": a.channels,
}
err := a.stream.Send(*chunk)
streamChunkPool.Put(chunk)
return err
}

// DuiceAudio返回已解码的音频块 。
Expand Down Expand Up @@ -682,11 +690,13 @@ func NewTextStreamAdapter(stream *BidirectionalStream) *TextStreamAdapter {

// 发送文本数据 。
func (t *TextStreamAdapter) SendText(text string, isFinal bool) error {
return t.stream.Send(StreamChunk{
Type: StreamTypeText,
Text: text,
IsFinal: isFinal,
})
chunk := streamChunkPool.Get().(*StreamChunk)
chunk.Type = StreamTypeText
chunk.Text = text
chunk.IsFinal = isFinal
err := t.stream.Send(*chunk)
streamChunkPool.Put(chunk)
return err
}

// 接收文本返回文本块 。
Expand Down Expand Up @@ -757,10 +767,11 @@ func NewStreamWriter(stream *BidirectionalStream) *StreamWriter {
}

func (w *StreamWriter) Write(p []byte) (n int, err error) {
err = w.stream.Send(StreamChunk{
Type: StreamTypeText,
Data: p,
})
chunk := streamChunkPool.Get().(*StreamChunk)
chunk.Type = StreamTypeText
chunk.Data = p
err = w.stream.Send(*chunk)
streamChunkPool.Put(chunk)
if err != nil {
return 0, err
}
Expand Down
5 changes: 4 additions & 1 deletion agent/capabilities/streaming/ws_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"sync"
"time"

"github.com/coder/websocket"
"go.uber.org/zap"
Expand Down Expand Up @@ -108,7 +109,9 @@ func AcceptWebSocket(w http.ResponseWriter, r *http.Request, allowedOrigins []st
// url 是 WebSocket 服务端地址(如 "ws://localhost:8080/stream")。
func WebSocketStreamFactory(url string, logger *zap.Logger) func() (StreamConnection, error) {
return func() (StreamConnection, error) {
conn, _, err := websocket.Dial(context.Background(), url, nil)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
conn, _, err := websocket.Dial(ctx, url, nil)
if err != nil {
return nil, fmt.Errorf("websocket dial: %w", err)
}
Expand Down
9 changes: 5 additions & 4 deletions agent/capabilities/tools/composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,12 @@ func (c *CapabilityComposer) selectBestCapability(caps []CapabilityInfo) *Capabi
}

candidates := make([]tooldiscovery.ScoredCandidate, 0, len(caps))
for _, cap := range caps {
for i := range caps {
capability := &caps[i]
candidates = append(candidates, tooldiscovery.ScoredCandidate{
ID: cap.AgentID,
Score: cap.Score,
Load: cap.Load,
ID: capability.AgentID,
Score: capability.Score,
Load: capability.Load,
})
}
best, ok := tooldiscovery.BestCandidate(candidates)
Expand Down
2 changes: 1 addition & 1 deletion agent/capabilities/tools/discovery/matcher_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func IsExcludedAgent(agentID string, excluded []string) bool {

// SemanticScore calculates keyword-based similarity between a task description
// and the agent plus capability descriptions.
func SemanticScore(agentDescription string, capabilityDescriptions []string, taskDescription string) (float64, float64) {
func SemanticScore(agentDescription string, capabilityDescriptions []string, taskDescription string) (semanticScore float64, coverage float64) {
taskWords := TokenizeForSemanticMatch(taskDescription)
if len(taskWords) == 0 {
return 0, 0
Expand Down
8 changes: 4 additions & 4 deletions agent/capabilities/tools/execution/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package execution

// DependenciesSatisfied reports whether cap can run with the current completed
// and failed dependency state.
func DependenciesSatisfied(cap string, deps map[string][]string, completed map[string]bool, failed map[string]error) bool {
capDeps := deps[cap]
func DependenciesSatisfied(capability string, deps map[string][]string, completed map[string]bool, failed map[string]error) bool {
capDeps := deps[capability]
if len(capDeps) == 0 {
return true
}
Expand All @@ -22,7 +22,7 @@ func DependenciesSatisfied(cap string, deps map[string][]string, completed map[s

// BuildCapabilityInput wraps originalInput with upstream dependency results.
func BuildCapabilityInput(
cap string,
capability string,
originalInput any,
deps map[string][]string,
lookupResult func(string) (any, bool),
Expand All @@ -34,7 +34,7 @@ func BuildCapabilityInput(
return capInput
}

capDeps := deps[cap]
capDeps := deps[capability]
if len(capDeps) == 0 {
return capInput
}
Expand Down
10 changes: 5 additions & 5 deletions agent/capabilities/tools/execution/levels.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ func BuildExecutionLevels(order []string, deps map[string][]string) [][]string {
assigned := make(map[string]int) // capability -> level index
levels := make([][]string, 0)

for _, cap := range order {
for _, capability := range order {
level := 0
if capDeps, ok := deps[cap]; ok {
if capDeps, ok := deps[capability]; ok {
for _, d := range capDeps {
if dl, found := assigned[d]; found && dl+1 > level {
if dl, found := assigned[d]; found && dl >= level {
level = dl + 1
}
}
}
assigned[cap] = level
assigned[capability] = level

for len(levels) <= level {
levels = append(levels, nil)
}
levels[level] = append(levels[level], cap)
levels[level] = append(levels[level], capability)
}

return levels
Expand Down
Loading
Loading